solve merge conflicts
8
.gitignore
vendored
@ -1,8 +1,8 @@
|
|||||||
images/*/*.png
|
images/*/*.png
|
||||||
*.log
|
|
||||||
node_modules/
|
node_modules/
|
||||||
.DS_Store
|
|
||||||
*.rdb
|
|
||||||
coverage/
|
coverage/
|
||||||
config.js
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
*.rdb
|
||||||
*.sublime-*
|
*.sublime-*
|
||||||
|
config.js
|
||||||
|
|||||||
40
README.md
@ -1,5 +1,5 @@
|
|||||||
# Crafatar [](https://travis-ci.org/crafatar/crafatar/) [](https://coveralls.io/r/crafatar/crafatar) [](https://codeclimate.com/github/crafatar/crafatar)
|
# Crafatar [](https://travis-ci.org/crafatar/crafatar/) [](https://coveralls.io/r/crafatar/crafatar) [](https://codeclimate.com/github/crafatar/crafatar)
|
||||||
[](https://webchat.esper.net/?channels=crafatar) [](https://david-dm.org/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar#info=devDependencies) [](https://inch-ci.org/github/crafatar/crafatar)
|
[](https://webchat.esper.net/?channels=crafatar "#crafatar") [](https://david-dm.org/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar#info=devDependencies) [](https://inch-ci.org/github/crafatar/crafatar)
|
||||||
|
|
||||||
|
|
||||||
<img alt="logo" src="lib/public/logo.png" align="right">
|
<img alt="logo" src="lib/public/logo.png" align="right">
|
||||||
@ -33,41 +33,11 @@ Please [visit the website](https://crafatar.com) for details.
|
|||||||
* Open an [issue](https://github.com/crafatar/crafatar/issues/) on GitHub
|
* Open an [issue](https://github.com/crafatar/crafatar/issues/) on GitHub
|
||||||
* You can [join IRC](https://webchat.esper.net/?channels=crafatar) in #crafatar on irc.esper.net.
|
* You can [join IRC](https://webchat.esper.net/?channels=crafatar) in #crafatar on irc.esper.net.
|
||||||
|
|
||||||
## Installation on Heroku
|
# Installation
|
||||||
[](https://heroku.com/deploy)
|
|
||||||
|
|
||||||
## Installation on Dokku
|
Have a look at [crafatar/setup](https://github.com/crafatar/setup) to see how we set things up at Crafatar.
|
||||||
##### [dokku server]
|
|
||||||
Install the [dokku-redis](https://github.com/ohardy/dokku-redis#redis-plugin-for-dokku) plugin.
|
|
||||||
```shell
|
|
||||||
dokku redis:start
|
|
||||||
dokku apps:create crafatar
|
|
||||||
dokku config:set crafatar BIND=0.0.0.0 PORT=5000
|
|
||||||
```
|
|
||||||
For persistent images and logs:
|
|
||||||
```shell
|
|
||||||
dokku docker-options:add crafatar deploy "-v /var/lib/crafatar/images:/app/images"
|
|
||||||
dokku docker-options:add crafatar deploy "-v /var/log/crafatar:/app/logs"
|
|
||||||
```
|
|
||||||
If you want to listen on extra domains:
|
|
||||||
```shell
|
|
||||||
dokku domains crafatar:add example.com
|
|
||||||
```
|
|
||||||
##### [your machine]
|
|
||||||
Add dokku remote and deploy!
|
|
||||||
```shell
|
|
||||||
git remote add dokku dokku@example.com:crafatar
|
|
||||||
git push dokku master
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation on your machine
|
|
||||||
* Use io.js
|
|
||||||
* [Install](https://github.com/Automattic/node-canvas/wiki) Cairo.
|
|
||||||
* `npm install`
|
|
||||||
* Start `redis-server`
|
|
||||||
* `npm start`
|
|
||||||
* Access [http://localhost:3000](http://localhost:3000)
|
|
||||||
|
|
||||||
|
For more info about local setup, Heroku, or Dokku please see [Installation](https://github.com/crafatar/crafatar/wiki/Installation) on the wiki.
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
```shell
|
```shell
|
||||||
@ -83,4 +53,4 @@ env VERBOSE_TEST=true npm test
|
|||||||
It can be helpful to monitor redis commands to debug caching errors:
|
It can be helpful to monitor redis commands to debug caching errors:
|
||||||
```shell
|
```shell
|
||||||
redis-cli monitor
|
redis-cli monitor
|
||||||
```
|
```
|
||||||
19
app.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Crafatar",
|
"name": "Crafatar",
|
||||||
"description": "A Minecraft Avatar API written in NodeJS",
|
"description": "A blazing fast API for Minecraft faces!",
|
||||||
"repository": "https://github.com/crafatar/crafatar",
|
"repository": "https://github.com/crafatar/crafatar",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"node",
|
"node",
|
||||||
@ -10,10 +10,21 @@
|
|||||||
],
|
],
|
||||||
"website": "https://crafatar.com/",
|
"website": "https://crafatar.com/",
|
||||||
"env": {
|
"env": {
|
||||||
"HEROKU": "true",
|
"EPHEMERAL_STORAGE": {
|
||||||
"BUILDPACK_URL": "https://github.com/mojodna/heroku-buildpack-multi.git#build-env"
|
"description": "Set to true if your storage is gone after deploying",
|
||||||
|
"required": false,
|
||||||
|
"value": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"addons": [
|
"addons": [
|
||||||
"rediscloud"
|
"rediscloud"
|
||||||
|
],
|
||||||
|
"buildpacks": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/mojodna/heroku-buildpack-cairo.git"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/heroku/heroku-buildpack-nodejs.git"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,36 +1,35 @@
|
|||||||
var config = {
|
var config = {
|
||||||
avatars: {
|
avatars: {
|
||||||
min_size: 1, // for avatars
|
min_size: 1, // for avatars
|
||||||
max_size: 512, // for avatars; too big values might lead to slow response time or DoS
|
max_size: 512, // for avatars; too big values might lead to slow response time or DoS
|
||||||
default_size: 160 // for avatars; size to be used when no size given
|
default_size: 160 // for avatars; size to be used when no size given
|
||||||
},
|
},
|
||||||
renders: {
|
renders: {
|
||||||
min_scale: 1, // for 3D rendered skins
|
min_scale: 1, // for 3D rendered skins
|
||||||
max_scale: 10, // for 3D rendered skins; too big values might lead to slow response time or DoS
|
max_scale: 10, // for 3D rendered skins; too big values might lead to slow response time or DoS
|
||||||
default_scale: 6 // for 3D rendered skins; scale to be used when no scale given
|
default_scale: 6 // for 3D rendered skins; scale to be used when no scale given
|
||||||
},
|
},
|
||||||
cleaner: {
|
cleaner: {
|
||||||
interval: 1800, // interval seconds to check limits
|
interval: 600, // interval seconds to check limits
|
||||||
disk_limit: 10240, // min allowed free KB on disk to trigger image deletion
|
disk_limit: 524288, // min allowed free KB on disk to trigger image deletion
|
||||||
redis_limit: 24576, // max allowed used KB on redis to trigger redis flush
|
redis_limit: 24576, // max allowed used KB on redis to trigger redis flush
|
||||||
amount: 50000 // amount of skins for which all iamge types are deleted
|
amount: 50000 // amount of skins for which all image types are deleted
|
||||||
},
|
},
|
||||||
directories: {
|
directories: {
|
||||||
faces: "images/faces/", // directory where faces are kept. should have trailing "/"
|
faces: "./images/faces/", // directory where faces are kept. must have trailing "/"
|
||||||
helms: "images/helms/", // directory where helms are kept. should have trailing "/"
|
helms: "./images/helms/", // directory where helms are kept. must have trailing "/"
|
||||||
skins: "images/skins/", // directory where skins are kept. should have trailing "/"
|
skins: "./images/skins/", // directory where skins are kept. must have trailing "/"
|
||||||
renders: "images/renders/", // directory where rendered skins are kept. should have trailing "/"
|
renders: "./images/renders/", // directory where rendered skins are kept. must have trailing "/"
|
||||||
capes: "images/capes/" // directory where capes are kept. should have trailing "/"
|
capes: "./images/capes/" // directory where capes are kept. must have trailing "/"
|
||||||
},
|
},
|
||||||
caching: {
|
caching: {
|
||||||
local: 1200, // seconds until we will check if user's skin changed. should be > 60 to comply with Mojang's rate limit
|
local: 1200, // seconds until we will check if user's skin changed. should be > 60 to comply with Mojang's rate limit
|
||||||
browser: 3600 // seconds until browser will request image again
|
browser: 3600 // seconds until browser will request image again
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
http_timeout: 1000, // ms until connection to Mojang is dropped
|
http_timeout: 2000, // ms until connection to Mojang is dropped
|
||||||
debug_enabled: false, // enables logging.debug
|
debug_enabled: false, // enables logging.debug & editing index page
|
||||||
clusters: 1, // we recommend not using multiple clusters YET, see issue #80
|
log_time: true // set to false if you use an external logger that provides timestamps
|
||||||
log_time: true // set to false if you use an external logger that provides timestamps
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
47
lib/cache.js
@ -1,18 +1,16 @@
|
|||||||
var logging = require("./logging");
|
var logging = require("./logging");
|
||||||
var node_redis = require("redis");
|
var node_redis = require("redis");
|
||||||
var config = require("../config");
|
var config = require("../config");
|
||||||
var path = require("path");
|
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
var fs = require("fs");
|
|
||||||
|
|
||||||
var redis = null;
|
var redis = null;
|
||||||
|
|
||||||
// sets up redis connection
|
// sets up redis connection
|
||||||
// flushes redis when running on heroku (files aren't kept between pushes)
|
// flushes redis when using ephemeral storage (e.g. Heroku)
|
||||||
function connect_redis() {
|
function connect_redis() {
|
||||||
logging.log("connecting to redis...");
|
logging.log("connecting to redis...");
|
||||||
// parse redis env
|
// parse redis env
|
||||||
var redis_env = (process.env.REDISCLOUD_URL || process.env.REDIS_URL);
|
var redis_env = process.env.REDISCLOUD_URL || process.env.REDIS_URL;
|
||||||
var redis_url = redis_env ? url.parse(redis_env) : {};
|
var redis_url = redis_env ? url.parse(redis_env) : {};
|
||||||
redis_url.port = redis_url.port || 6379;
|
redis_url.port = redis_url.port || 6379;
|
||||||
redis_url.hostname = redis_url.hostname || "localhost";
|
redis_url.hostname = redis_url.hostname || "localhost";
|
||||||
@ -23,39 +21,19 @@ function connect_redis() {
|
|||||||
}
|
}
|
||||||
redis.on("ready", function() {
|
redis.on("ready", function() {
|
||||||
logging.log("Redis connection established.");
|
logging.log("Redis connection established.");
|
||||||
if (process.env.HEROKU) {
|
if (process.env.EPHEMERAL_STORAGE) {
|
||||||
logging.log("Running on heroku, flushing redis");
|
logging.log("Storage is ephemeral, flushing redis");
|
||||||
redis.flushall();
|
redis.flushall();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
redis.on("error", function (err) {
|
redis.on("error", function(err) {
|
||||||
logging.error(err);
|
logging.error(err);
|
||||||
});
|
});
|
||||||
redis.on("end", function () {
|
redis.on("end", function() {
|
||||||
logging.warn("Redis connection lost!");
|
logging.warn("Redis connection lost!");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// sets the date of the face file belonging to +skin_hash+ to now
|
|
||||||
// the helms file is ignored because we only need 1 file to read/write from
|
|
||||||
function update_file_date(rid, skin_hash) {
|
|
||||||
if (skin_hash) {
|
|
||||||
var face_path = path.join(__dirname, "..", config.directories.faces, skin_hash + ".png");
|
|
||||||
fs.exists(face_path, function(exists) {
|
|
||||||
if (exists) {
|
|
||||||
var date = new Date();
|
|
||||||
fs.utimes(face_path, date, date, function(err) {
|
|
||||||
if (err) {
|
|
||||||
logging.error(rid, "Error:", err.stack);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logging.error(rid, "tried to update", face_path + " date, but it does not exist");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var exp = {};
|
var exp = {};
|
||||||
|
|
||||||
// returns the redis instance
|
// returns the redis instance
|
||||||
@ -92,20 +70,19 @@ exp.info = function(callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// sets the timestamp for +userId+ and its face file's (+hash+) date to the current time
|
// sets the timestamp for +userId+
|
||||||
// if +temp+ is true, the timestamp is set so that the record will be outdated after 60 seconds
|
// if +temp+ is true, the timestamp is set so that the record will be outdated after 60 seconds
|
||||||
// these 60 seconds match the duration of Mojang's rate limit ban
|
// these 60 seconds match the duration of Mojang's rate limit ban
|
||||||
// callback: error
|
// callback: error
|
||||||
exp.update_timestamp = function(rid, userId, hash, temp, callback) {
|
exp.update_timestamp = function(rid, userId, temp, callback) {
|
||||||
logging.debug(rid, "updating cache timestamp");
|
logging.debug(rid, "updating cache timestamp (" + temp + ")");
|
||||||
var sub = temp ? (config.caching.local - 60) : 0;
|
var sub = temp ? config.caching.local - 60 : 0;
|
||||||
var time = Date.now() - sub;
|
var time = Date.now() - sub;
|
||||||
// store userId in lower case if not null
|
// store userId in lower case if not null
|
||||||
userId = userId && userId.toLowerCase();
|
userId = userId && userId.toLowerCase();
|
||||||
redis.hmset(userId, "t", time, function(err) {
|
redis.hmset(userId, "t", time, function(err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
update_file_date(rid, hash);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// create the key +userId+, store +skin_hash+, +cape_hash+, +slim+ and current time
|
// create the key +userId+, store +skin_hash+, +cape_hash+, +slim+ and current time
|
||||||
@ -114,12 +91,10 @@ exp.update_timestamp = function(rid, userId, hash, temp, callback) {
|
|||||||
// +slim+ can be true (alex) or false (steve)
|
// +slim+ can be true (alex) or false (steve)
|
||||||
// +callback+ contans error
|
// +callback+ contans error
|
||||||
exp.save_hash = function(rid, userId, skin_hash, cape_hash, slim, callback) {
|
exp.save_hash = function(rid, userId, skin_hash, cape_hash, slim, callback) {
|
||||||
logging.debug(rid, "caching skin:" + skin_hash + " cape:" + cape_hash);
|
logging.debug(rid, "caching skin:" + skin_hash + " cape:" + cape_hash + " slim:" + slim);
|
||||||
|
|
||||||
// store shorter null value instead of "null" string
|
// store shorter null value instead of "null" string
|
||||||
skin_hash = skin_hash === null ? "" : skin_hash;
|
skin_hash = skin_hash === null ? "" : skin_hash;
|
||||||
cape_hash = cape_hash === null ? "" : cape_hash;
|
cape_hash = cape_hash === null ? "" : cape_hash;
|
||||||
|
|
||||||
// store userId in lower case if not null
|
// store userId in lower case if not null
|
||||||
userId = userId && userId.toLowerCase();
|
userId = userId && userId.toLowerCase();
|
||||||
|
|
||||||
|
|||||||
@ -20,10 +20,11 @@ function should_clean_redis(callback) {
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
// logging.debug(info.toString());
|
// logging.debug(info.toString());
|
||||||
logging.debug("used mem:" + info.used_memory);
|
|
||||||
var used = parseInt(info.used_memory) / 1024;
|
var used = parseInt(info.used_memory) / 1024;
|
||||||
logging.log("RedisCleaner:", used + "KB used");
|
var result = used >= config.cleaner.redis_limit;
|
||||||
callback(err, used >= config.cleaner.redis_limit);
|
var msg = "RedisCleaner: " + used + "KB used";
|
||||||
|
(result ? logging.log : logging.debug)(msg);
|
||||||
|
callback(err, result);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
callback(e, false);
|
callback(e, false);
|
||||||
}
|
}
|
||||||
@ -35,17 +36,19 @@ function should_clean_redis(callback) {
|
|||||||
// callback: error, true|false
|
// callback: error, true|false
|
||||||
function should_clean_disk(callback) {
|
function should_clean_disk(callback) {
|
||||||
df({
|
df({
|
||||||
file: path.join(__dirname, "..", config.directories.faces),
|
file: config.directories.faces,
|
||||||
prefixMultiplier: "KiB",
|
prefixMultiplier: "KiB",
|
||||||
isDisplayPrefixMultiplier: false,
|
isDisplayPrefixMultiplier: false,
|
||||||
precision: 2
|
precision: 2
|
||||||
}, function (err, response) {
|
}, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err, false);
|
callback(err, false);
|
||||||
} else {
|
} else {
|
||||||
var available = response[0].available;
|
var available = response[0].available;
|
||||||
logging.log("DiskCleaner:", available + "KB available");
|
var result = available < config.cleaner.disk_limit;
|
||||||
callback(err, available < config.cleaner.disk_limit);
|
var msg = "DiskCleaner: " + available + "KB available";
|
||||||
|
(result ? logging.log : logging.debug)(msg);
|
||||||
|
callback(err, result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -71,28 +74,45 @@ exp.run = function() {
|
|||||||
logging.error(err);
|
logging.error(err);
|
||||||
} else if (clean) {
|
} else if (clean) {
|
||||||
logging.warn("DiskCleaner: Disk limit reached! Cleaning images now");
|
logging.warn("DiskCleaner: Disk limit reached! Cleaning images now");
|
||||||
var facesdir = path.join(__dirname, "..", config.directories.faces);
|
|
||||||
var helmdir = path.join(__dirname, "..", config.directories.helms);
|
// hotfix for #139 | FIXME
|
||||||
var renderdir = path.join(__dirname, "..", config.directories.renders);
|
logging.warn("DiskCleaner: Flushing Redis to prevent ENOENT");
|
||||||
var skindir = path.join(__dirname, "..", config.directories.skins);
|
redis.flushall();
|
||||||
fs.readdir(facesdir, function (readerr, files) {
|
// end hotfix
|
||||||
|
|
||||||
|
var skinsdir = config.directories.skins;
|
||||||
|
var capesdir = config.directories.capes;
|
||||||
|
var facesdir = config.directories.faces;
|
||||||
|
var helmsdir = config.directories.helms;
|
||||||
|
var rendersdir = config.directories.renders;
|
||||||
|
fs.readdir(skinsdir, function(readerr, files) {
|
||||||
if (!readerr) {
|
if (!readerr) {
|
||||||
for (var i = 0, l = Math.min(files.length, config.cleaner.amount); i < l; i++) {
|
for (var i = 0, l = Math.min(files.length, config.cleaner.amount); i < l; i++) {
|
||||||
var filename = files[i];
|
var filename = files[i];
|
||||||
if (filename[0] !== ".") {
|
if (filename[0] !== ".") {
|
||||||
fs.unlink(path.join(facesdir, filename), nil);
|
fs.unlink(path.join(facesdir, filename), nil);
|
||||||
fs.unlink(path.join(helmdir, filename), nil);
|
fs.unlink(path.join(helmsdir, filename), nil);
|
||||||
fs.unlink(path.join(skindir, filename), nil);
|
fs.unlink(path.join(skinsdir, filename), nil);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
fs.readdir(renderdir, function (readerr, files) {
|
fs.readdir(rendersdir, function(readerr, files) {
|
||||||
if (!readerr) {
|
if (!readerr) {
|
||||||
for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
|
for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
|
||||||
var filename = files[j];
|
var filename = files[j];
|
||||||
if (filename[0] !== ".") {
|
if (filename[0] !== ".") {
|
||||||
fs.unlink(renderdir + filename, nil);
|
fs.unlink(rendersdir + filename, nil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fs.readdir(capesdir, function(readerr, files) {
|
||||||
|
if (!readerr) {
|
||||||
|
for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
|
||||||
|
var filename = files[j];
|
||||||
|
if (filename[0] !== ".") {
|
||||||
|
fs.unlink(capesdir + filename, nil);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,14 +25,14 @@ function store_skin(rid, userId, profile, cache_details, callback) {
|
|||||||
if (!err && url) {
|
if (!err && url) {
|
||||||
var skin_hash = get_hash(url);
|
var skin_hash = get_hash(url);
|
||||||
if (cache_details && cache_details.skin === skin_hash) {
|
if (cache_details && cache_details.skin === skin_hash) {
|
||||||
cache.update_timestamp(rid, userId, skin_hash, false, function(cache_err) {
|
cache.update_timestamp(rid, userId, false, function(cache_err) {
|
||||||
callback(cache_err, skin_hash, slim);
|
callback(cache_err, skin_hash, slim);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logging.debug(rid, "new skin hash:", skin_hash);
|
logging.debug(rid, "new skin hash:", skin_hash);
|
||||||
var facepath = path.join(__dirname, "..", config.directories.faces, skin_hash + ".png");
|
var facepath = path.join(config.directories.faces, skin_hash + ".png");
|
||||||
var helmpath = path.join(__dirname, "..", config.directories.helms, skin_hash + ".png");
|
var helmpath = path.join(config.directories.helms, skin_hash + ".png");
|
||||||
var skinpath = path.join(__dirname, "..", config.directories.skins, skin_hash + ".png");
|
var skinpath = path.join(config.directories.skins, skin_hash + ".png");
|
||||||
fs.exists(facepath, function(exists) {
|
fs.exists(facepath, function(exists) {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
logging.debug(rid, "skin already exists, not downloading");
|
logging.debug(rid, "skin already exists, not downloading");
|
||||||
@ -42,14 +42,12 @@ function store_skin(rid, userId, profile, cache_details, callback) {
|
|||||||
if (err1 || !img) {
|
if (err1 || !img) {
|
||||||
callback(err1, null, slim);
|
callback(err1, null, slim);
|
||||||
} else {
|
} else {
|
||||||
skins.save_image(img, skinpath, function(skin_err) {
|
skins.save_image(img, skinpath, function(skin_err, skin_img) {
|
||||||
if (skin_err) {
|
if (skin_err) {
|
||||||
logging.error(rid, skin_err);
|
|
||||||
callback(skin_err, null, slim);
|
callback(skin_err, null, slim);
|
||||||
} else {
|
} else {
|
||||||
skins.extract_face(img, facepath, function(err2) {
|
skins.extract_face(img, facepath, function(err2) {
|
||||||
if (err2) {
|
if (err2) {
|
||||||
logging.error(rid, err2.stack);
|
|
||||||
callback(err2, null, slim);
|
callback(err2, null, slim);
|
||||||
} else {
|
} else {
|
||||||
logging.debug(rid, "face extracted");
|
logging.debug(rid, "face extracted");
|
||||||
@ -82,12 +80,12 @@ function store_cape(rid, userId, profile, cache_details, callback) {
|
|||||||
if (!err && url) {
|
if (!err && url) {
|
||||||
var cape_hash = get_hash(url);
|
var cape_hash = get_hash(url);
|
||||||
if (cache_details && cache_details.cape === cape_hash) {
|
if (cache_details && cache_details.cape === cape_hash) {
|
||||||
cache.update_timestamp(rid, userId, cape_hash, false, function(cache_err) {
|
cache.update_timestamp(rid, userId, false, function(cache_err) {
|
||||||
callback(cache_err, cape_hash);
|
callback(cache_err, cape_hash);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logging.debug(rid, "new cape hash:", cape_hash);
|
logging.debug(rid, "new cape hash:", cape_hash);
|
||||||
var capepath = path.join(__dirname, "..", config.directories.capes, cape_hash + ".png");
|
var capepath = path.join(config.directories.capes, cape_hash + ".png");
|
||||||
fs.exists(capepath, function(exists) {
|
fs.exists(capepath, function(exists) {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
logging.debug(rid, "cape already exists, not downloading");
|
logging.debug(rid, "cape already exists, not downloading");
|
||||||
@ -95,10 +93,9 @@ function store_cape(rid, userId, profile, cache_details, callback) {
|
|||||||
} else {
|
} else {
|
||||||
networking.get_from(rid, url, function(img, response, net_err) {
|
networking.get_from(rid, url, function(img, response, net_err) {
|
||||||
if (net_err || !img) {
|
if (net_err || !img) {
|
||||||
logging.error(rid, net_err.stack);
|
|
||||||
callback(net_err, null);
|
callback(net_err, null);
|
||||||
} else {
|
} else {
|
||||||
skins.save_image(img, capepath, function(skin_err) {
|
skins.save_image(img, capepath, function(skin_err, skin_img) {
|
||||||
logging.debug(rid, "cape saved");
|
logging.debug(rid, "cape saved");
|
||||||
callback(skin_err, cape_hash);
|
callback(skin_err, cape_hash);
|
||||||
});
|
});
|
||||||
@ -122,15 +119,18 @@ var requests = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function push_request(userId, type, fun) {
|
function push_request(userId, type, fun) {
|
||||||
if (!requests[type][userId]) {
|
// avoid special properties (e.g. 'constructor')
|
||||||
requests[type][userId] = [];
|
var userId_safe = "!" + userId;
|
||||||
|
if (!requests[type][userId_safe]) {
|
||||||
|
requests[type][userId_safe] = [];
|
||||||
}
|
}
|
||||||
requests[type][userId].push(fun);
|
requests[type][userId_safe].push(fun);
|
||||||
}
|
}
|
||||||
|
|
||||||
// calls back all queued requests that match userId and type
|
// calls back all queued requests that match userId and type
|
||||||
function resume(userId, type, err, hash, slim) {
|
function resume(userId, type, err, hash, slim) {
|
||||||
var callbacks = requests[type][userId];
|
var userId_safe = "!" + userId;
|
||||||
|
var callbacks = requests[type][userId_safe];
|
||||||
if (callbacks) {
|
if (callbacks) {
|
||||||
if (callbacks.length > 1) {
|
if (callbacks.length > 1) {
|
||||||
logging.debug(callbacks.length, "simultaneous requests for", userId);
|
logging.debug(callbacks.length, "simultaneous requests for", userId);
|
||||||
@ -145,17 +145,17 @@ function resume(userId, type, err, hash, slim) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// it's still an empty array
|
// it's still an empty array
|
||||||
delete requests[type][userId];
|
delete requests[type][userId_safe];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloads the images for +userId+ while checking the cache
|
// downloads the images for +userId+ while checking the cache
|
||||||
// status based on +cache_details+. +type+ specifies which
|
// status based on +cache_details+. +type+ specifies which
|
||||||
// image type should be called back on
|
// image type should be called back on
|
||||||
// callback: error, image hash
|
// callback: error, image hash, slim
|
||||||
function store_images(rid, userId, cache_details, type, callback) {
|
function store_images(rid, userId, cache_details, type, callback) {
|
||||||
var is_uuid = userId.length > 16;
|
var is_uuid = userId.length > 16;
|
||||||
if (requests[type][userId]) {
|
if (requests[type]["!" + userId]) {
|
||||||
logging.debug(rid, "adding to request queue");
|
logging.debug(rid, "adding to request queue");
|
||||||
push_request(userId, type, callback);
|
push_request(userId, type, callback);
|
||||||
} else {
|
} else {
|
||||||
@ -229,8 +229,9 @@ exp.get_image_hash = function(rid, userId, type, callback) {
|
|||||||
callback(null, (cached_hash ? 1 : 0), cached_hash, cache_details.slim);
|
callback(null, (cached_hash ? 1 : 0), cached_hash, cache_details.slim);
|
||||||
} else {
|
} else {
|
||||||
// download image
|
// download image
|
||||||
if (cache_details) {
|
if (cache_details && cache_details[type] !== undefined) {
|
||||||
logging.debug(rid, "userId cached, but too old");
|
logging.debug(rid, "userId cached, but too old");
|
||||||
|
logging.debug(rid, JSON.stringify(cache_details));
|
||||||
} else {
|
} else {
|
||||||
logging.debug(rid, "userId not cached");
|
logging.debug(rid, "userId not cached");
|
||||||
}
|
}
|
||||||
@ -238,7 +239,7 @@ exp.get_image_hash = function(rid, userId, type, callback) {
|
|||||||
if (store_err) {
|
if (store_err) {
|
||||||
// we might have a cached hash although an error occured
|
// we might have a cached hash although an error occured
|
||||||
// (e.g. Mojang servers not reachable, using outdated hash)
|
// (e.g. Mojang servers not reachable, using outdated hash)
|
||||||
cache.update_timestamp(rid, userId, cached_hash, true, function(err2) {
|
cache.update_timestamp(rid, userId, true, function(err2) {
|
||||||
callback(err2 || store_err, -1, cache_details && cached_hash, slim);
|
callback(err2 || store_err, -1, cache_details && cached_hash, slim);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -256,16 +257,16 @@ exp.get_image_hash = function(rid, userId, type, callback) {
|
|||||||
|
|
||||||
// handles requests for +userId+ avatars with +size+
|
// handles requests for +userId+ avatars with +size+
|
||||||
// callback: error, status, image buffer, skin hash
|
// callback: error, status, image buffer, skin hash
|
||||||
// image is the user's face+helm when helm is true, or the face otherwise
|
// image is the user's face+overlay when overlay is true, or the face otherwise
|
||||||
// for status, see get_image_hash
|
// for status, see get_image_hash
|
||||||
exp.get_avatar = function(rid, userId, helm, size, callback) {
|
exp.get_avatar = function(rid, userId, overlay, size, callback) {
|
||||||
exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash, slim) {
|
exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash, slim) {
|
||||||
if (skin_hash) {
|
if (skin_hash) {
|
||||||
var facepath = path.join(__dirname, "..", config.directories.faces, skin_hash + ".png");
|
var facepath = path.join(config.directories.faces, skin_hash + ".png");
|
||||||
var helmpath = path.join(__dirname, "..", config.directories.helms, skin_hash + ".png");
|
var helmpath = path.join(config.directories.helms, skin_hash + ".png");
|
||||||
var filepath = facepath;
|
var filepath = facepath;
|
||||||
fs.exists(helmpath, function(exists) {
|
fs.exists(helmpath, function(exists) {
|
||||||
if (helm && exists) {
|
if (overlay && exists) {
|
||||||
filepath = helmpath;
|
filepath = helmpath;
|
||||||
}
|
}
|
||||||
skins.resize_img(filepath, size, function(img_err, image) {
|
skins.resize_img(filepath, size, function(img_err, image) {
|
||||||
@ -284,11 +285,11 @@ exp.get_avatar = function(rid, userId, helm, size, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// handles requests for +userId+ skins
|
// handles requests for +userId+ skins
|
||||||
// callback: error, skin hash, status, image buffer
|
// callback: error, skin hash, status, image buffer, slim
|
||||||
exp.get_skin = function(rid, userId, callback) {
|
exp.get_skin = function(rid, userId, callback) {
|
||||||
exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash, slim) {
|
exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash, slim) {
|
||||||
if (skin_hash) {
|
if (skin_hash) {
|
||||||
var skinpath = path.join(__dirname, "..", config.directories.skins, skin_hash + ".png");
|
var skinpath = path.join(config.directories.skins, skin_hash + ".png");
|
||||||
fs.exists(skinpath, function(exists) {
|
fs.exists(skinpath, function(exists) {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
logging.debug(rid, "skin already exists, not downloading");
|
logging.debug(rid, "skin already exists, not downloading");
|
||||||
@ -308,22 +309,22 @@ exp.get_skin = function(rid, userId, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// helper method used for file names
|
// helper method used for file names
|
||||||
// possible returned names based on +helm+ and +body+ are:
|
// possible returned names based on +overlay+ and +body+ are:
|
||||||
// body, bodyhelm, head, headhelm
|
// body, bodyhelm, head, headhelm
|
||||||
function get_type(helm, body) {
|
function get_type(overlay, body) {
|
||||||
var text = body ? "body" : "head";
|
var text = body ? "body" : "head";
|
||||||
return helm ? text + "helm" : text;
|
return overlay ? text + "helm" : text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// handles creations of 3D renders
|
// handles creations of 3D renders
|
||||||
// callback: error, skin hash, image buffer
|
// callback: error, skin hash, image buffer
|
||||||
exp.get_render = function(rid, userId, scale, helm, body, callback) {
|
exp.get_render = function(rid, userId, scale, overlay, body, callback) {
|
||||||
exp.get_skin(rid, userId, function(err, skin_hash, status, img, slim) {
|
exp.get_skin(rid, userId, function(err, skin_hash, status, img, slim) {
|
||||||
if (!skin_hash) {
|
if (!skin_hash) {
|
||||||
callback(err, status, skin_hash, null);
|
callback(err, status, skin_hash, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var renderpath = path.join(__dirname, "..", config.directories.renders, [skin_hash, scale, get_type(helm, body), slim ? "s" : "t"].join("-") + ".png");
|
var renderpath = path.join(config.directories.renders, [skin_hash, scale, get_type(overlay, body), slim ? "s" : "t"].join("-") + ".png");
|
||||||
fs.exists(renderpath, function(exists) {
|
fs.exists(renderpath, function(exists) {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
renders.open_render(rid, renderpath, function(render_err, rendered_img) {
|
renders.open_render(rid, renderpath, function(render_err, rendered_img) {
|
||||||
@ -335,17 +336,14 @@ exp.get_render = function(rid, userId, scale, helm, body, callback) {
|
|||||||
callback(err, 0, skin_hash, null);
|
callback(err, 0, skin_hash, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
renders.draw_model(rid, img, scale, helm, body, slim, function(draw_err, drawn_img) {
|
renders.draw_model(rid, img, scale, overlay, body, slim, function(draw_err, drawn_img) {
|
||||||
if (draw_err) {
|
if (draw_err) {
|
||||||
callback(draw_err, -1, skin_hash, null);
|
callback(draw_err, -1, skin_hash, null);
|
||||||
} else if (!drawn_img) {
|
} else if (!drawn_img) {
|
||||||
callback(null, 0, skin_hash, null);
|
callback(null, 0, skin_hash, null);
|
||||||
} else {
|
} else {
|
||||||
fs.writeFile(renderpath, drawn_img, "binary", function(fs_err) {
|
fs.writeFile(renderpath, drawn_img, "binary", function(fs_err) {
|
||||||
if (fs_err) {
|
callback(fs_err, 2, skin_hash, drawn_img);
|
||||||
logging.error(rid, fs_err.stack);
|
|
||||||
}
|
|
||||||
callback(null, 2, skin_hash, drawn_img);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -362,7 +360,7 @@ exp.get_cape = function(rid, userId, callback) {
|
|||||||
callback(err, null, status, null);
|
callback(err, null, status, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var capepath = path.join(__dirname, "..", config.directories.capes, cape_hash + ".png");
|
var capepath = path.join(config.directories.capes, cape_hash + ".png");
|
||||||
fs.exists(capepath, function(exists) {
|
fs.exists(capepath, function(exists) {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
logging.debug(rid, "cape already exists, not downloading");
|
logging.debug(rid, "cape already exists, not downloading");
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
var cluster = require("cluster");
|
|
||||||
var config = require("../config");
|
var config = require("../config");
|
||||||
|
|
||||||
var exp = {};
|
var exp = {};
|
||||||
@ -18,10 +17,9 @@ function join_args(args) {
|
|||||||
function log(level, args, logger) {
|
function log(level, args, logger) {
|
||||||
logger = logger || console.log;
|
logger = logger || console.log;
|
||||||
var time = config.server.log_time ? new Date().toISOString() + " " : "";
|
var time = config.server.log_time ? new Date().toISOString() + " " : "";
|
||||||
var clid = (cluster.worker && cluster.worker.id || "M");
|
|
||||||
var lines = join_args(args).split("\n");
|
var lines = join_args(args).split("\n");
|
||||||
for (var i = 0, l = lines.length; i < l; i++) {
|
for (var i = 0, l = lines.length; i < l; i++) {
|
||||||
logger(time + clid, level + ":", lines[i]);
|
logger(time, level + ":", lines[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -76,35 +76,56 @@ exp.get_from_options = function(rid, url, options, callback) {
|
|||||||
},
|
},
|
||||||
timeout: config.server.http_timeout,
|
timeout: config.server.http_timeout,
|
||||||
followRedirect: false,
|
followRedirect: false,
|
||||||
encoding: (options.encoding || null),
|
encoding: options.encoding || null,
|
||||||
}, function(error, response, body) {
|
}, function(error, response, body) {
|
||||||
// log url + code + description
|
// log url + code + description
|
||||||
var code = response && response.statusCode;
|
var code = response && response.statusCode;
|
||||||
if (error) {
|
|
||||||
logging.error(rid, url, error);
|
var logfunc = code && code < 405 ? logging.debug : logging.warn;
|
||||||
} else {
|
logfunc(rid, url, code || error && error.code, http_code[code]);
|
||||||
var logfunc = code && code < 405 ? logging.debug : logging.warn;
|
|
||||||
logfunc(rid, url, code, http_code[code]);
|
// not necessarily used
|
||||||
|
var e = new Error(code);
|
||||||
|
e.name = "HTTP";
|
||||||
|
e.code = "HTTPERROR";
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 200:
|
||||||
|
case 301:
|
||||||
|
case 302: // never seen, but mojang might use it in future
|
||||||
|
case 307: // never seen, but mojang might use it in future
|
||||||
|
case 308: // never seen, but mojang might use it in future
|
||||||
|
// these are okay
|
||||||
|
break;
|
||||||
|
case 204: // no content, used like 404 by mojang. making sure it really has no content
|
||||||
|
case 404:
|
||||||
|
// can be cached as null
|
||||||
|
body = null;
|
||||||
|
break;
|
||||||
|
case 429: // this shouldn't usually happen, but occasionally does
|
||||||
|
case 500:
|
||||||
|
case 503:
|
||||||
|
case 504:
|
||||||
|
// we don't want to cache this
|
||||||
|
error = error || e;
|
||||||
|
body = null;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!error) {
|
||||||
|
// Probably 500 or the likes
|
||||||
|
logging.error(rid, "Unexpected response:", code, body);
|
||||||
|
}
|
||||||
|
error = error || e;
|
||||||
|
body = null;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 200 or 301 depending on content type
|
if (body && !body.length) {
|
||||||
if (!error && (code === 200 || code === 301)) {
|
// empty response
|
||||||
// response received successfully
|
body = null;
|
||||||
callback(body, response, null);
|
|
||||||
} else if (error) {
|
|
||||||
callback(body || null, response, error);
|
|
||||||
} else if (code === 404 || code === 204) {
|
|
||||||
// page does not exist
|
|
||||||
callback(null, response, null);
|
|
||||||
} else if (code === 429) {
|
|
||||||
// Too Many Requests exception - code 429
|
|
||||||
// cause error so the image will not be cached
|
|
||||||
callback(body || null, response, (error || "TooManyRequests"));
|
|
||||||
} else {
|
|
||||||
logging.error(rid, " Unknown reply:");
|
|
||||||
logging.error(rid, JSON.stringify(response));
|
|
||||||
callback(body || null, response, error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callback(body, response, error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -161,7 +182,18 @@ exp.get_profile = function(rid, uuid, callback) {
|
|||||||
callback(null, null);
|
callback(null, null);
|
||||||
} else {
|
} else {
|
||||||
exp.get_from_options(rid, session_url + uuid, { encoding: "utf8" }, function(body, response, err) {
|
exp.get_from_options(rid, session_url + uuid, { encoding: "utf8" }, function(body, response, err) {
|
||||||
callback(err || null, (body !== null ? JSON.parse(body) : null));
|
try {
|
||||||
|
body = body ? JSON.parse(body) : null;
|
||||||
|
callback(err || null, body);
|
||||||
|
} catch(e) {
|
||||||
|
if (e instanceof SyntaxError) {
|
||||||
|
logging.warn(rid, "Failed to parse JSON", e);
|
||||||
|
logging.debug(rid, body);
|
||||||
|
callback(err || null, null);
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -187,11 +219,10 @@ exp.save_texture = function(rid, tex_hash, outpath, callback) {
|
|||||||
var textureurl = textures_url + tex_hash;
|
var textureurl = textures_url + tex_hash;
|
||||||
exp.get_from(rid, textureurl, function(img, response, err) {
|
exp.get_from(rid, textureurl, function(img, response, err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logging.error(rid, "error while downloading texture");
|
|
||||||
callback(err, response, null);
|
callback(err, response, null);
|
||||||
} else {
|
} else {
|
||||||
skins.save_image(img, outpath, function(img_err) {
|
skins.save_image(img, outpath, function(img_err, saved_img) {
|
||||||
callback(img_err, response, img);
|
callback(img_err, response, saved_img);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 830 B After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 150 B After Width: | Height: | Size: 150 B |
|
Before Width: | Height: | Size: 997 B After Width: | Height: | Size: 997 B |
|
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 835 B After Width: | Height: | Size: 835 B |
|
Before Width: | Height: | Size: 371 B |
60
lib/public/javascript/crafatar.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
var valid_user_id = /^([0-9a-f-A-F-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.onload = function() {
|
||||||
|
var response = JSON.parse(xhr.responseText);
|
||||||
|
var status = {};
|
||||||
|
response.map(function(elem) {
|
||||||
|
var key = Object.keys(elem)[0];
|
||||||
|
status[key] = elem[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
var textures = status["textures.minecraft.net"] !== "green";
|
||||||
|
var session = status["sessionserver.mojang.com"] !== "green";
|
||||||
|
var skins = status["skins.minecraft.net"] !== "green";
|
||||||
|
var error = null;
|
||||||
|
|
||||||
|
if (textures || session && skins) {
|
||||||
|
error = "all";
|
||||||
|
} else if (skins) {
|
||||||
|
error = "username";
|
||||||
|
} else if (session) {
|
||||||
|
error = "UUID";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
var warn = document.createElement("div");
|
||||||
|
warn.setAttribute("class", "alert alert-warning");
|
||||||
|
warn.setAttribute("role", "alert");
|
||||||
|
warn.innerHTML = "<h5>Mojang issues</h5> Mojang's servers are having trouble <i>right now</i>, this may affect <b>" + error + "</b> requests at Crafatar. <small><a href=\"https://help.mojang.com\" target=\"_blank\">check status</a>";
|
||||||
|
document.querySelector("#alerts").appendChild(warn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", function(event) {
|
||||||
|
var avatars = document.querySelector("#avatar-wrapper");
|
||||||
|
for (var i = 0; i < avatars.children.length; i++) {
|
||||||
|
// shake 'em on down!
|
||||||
|
// https://stackoverflow.com/a/11972692/2517068
|
||||||
|
avatars.appendChild(avatars.children[Math.random() * i | 0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tryit = document.querySelector("#tryit");
|
||||||
|
var tryname = document.querySelector("#tryname");
|
||||||
|
var images = document.querySelectorAll(".tryit");
|
||||||
|
tryit.onsubmit = function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
tryname.value = tryname.value.trim();
|
||||||
|
var value = tryname.value || "853c80ef3c3749fdaa49938b674adae6";
|
||||||
|
if (!valid_user_id.test(value)) {
|
||||||
|
tryname.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (var j = 0; j < images.length; j++) {
|
||||||
|
images[j].src = images[j].dataset.src.replace("$", value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open("GET", "https://status.mojang.com/check", true);
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
5
lib/public/stylesheets/bootstrap.min.css
vendored
Normal file
@ -13,11 +13,6 @@ a {
|
|||||||
color: #00B7FF;
|
color: #00B7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.anchor {
|
|
||||||
position: relative;
|
|
||||||
top: -50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.forkme {
|
a.forkme {
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
@ -25,14 +20,14 @@ a.forkme {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
background: #008000;
|
background: #008000;
|
||||||
box-shadow: 0 0 5px #000;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 3px 40px;
|
padding: 3px 100px;
|
||||||
border: 2px solid #006400;
|
border: 2px solid #006400;
|
||||||
-webkit-transform: rotate(45deg) translate(65px);
|
-webkit-transform: rotate(45deg) translate(108px, -46px);
|
||||||
transform: rotate(45deg) translate(65px);
|
transform: rotate(45deg) translate(108px, -46px);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.forkme:hover {
|
a.forkme:hover {
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@ -40,60 +35,89 @@ a.forkme:hover {
|
|||||||
|
|
||||||
a.sponsor {
|
a.sponsor {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
z-index: 1041;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
height: 40px;
|
margin: 5px;
|
||||||
width: 40px;
|
|
||||||
z-index: 1041;
|
|
||||||
margin: 5px 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container > .navbar-header {
|
.alert {
|
||||||
display: inline-block;
|
font-size: 1rem;
|
||||||
margin: inherit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.navbar-brand.twitter {
|
#documentation .row {
|
||||||
color: #55acee;
|
background: #eee;
|
||||||
font-size: 16px;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.navbar-brand.twitter:before {
|
#documentation .row .col-md-2 {
|
||||||
content: "";
|
text-align: center;
|
||||||
background: url("/images/twitter.png");
|
|
||||||
display: inline-block;
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mark.green {
|
#documentation .row > div {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#try input {
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
padding: 0.3em;
|
||||||
|
line-height: 1.5em;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.tryit {
|
||||||
|
-webkit-filter: drop-shadow(0px 0px 6px);
|
||||||
|
filter: drop-shadow(0px 0px 6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
background: inherit;
|
background: inherit;
|
||||||
color: #008000;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead {
|
mark.green {
|
||||||
font-weight: bold;
|
color: #080;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mark.blue {
|
||||||
|
color: #08f;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[title] {
|
||||||
|
cursor: help;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h6 {
|
||||||
color: #333;
|
font-weight: 200;
|
||||||
font-weight: normal;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h1 {
|
||||||
|
font-size: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h3 {
|
||||||
margin-top: 1em;
|
font-size: 1.3rem;
|
||||||
|
margin-top: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code {
|
.code {
|
||||||
@ -110,186 +134,88 @@ h4 {
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code .example {
|
.jumbotron {
|
||||||
cursor: text;
|
padding: 1em 0 3em;
|
||||||
}
|
|
||||||
|
|
||||||
.code .example:hover {
|
|
||||||
color: #000;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.preview-background {
|
|
||||||
background: #eee;
|
|
||||||
height: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code .example-wrapper .preview, .code .preview-placeholder {
|
|
||||||
display: none;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
position: absolute;
|
|
||||||
bottom: -260px;
|
|
||||||
padding-left: 10px;
|
|
||||||
height: 220px;
|
|
||||||
background-position: 10px center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code .preview-placeholder {
|
|
||||||
display: block;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code .preview-placeholder:hover {
|
|
||||||
/* fixes glitchy blinking */
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code:hover .preview-placeholder {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code .example-wrapper .preview i {
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.code .example-wrapper:hover .preview {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#avatar-example-1:hover .preview {
|
|
||||||
background-image: url("/avatars/jeb_");
|
|
||||||
}
|
|
||||||
#avatar-example-2:hover .preview {
|
|
||||||
background-image: url("/avatars/jeb_?helm");
|
|
||||||
}
|
|
||||||
#avatar-example-3:hover .preview {
|
|
||||||
background-image: url("/avatars/jeb_?size=128");
|
|
||||||
}
|
|
||||||
#avatar-example-4:hover .preview {
|
|
||||||
background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6");
|
|
||||||
}
|
|
||||||
#avatar-example-5:hover .preview {
|
|
||||||
background-image: url("/avatars/0?default=alex");
|
|
||||||
}
|
|
||||||
#avatar-example-6:hover .preview {
|
|
||||||
background-image: url("/avatars/0?default=https%3A%2F%2Fi.imgur.com%2FocJVWAc.png");
|
|
||||||
}
|
|
||||||
|
|
||||||
#render-example-1:hover .preview {
|
|
||||||
background-image: url("/renders/body/jeb_?helm&scale=4");
|
|
||||||
}
|
|
||||||
#render-example-2:hover .preview {
|
|
||||||
background-image: url("/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8");
|
|
||||||
}
|
|
||||||
|
|
||||||
#skin-example-1:hover .preview {
|
|
||||||
background-image: url("/skins/jeb_");
|
|
||||||
}
|
|
||||||
#skin-example-2:hover .preview {
|
|
||||||
background-image: url("/skins/0?default=alex");
|
|
||||||
}
|
|
||||||
|
|
||||||
#cape-example-1:hover .preview {
|
|
||||||
background-image: url("/capes/Dinnerbone");
|
|
||||||
}
|
|
||||||
#cape-example-2:hover .preview {
|
|
||||||
background-image: url("/capes/md_5");
|
|
||||||
}
|
|
||||||
|
|
||||||
img.preload {
|
|
||||||
/*
|
|
||||||
preload hover images
|
|
||||||
browsers don't load 0x0 images
|
|
||||||
*/
|
|
||||||
position: fixed;
|
|
||||||
top: -9999px;
|
|
||||||
left: -9999px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.jumbotron img {
|
.jumbotron img {
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-wrapper {
|
#avatar-wrapper {
|
||||||
height: 64px;
|
height: 64px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
font-size: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 0.5em;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar.jomo {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64")}
|
.avatar.jomo {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64")}
|
||||||
.avatar.jomo:hover {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64&helm")}
|
.avatar.jomo:hover {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.jake_0 {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64")}
|
.avatar.jake_0 {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64")}
|
||||||
.avatar.jake_0:hover {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64&helm")}
|
.avatar.jake_0:hover {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.sk89q {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64")}
|
.avatar.sk89q {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64")}
|
||||||
.avatar.sk89q:hover {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64&helm")}
|
.avatar.sk89q:hover {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.md_5 {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64")}
|
.avatar.md_5 {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64")}
|
||||||
.avatar.md_5:hover {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64&helm")}
|
.avatar.md_5:hover {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.jeb {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64")}
|
.avatar.jeb {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64")}
|
||||||
.avatar.jeb:hover {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&helm")}
|
.avatar.jeb:hover {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.notch {background-image: url("/avatars/069a79f444e94726a5befca90e38aaf5?size=64")}
|
.avatar.notch {background-image: url("/avatars/069a79f444e94726a5befca90e38aaf5?size=64")}
|
||||||
.avatar.notch:hover {background-image: url("/avatars/069a79f444e94726a5befca90e38aaf5?size=64&helm")}
|
.avatar.notch:hover {background-image: url("/avatars/069a79f444e94726a5befca90e38aaf5?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.dinnerbone {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64")}
|
.avatar.dinnerbone {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64")}
|
||||||
.avatar.dinnerbone:hover {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64&helm")}
|
.avatar.dinnerbone:hover {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.ez {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64")}
|
.avatar.ez {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64")}
|
||||||
.avatar.ez:hover {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64&helm")}
|
.avatar.ez:hover {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.grumm {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64")}
|
.avatar.grumm {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64")}
|
||||||
.avatar.grumm:hover {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64&helm")}
|
.avatar.grumm:hover {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.themogmimer {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64")}
|
.avatar.themogmimer {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64")}
|
||||||
.avatar.themogmimer:hover {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64&helm")}
|
.avatar.themogmimer:hover {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.marc {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64")}
|
.avatar.marc {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64")}
|
||||||
.avatar.marc:hover {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64&helm")}
|
.avatar.marc:hover {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.searge {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64")}
|
.avatar.searge {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64")}
|
||||||
.avatar.searge:hover {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64&helm")}
|
.avatar.searge:hover {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.xlson {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64")}
|
.avatar.xlson {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64")}
|
||||||
.avatar.xlson:hover {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64&helm")}
|
.avatar.xlson:hover {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.minecraftchick {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64")}
|
.avatar.minecraftchick {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64")}
|
||||||
.avatar.minecraftchick:hover {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64&helm")}
|
.avatar.minecraftchick:hover {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.kappe {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64")}
|
.avatar.kappe {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64")}
|
||||||
.avatar.kappe:hover {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64&helm")}
|
.avatar.kappe:hover {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.krisjelbring {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64")}
|
.avatar.krisjelbring {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64")}
|
||||||
.avatar.krisjelbring:hover {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64&helm")}
|
.avatar.krisjelbring:hover {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.thinkofdeath {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64")}
|
.avatar.thinkofdeath {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64")}
|
||||||
.avatar.thinkofdeath:hover {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64&helm")}
|
.avatar.thinkofdeath:hover {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.evilseph {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64")}
|
.avatar.evilseph {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64")}
|
||||||
.avatar.evilseph:hover {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64&helm")}
|
.avatar.evilseph:hover {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.mollstam {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64")}
|
.avatar.mollstam {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64")}
|
||||||
.avatar.mollstam:hover {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64&helm")}
|
.avatar.mollstam:hover {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.mollstam {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64")}
|
.avatar.mollstam {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64")}
|
||||||
.avatar.mollstam:hover {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64&helm")}
|
.avatar.mollstam:hover {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64&overlay")}
|
||||||
|
|
||||||
.avatar.flipped {
|
.avatar.flipped {
|
||||||
-webkit-transform: rotate(180deg);
|
-webkit-transform: rotate(180deg);
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
@ -247,12 +247,7 @@ exp.draw_model = function(rid, img, scale, overlay, is_body, slim, callback) {
|
|||||||
// helper method to open a render from +renderpath+
|
// helper method to open a render from +renderpath+
|
||||||
// callback: error, image buffer
|
// callback: error, image buffer
|
||||||
exp.open_render = function(rid, renderpath, callback) {
|
exp.open_render = function(rid, renderpath, callback) {
|
||||||
fs.readFile(renderpath, function(err, buf) {
|
fs.readFile(renderpath, callback);
|
||||||
if (err) {
|
|
||||||
logging.error(rid, "error while opening skin file:", err);
|
|
||||||
}
|
|
||||||
callback(err, buf);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = exp;
|
module.exports = exp;
|
||||||
@ -12,6 +12,9 @@ var human_status = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// print these, but without stacktrace
|
||||||
|
var silent_errors = ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ECONNREFUSED", "HTTPERROR"];
|
||||||
|
|
||||||
// handles HTTP responses
|
// handles HTTP responses
|
||||||
// +request+ a http.IncomingMessage
|
// +request+ a http.IncomingMessage
|
||||||
// +response+ a http.ServerResponse
|
// +response+ a http.ServerResponse
|
||||||
@ -23,31 +26,36 @@ var human_status = {
|
|||||||
// * hash: image hash, required when body is an image
|
// * hash: image hash, required when body is an image
|
||||||
// * err: a possible Error
|
// * err: a possible Error
|
||||||
module.exports = function(request, response, result) {
|
module.exports = function(request, response, result) {
|
||||||
|
|
||||||
response.on("close", function() {
|
|
||||||
logging.warn(request.id, "Connection closed");
|
|
||||||
});
|
|
||||||
|
|
||||||
response.on("finish", function() {
|
|
||||||
logging.log(request.method, request.url.href, request.id, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
|
|
||||||
});
|
|
||||||
|
|
||||||
response.on("error", function(err) {
|
|
||||||
logging.error(request.id, err);
|
|
||||||
});
|
|
||||||
|
|
||||||
// These headers are the same for every response
|
// These headers are the same for every response
|
||||||
var headers = {
|
var headers = {
|
||||||
"Content-Type": (result.body && result.type) || "text/plain",
|
"Content-Type": result.body && result.type || "text/plain",
|
||||||
"Cache-Control": "max-age=" + config.caching.browser + ", public",
|
"Cache-Control": "max-age=" + config.caching.browser + ", public",
|
||||||
"Response-Time": Date.now() - request.start,
|
"Response-Time": Date.now() - request.start,
|
||||||
"X-Request-ID": request.id,
|
"X-Request-ID": request.id,
|
||||||
"Access-Control-Allow-Origin": "*"
|
"Access-Control-Allow-Origin": "*"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
response.on("close", function() {
|
||||||
|
logging.warn(request.id, "Connection closed");
|
||||||
|
});
|
||||||
|
|
||||||
|
response.on("finish", function() {
|
||||||
|
logging.log(request.id, request.method, request.url.href, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
|
||||||
|
});
|
||||||
|
|
||||||
|
response.on("error", function(err) {
|
||||||
|
logging.error(request.id, err);
|
||||||
|
});
|
||||||
|
|
||||||
if (result.err) {
|
if (result.err) {
|
||||||
logging.error(request.id, result.err);
|
var silent = silent_errors.indexOf(result.err.code) !== -1;
|
||||||
logging.error(request.id, result.err.stack);
|
if (result.err.stack && !silent) {
|
||||||
|
logging.error(request.id, result.err.stack);
|
||||||
|
} else if (silent) {
|
||||||
|
logging.warn(request.id, result.err);
|
||||||
|
} else {
|
||||||
|
logging.error(request.id, result.err);
|
||||||
|
}
|
||||||
result.status = -1;
|
result.status = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
var logging = require("../logging");
|
|
||||||
var helpers = require("../helpers");
|
var helpers = require("../helpers");
|
||||||
var config = require("../../config");
|
var config = require("../../config");
|
||||||
var skins = require("../skins");
|
var skins = require("../skins");
|
||||||
@ -8,7 +7,7 @@ var url = require("url");
|
|||||||
|
|
||||||
function handle_default(img_status, userId, size, def, req, err, callback) {
|
function handle_default(img_status, userId, size, def, req, err, callback) {
|
||||||
def = def || skins.default_skin(userId);
|
def = def || skins.default_skin(userId);
|
||||||
if (def !== "steve" && def !== "alex") {
|
if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") {
|
||||||
if (helpers.id_valid(def)) {
|
if (helpers.id_valid(def)) {
|
||||||
// clean up the old URL to match new image
|
// clean up the old URL to match new image
|
||||||
var parsed = req.url;
|
var parsed = req.url;
|
||||||
@ -30,6 +29,10 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// handle steve and alex
|
// handle steve and alex
|
||||||
|
def = def.toLowerCase();
|
||||||
|
if (def.substr(0, 4) !== "mhf_") {
|
||||||
|
def = "mhf_" + def;
|
||||||
|
}
|
||||||
skins.resize_img(path.join(__dirname, "..", "public", "images", def + ".png"), size, function(resize_err, image) {
|
skins.resize_img(path.join(__dirname, "..", "public", "images", def + ".png"), size, function(resize_err, image) {
|
||||||
callback({
|
callback({
|
||||||
status: img_status,
|
status: img_status,
|
||||||
@ -47,7 +50,7 @@ module.exports = function(req, callback) {
|
|||||||
var userId = (req.url.path_list[1] || "").split(".")[0];
|
var userId = (req.url.path_list[1] || "").split(".")[0];
|
||||||
var size = parseInt(req.url.query.size) || config.avatars.default_size;
|
var size = parseInt(req.url.query.size) || config.avatars.default_size;
|
||||||
var def = req.url.query.default;
|
var def = req.url.query.default;
|
||||||
var helm = req.url.query.hasOwnProperty("helm");
|
var overlay = req.url.query.hasOwnProperty("overlay") || req.url.query.hasOwnProperty("helm");
|
||||||
|
|
||||||
// check for extra paths
|
// check for extra paths
|
||||||
if (req.url.path_list.length > 2) {
|
if (req.url.path_list.length > 2) {
|
||||||
@ -80,9 +83,8 @@ module.exports = function(req, callback) {
|
|||||||
userId = userId.replace(/-/g, "");
|
userId = userId.replace(/-/g, "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
helpers.get_avatar(req.id, userId, helm, size, function(err, status, image, hash) {
|
helpers.get_avatar(req.id, userId, overlay, size, function(err, status, image, hash) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logging.error(req.id, err);
|
|
||||||
if (err.code === "ENOENT") {
|
if (err.code === "ENOENT") {
|
||||||
// no such file
|
// no such file
|
||||||
cache.remove_hash(req.id, userId);
|
cache.remove_hash(req.id, userId);
|
||||||
@ -101,7 +103,6 @@ module.exports = function(req, callback) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logging.error(req.id, "error:", e.stack);
|
|
||||||
handle_default(-1, userId, size, def, req, e, callback);
|
handle_default(-1, userId, size, def, req, e, callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1,4 +1,3 @@
|
|||||||
var logging = require("../logging");
|
|
||||||
var helpers = require("../helpers");
|
var helpers = require("../helpers");
|
||||||
var cache = require("../cache");
|
var cache = require("../cache");
|
||||||
|
|
||||||
@ -32,7 +31,6 @@ module.exports = function(req, callback) {
|
|||||||
try {
|
try {
|
||||||
helpers.get_cape(rid, userId, function(err, hash, status, image) {
|
helpers.get_cape(rid, userId, function(err, hash, status, image) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logging.error(rid, err);
|
|
||||||
if (err.code === "ENOENT") {
|
if (err.code === "ENOENT") {
|
||||||
// no such file
|
// no such file
|
||||||
cache.remove_hash(rid, userId);
|
cache.remove_hash(rid, userId);
|
||||||
|
|||||||
@ -1,11 +1,25 @@
|
|||||||
|
var logging = require("../logging");
|
||||||
var config = require("../../config");
|
var config = require("../../config");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var jade = require("jade");
|
var read = require("fs").readFileSync;
|
||||||
|
var ejs = require("ejs");
|
||||||
|
|
||||||
// compile jade
|
var str;
|
||||||
var index = jade.compileFile(path.join(__dirname, "..", "views", "index.jade"));
|
var index;
|
||||||
|
|
||||||
|
function compile() {
|
||||||
|
logging.log("Compiling index page");
|
||||||
|
str = read(path.join(__dirname, "..", "views", "index.html.ejs"), "utf-8");
|
||||||
|
index = ejs.compile(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
compile();
|
||||||
|
|
||||||
module.exports = function(req, callback) {
|
module.exports = function(req, callback) {
|
||||||
|
if (config.server.debug_enabled) {
|
||||||
|
// allow changes without reloading
|
||||||
|
compile();
|
||||||
|
}
|
||||||
var html = index({
|
var html = index({
|
||||||
title: "Crafatar",
|
title: "Crafatar",
|
||||||
domain: "https://" + req.headers.host,
|
domain: "https://" + req.headers.host,
|
||||||
|
|||||||
@ -9,10 +9,10 @@ var url = require("url");
|
|||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
|
||||||
// valid types: head, body
|
// valid types: head, body
|
||||||
// helmet is query param
|
// overlay is query param
|
||||||
function handle_default(rid, scale, helm, body, img_status, userId, size, def, req, err, callback) {
|
function handle_default(rid, scale, overlay, body, img_status, userId, size, def, req, err, callback) {
|
||||||
def = def || skins.default_skin(userId);
|
def = def || skins.default_skin(userId);
|
||||||
if (def !== "steve" && def !== "alex") {
|
if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") {
|
||||||
if (helpers.id_valid(def)) {
|
if (helpers.id_valid(def)) {
|
||||||
// clean up the old URL to match new image
|
// clean up the old URL to match new image
|
||||||
var parsed = req.url;
|
var parsed = req.url;
|
||||||
@ -34,9 +34,13 @@ function handle_default(rid, scale, helm, body, img_status, userId, size, def, r
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// handle steve and alex
|
// handle steve and alex
|
||||||
|
def = def.toLowerCase();
|
||||||
|
if (def.substr(0, 4) !== "mhf_") {
|
||||||
|
def = "mhf_" + def;
|
||||||
|
}
|
||||||
fs.readFile(path.join(__dirname, "..", "public", "images", def + "_skin.png"), function(fs_err, buf) {
|
fs.readFile(path.join(__dirname, "..", "public", "images", def + "_skin.png"), function(fs_err, buf) {
|
||||||
// we render the default skins, but not custom images
|
// we render the default skins, but not custom images
|
||||||
renders.draw_model(rid, buf, scale, helm, body, def === "alex", function(render_err, def_img) {
|
renders.draw_model(rid, buf, scale, overlay, body, def === "mhf_alex", function(render_err, def_img) {
|
||||||
callback({
|
callback({
|
||||||
status: img_status,
|
status: img_status,
|
||||||
body: def_img,
|
body: def_img,
|
||||||
@ -57,7 +61,7 @@ module.exports = function(req, callback) {
|
|||||||
var userId = (req.url.path_list[2] || "").split(".")[0];
|
var userId = (req.url.path_list[2] || "").split(".")[0];
|
||||||
var def = req.url.query.default;
|
var def = req.url.query.default;
|
||||||
var scale = parseInt(req.url.query.scale) || config.renders.default_scale;
|
var scale = parseInt(req.url.query.scale) || config.renders.default_scale;
|
||||||
var helm = req.url.query.hasOwnProperty("helm");
|
var overlay = req.url.query.hasOwnProperty("overlay") || req.url.query.hasOwnProperty("helm");
|
||||||
|
|
||||||
// check for extra paths
|
// check for extra paths
|
||||||
if (req.url.path_list.length > 3) {
|
if (req.url.path_list.length > 3) {
|
||||||
@ -96,9 +100,8 @@ module.exports = function(req, callback) {
|
|||||||
userId = userId.replace(/-/g, "");
|
userId = userId.replace(/-/g, "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
helpers.get_render(rid, userId, scale, helm, body, function(err, status, hash, image) {
|
helpers.get_render(rid, userId, scale, overlay, body, function(err, status, hash, image) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logging.error(rid, err);
|
|
||||||
if (err.code === "ENOENT") {
|
if (err.code === "ENOENT") {
|
||||||
// no such file
|
// no such file
|
||||||
cache.remove_hash(rid, userId);
|
cache.remove_hash(rid, userId);
|
||||||
@ -114,11 +117,10 @@ module.exports = function(req, callback) {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logging.debug(rid, "image not found, using default.");
|
logging.debug(rid, "image not found, using default.");
|
||||||
handle_default(rid, scale, helm, body, status, userId, scale, def, req, err, callback);
|
handle_default(rid, scale, overlay, body, status, userId, scale, def, req, err, callback);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
logging.error(rid, "error:", e.stack);
|
handle_default(rid, scale, overlay, body, -1, userId, scale, def, req, e, callback);
|
||||||
handle_default(rid, scale, helm, body, -1, userId, scale, def, req, e, callback);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1,13 +1,14 @@
|
|||||||
var logging = require("../logging");
|
var logging = require("../logging");
|
||||||
var helpers = require("../helpers");
|
var helpers = require("../helpers");
|
||||||
var skins = require("../skins");
|
var skins = require("../skins");
|
||||||
|
var cache = require("../cache");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var lwip = require("lwip");
|
var lwip = require("lwip");
|
||||||
var url = require("url");
|
var url = require("url");
|
||||||
|
|
||||||
function handle_default(img_status, userId, def, req, err, callback) {
|
function handle_default(img_status, userId, def, req, err, callback) {
|
||||||
def = def || skins.default_skin(userId);
|
def = def || skins.default_skin(userId);
|
||||||
if (def !== "steve" && def !== "alex") {
|
if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") {
|
||||||
if (helpers.id_valid(def)) {
|
if (helpers.id_valid(def)) {
|
||||||
// clean up the old URL to match new image
|
// clean up the old URL to match new image
|
||||||
var parsed = req.url;
|
var parsed = req.url;
|
||||||
@ -29,6 +30,10 @@ function handle_default(img_status, userId, def, req, err, callback) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// handle steve and alex
|
// handle steve and alex
|
||||||
|
def = def.toLowerCase();
|
||||||
|
if (def.substr(0, 4) !== "mhf_") {
|
||||||
|
def = "mhf_" + def;
|
||||||
|
}
|
||||||
lwip.open(path.join(__dirname, "..", "public", "images", def + "_skin.png"), function(lwip_err, image) {
|
lwip.open(path.join(__dirname, "..", "public", "images", def + "_skin.png"), function(lwip_err, image) {
|
||||||
if (image) {
|
if (image) {
|
||||||
image.toBuffer("png", function(buf_err, buffer) {
|
image.toBuffer("png", function(buf_err, buffer) {
|
||||||
@ -78,9 +83,8 @@ module.exports = function(req, callback) {
|
|||||||
userId = userId.replace(/-/g, "");
|
userId = userId.replace(/-/g, "");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
helpers.get_skin(rid, userId, function(err, hash, status, image) {
|
helpers.get_skin(rid, userId, function(err, hash, status, image, slim) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logging.error(req.id, err);
|
|
||||||
if (err.code === "ENOENT") {
|
if (err.code === "ENOENT") {
|
||||||
// no such file
|
// no such file
|
||||||
cache.remove_hash(req.id, userId);
|
cache.remove_hash(req.id, userId);
|
||||||
@ -99,7 +103,6 @@ module.exports = function(req, callback) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
logging.error(rid, "error:", e.stack);
|
|
||||||
handle_default(-1, userId, def, req, e, callback);
|
handle_default(-1, userId, def, req, e, callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -131,18 +131,33 @@ var exp = {};
|
|||||||
exp.boot = function(callback) {
|
exp.boot = function(callback) {
|
||||||
var port = process.env.PORT || 3000;
|
var port = process.env.PORT || 3000;
|
||||||
var bind_ip = process.env.BIND || "0.0.0.0";
|
var bind_ip = process.env.BIND || "0.0.0.0";
|
||||||
logging.log("Server running on http://" + bind_ip + ":" + port + "/");
|
|
||||||
server = http.createServer(requestHandler).listen(port, bind_ip, function() {
|
server = http.createServer(requestHandler).listen(port, bind_ip, function() {
|
||||||
|
logging.log("Server running on http://" + bind_ip + ":" + port + "/");
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// stop accepting new connections,
|
||||||
|
// wait for established connections to finish (30s max),
|
||||||
|
// then exit
|
||||||
|
process.on("SIGTERM", function() {
|
||||||
|
logging.warn("Got SIGTERM, no longer accepting connections!");
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
logging.error("Dropping connections after 30s. Force quit.");
|
||||||
|
process.exit(1);
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
server.close(function() {
|
||||||
|
logging.log("All connections closed, shutting down.");
|
||||||
|
process.exit();
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exp.close = function(callback) {
|
exp.close = function(callback) {
|
||||||
server.close(function() {
|
server.close(callback);
|
||||||
callback();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = exp;
|
module.exports = exp;
|
||||||
|
|||||||
21
lib/skins.js
@ -56,7 +56,7 @@ exp.extract_helm = function(rid, facefile, buffer, outname, callback) {
|
|||||||
} else {
|
} else {
|
||||||
face_helm_img.toBuffer("png", {compression: "none"}, function(buf_err2, face_helm_buffer) {
|
face_helm_img.toBuffer("png", {compression: "none"}, function(buf_err2, face_helm_buffer) {
|
||||||
if (buf_err2) {
|
if (buf_err2) {
|
||||||
callback(buf_err2)
|
callback(buf_err2);
|
||||||
} else {
|
} else {
|
||||||
if (face_helm_buffer.toString() !== face_buffer.toString()) {
|
if (face_helm_buffer.toString() !== face_buffer.toString()) {
|
||||||
face_helm_img.writeFile(outname, function(write_err) {
|
face_helm_img.writeFile(outname, function(write_err) {
|
||||||
@ -101,11 +101,11 @@ exp.resize_img = function(inname, size, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// returns "alex" or "steve" calculated by the +uuid+
|
// returns "mhf_alex" or "mhf_steve" calculated by the +uuid+
|
||||||
exp.default_skin = function(uuid) {
|
exp.default_skin = function(uuid) {
|
||||||
if (uuid.length <= 16) {
|
if (uuid.length <= 16) {
|
||||||
// we can't get the skin type by username
|
// we can't get the skin type by username
|
||||||
return "steve";
|
return "mhf_steve";
|
||||||
} else {
|
} else {
|
||||||
// great thanks to Minecrell for research into Minecraft and Java's UUID hashing!
|
// great thanks to Minecrell for research into Minecraft and Java's UUID hashing!
|
||||||
// https://git.io/xJpV
|
// https://git.io/xJpV
|
||||||
@ -117,7 +117,7 @@ exp.default_skin = function(uuid) {
|
|||||||
parseInt(uuid[15], 16) ^
|
parseInt(uuid[15], 16) ^
|
||||||
parseInt(uuid[23], 16) ^
|
parseInt(uuid[23], 16) ^
|
||||||
parseInt(uuid[31], 16);
|
parseInt(uuid[31], 16);
|
||||||
return lsbs_even ? "alex" : "steve";
|
return lsbs_even ? "mhf_alex" : "mhf_steve";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -126,7 +126,6 @@ exp.default_skin = function(uuid) {
|
|||||||
exp.open_skin = function(rid, skinpath, callback) {
|
exp.open_skin = function(rid, skinpath, callback) {
|
||||||
fs.readFile(skinpath, function(err, buf) {
|
fs.readFile(skinpath, function(err, buf) {
|
||||||
if (err) {
|
if (err) {
|
||||||
logging.error(rid, "error while opening skin file:", err);
|
|
||||||
callback(err, null);
|
callback(err, null);
|
||||||
} else {
|
} else {
|
||||||
callback(null, buf);
|
callback(null, buf);
|
||||||
@ -135,18 +134,18 @@ exp.open_skin = function(rid, skinpath, callback) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// write the image +buffer+ to the +outpath+ file
|
// write the image +buffer+ to the +outpath+ file
|
||||||
// callback: error
|
// the image is stripped down by lwip.
|
||||||
|
// callback: error, image
|
||||||
exp.save_image = function(buffer, outpath, callback) {
|
exp.save_image = function(buffer, outpath, callback) {
|
||||||
lwip.open(buffer, "png", function(err, image) {
|
lwip.open(buffer, "png", function(err, image) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err, image);
|
||||||
} else {
|
} else {
|
||||||
image.batch()
|
image.writeFile(outpath, function(write_err) {
|
||||||
.writeFile(outpath, function(write_err) {
|
|
||||||
if (write_err) {
|
if (write_err) {
|
||||||
callback(write_err);
|
callback(write_err, image);
|
||||||
} else {
|
} else {
|
||||||
callback(null);
|
callback(null, image);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
306
lib/views/index.html.ejs
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Crafatar – A blazing fast API for Minecraft faces!</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="icon" sizes="16x16" type="image/png" href="/favicon.png">
|
||||||
|
<%# FIXME: Use CDN %><link rel="stylesheet" href="/stylesheets/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="/stylesheets/style.css">
|
||||||
|
<meta name="description" content="A blazing fast API for Minecraft faces with support for avatars, skins, and 3D renders!">
|
||||||
|
<meta name="keywords" content="minecraft, avatar, renders, skins, uuid">
|
||||||
|
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
|
||||||
|
<meta name="copyright" content="Crafatar">
|
||||||
|
<meta name="language" content="en-US">
|
||||||
|
<meta name="robots" content="index">
|
||||||
|
<meta property="og:title" content="Crafatar">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="<%= domain %>">
|
||||||
|
<meta property="og:image" content="<%= domain %>/logo.png">
|
||||||
|
<meta property="og:description" content="blazing fast API for Minecraft faces with support for avatars, skins, and 3D renders!">
|
||||||
|
<meta property="og:determiner" content="a">
|
||||||
|
<meta property="og:locale" content="en_US">
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
<meta name="twitter:creator" content="@Crafatar">
|
||||||
|
<script src="/javascript/crafatar.js"></script>
|
||||||
|
</head>
|
||||||
|
<body lang="en-US">
|
||||||
|
<a href="https://github.com/crafatar/crafatar" target="_blank" class="forkme">Fork me on GitHub</a>
|
||||||
|
<a href="https://akliz.net/crafatar" target="_blank" title="Crafatar is sponsored by Akliz" class="sponsor">
|
||||||
|
<img src="/images/akliz.png" alt="Akliz"></a>
|
||||||
|
<div class="jumbotron">
|
||||||
|
<div class="container">
|
||||||
|
<h1>Crafatar</h1>
|
||||||
|
<h2>A blazing fast API for Minecraft faces!</h2>
|
||||||
|
|
||||||
|
<div id="avatar-wrapper">
|
||||||
|
<%# These are shuffled by JS %>
|
||||||
|
<div title="jomo's avatar" class="avatar jomo"></div>
|
||||||
|
<div title="jake_0's avatar" class="avatar jake_0"></div>
|
||||||
|
<div title="sk89q's avatar" class="avatar sk89q"></div>
|
||||||
|
<div title="md_5's avatar" class="avatar md_5"></div>
|
||||||
|
<div title="notch's avatar" class="avatar notch"></div>
|
||||||
|
<div title="jeb's avatar" class="avatar jeb"></div>
|
||||||
|
<div title="dinnerbone's avatar" class="avatar dinnerbone flipped"></div>
|
||||||
|
<div title="ez' avatar" class="avatar ez"></div>
|
||||||
|
<div title="grumm's avatar" class="avatar grumm flipped"></div>
|
||||||
|
<div title="themogmimer's avatar" class="avatar themogmimer"></div>
|
||||||
|
<div title="searge's avatar" class="avatar searge"></div>
|
||||||
|
<div title="xlson's avatar" class="avatar xlson"></div>
|
||||||
|
<div title="krisjelbring's avatar" class="avatar krisjelbring"></div>
|
||||||
|
<div title="minecraftchick's avatar" class="avatar minecraftchick"></div>
|
||||||
|
<div title="kappe's avatar" class="avatar kappe"></div>
|
||||||
|
<div title="marc's avatar" class="avatar marc"></div>
|
||||||
|
<div title="mollstam's avatar" class="avatar mollstam"></div>
|
||||||
|
<div title="evilseph's avatar" class="avatar evilseph"></div>
|
||||||
|
<div title="thinkofdeath's avatar" class="avatar thinkofdeath"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container row">
|
||||||
|
<div class="col-md-9">
|
||||||
|
<section id="documentation">
|
||||||
|
<div id="alerts">
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<h5>Usernames are deprecated!</h5>
|
||||||
|
You should only use usernames for <i>testing</i>.<br>
|
||||||
|
Updates are slower, some features are not available, and it may <strong>break anytime</strong>!<br>
|
||||||
|
<i>We strongly advise you to use UUIDs instead of usernames.</i> <small><a href="#meta-usernames">more info</a></small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="try">
|
||||||
|
<h2><a href="#try">Try it</a></h2>
|
||||||
|
<form id="tryit" action="#">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-11">
|
||||||
|
<input id="tryname" type="text" placeholder="Enter valid username or UUID">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1">
|
||||||
|
<input type="submit" value="Go!">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="avatars">
|
||||||
|
<h2><a href="#avatars">Avatars</a></h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<img class="tryit" data-src="/avatars/$?size=100" src="/avatars/853c80ef3c3749fdaa49938b674adae6?size=100" alt="avatar">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="code">
|
||||||
|
<%= domain %>/avatars/<mark class="green">uuid</mark>
|
||||||
|
</div>
|
||||||
|
<p>Accepted <a href="#meta-parameters">modifiers</a>: <i><b>size</b>, <b>overlay</b>, <b>default</b></i>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="head-renders">
|
||||||
|
<h2><a href="#head-renders">Head Renders</a></h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<img class="tryit" data-src="/renders/head/$" src="/renders/head/853c80ef3c3749fdaa49938b674adae6" alt="head">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="code">
|
||||||
|
<%= domain %>/renders/head/<mark class="green">uuid</mark>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Accepted <a href="#meta-parameters">modifiers</a>: <i><b>scale</b>, <b>overlay</b>, <b>default</b></i>.<br>
|
||||||
|
Please note that renders are still beta and have some issues. New renders are <a href="https://github.com/crafatar/crafatar/pull/134" target="_blank">in progress</a>!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="body-renders">
|
||||||
|
<h2><a href="#body-renders">Body Renders</a></h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<img class="tryit" data-src="/renders/body/$" src="/renders/body/853c80ef3c3749fdaa49938b674adae6" alt="body">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="code">
|
||||||
|
<%= domain %>/renders/body/<mark class="green">uuid</mark>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
Accepted <a href="#meta-parameters">modifiers</a>: <i><b>scale</b>, <b>overlay</b>, <b>default</b></i>.<br>
|
||||||
|
Please note that renders are still beta and have some issues. New renders are <a href="https://github.com/crafatar/crafatar/pull/134" target="_blank">in progress</a>!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="skins">
|
||||||
|
<h2><a href="#skins">Skins</a></h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<img class="tryit" data-src="/skins/$" src="/skins/853c80ef3c3749fdaa49938b674adae6" alt="skin">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="code">
|
||||||
|
<%= domain %>/skins/<mark class="green">uuid</mark>
|
||||||
|
</div>
|
||||||
|
<p>Accepted <a href="#meta-parameters">modifiers</a>: <i><b>default</b></i>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="capes">
|
||||||
|
<h2><a href="#capes">Capes</a></h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<img class="tryit" data-src="/capes/$?default=853c80ef3c3749fdaa49938b674adae6" src="/capes/069a79f444e94726a5befca90e38aaf5?default=853c80ef3c3749fdaa49938b674adae6" alt="cape">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-10">
|
||||||
|
<div class="code">
|
||||||
|
<%= domain %>/capes/<mark class="green">uuid</mark>
|
||||||
|
</div>
|
||||||
|
<p>Accepted <a href="#meta-parameters">modifiers</a>: <i><b>default</b></i>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<section id="meta">
|
||||||
|
<h2><a href="#meta">Meta</a></h2>
|
||||||
|
<p>
|
||||||
|
In the examples above, you can generally use usernames instead of <mark class="green">uuid</mark>. However, apart from the special cases <code><a href="/renders/body/0?default=MHF_Steve" target="_blank">MHF_Steve</a></code> and <code><a href="/renders/body/0?default=MHF_Alex" target="_blank">MHF_Alex</a></code> this is discouraged as explained below.<br>
|
||||||
|
You can append <code>.png</code> or any other file extension to the URL path if you like to, but all images are PNG.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<section id="meta-attribution">
|
||||||
|
<h3><a href="#meta-attribution">Attribution</a></h3>
|
||||||
|
<p>
|
||||||
|
Attribution is not required, but it is <strong>encouraged</strong>.<br>
|
||||||
|
If you want to show some support for this (free!) service, place a notice like this somewhere:
|
||||||
|
<span class="code">
|
||||||
|
Thank you to <a href="https://crafatar.com">Crafatar</a> for providing avatars.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="meta-parameters">
|
||||||
|
<h3><a href="#meta-parameters">URL Parameters</a></h3>
|
||||||
|
<p>
|
||||||
|
You can tweak images using <a href="https://en.wikipedia.org/wiki/Query_string" target="_blank">query string parameters</a>.<br>
|
||||||
|
Example: <code><%= domain %>/avatars/853c80ef3c3749fdaa49938b674adae6<mark class="blue">?</mark><mark class="green">size=4</mark><mark class="blue">&</mark><mark class="green">default=MHF_Steve</mark><mark class="blue">&</mark><mark class="green">overlay</mark></code>
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><b>size</b>: The size for avatars in pixels. <code><%= config.avatars.min_size %> - <%= config.avatars.max_size %></code>
|
||||||
|
<li><b>scale</b>: The scale factor for renders. <code><%= config.renders.min_scale %> - <%= config.renders.max_scale %></code>
|
||||||
|
<li><b>overlay</b>: Apply the <span title="Also known as 'hat' or 'jacket' or 'helm'">overlay</span> to the avatar. Presence of this parameter implies <code>true</code>. This option was previously known as <code>helm</code>.
|
||||||
|
<li>
|
||||||
|
<b>default</b>: The fallback to be used when the requested image cannot be served. You can use a <span title="Make sure to properly percent-encode this!">custom URL</span> or any <mark class="green">uuid</mark>.<br>
|
||||||
|
The option defaults to either <code>MHF_Steve</code> or <code>MHF_Alex</code>, depending on the requested UUID. All usernames default to <code>MHF_Steve</code>.
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="meta-uuids">
|
||||||
|
<h3><a href="#meta-uuids">About UUIDs</a></h3>
|
||||||
|
<p>UUIDs may be any valid Mojang UUID in the blank or dashed format.</p>
|
||||||
|
<p>Malformed UUIDs are rejected.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="meta-usernames">
|
||||||
|
<h3><a href="#meta-usernames">About Usernames</a></h3>
|
||||||
|
<p>
|
||||||
|
We <strong>strongly</strong> advise you to use UUIDs instead of usernames! UUIDs never change while usernames do.<br>
|
||||||
|
Looking up players by username has officially been deprecated by Mojang ever since UUIDs were introduced.<br>
|
||||||
|
Crafatar uses a legacy <span title="Mojang interface we get data from">API</span> which updates very slowly to retrieve skins for usernames.<br>
|
||||||
|
Skins come without any details, including whether a player uses the Alex or Steve skin model.<br>
|
||||||
|
Additionally, Mojang has stated that this legacy interface may be disabled anytime, causing all requests to fail.
|
||||||
|
</p>
|
||||||
|
<p>Malformed usernames are rejected.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="meta-caching">
|
||||||
|
<h3><a href="#meta-caching">About Caching</a></h3>
|
||||||
|
<p>
|
||||||
|
Crafatar checks for skin updates every <%= config.caching.local / 60 %> minutes.<br>
|
||||||
|
Images are cached in your browser for <%= config.caching.browser / 60 %> minutes until a new request to Crafatar is made.<br>
|
||||||
|
In addition, <span title="A CDN and caching proxy">CloudFlare</span> caches up to 2 hours on a per-url basis.
|
||||||
|
</p>
|
||||||
|
<p>When you changed your skin you can try clearing your browser cache to see the change faster.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="meta-cors">
|
||||||
|
<h3><a href="#meta-cors">CORS</a></h3>
|
||||||
|
<p>Crafatar supports Cross-Origin Resource Sharing, so you can make AJAX request from other sites!</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="meta-http-headers">
|
||||||
|
<h3><a href="#meta-http-headers">HTTP Headers</a></h3>
|
||||||
|
<p>
|
||||||
|
Responses come with some custom HTTP headers, useful for debugging.<br>
|
||||||
|
Please note that these headers may be cached by <span title="A CDN and caching proxy">CloudFlare</span>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>X-Storage-Type</b>: Details about how the requested image was stored on the server
|
||||||
|
<ul>
|
||||||
|
<li><b>none</b>: No external requests. Player has no skin (cached)</li>
|
||||||
|
<li><b>cached</b>: No external requests. (skin cached)</li>
|
||||||
|
<li><b>checked</b>: Requested skin details, skin cached. (1 external request)<br>
|
||||||
|
This happens either when the user removed their skin or when it didn't change.</li>
|
||||||
|
<li><b>downloaded</b>: Requested skin details, skin downloaded. (2 external requests)</li>
|
||||||
|
<li><b>server error</b>: This can happen, for example, when Mojang's servers are down.<br>
|
||||||
|
If possible, a cached image is served instead.</li>
|
||||||
|
<li><b>user error</b>: You have done something wrong, such as requesting a malformed uuid.<br>
|
||||||
|
Check the response body for details.</li>
|
||||||
|
</ul>
|
||||||
|
<li>
|
||||||
|
<b>X-Request-ID</b>: The internal ID assigned to this request.<br>
|
||||||
|
If you think something is wrong with your request, please <a href="#contact">contact us</a> and provide this ID.
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="contact">
|
||||||
|
<h2><a href="#contact">Contact</a></h2>
|
||||||
|
<ul>
|
||||||
|
<li>Follow us on twitter <a href="https://twitter.com/crafatar" target="_blank">@crafatar</a></li>
|
||||||
|
<li>Open an issue <a href="https://github.com/crafatar/crafatar/issues" target="_blank">on GitHub</a></li>
|
||||||
|
<li><a href="https://webchat.esper.net/?channels=crafatar" target="_blank">Join us</a> in <a href="irc://irc.esper.net/crafatar">#crafatar</a> on irc.esper.net</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<h4>Popular Crafatar users</h4>
|
||||||
|
<div class="list-group">
|
||||||
|
<a rel="nofollow" href="http://technicpack.net" target="_blank" class="list-group-item">Technic</a>
|
||||||
|
<a rel="nofollow" href="https://hypixel.net" target="_blank" class="list-group-item">Hypixel</a>
|
||||||
|
<a rel="nofollow" href="http://playmindcrack.com" target="_blank" class="list-group-item">Play Mindrack</a>
|
||||||
|
<a rel="nofollow" href="https://shotbow.net" target="_blank" class="list-group-item">Shotbow Network</a>
|
||||||
|
<a rel="nofollow" href="https://namemc.com" target="_blank" class="list-group-item">NameMC</a>
|
||||||
|
<a rel="nofollow" href="https://thenexusmc.com" target="_blank" class="list-group-item">The Nexus</a>
|
||||||
|
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F" target="_blank" class="list-group-item">and many more…</a>
|
||||||
|
</div>
|
||||||
|
<p>See also: <a rel="nofollow" href="https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar" target="_blank">what users say</a> about Crafatar</p>
|
||||||
|
<hr>
|
||||||
|
<h4>Crafatar Tools & Plugins</h4>
|
||||||
|
<div class="list-group">
|
||||||
|
<a rel="nofollow" href="https://xenforo.com/community/resources/associationmc.3232/" target="_blank" class="list-group-item">AssociationMc <i>(XenForo)</i></a>
|
||||||
|
<a rel="nofollow" href="https://github.com/yeahwhat-mc/discourse-yeahwhat" target="_blank" class="list-group-item">Minecraft Heads <i>(Discourse)</i></a>
|
||||||
|
<a rel="nofollow" href="http://vanillaforums.org/addon/crafatar-plugin" target="_blank" class="list-group-item">Crafatar Avatars <i>(Vanilla)</i></a>
|
||||||
|
<a rel="nofollow" href="https://www.spigotmc.org/resources/picture-login.4514/" target="_blank" class="list-group-item">Picture Login <i>(Bukkit)</i></a>
|
||||||
|
<a rel="nofollow" href="https://github.com/qrush/wither" target="_blank" class="list-group-item">wither <i>(Slack)</i></a>
|
||||||
|
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F#other-services-using-crafatar" target="_blank" class="list-group-item">and many more…</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer id="footer">
|
||||||
|
<hr>
|
||||||
|
<div class="container row">
|
||||||
|
<p class="pull-right">Copyright Crafatar <%= new Date().getFullYear() %></p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -1,397 +0,0 @@
|
|||||||
extends layout
|
|
||||||
|
|
||||||
block content
|
|
||||||
.jumbotron
|
|
||||||
.container
|
|
||||||
h1 Crafatar
|
|
||||||
p A blazing fast API for Minecraft faces!
|
|
||||||
.avatar-wrapper
|
|
||||||
.avatar.jomo(title="jomo's avatar")
|
|
||||||
.avatar.jake_0(title="jake_0's avatar")
|
|
||||||
.avatar.sk89q(title="sk89q's avatar")
|
|
||||||
.avatar.md_5(title="md_5's avatar")
|
|
||||||
.avatar.notch(title="notch's avatar")
|
|
||||||
.avatar.jeb(title="jeb's avatar")
|
|
||||||
.avatar.dinnerbone.flipped(title="dinnerbone's avatar")
|
|
||||||
.avatar.ez(title="ez' avatar")
|
|
||||||
.avatar.grumm.flipped(title="grumm's avatar")
|
|
||||||
.avatar.themogmimer(title="themogmimer's avatar")
|
|
||||||
.avatar.searge(title="searge's avatar")
|
|
||||||
.avatar.xlson(title="xlson's avatar")
|
|
||||||
.avatar.krisjelbring(title="krisjelbring's avatar")
|
|
||||||
.avatar.minecraftchick(title="minecraftchick's avatar")
|
|
||||||
.avatar.kappe(title="kappe's avatar")
|
|
||||||
.avatar.marc(title="marc's avatar")
|
|
||||||
.avatar.mollstam(title="mollstam's avatar")
|
|
||||||
.avatar.evilseph(title="evilseph's avatar")
|
|
||||||
.avatar.thinkofdeath(title="thinkofdeath's avatar")
|
|
||||||
|
|
||||||
.container
|
|
||||||
section(id="documentation")
|
|
||||||
h2 Documentation
|
|
||||||
.row
|
|
||||||
section
|
|
||||||
a(id="avatars", class="anchor")
|
|
||||||
a(href="#avatars")
|
|
||||||
h3 Avatars
|
|
||||||
| Replace
|
|
||||||
mark.green userid
|
|
||||||
| with a Mojang <b>UUID</b> or <b>username</b> to get the related head. All images are PNGs.
|
|
||||||
.code
|
|
||||||
| #{domain}/avatars/
|
|
||||||
mark.green userid
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="avatar-parameters" class="anchor")
|
|
||||||
a(href="#avatar-parameters")
|
|
||||||
h4 Avatar Parameters
|
|
||||||
table(class="table table-striped")
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
td parameter
|
|
||||||
td type
|
|
||||||
td default
|
|
||||||
td description
|
|
||||||
tbody
|
|
||||||
tr
|
|
||||||
td size
|
|
||||||
td integer
|
|
||||||
td #{config.avatars.default_size}
|
|
||||||
td The size of the image in pixels, #{config.avatars.min_size} - #{config.avatars.max_size}.
|
|
||||||
tr
|
|
||||||
td default
|
|
||||||
td string
|
|
||||||
td
|
|
||||||
| The standard value is calculated based on the UUID (even = alex, odd = steve).<br>
|
|
||||||
| Usernames always default to steve.
|
|
||||||
td
|
|
||||||
| The image to be served when the userid has no skin.<br>
|
|
||||||
| Valid options are
|
|
||||||
a(href="/avatars/0?default=steve") steve
|
|
||||||
| ,
|
|
||||||
a(href="/avatars/0?default=alex") alex
|
|
||||||
| , or a custom URL.
|
|
||||||
tr
|
|
||||||
td helm
|
|
||||||
td null
|
|
||||||
td
|
|
||||||
td Apply the "second" layer (hat) to the avatar.
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="avatar-examples", class="anchor")
|
|
||||||
a(href="#avatar-examples")
|
|
||||||
h4 Avatar Examples
|
|
||||||
.code
|
|
||||||
#avatar-example-1.example-wrapper
|
|
||||||
.example #{domain}/avatars/jeb_
|
|
||||||
p.preview Jeb's avatar
|
|
||||||
#avatar-example-2.example-wrapper
|
|
||||||
.example #{domain}/avatars/jeb_?helm
|
|
||||||
p.preview Jeb's avatar with helm
|
|
||||||
#avatar-example-3.example-wrapper
|
|
||||||
.example #{domain}/avatars/jeb_?size=128
|
|
||||||
p.preview Jeb's avatar, 128 × 128
|
|
||||||
#avatar-example-4.example-wrapper
|
|
||||||
.example #{domain}/avatars/853c80ef3c3749fdaa49938b674adae6
|
|
||||||
p.preview Jeb's avatar by UUID
|
|
||||||
#avatar-example-5.example-wrapper
|
|
||||||
.example #{domain}/avatars/jeb_?default=alex
|
|
||||||
p.preview Jeb's avatar, or fall back to alex <i>(this example assumes jeb_ does not exist)</i>
|
|
||||||
#avatar-example-6.example-wrapper
|
|
||||||
.example #{domain}/avatars/jeb_?default=https%3A%2F%2Fi.imgur.com%2FocJVWAc.png
|
|
||||||
p.preview
|
|
||||||
| Jeb's avatar, or fall back to a custom image <i>(this example assumes jeb_ does not exist)</i>
|
|
||||||
p.preview-placeholder
|
|
||||||
| Hover over the example URLs above for a preview!
|
|
||||||
.preview-background
|
|
||||||
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="renders" class="anchor")
|
|
||||||
a(href="#renders")
|
|
||||||
h3 3D Renders
|
|
||||||
p
|
|
||||||
| Crafatar also provides support for 3D renders of Minecraft skins.<br>
|
|
||||||
| Please note that <b>this feature is currently beta</b>!<br>
|
|
||||||
| Replace
|
|
||||||
mark.green userid
|
|
||||||
| with a Mojang <b>UUID</b> or <b>username</b> to get a render of the skin.
|
|
||||||
| The <b>head</b> render type returns a render of the skin's head.
|
|
||||||
span.code
|
|
||||||
| #{domain}/renders/head/
|
|
||||||
mark.green userid
|
|
||||||
| The <b>body</b> render returns a render of the entire skin.
|
|
||||||
span.code
|
|
||||||
| #{domain}/renders/body/
|
|
||||||
mark.green userid
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="render-parameters" class="anchor")
|
|
||||||
a(href="#render-parameters")
|
|
||||||
h4 Render Parameters
|
|
||||||
table(class="table table-striped")
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
td parameter
|
|
||||||
td type
|
|
||||||
td default
|
|
||||||
td description
|
|
||||||
tbody
|
|
||||||
tr
|
|
||||||
td scale
|
|
||||||
td integer
|
|
||||||
td #{config.renders.default_scale}. The actual size differs between the type of render.
|
|
||||||
td The scale factor of the image #{config.renders.min_scale} - #{config.renders.max_scale}.
|
|
||||||
tr
|
|
||||||
td helm
|
|
||||||
td null
|
|
||||||
td
|
|
||||||
td Apply the "second" layer (hat) to the avatar.
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="render-examples", class="anchor")
|
|
||||||
a(href="#render-examples")
|
|
||||||
h4 Render Examples
|
|
||||||
.code
|
|
||||||
#render-example-1.example-wrapper
|
|
||||||
.example #{domain}/renders/body/jeb_?helm&scale=4
|
|
||||||
p.preview Jeb's body, with helmet, scale 4
|
|
||||||
#render-example-2.example-wrapper
|
|
||||||
.example #{domain}/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8
|
|
||||||
p.preview Jeb's head, by UUID, scale 8
|
|
||||||
p.preview-placeholder
|
|
||||||
| Hover over the example URLs above for a preview!
|
|
||||||
.preview-background
|
|
||||||
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="skins" class="anchor")
|
|
||||||
a(href="#skins")
|
|
||||||
h3 Skins
|
|
||||||
p
|
|
||||||
| You can also get the full skin file of a player.<br>
|
|
||||||
| Replace
|
|
||||||
mark.green userid
|
|
||||||
| with a Mojang <b>UUID</b> or <b>username</b> to get the related skin.<br>
|
|
||||||
| The user's skin is returned, or the default image is served.<br>
|
|
||||||
| You can use the default parameter here as well.
|
|
||||||
span.code
|
|
||||||
| #{domain}/skins/
|
|
||||||
mark.green userid
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="skin-parameters" class="anchor")
|
|
||||||
a(href="#skin-parameters")
|
|
||||||
h4 Skin Parameters
|
|
||||||
table(class="table table-striped")
|
|
||||||
thead
|
|
||||||
tr
|
|
||||||
td parameter
|
|
||||||
td type
|
|
||||||
td default
|
|
||||||
td description
|
|
||||||
tbody
|
|
||||||
tr
|
|
||||||
td default
|
|
||||||
td string
|
|
||||||
td
|
|
||||||
| The standard value is calculated based on the UUID (even = alex, odd = steve).<br>
|
|
||||||
| Usernames always default to steve.
|
|
||||||
td
|
|
||||||
| The image to be served when the userid has no skin.<br>
|
|
||||||
| Valid options are
|
|
||||||
a(href="/skins/0?default=steve") steve
|
|
||||||
| ,
|
|
||||||
a(href="/skins/0?default=alex") alex
|
|
||||||
| , or a custom URL.
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="skin-examples", class="anchor")
|
|
||||||
a(href="#skin-examples")
|
|
||||||
h4 Skin Examples
|
|
||||||
.code
|
|
||||||
#skin-example-1.example-wrapper
|
|
||||||
.example #{domain}/skins/jeb_
|
|
||||||
p.preview Jeb's skin
|
|
||||||
#skin-example-2.example-wrapper
|
|
||||||
.example #{domain}/skins/jeb_?default=alex
|
|
||||||
p.preview Jeb's skin, or fall back to alex <i>(this example assumes jeb_ does not exist)</i>
|
|
||||||
p.preview-placeholder
|
|
||||||
| Hover over the example URLs above for a preview!
|
|
||||||
.preview-background
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="capes" class="anchor")
|
|
||||||
a(href="#capes")
|
|
||||||
h3 Capes
|
|
||||||
p
|
|
||||||
| A cape endpoint is also available to get the active cape of a user.<br>
|
|
||||||
| Replace
|
|
||||||
mark.green userid
|
|
||||||
| with a Mojang <b>UUID</b> or <b>username</b> to get the related cape.<br>
|
|
||||||
| The user's cape is returned, otherwise a 404 is returned.<br>
|
|
||||||
.code
|
|
||||||
| #{domain}/capes/
|
|
||||||
mark.green userid
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="cape-examples", class="anchor")
|
|
||||||
a(href="#cape-examples")
|
|
||||||
h4 Cape Examples
|
|
||||||
.code
|
|
||||||
#cape-example-1.example-wrapper
|
|
||||||
.example #{domain}/capes/Dinnerbone
|
|
||||||
p.preview Dinnerbone's Cape <i>Mojang capes are not transparent...</i>
|
|
||||||
#cape-example-2.example-wrapper
|
|
||||||
.example #{domain}/capes/md_5
|
|
||||||
p.preview md_5's Cape
|
|
||||||
p.preview-placeholder
|
|
||||||
| Hover over the example URLs above for a preview!
|
|
||||||
.preview-background
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="meta" class="anchor")
|
|
||||||
a(href="#meta")
|
|
||||||
h2 Meta
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="meta-cors" class="anchor")
|
|
||||||
a(href="#meta-cors")
|
|
||||||
h3 CORS
|
|
||||||
p
|
|
||||||
| Crafatar supports CORS so you can make AJAX request from within the browser!
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="meta-http-headers" class="anchor")
|
|
||||||
a(href="#meta-http-headers")
|
|
||||||
h3 HTTP Headers
|
|
||||||
p
|
|
||||||
| Responses come with these HTTP headers, useful for debugging.<br>
|
|
||||||
| Please note that these headers are cached by CloudFlare <small>(CF-Cache-Status: HIT)</small>.
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="meta-response-time" class="anchor")
|
|
||||||
a(href="#meta-response-time")
|
|
||||||
h4 Response-Time
|
|
||||||
p The time, in milliseconds, it took Crafatar to process the request.
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="meta-x-storage-type" class="anchor")
|
|
||||||
a(href="#meta-x-storage-type")
|
|
||||||
h4 X-Storage-Type
|
|
||||||
p Details about how the requested image was stored on the server
|
|
||||||
ul
|
|
||||||
li <b>none</b>: No external requests. Cached: User has no skin.
|
|
||||||
li <b>cached</b>: No external requests. Skin cached and stored locally.
|
|
||||||
li
|
|
||||||
| <b>checked</b>: 1 external request. Skin cached, checked for updates, no skin downloaded.<br>
|
|
||||||
| This happens either when the user removed their skin or when it didn't change.
|
|
||||||
li <b>downloaded</b>: 2 external requests. First request or skin changed, skin downloaded.
|
|
||||||
li
|
|
||||||
| <b>server error</b>: This can happen, for example, when Mojang's servers are down.<br>
|
|
||||||
| If possible, a cached image is served instead.
|
|
||||||
li
|
|
||||||
| <b>user error</b>: You have done something wrong, such as requesting a malformed userid.<br>
|
|
||||||
| Check the response body for details.
|
|
||||||
section
|
|
||||||
a(id="meta-x-request-id" class="anchor")
|
|
||||||
a(href="#meta-x-request-id")
|
|
||||||
h4 X-Request-ID
|
|
||||||
p
|
|
||||||
| The internal ID assigned to this request.<br>
|
|
||||||
| If you think something is wrong with your request, please <a href="#contact">contact us</a> and provide this ID.
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="meta-about-usernames" class="anchor")
|
|
||||||
a(href="#meta-about-usernames")
|
|
||||||
h3 About Usernames
|
|
||||||
p
|
|
||||||
| We strongly advise you to use UUIDs instead of usernames in production.<br>
|
|
||||||
| Usernames are deprecated by Mojang and you should only use usernames for testing.<br>
|
|
||||||
| You don't have to change anything when using UUIDs and someone changes their Username.<br>
|
|
||||||
| Malformed usernames are rejected.
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="meta-about-uuids" class="anchor")
|
|
||||||
a(href="#meta-about-uuids")
|
|
||||||
h3 About UUIDs
|
|
||||||
p
|
|
||||||
| UUIDs may use the blank or dashed format.<br>
|
|
||||||
| Malformed UUIDs are rejected.
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="meta-about-caching" class="anchor")
|
|
||||||
a(href="#meta-about-caching")
|
|
||||||
h3 About Caching
|
|
||||||
p
|
|
||||||
| Crafatar caches skins for #{config.caching.local/60} minutes before checking for skin changes.<br>
|
|
||||||
| Images are cached in your browser for #{config.caching.browser/60} minutes until a new request to Crafatar is made.<br>
|
|
||||||
| When you changed your skin you can try clearing your browser cache to see the change faster.
|
|
||||||
|
|
||||||
|
|
||||||
section
|
|
||||||
a(id="contact" class="anchor")
|
|
||||||
a(href="#contact")
|
|
||||||
h2 Contact
|
|
||||||
ul
|
|
||||||
li Follow us on twitter <a href="https://twitter.com/crafatar" target="_blank">@crafatar</a>
|
|
||||||
li Open an issue <a href="https://github.com/crafatar/crafatar/issues" target="_blank">on GitHub</a>
|
|
||||||
li <a href="https://webchat.esper.net/?channels=crafatar" target="_blank">Join us</a> in #crafatar on irc.esper.net
|
|
||||||
|
|
||||||
footer
|
|
||||||
hr
|
|
||||||
p(class="pull-right") Copyright Crafatar #{new Date().getFullYear()}
|
|
||||||
|
|
||||||
|
|
||||||
// preload hover images
|
|
||||||
img.preload(src="/avatars/020242a17b9441799eff511eea1221da?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/020242a17b9441799eff511eea1221da?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/069a79f444e94726a5befca90e38aaf5?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/0?default=alex", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/0?default=https%3A%2F%2Fi.imgur.com%2FocJVWAc.png", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/7d043c7389524696bfba571c05b6aec0?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/7d043c7389524696bfba571c05b6aec0?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/af74a02d19cb445bb07f6866a861f783?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/af74a02d19cb445bb07f6866a861f783?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/b05881186e75410db2db4d3066b223f7?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/b05881186e75410db2db4d3066b223f7?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/c9b54008fd8047428b238787b5f2401c?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/c9b54008fd8047428b238787b5f2401c?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/e6b5c088068044df9e1b9bf11792291b?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/e6b5c088068044df9e1b9bf11792291b?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64&helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/jeb_", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/jeb_?helm", alt="preloaded image")
|
|
||||||
img.preload(src="/avatars/jeb_?size=128", alt="preloaded image")
|
|
||||||
img.preload(src="/capes/Dinnerbone", alt="preloaded image")
|
|
||||||
img.preload(src="/capes/md_5", alt="preloaded image")
|
|
||||||
img.preload(src="/renders/body/jeb_?helm&scale=4", alt="preloaded image")
|
|
||||||
img.preload(src="/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8", alt="preloaded image")
|
|
||||||
img.preload(src="/skins/0?default=alex", alt="preloaded image")
|
|
||||||
img.preload(src="/skins/jeb_", alt="preloaded image")
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
doctype html
|
|
||||||
html(lang="en")
|
|
||||||
head
|
|
||||||
title= title
|
|
||||||
link(rel="icon", sizes="16x16", type="image/png", href="/favicon.png")
|
|
||||||
link(href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css", rel="stylesheet")
|
|
||||||
link(rel="stylesheet", href="/stylesheets/style.css")
|
|
||||||
|
|
||||||
meta(name="description", content="Crafatar is a blazing fast Minecraft avatar API with support for avatars, skins, and even 3D renders!")
|
|
||||||
meta(name="keywords", content="minecraft, avatar, renders, skins, uuid, username")
|
|
||||||
meta(name="viewport", content="initial-scale=1,maximum-scale=1")
|
|
||||||
|
|
||||||
meta(charset='utf-8')
|
|
||||||
meta(property='og:title', content='Crafatar')
|
|
||||||
meta(property='og:type', content='website')
|
|
||||||
meta(property='og:url', content='https://crafatar.com')
|
|
||||||
meta(property='og:image', content='https://crafatar.com/logo.png')
|
|
||||||
meta(property='og:description', content='A blazing fast Minecraft avatar API with support for avatars, skins, and 3D renders.')
|
|
||||||
|
|
||||||
meta(name='twitter:card', content='summary')
|
|
||||||
meta(name='twitter:creator', content='@Crafatar')
|
|
||||||
body
|
|
||||||
a.forkme(href="https://github.com/crafatar/crafatar", target="_blank") Fork me on GitHub
|
|
||||||
a.sponsor(href="https://akliz.net/crafatar", target="_blank", title="Crafatar is sponsored by Akliz")
|
|
||||||
img(src="/images/akliz.png", alt="Akliz")
|
|
||||||
.navbar.navbar-default.navbar-fixed-top
|
|
||||||
.container
|
|
||||||
.navbar-header
|
|
||||||
a.navbar-brand(href="/") Crafatar
|
|
||||||
a.navbar-brand.twitter(href="https://twitter.com/Crafatar", target="_blank") crafatar
|
|
||||||
block content
|
|
||||||
27
package.json
@ -2,11 +2,15 @@
|
|||||||
"name": "crafatar",
|
"name": "crafatar",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"author": "Jake0oo0",
|
"description": "A blazing fast API for Minecraft faces!",
|
||||||
"description": "A Minecraft avatar service with support for avatars, 1.8 skins, and even 3D renders!",
|
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"name": "jomo"
|
"name": "jomo",
|
||||||
|
"url": "https://github.com/jomo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jake",
|
||||||
|
"url": "https://github.com/Jake0oo0"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -22,7 +26,7 @@
|
|||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "cp 'config.example.js' 'config.js'",
|
"postinstall": "cp 'config.example.js' 'config.js'",
|
||||||
"start": "forever -l logs/log.log -o logs/out.log -e logs/error.log -p ./ -a --minUptime 8000 --spinSleepTime 1500 www.js",
|
"start": "node www.js",
|
||||||
"test": "mocha",
|
"test": "mocha",
|
||||||
"test-travis": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
|
"test-travis": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
|
||||||
},
|
},
|
||||||
@ -32,19 +36,18 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"canvas": "^1.3.4",
|
"canvas": "^1.3.4",
|
||||||
"crc": "~3.3.0",
|
"crc": "~3.3.0",
|
||||||
"forever": "~0.14.2",
|
"ejs": "^2.3.4",
|
||||||
"jade": "~1.11.0",
|
|
||||||
"lwip": "~0.0.7",
|
"lwip": "~0.0.7",
|
||||||
"mime": "~1.3.4",
|
"mime": "~1.3.4",
|
||||||
"node-df": "~0.1.1",
|
"node-df": "crafatar/node-df",
|
||||||
"redis": "~0.12.1",
|
"redis": "~2.0.0",
|
||||||
"request": "~2.58.0",
|
"request": "~2.64.0",
|
||||||
"toobusy-js": "~0.4.2"
|
"toobusy-js": "~0.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"coveralls": "~2.11.2",
|
"coveralls": "~2.11.2",
|
||||||
"istanbul": "~0.3.17",
|
"istanbul": "~0.3.20",
|
||||||
"mocha": "~2.2.5",
|
"mocha": "~2.3.3",
|
||||||
"mocha-lcov-reporter": "~0.0.2"
|
"mocha-lcov-reporter": "~1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,9 +25,9 @@ bulk() {
|
|||||||
trap return INT
|
trap return INT
|
||||||
echo "$ids" | while read id; do
|
echo "$ids" | while read id; do
|
||||||
if [ -z "$async" ]; then
|
if [ -z "$async" ]; then
|
||||||
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?helm"
|
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay"
|
||||||
else
|
else
|
||||||
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?helm" &
|
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay" &
|
||||||
sleep "$interval"
|
sleep "$interval"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
424
test/test.js
@ -52,7 +52,10 @@ var alex_ids = [
|
|||||||
"fffffff1" + "fffffff1" + "fffffff1" + "fffffff0",
|
"fffffff1" + "fffffff1" + "fffffff1" + "fffffff0",
|
||||||
];
|
];
|
||||||
|
|
||||||
var rid = "TestReqID: ";
|
// generates a 12 character random string
|
||||||
|
function rid() {
|
||||||
|
return Math.random().toString(36).substring(2, 14);
|
||||||
|
}
|
||||||
|
|
||||||
function getRandomInt(min, max) {
|
function getRandomInt(min, max) {
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
@ -120,14 +123,14 @@ describe("Crafatar", function() {
|
|||||||
});
|
});
|
||||||
it("should not exist (uuid)", function(done) {
|
it("should not exist (uuid)", function(done) {
|
||||||
var number = getRandomInt(0, 9).toString();
|
var number = getRandomInt(0, 9).toString();
|
||||||
networking.get_profile(rid, Array(33).join(number), function(err, profile) {
|
networking.get_profile(rid(), Array(33).join(number), function(err, profile) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.strictEqual(profile, null);
|
assert.strictEqual(profile, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should not exist (username)", function(done) {
|
it("should not exist (username)", function(done) {
|
||||||
networking.get_username_url(rid, "Steve", 0, function(err, profile) {
|
networking.get_username_url(rid(), "Steve", 0, function(err, profile) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -136,7 +139,7 @@ describe("Crafatar", function() {
|
|||||||
describe("Avatar", function() {
|
describe("Avatar", function() {
|
||||||
it("uuid's account should exist, but skin should not", function(done) {
|
it("uuid's account should exist, but skin should not", function(done) {
|
||||||
// profile "Alex" - hoping it'll never have a skin
|
// profile "Alex" - hoping it'll never have a skin
|
||||||
networking.get_profile(rid, "ec561538f3fd461daff5086b22154bce", function(err, profile) {
|
networking.get_profile(rid(), "ec561538f3fd461daff5086b22154bce", function(err, profile) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.notStrictEqual(profile, null);
|
assert.notStrictEqual(profile, null);
|
||||||
networking.get_uuid_info(profile, "CAPE", function(url) {
|
networking.get_uuid_info(profile, "CAPE", function(url) {
|
||||||
@ -145,15 +148,15 @@ describe("Crafatar", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("Username should default to Steve", function(done) {
|
it("Username should default to MHF_Steve", function(done) {
|
||||||
assert.strictEqual(skins.default_skin("TestUser"), "steve");
|
assert.strictEqual(skins.default_skin("TestUser"), "mhf_steve");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
for (var a in alex_ids) {
|
for (var a in alex_ids) {
|
||||||
var alexid = alex_ids[a];
|
var alexid = alex_ids[a];
|
||||||
(function(alex_id) {
|
(function(alex_id) {
|
||||||
it("UUID " + alex_id + " should default to Alex", function(done) {
|
it("UUID " + alex_id + " should default to MHF_Alex", function(done) {
|
||||||
assert.strictEqual(skins.default_skin(alex_id), "alex");
|
assert.strictEqual(skins.default_skin(alex_id), "mhf_alex");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}(alexid));
|
}(alexid));
|
||||||
@ -161,8 +164,8 @@ describe("Crafatar", function() {
|
|||||||
for (var s in steve_ids) {
|
for (var s in steve_ids) {
|
||||||
var steveid = steve_ids[s];
|
var steveid = steve_ids[s];
|
||||||
(function(steve_id) {
|
(function(steve_id) {
|
||||||
it("UUID " + steve_id + " should default to Steve", function(done) {
|
it("UUID " + steve_id + " should default to MHF_Steve", function(done) {
|
||||||
assert.strictEqual(skins.default_skin(steve_id), "steve");
|
assert.strictEqual(skins.default_skin(steve_id), "mhf_steve");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
}(steveid));
|
}(steveid));
|
||||||
@ -172,7 +175,7 @@ describe("Crafatar", function() {
|
|||||||
it("should time out on uuid info download", function(done) {
|
it("should time out on uuid info download", function(done) {
|
||||||
var original_timeout = config.server.http_timeout;
|
var original_timeout = config.server.http_timeout;
|
||||||
config.server.http_timeout = 1;
|
config.server.http_timeout = 1;
|
||||||
networking.get_profile(rid, "069a79f444e94726a5befca90e38aaf5", function(err, profile) {
|
networking.get_profile(rid(), "069a79f444e94726a5befca90e38aaf5", function(err, profile) {
|
||||||
assert.strictEqual(err.code, "ETIMEDOUT");
|
assert.strictEqual(err.code, "ETIMEDOUT");
|
||||||
config.server.http_timeout = original_timeout;
|
config.server.http_timeout = original_timeout;
|
||||||
done();
|
done();
|
||||||
@ -181,7 +184,7 @@ describe("Crafatar", function() {
|
|||||||
it("should time out on username info download", function(done) {
|
it("should time out on username info download", function(done) {
|
||||||
var original_timeout = config.server.http_timeout;
|
var original_timeout = config.server.http_timeout;
|
||||||
config.server.http_timeout = 1;
|
config.server.http_timeout = 1;
|
||||||
networking.get_username_url(rid, "jomo", 0, function(err, url) {
|
networking.get_username_url(rid(), "jomo", 0, function(err, url) {
|
||||||
assert.strictEqual(err.code, "ETIMEDOUT");
|
assert.strictEqual(err.code, "ETIMEDOUT");
|
||||||
config.server.http_timeout = original_timeout;
|
config.server.http_timeout = original_timeout;
|
||||||
done();
|
done();
|
||||||
@ -190,7 +193,7 @@ describe("Crafatar", function() {
|
|||||||
it("should time out on skin download", function(done) {
|
it("should time out on skin download", function(done) {
|
||||||
var original_timeout = config.http_timeout;
|
var original_timeout = config.http_timeout;
|
||||||
config.server.http_timeout = 1;
|
config.server.http_timeout = 1;
|
||||||
networking.get_from(rid, "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
|
networking.get_from(rid(), "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
|
||||||
assert.strictEqual(error.code, "ETIMEDOUT");
|
assert.strictEqual(error.code, "ETIMEDOUT");
|
||||||
config.server.http_timeout = original_timeout;
|
config.server.http_timeout = original_timeout;
|
||||||
done();
|
done();
|
||||||
@ -198,22 +201,14 @@ describe("Crafatar", function() {
|
|||||||
});
|
});
|
||||||
it("should not find the skin", function(done) {
|
it("should not find the skin", function(done) {
|
||||||
assert.doesNotThrow(function() {
|
assert.doesNotThrow(function() {
|
||||||
networking.get_from(rid, "http://textures.minecraft.net/texture/this-does-not-exist", function(img, response, err) {
|
networking.get_from(rid(), "http://textures.minecraft.net/texture/this-does-not-exist", function(img, response, err) {
|
||||||
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
|
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should ignore file updates on invalid files", function(done) {
|
|
||||||
assert.doesNotThrow(function() {
|
|
||||||
cache.update_timestamp(rid, "0123456789abcdef0123456789abcdef", "invalid-file.png", false, function(err) {
|
|
||||||
assert.ifError(err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it("should not find the file", function(done) {
|
it("should not find the file", function(done) {
|
||||||
skins.open_skin(rid, "non/existent/path", function(err, img) {
|
skins.open_skin(rid(), "non/existent/path", function(err, img) {
|
||||||
assert(err);
|
assert(err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -309,30 +304,37 @@ describe("Crafatar", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not fail on simultaneous requests", function(done) {
|
it("should not fail on simultaneous requests", function(done) {
|
||||||
var url = "http://localhost:3000/avatars/696a82ce41f44b51aa31b8709b8686f0";
|
// do not change "constructor" !
|
||||||
// 10 requests at once
|
// it's a reserved property name, we're testing for that
|
||||||
var requests = 10;
|
var sids = ["696a82ce41f44b51aa31b8709b8686f0", "constructor"];
|
||||||
var finished = 0;
|
|
||||||
function partDone() {
|
for (var j in sids) {
|
||||||
finished++;
|
var id = sids[j];
|
||||||
if (requests === finished) {
|
var url = "http://localhost:3000/avatars/" + id;
|
||||||
done();
|
// 10 requests at once
|
||||||
|
var requests = 10;
|
||||||
|
var finished = 0;
|
||||||
|
function partDone() {
|
||||||
|
finished++;
|
||||||
|
if (requests === finished) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function req() {
|
||||||
|
request.get(url, function(error, res, body) {
|
||||||
|
assert.ifError(error);
|
||||||
|
assert.strictEqual(res.statusCode, 200);
|
||||||
|
assert_headers(res);
|
||||||
|
assert(res.headers.etag);
|
||||||
|
assert.strictEqual(res.headers["content-type"], "image/png");
|
||||||
|
assert(body);
|
||||||
|
partDone();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// make simultanous requests
|
||||||
|
for (var k = 0; k < requests; k++) {
|
||||||
|
req(k);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
function req() {
|
|
||||||
request.get(url, function(error, res, body) {
|
|
||||||
assert.ifError(error);
|
|
||||||
assert.strictEqual(res.statusCode, 200);
|
|
||||||
assert_headers(res);
|
|
||||||
assert(res.headers.etag);
|
|
||||||
assert.strictEqual(res.headers["content-type"], "image/png");
|
|
||||||
assert(body);
|
|
||||||
partDone();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// make simultanous requests
|
|
||||||
for (var j = 0; j < requests; j++) {
|
|
||||||
req(j);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -344,12 +346,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"avatar with non-existent username": {
|
"avatar with non-existent username": {
|
||||||
url: "http://localhost:3000/avatars/0?size=16",
|
url: "http://localhost:3000/avatars/0?size=16",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [2416827277, 1243826040]
|
crc32: [2416827277, 1243826040]
|
||||||
},
|
},
|
||||||
"avatar with non-existent username defaulting to alex": {
|
"avatar with non-existent username defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/avatars/0?size=16&default=alex",
|
url: "http://localhost:3000/avatars/0?size=16&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [862751081, 809395677]
|
crc32: [862751081, 809395677]
|
||||||
},
|
},
|
||||||
"avatar with non-existent username defaulting to username": {
|
"avatar with non-existent username defaulting to username": {
|
||||||
@ -363,39 +365,39 @@ describe("Crafatar", function() {
|
|||||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
|
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
|
||||||
},
|
},
|
||||||
"avatar with non-existent username defaulting to url": {
|
"avatar with non-existent username defaulting to url": {
|
||||||
url: "http://localhost:3000/avatars/0?size=16&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/avatars/0?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"helm avatar with existing username": {
|
"overlay avatar with existing username": {
|
||||||
url: "http://localhost:3000/avatars/jeb_?size=16&helm",
|
url: "http://localhost:3000/avatars/jeb_?size=16&overlay",
|
||||||
etag: '"a846b82963"',
|
etag: '"a846b82963"',
|
||||||
crc32: 646871998
|
crc32: 646871998
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent username": {
|
"overlay avatar with non-existent username": {
|
||||||
url: "http://localhost:3000/avatars/0?size=16&helm",
|
url: "http://localhost:3000/avatars/0?size=16&overlay",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [2416827277, 1243826040]
|
crc32: [2416827277, 1243826040]
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent username defaulting to alex": {
|
"overlay avatar with non-existent username defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/avatars/0?size=16&helm&default=alex",
|
url: "http://localhost:3000/avatars/0?size=16&overlay&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [862751081, 809395677]
|
crc32: [862751081, 809395677]
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent username defaulting to username": {
|
"overlay avatar with non-existent username defaulting to username": {
|
||||||
url: "http://localhost:3000/avatars/0?size=16&helm&default=jeb_",
|
url: "http://localhost:3000/avatars/0?size=16&overlay&default=jeb_",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/avatars/jeb_?size=16&helm="
|
redirect: "/avatars/jeb_?size=16&overlay="
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent username defaulting to uuid": {
|
"overlay avatar with non-existent username defaulting to uuid": {
|
||||||
url: "http://localhost:3000/avatars/0?size=16&helm&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/avatars/0?size=16&overlay&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&helm="
|
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay="
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent username defaulting to url": {
|
"overlay avatar with non-existent username defaulting to url": {
|
||||||
url: "http://localhost:3000/avatars/0?size=16&helm&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/avatars/0?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"avatar with existing uuid": {
|
"avatar with existing uuid": {
|
||||||
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||||
@ -404,12 +406,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"avatar with non-existent uuid": {
|
"avatar with non-existent uuid": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [2416827277, 1243826040]
|
crc32: [2416827277, 1243826040]
|
||||||
},
|
},
|
||||||
"avatar with non-existent uuid defaulting to alex": {
|
"avatar with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=alex",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [862751081, 809395677]
|
crc32: [862751081, 809395677]
|
||||||
},
|
},
|
||||||
"avatar with non-existent uuid defaulting to username": {
|
"avatar with non-existent uuid defaulting to username": {
|
||||||
@ -423,39 +425,39 @@ describe("Crafatar", function() {
|
|||||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
|
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
|
||||||
},
|
},
|
||||||
"avatar with non-existent uuid defaulting to url": {
|
"avatar with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"helm avatar with existing uuid": {
|
"overlay avatar with existing uuid": {
|
||||||
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&helm",
|
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay",
|
||||||
etag: '"a846b82963"',
|
etag: '"a846b82963"',
|
||||||
crc32: 646871998
|
crc32: 646871998
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent uuid": {
|
"overlay avatar with non-existent uuid": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&helm",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [2416827277, 1243826040]
|
crc32: [2416827277, 1243826040]
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent uuid defaulting to alex": {
|
"overlay avatar with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&helm&default=alex",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [862751081, 809395677]
|
crc32: [862751081, 809395677]
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent uuid defaulting to username": {
|
"overlay avatar with non-existent uuid defaulting to username": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/avatars/jeb_?size=16"
|
redirect: "/avatars/jeb_?size=16"
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent uuid defaulting to uuid": {
|
"overlay avatar with non-existent uuid defaulting to uuid": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
|
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16"
|
||||||
},
|
},
|
||||||
"helm avatar with non-existent uuid defaulting to url": {
|
"overlay avatar with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&helm&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"cape with existing username": {
|
"cape with existing username": {
|
||||||
url: "http://localhost:3000/capes/jeb_",
|
url: "http://localhost:3000/capes/jeb_",
|
||||||
@ -467,9 +469,9 @@ describe("Crafatar", function() {
|
|||||||
crc32: 0
|
crc32: 0
|
||||||
},
|
},
|
||||||
"cape with non-existent username defaulting to url": {
|
"cape with non-existent username defaulting to url": {
|
||||||
url: "http://localhost:3000/capes/0?default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/capes/0?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"cape with existing uuid": {
|
"cape with existing uuid": {
|
||||||
url: "http://localhost:3000/capes/853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/capes/853c80ef3c3749fdaa49938b674adae6",
|
||||||
@ -481,9 +483,9 @@ describe("Crafatar", function() {
|
|||||||
crc32: 0
|
crc32: 0
|
||||||
},
|
},
|
||||||
"cape with non-existent uuid defaulting to url": {
|
"cape with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/capes/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/capes/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"skin with existing username": {
|
"skin with existing username": {
|
||||||
url: "http://localhost:3000/skins/jeb_",
|
url: "http://localhost:3000/skins/jeb_",
|
||||||
@ -492,12 +494,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"skin with non-existent username": {
|
"skin with non-existent username": {
|
||||||
url: "http://localhost:3000/skins/0",
|
url: "http://localhost:3000/skins/0",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: 981937087
|
crc32: 981937087
|
||||||
},
|
},
|
||||||
"skin with non-existent username defaulting to alex": {
|
"skin with non-existent username defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/skins/0?default=alex",
|
url: "http://localhost:3000/skins/0?default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: 2298915739
|
crc32: 2298915739
|
||||||
},
|
},
|
||||||
"skin with non-existent username defaulting to username": {
|
"skin with non-existent username defaulting to username": {
|
||||||
@ -511,9 +513,9 @@ describe("Crafatar", function() {
|
|||||||
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16"
|
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16"
|
||||||
},
|
},
|
||||||
"skin with non-existent username defaulting to url": {
|
"skin with non-existent username defaulting to url": {
|
||||||
url: "http://localhost:3000/skins/0?default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/skins/0?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"skin with existing uuid": {
|
"skin with existing uuid": {
|
||||||
url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6",
|
||||||
@ -522,12 +524,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"skin with non-existent uuid": {
|
"skin with non-existent uuid": {
|
||||||
url: "http://localhost:3000/skins/00000000000000000000000000000000",
|
url: "http://localhost:3000/skins/00000000000000000000000000000000",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: 981937087
|
crc32: 981937087
|
||||||
},
|
},
|
||||||
"skin with non-existent uuid defaulting to alex": {
|
"skin with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=alex",
|
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: 2298915739
|
crc32: 2298915739
|
||||||
},
|
},
|
||||||
"skin with non-existent uuid defaulting to username": {
|
"skin with non-existent uuid defaulting to username": {
|
||||||
@ -541,9 +543,9 @@ describe("Crafatar", function() {
|
|||||||
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16"
|
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16"
|
||||||
},
|
},
|
||||||
"skin with non-existent uuid defaulting to url": {
|
"skin with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"head render with existing username": {
|
"head render with existing username": {
|
||||||
url: "http://localhost:3000/renders/head/jeb_?scale=2",
|
url: "http://localhost:3000/renders/head/jeb_?scale=2",
|
||||||
@ -552,12 +554,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"head render with non-existent username": {
|
"head render with non-existent username": {
|
||||||
url: "http://localhost:3000/renders/head/0?scale=2",
|
url: "http://localhost:3000/renders/head/0?scale=2",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [3257141069, 214248305]
|
crc32: [3257141069, 214248305]
|
||||||
},
|
},
|
||||||
"head render with non-existent username defaulting to alex": {
|
"head render with non-existent username defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/head/0?scale=2&default=alex",
|
url: "http://localhost:3000/renders/head/0?scale=2&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [263450586, 3116770561]
|
crc32: [263450586, 3116770561]
|
||||||
},
|
},
|
||||||
"head render with non-existent username defaulting to username": {
|
"head render with non-existent username defaulting to username": {
|
||||||
@ -571,39 +573,39 @@ describe("Crafatar", function() {
|
|||||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?scale=2"
|
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?scale=2"
|
||||||
},
|
},
|
||||||
"head render with non-existent username defaulting to url": {
|
"head render with non-existent username defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/head/0?scale=2&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/renders/head/0?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"helm head render with existing username": {
|
"overlay head render with existing username": {
|
||||||
url: "http://localhost:3000/renders/head/jeb_?scale=2&helm",
|
url: "http://localhost:3000/renders/head/jeb_?scale=2&overlay",
|
||||||
etag: '"a846b82963"',
|
etag: '"a846b82963"',
|
||||||
crc32: [762377383, 1726474987]
|
crc32: [762377383, 1726474987]
|
||||||
},
|
},
|
||||||
"helm head render with non-existent username": {
|
"overlay head render with non-existent username": {
|
||||||
url: "http://localhost:3000/renders/head/0?scale=2&helm",
|
url: "http://localhost:3000/renders/head/0?scale=2&overlay",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [3257141069, 214248305]
|
crc32: [3257141069, 214248305]
|
||||||
},
|
},
|
||||||
"helm head render with non-existent username defaulting to alex": {
|
"overlay head render with non-existent username defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/head/0?scale=2&helm&default=alex",
|
url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [263450586, 3116770561]
|
crc32: [263450586, 3116770561]
|
||||||
},
|
},
|
||||||
"helm head render with non-existent username defaulting to username": {
|
"overlay head render with non-existent username defaulting to username": {
|
||||||
url: "http://localhost:3000/renders/head/0?scale=2&helm&default=jeb_",
|
url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=jeb_",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/renders/head/jeb_?scale=2&helm="
|
redirect: "/renders/head/jeb_?scale=2&overlay="
|
||||||
},
|
},
|
||||||
"helm head render with non-existent username defaulting to uuid": {
|
"overlay head render with non-existent username defaulting to uuid": {
|
||||||
url: "http://localhost:3000/renders/head/0?scale=2&helm&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&helm="
|
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay="
|
||||||
},
|
},
|
||||||
"helm head render with non-existent username defaulting to url": {
|
"overlay head render with non-existent username defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/head/0?scale=2&helm&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"head render with existing uuid": {
|
"head render with existing uuid": {
|
||||||
url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||||
@ -612,12 +614,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"head render with non-existent uuid": {
|
"head render with non-existent uuid": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [3257141069, 214248305]
|
crc32: [3257141069, 214248305]
|
||||||
},
|
},
|
||||||
"head render with non-existent uuid defaulting to alex": {
|
"head render with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=alex",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [263450586, 3116770561]
|
crc32: [263450586, 3116770561]
|
||||||
},
|
},
|
||||||
"head render with non-existent uuid defaulting to username": {
|
"head render with non-existent uuid defaulting to username": {
|
||||||
@ -631,39 +633,39 @@ describe("Crafatar", function() {
|
|||||||
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2"
|
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2"
|
||||||
},
|
},
|
||||||
"head render with non-existent uuid defaulting to url": {
|
"head render with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"helm head render with existing uuid": {
|
"overlay head render with existing uuid": {
|
||||||
url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&helm",
|
url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
|
||||||
etag: '"a846b82963"',
|
etag: '"a846b82963"',
|
||||||
crc32: [762377383]
|
crc32: [762377383]
|
||||||
},
|
},
|
||||||
"helm head render with non-existent uuid": {
|
"overlay head render with non-existent uuid": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [3257141069, 214248305]
|
crc32: [3257141069, 214248305]
|
||||||
},
|
},
|
||||||
"helm head render with non-existent uuid defaulting to alex": {
|
"overlay head render with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=alex",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [263450586, 3116770561]
|
crc32: [263450586, 3116770561]
|
||||||
},
|
},
|
||||||
"helm head with non-existent uuid defaulting to username": {
|
"overlay head with non-existent uuid defaulting to username": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=jeb_",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=jeb_",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/renders/head/jeb_?scale=2&helm="
|
redirect: "/renders/head/jeb_?scale=2&overlay="
|
||||||
},
|
},
|
||||||
"helm head with non-existent uuid defaulting to uuid": {
|
"overlay head with non-existent uuid defaulting to uuid": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&helm="
|
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay="
|
||||||
},
|
},
|
||||||
"helm head render with non-existent uuid defaulting to url": {
|
"overlay head render with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&helm&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"body render with existing username": {
|
"body render with existing username": {
|
||||||
url: "http://localhost:3000/renders/body/jeb_?scale=2",
|
url: "http://localhost:3000/renders/body/jeb_?scale=2",
|
||||||
@ -672,12 +674,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"body render with non-existent username": {
|
"body render with non-existent username": {
|
||||||
url: "http://localhost:3000/renders/body/0?scale=2",
|
url: "http://localhost:3000/renders/body/0?scale=2",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [1046655221, 1620063267]
|
crc32: [1046655221, 1620063267]
|
||||||
},
|
},
|
||||||
"body render with non-existent username defaulting to alex": {
|
"body render with non-existent username defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/body/0?scale=2&default=alex",
|
url: "http://localhost:3000/renders/body/0?scale=2&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [549240598, 3952648540]
|
crc32: [549240598, 3952648540]
|
||||||
},
|
},
|
||||||
"body render with non-existent username defaulting to username": {
|
"body render with non-existent username defaulting to username": {
|
||||||
@ -691,39 +693,39 @@ describe("Crafatar", function() {
|
|||||||
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2"
|
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2"
|
||||||
},
|
},
|
||||||
"body render with non-existent username defaulting to url": {
|
"body render with non-existent username defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/body/0?scale=2&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/renders/body/0?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"helm body render with existing username": {
|
"overlay body render with existing username": {
|
||||||
url: "http://localhost:3000/renders/body/jeb_?scale=2&helm",
|
url: "http://localhost:3000/renders/body/jeb_?scale=2&overlay",
|
||||||
etag: '"a846b82963"',
|
etag: '"a846b82963"',
|
||||||
crc32: [699892097, 2732138694]
|
crc32: [699892097, 2732138694]
|
||||||
},
|
},
|
||||||
"helm body render with non-existent username": {
|
"overlay body render with non-existent username": {
|
||||||
url: "http://localhost:3000/renders/body/0?scale=2&helm",
|
url: "http://localhost:3000/renders/body/0?scale=2&overlay",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [1046655221, 1620063267]
|
crc32: [1046655221, 1620063267]
|
||||||
},
|
},
|
||||||
"helm body render with non-existent username defaulting to alex": {
|
"overlay body render with non-existent username defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/body/0?scale=2&helm&default=alex",
|
url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [549240598, 3952648540]
|
crc32: [549240598, 3952648540]
|
||||||
},
|
},
|
||||||
"helm body render with non-existent username defaulting to username": {
|
"overlay body render with non-existent username defaulting to username": {
|
||||||
url: "http://localhost:3000/renders/body/0?scale=2&helm&default=jeb_",
|
url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=jeb_",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/renders/body/jeb_?scale=2&helm="
|
redirect: "/renders/body/jeb_?scale=2&overlay="
|
||||||
},
|
},
|
||||||
"helm body render with non-existent username defaulting to uuid": {
|
"overlay body render with non-existent username defaulting to uuid": {
|
||||||
url: "http://localhost:3000/renders/body/0?scale=2&helm&default=853c80ef3c3749fdaa49938b674adae6",
|
url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&helm="
|
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay="
|
||||||
},
|
},
|
||||||
"helm body render with non-existent username defaulting to url": {
|
"overlay body render with non-existent username defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/body/0?scale=2&helm&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"body render with existing uuid": {
|
"body render with existing uuid": {
|
||||||
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||||
@ -732,12 +734,12 @@ describe("Crafatar", function() {
|
|||||||
},
|
},
|
||||||
"body render with non-existent uuid": {
|
"body render with non-existent uuid": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [1046655221, 1620063267]
|
crc32: [1046655221, 1620063267]
|
||||||
},
|
},
|
||||||
"body render with non-existent uuid defaulting to alex": {
|
"body render with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=alex",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [549240598, 3952648540]
|
crc32: [549240598, 3952648540]
|
||||||
},
|
},
|
||||||
"body render with non-existent uuid defaulting to username": {
|
"body render with non-existent uuid defaulting to username": {
|
||||||
@ -751,29 +753,29 @@ describe("Crafatar", function() {
|
|||||||
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2"
|
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2"
|
||||||
},
|
},
|
||||||
"body render with non-existent uuid defaulting to url": {
|
"body render with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
"helm body render with existing uuid": {
|
"overlay body render with existing uuid": {
|
||||||
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&helm",
|
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
|
||||||
etag: '"a846b82963"',
|
etag: '"a846b82963"',
|
||||||
crc32: [699892097]
|
crc32: [699892097]
|
||||||
},
|
},
|
||||||
"helm body render with non-existent uuid": {
|
"overlay body render with non-existent uuid": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&helm",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay",
|
||||||
etag: '"steve"',
|
etag: '"mhf_steve"',
|
||||||
crc32: [1046655221, 1620063267]
|
crc32: [1046655221, 1620063267]
|
||||||
},
|
},
|
||||||
"helm body render with non-existent uuid defaulting to alex": {
|
"overlay body render with non-existent uuid defaulting to mhf_alex": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&helm&default=alex",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
|
||||||
etag: '"alex"',
|
etag: '"mhf_alex"',
|
||||||
crc32: [549240598, 3952648540]
|
crc32: [549240598, 3952648540]
|
||||||
},
|
},
|
||||||
"helm body render with non-existent uuid defaulting to url": {
|
"overlay body render with non-existent uuid defaulting to url": {
|
||||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&helm&default=http%3A%2F%2Fexample.com",
|
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||||
crc32: 0,
|
crc32: 0,
|
||||||
redirect: "http://example.com"
|
redirect: "http://example.com/CaseSensitive"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -800,7 +802,7 @@ describe("Crafatar", function() {
|
|||||||
try {
|
try {
|
||||||
assert.ok(matches);
|
assert.ok(matches);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
throw new Error(crc(body) + " != " + location.crc32);
|
throw new Error(crc(body) + " != " + location.crc32 + " | " + body.toString("base64"));
|
||||||
}
|
}
|
||||||
assert.strictEqual(res.headers.location, location.redirect);
|
assert.strictEqual(res.headers.location, location.redirect);
|
||||||
if (location.etag === undefined) {
|
if (location.etag === undefined) {
|
||||||
@ -879,13 +881,13 @@ describe("Crafatar", function() {
|
|||||||
// we have to make sure that we test both a 32x64 and 64x64 skin
|
// we have to make sure that we test both a 32x64 and 64x64 skin
|
||||||
describe("Networking: Render", function() {
|
describe("Networking: Render", function() {
|
||||||
it("should not fail (username, 32x64 skin)", function(done) {
|
it("should not fail (username, 32x64 skin)", function(done) {
|
||||||
helpers.get_render(rid, "md_5", 6, true, true, function(err, hash, img) {
|
helpers.get_render(rid(), "md_5", 6, true, true, function(err, hash, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should not fail (username, 64x64 skin)", function(done) {
|
it("should not fail (username, 64x64 skin)", function(done) {
|
||||||
helpers.get_render(rid, "Jake_0", 6, true, true, function(err, hash, img) {
|
helpers.get_render(rid(), "Jake_0", 6, true, true, function(err, hash, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -894,7 +896,7 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
describe("Networking: Cape", function() {
|
describe("Networking: Cape", function() {
|
||||||
it("should not fail (guaranteed cape)", function(done) {
|
it("should not fail (guaranteed cape)", function(done) {
|
||||||
helpers.get_cape(rid, "Dinnerbone", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -903,13 +905,13 @@ describe("Crafatar", function() {
|
|||||||
before(function() {
|
before(function() {
|
||||||
cache.get_redis().flushall();
|
cache.get_redis().flushall();
|
||||||
});
|
});
|
||||||
helpers.get_cape(rid, "Dinnerbone", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should not be found", function(done) {
|
it("should not be found", function(done) {
|
||||||
helpers.get_cape(rid, "Jake_0", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.strictEqual(img, null);
|
assert.strictEqual(img, null);
|
||||||
done();
|
done();
|
||||||
@ -919,7 +921,7 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
describe("Networking: Skin", function() {
|
describe("Networking: Skin", function() {
|
||||||
it("should not fail", function(done) {
|
it("should not fail", function(done) {
|
||||||
helpers.get_cape(rid, "Jake_0", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -928,7 +930,7 @@ describe("Crafatar", function() {
|
|||||||
before(function() {
|
before(function() {
|
||||||
cache.get_redis().flushall();
|
cache.get_redis().flushall();
|
||||||
});
|
});
|
||||||
helpers.get_cape(rid, "Jake_0", function(err, hash, status, img) {
|
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -949,14 +951,14 @@ describe("Crafatar", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should be downloaded", function(done) {
|
it("should be downloaded", function(done) {
|
||||||
helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
|
helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.strictEqual(status, 2);
|
assert.strictEqual(status, 2);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should be cached", function(done) {
|
it("should be cached", function(done) {
|
||||||
helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
|
helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.strictEqual(status === 0 || status === 1, true);
|
assert.strictEqual(status === 0 || status === 1, true);
|
||||||
done();
|
done();
|
||||||
@ -968,7 +970,7 @@ describe("Crafatar", function() {
|
|||||||
it("should be checked", function(done) {
|
it("should be checked", function(done) {
|
||||||
var original_cache_time = config.caching.local;
|
var original_cache_time = config.caching.local;
|
||||||
config.caching.local = 0;
|
config.caching.local = 0;
|
||||||
helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
|
helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
assert.strictEqual(status, 3);
|
assert.strictEqual(status, 3);
|
||||||
config.caching.local = original_cache_time;
|
config.caching.local = original_cache_time;
|
||||||
@ -980,7 +982,7 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
describe("Networking: Skin", function() {
|
describe("Networking: Skin", function() {
|
||||||
it("should not fail (uuid)", function(done) {
|
it("should not fail (uuid)", function(done) {
|
||||||
helpers.get_skin(rid, id, function(err, hash, status, img) {
|
helpers.get_skin(rid(), id, function(err, hash, status, img) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -989,13 +991,13 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
describe("Networking: Render", function() {
|
describe("Networking: Render", function() {
|
||||||
it("should not fail (full body)", function(done) {
|
it("should not fail (full body)", function(done) {
|
||||||
helpers.get_render(rid, id, 6, true, true, function(err, hash, img) {
|
helpers.get_render(rid(), id, 6, true, true, function(err, hash, img) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should not fail (only head)", function(done) {
|
it("should not fail (only head)", function(done) {
|
||||||
helpers.get_render(rid, id, 6, true, false, function(err, hash, img) {
|
helpers.get_render(rid(), id, 6, true, false, function(err, hash, img) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -1004,7 +1006,7 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
describe("Networking: Cape", function() {
|
describe("Networking: Cape", function() {
|
||||||
it("should not fail (possible cape)", function(done) {
|
it("should not fail (possible cape)", function(done) {
|
||||||
helpers.get_cape(rid, id, function(err, hash, status, img) {
|
helpers.get_cape(rid(), id, function(err, hash, status, img) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -1019,18 +1021,18 @@ describe("Crafatar", function() {
|
|||||||
|
|
||||||
if (id_type === "uuid") {
|
if (id_type === "uuid") {
|
||||||
it("uuid should be rate limited", function(done) {
|
it("uuid should be rate limited", function(done) {
|
||||||
networking.get_profile(rid, id, function() {
|
networking.get_profile(rid(), id, function() {
|
||||||
networking.get_profile(rid, id, function(err, profile) {
|
networking.get_profile(rid(), id, function(err, profile) {
|
||||||
assert.strictEqual(err, "TooManyRequests");
|
assert.strictEqual(err.toString(), "HTTP: 429");
|
||||||
assert.strictEqual(profile.error, "TooManyRequestsException");
|
assert.strictEqual(profile, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
it("username should NOT be rate limited (username)", function(done) {
|
it("username should NOT be rate limited (username)", function(done) {
|
||||||
helpers.get_avatar(rid, id, false, 160, function() {
|
helpers.get_avatar(rid(), id, false, 160, function() {
|
||||||
helpers.get_avatar(rid, id, false, 160, function(err, status, image) {
|
helpers.get_avatar(rid(), id, false, 160, function(err, status, image) {
|
||||||
assert.strictEqual(err, null);
|
assert.strictEqual(err, null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|||||||
21
www.js
@ -1,25 +1,12 @@
|
|||||||
var logging = require("./lib/logging");
|
var logging = require("./lib/logging");
|
||||||
var cleaner = require("./lib/cleaner");
|
var cleaner = require("./lib/cleaner");
|
||||||
var config = require("./config");
|
var config = require("./config");
|
||||||
var cluster = require("cluster");
|
|
||||||
|
|
||||||
process.on("uncaughtException", function (err) {
|
process.on("uncaughtException", function(err) {
|
||||||
logging.error("uncaughtException", err.stack || err.toString());
|
logging.error("uncaughtException", err.stack || err.toString());
|
||||||
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (cluster.isMaster) {
|
setInterval(cleaner.run, config.cleaner.interval * 1000);
|
||||||
var cores = config.server.clusters || require("os").cpus().length;
|
|
||||||
logging.log("Starting", cores + " worker" + (cores > 1 ? "s" : ""));
|
|
||||||
for (var i = 0; i < cores; i++) {
|
|
||||||
cluster.fork();
|
|
||||||
}
|
|
||||||
|
|
||||||
cluster.on("exit", function (worker) {
|
require("./lib/server.js").boot();
|
||||||
logging.error("Worker #" + worker.id + " died. Rebooting a new one.");
|
|
||||||
setTimeout(cluster.fork, 100);
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(cleaner.run, config.cleaner.interval * 1000);
|
|
||||||
} else {
|
|
||||||
require("./lib/server.js").boot();
|
|
||||||
}
|
|
||||||