mirror of
https://github.com/azures04/crafatar.git
synced 2026-03-21 23:41:18 +01:00
Merge branch 'master' of github.com:Jake0oo0/crafatar
This commit is contained in:
commit
480385559d
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ node_modules/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.rdb
|
*.rdb
|
||||||
coverage/
|
coverage/
|
||||||
|
modules/config.js
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "0.10"
|
- "0.10"
|
||||||
|
before_script:
|
||||||
|
- cp "modules/config.example.js" "modules/config.js"
|
||||||
notifications:
|
notifications:
|
||||||
irc:
|
irc:
|
||||||
channels:
|
channels:
|
||||||
- "irc.esper.net#spongy"
|
- "irc.esper.net#spongy"
|
||||||
skip_join: true
|
skip_join: true
|
||||||
services:
|
services:
|
||||||
- redis-server
|
- redis-server
|
||||||
skip_join: true
|
|
||||||
2
Procfile
2
Procfile
@ -1 +1 @@
|
|||||||
web: npm start
|
web: cp "modules/config.example.js" "modules/config.js" && npm start
|
||||||
@ -11,10 +11,15 @@ Image manipulation is done by [lwip](https://github.com/EyalAr/lwip)
|
|||||||
|
|
||||||
See the [API Usage](https://crafatar.com)
|
See the [API Usage](https://crafatar.com)
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
You can [join us](https://webchat.esper.net/?channels=spongy) in #spongy on irc.esper.net.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
* Clone the repository
|
* Clone the repository
|
||||||
* `npm install`
|
* `npm install`
|
||||||
* `redis-server`
|
* `redis-server`
|
||||||
|
* `cp "modules/config.example.js" "modules/config.js"`
|
||||||
* `npm start`
|
* `npm start`
|
||||||
* Access [http://localhost:3000](http://localhost:3000)
|
* Access [http://localhost:3000](http://localhost:3000)
|
||||||
2
app.js
2
app.js
@ -20,7 +20,7 @@ app.use(cookieParser());
|
|||||||
app.use(express.static(path.join(__dirname, 'public')));
|
app.use(express.static(path.join(__dirname, 'public')));
|
||||||
|
|
||||||
app.use('/', routes);
|
app.use('/', routes);
|
||||||
app.use('/avatars', avatars);
|
app.use('/', avatars);
|
||||||
|
|
||||||
|
|
||||||
// catch 404 and forward to error handler
|
// catch 404 and forward to error handler
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
var logging = require('./logging');
|
||||||
var config = require("./config");
|
var config = require("./config");
|
||||||
var redis = null;
|
var redis = null;
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
@ -5,7 +6,7 @@ var fs = require("fs");
|
|||||||
// sets up redis connection
|
// sets up redis connection
|
||||||
// flushes redis when running on heroku (files aren't kept between pushes)
|
// flushes redis when running on heroku (files aren't kept between pushes)
|
||||||
function connect_redis() {
|
function connect_redis() {
|
||||||
console.log("connecting to redis...");
|
logging.log("connecting to redis...");
|
||||||
if (process.env.REDISCLOUD_URL) {
|
if (process.env.REDISCLOUD_URL) {
|
||||||
var redisURL = require("url").parse(process.env.REDISCLOUD_URL);
|
var redisURL = require("url").parse(process.env.REDISCLOUD_URL);
|
||||||
redis = require("redis").createClient(redisURL.port, redisURL.hostname);
|
redis = require("redis").createClient(redisURL.port, redisURL.hostname);
|
||||||
@ -14,17 +15,17 @@ function connect_redis() {
|
|||||||
redis = require("redis").createClient();
|
redis = require("redis").createClient();
|
||||||
}
|
}
|
||||||
redis.on("ready", function() {
|
redis.on("ready", function() {
|
||||||
console.log("Redis connection established.");
|
logging.log("Redis connection established.");
|
||||||
if(process.env.HEROKU) {
|
if(process.env.HEROKU) {
|
||||||
console.log("Running on heroku, flushing redis");
|
logging.log("Running on heroku, flushing redis");
|
||||||
redis.flushall();
|
redis.flushall();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
redis.on("error", function (err) {
|
redis.on("error", function (err) {
|
||||||
console.error(err);
|
logging.error(err);
|
||||||
});
|
});
|
||||||
redis.on("end", function () {
|
redis.on("end", function () {
|
||||||
console.warn("Redis connection lost!");
|
logging.warn("Redis connection lost!");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,11 +39,11 @@ function update_file_date(hash) {
|
|||||||
var date = new Date();
|
var date = new Date();
|
||||||
fs.utimes(path, date, date, function(err){
|
fs.utimes(path, date, date, function(err){
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
logging.error(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("Tried to update " + path + " date, but it doesn't exist");
|
logging.error("Tried to update " + path + " date, but it doesn't exist");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -56,7 +57,7 @@ exp.get_redis = function() {
|
|||||||
|
|
||||||
// sets the timestamp for +uuid+ and its face file's date to now
|
// sets the timestamp for +uuid+ and its face file's date to now
|
||||||
exp.update_timestamp = function(uuid, hash) {
|
exp.update_timestamp = function(uuid, hash) {
|
||||||
console.log(uuid + " cache: updating timestamp");
|
logging.log(uuid + " cache: updating timestamp");
|
||||||
var time = new Date().getTime();
|
var time = new Date().getTime();
|
||||||
redis.hmset(uuid, "t", time);
|
redis.hmset(uuid, "t", time);
|
||||||
update_file_date(hash);
|
update_file_date(hash);
|
||||||
@ -64,7 +65,7 @@ exp.update_timestamp = function(uuid, hash) {
|
|||||||
|
|
||||||
// create the key +uuid+, store +hash+ and time
|
// create the key +uuid+, store +hash+ and time
|
||||||
exp.save_hash = function(uuid, hash) {
|
exp.save_hash = function(uuid, hash) {
|
||||||
console.log(uuid + " cache: saving hash");
|
logging.log(uuid + " cache: saving hash");
|
||||||
var time = new Date().getTime();
|
var time = new Date().getTime();
|
||||||
redis.hmset(uuid, "h", hash, "t", time);
|
redis.hmset(uuid, "h", hash, "t", time);
|
||||||
};
|
};
|
||||||
|
|||||||
13
modules/config.example.js
Normal file
13
modules/config.example.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
var config = {
|
||||||
|
min_size: 1, // < 1 will (obviously) cause crash
|
||||||
|
max_size: 512, // too big values might lead to slow response time or DoS
|
||||||
|
default_size: 160, // size to be used when no size given
|
||||||
|
local_cache_time: 3600, // seconds until we will check if the image changed. should be > 60 to prevent mojang 429 response
|
||||||
|
browser_cache_time: 3600, // seconds until browser will request image again
|
||||||
|
http_timeout: 1000, // ms until connection to mojang is dropped
|
||||||
|
faces_dir: 'skins/faces/', // directory where faces are kept. should have trailing '/'
|
||||||
|
helms_dir: 'skins/helms/', // directory where helms are kept. should have trailing '/'
|
||||||
|
debug_enabled: false // enables logging.debug
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
@ -4,9 +4,10 @@ var config = {
|
|||||||
default_size: 160, // size to be used when no size given
|
default_size: 160, // size to be used when no size given
|
||||||
local_cache_time: 3600, // seconds until we will check if the image changed. should be > 60 to prevent mojang 429 response
|
local_cache_time: 3600, // seconds until we will check if the image changed. should be > 60 to prevent mojang 429 response
|
||||||
browser_cache_time: 3600, // seconds until browser will request image again
|
browser_cache_time: 3600, // seconds until browser will request image again
|
||||||
http_timeout: 1000, // ms until connection to mojang is dropped
|
http_timeout: 3000, // ms until connection to mojang is dropped
|
||||||
faces_dir: 'skins/faces/', // directory where faces are kept. should have trailing '/'
|
faces_dir: 'skins/faces/', // directory where faces are kept. should have trailing '/'
|
||||||
helms_dir: 'skins/helms/' // directory where helms are kept. should have trailing '/'
|
helms_dir: 'skins/helms/', // directory where helms are kept. should have trailing '/'
|
||||||
|
debug_enabled: true // enables logging.debug
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
@ -1,11 +1,12 @@
|
|||||||
var networking = require('./networking');
|
var networking = require('./networking');
|
||||||
|
var logging = require('./logging');
|
||||||
var config = require('./config');
|
var config = require('./config');
|
||||||
var cache = require('./cache');
|
var cache = require('./cache');
|
||||||
var skins = require('./skins');
|
var skins = require('./skins');
|
||||||
|
|
||||||
// 0098cb60-fa8e-427c-b299-793cbd302c9a
|
// 0098cb60-fa8e-427c-b299-793cbd302c9a
|
||||||
var valid_uuid = /^([0-9a-f-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
|
var valid_uuid = /^([0-9a-f-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
|
||||||
var hash_pattern = /([^\/]+)(?=\.\w{0,16}$)|((?:[a-z][a-z]*[0-9]+[a-z0-9]*))/;
|
var hash_pattern = /[0-9a-f]+$/;
|
||||||
|
|
||||||
function get_hash(url) {
|
function get_hash(url) {
|
||||||
return hash_pattern.exec(url)[0].toLowerCase();
|
return hash_pattern.exec(url)[0].toLowerCase();
|
||||||
@ -14,40 +15,27 @@ function get_hash(url) {
|
|||||||
// requests skin for +uuid+ and extracts face/helm if image hash in +details+ changed
|
// requests skin for +uuid+ and extracts face/helm if image hash in +details+ changed
|
||||||
// callback contains error, image hash
|
// callback contains error, image hash
|
||||||
function store_images(uuid, details, callback) {
|
function store_images(uuid, details, callback) {
|
||||||
// get profile for +uuid+
|
// get skin_url for +uuid+
|
||||||
networking.get_profile(uuid, function(err, profile) {
|
networking.get_skin_url(uuid, function(err, skin_url) {
|
||||||
if (err === 0) {
|
if (err) {
|
||||||
// uuid does not exist
|
|
||||||
cache.save_hash(uuid, null);
|
|
||||||
callback(null, null);
|
|
||||||
} else if (err) {
|
|
||||||
callback(err, null);
|
callback(err, null);
|
||||||
} else {
|
} else {
|
||||||
var skinurl = null;
|
if (skin_url) {
|
||||||
|
logging.log(uuid + " " + skin_url);
|
||||||
// Username handling
|
|
||||||
if (uuid.length <= 16) {
|
|
||||||
skinurl = "https://skins.minecraft.net/MinecraftSkins/" + uuid + ".png";
|
|
||||||
console.log(uuid + " is a username");
|
|
||||||
} else {
|
|
||||||
skinurl = skin_url(profile);
|
|
||||||
}
|
|
||||||
if (skinurl) {
|
|
||||||
console.log(uuid + " " + skinurl);
|
|
||||||
// set file paths
|
// set file paths
|
||||||
var hash = get_hash(skinurl);
|
var hash = get_hash(skin_url);
|
||||||
if (details && details.hash == hash) {
|
if (details && details.hash == hash) {
|
||||||
// hash hasn't changed
|
// hash hasn't changed
|
||||||
console.log(uuid + " hash has not changed");
|
logging.log(uuid + " hash has not changed");
|
||||||
cache.update_timestamp(uuid, hash);
|
cache.update_timestamp(uuid, hash);
|
||||||
callback(null, hash);
|
callback(null, hash);
|
||||||
} else {
|
} else {
|
||||||
// hash has changed
|
// hash has changed
|
||||||
console.log(uuid + " new hash: " + hash);
|
logging.log(uuid + " new hash: " + hash);
|
||||||
var facepath = __dirname + '/../' + config.faces_dir + hash + ".png";
|
var facepath = __dirname + '/../' + config.faces_dir + hash + ".png";
|
||||||
var helmpath = __dirname + '/../' + config.helms_dir + hash + ".png";
|
var helmpath = __dirname + '/../' + config.helms_dir + hash + ".png";
|
||||||
// download skin, extract face/helm
|
// download skin, extract face/helm
|
||||||
networking.skin_file(skinurl, facepath, helmpath, function(err) {
|
networking.skin_file(skin_url, facepath, helmpath, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err, null);
|
callback(err, null);
|
||||||
} else {
|
} else {
|
||||||
@ -65,53 +53,6 @@ function store_images(uuid, details, callback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// exracts the skin url of a +profile+ object
|
|
||||||
// returns null when no url found (user has no skin)
|
|
||||||
function skin_url(profile) {
|
|
||||||
var url = null;
|
|
||||||
if (profile && profile.properties) {
|
|
||||||
profile.properties.forEach(function(prop) {
|
|
||||||
if (prop.name == 'textures') {
|
|
||||||
var json = Buffer(prop.value, 'base64').toString();
|
|
||||||
var props = JSON.parse(json);
|
|
||||||
url = props && props.textures && props.textures.SKIN && props.textures.SKIN.url || null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// decides whether to get an image from disk or to download it
|
|
||||||
// callback contains error, status, hash
|
|
||||||
// the status gives information about how the image was received
|
|
||||||
// -1: error
|
|
||||||
// 0: cached as null
|
|
||||||
// 1: found on disk
|
|
||||||
// 2: profile requested/found, skin downloaded from mojang servers
|
|
||||||
// 3: profile requested/found, but it has not changed or no skin
|
|
||||||
function get_image_hash(uuid, callback) {
|
|
||||||
cache.get_details(uuid, function(err, details) {
|
|
||||||
if (err) {
|
|
||||||
callback(err, -1, null);
|
|
||||||
} else {
|
|
||||||
if (details && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
|
|
||||||
// uuid known + recently updated
|
|
||||||
console.log(uuid + " uuid known & recently updated");
|
|
||||||
callback(null, (details.hash ? 1 : 0), details.hash);
|
|
||||||
} else {
|
|
||||||
console.log(uuid + " uuid not known or too old");
|
|
||||||
store_images(uuid, details, function(err, hash) {
|
|
||||||
if (err) {
|
|
||||||
callback(err, -1, details && details.hash);
|
|
||||||
} else {
|
|
||||||
console.log(uuid + " hash: " + hash);
|
|
||||||
callback(null, (hash != (details && details.hash) ? 2 : 3), hash);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var exp = {};
|
var exp = {};
|
||||||
|
|
||||||
@ -121,13 +62,49 @@ exp.uuid_valid = function(uuid) {
|
|||||||
return valid_uuid.test(uuid);
|
return valid_uuid.test(uuid);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// decides whether to get an image from disk or to download it
|
||||||
|
// callback contains error, status, hash
|
||||||
|
// the status gives information about how the image was received
|
||||||
|
// -1: "error"
|
||||||
|
// 0: "none" - cached as null
|
||||||
|
// 1: "cached" - found on disk
|
||||||
|
// 2: "downloaded" - profile downloaded, skin downloaded from mojang servers
|
||||||
|
// 3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
|
||||||
|
exp.get_image_hash = function(uuid, callback) {
|
||||||
|
cache.get_details(uuid, function(err, details) {
|
||||||
|
if (err) {
|
||||||
|
callback(err, -1, null);
|
||||||
|
} else {
|
||||||
|
if (details && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
|
||||||
|
// uuid known + recently updated
|
||||||
|
logging.log(uuid + " uuid known & recently updated");
|
||||||
|
callback(null, (details.hash ? 1 : 0), details.hash);
|
||||||
|
} else {
|
||||||
|
logging.log(uuid + " uuid not known or too old");
|
||||||
|
store_images(uuid, details, function(err, hash) {
|
||||||
|
if (err) {
|
||||||
|
callback(err, -1, details && details.hash);
|
||||||
|
} else {
|
||||||
|
logging.log(uuid + " hash: " + hash);
|
||||||
|
var oldhash = details && details.hash;
|
||||||
|
var status = hash !== oldhash ? 2 : 3;
|
||||||
|
callback(null, status, hash);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// handles requests for +uuid+ images with +size+
|
// handles requests for +uuid+ images with +size+
|
||||||
// callback contains error, status, image buffer
|
// callback contains error, status, image buffer
|
||||||
// image is the user's face+helm when helm is true, or the face otherwise
|
// image is the user's face+helm when helm is true, or the face otherwise
|
||||||
// for status, see get_image_hash
|
// for status, see get_image_hash
|
||||||
exp.get_avatar = function(uuid, helm, size, callback) {
|
exp.get_avatar = function(uuid, helm, size, callback) {
|
||||||
console.log("\nrequest: " + uuid);
|
logging.log("\nrequest: " + uuid);
|
||||||
get_image_hash(uuid, function(err, status, hash) {
|
exp.get_image_hash(uuid, function(err, status, hash) {
|
||||||
if (hash) {
|
if (hash) {
|
||||||
var filepath = __dirname + '/../' + (helm ? config.helms_dir : config.faces_dir) + hash + ".png";
|
var filepath = __dirname + '/../' + (helm ? config.helms_dir : config.faces_dir) + hash + ".png";
|
||||||
skins.resize_img(filepath, size, function(img_err, result) {
|
skins.resize_img(filepath, size, function(img_err, result) {
|
||||||
|
|||||||
16
modules/logging.js
Normal file
16
modules/logging.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
var config = require("./config");
|
||||||
|
|
||||||
|
var exp = {};
|
||||||
|
|
||||||
|
function debug() {
|
||||||
|
if (config.debug_enabled) {
|
||||||
|
console.log(Array.prototype.slice.call(arguments).join(" "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exp.log = console.log;
|
||||||
|
exp.warn = console.warn;
|
||||||
|
exp.error = console.error;
|
||||||
|
exp.debug = debug;
|
||||||
|
|
||||||
|
module.exports = exp;
|
||||||
@ -1,54 +1,115 @@
|
|||||||
|
var logging = require('./logging');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var config = require('./config');
|
var config = require('./config');
|
||||||
var skins = require('./skins');
|
var skins = require('./skins');
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
|
||||||
var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
||||||
|
var skins_url = "https://skins.minecraft.net/MinecraftSkins/";
|
||||||
|
|
||||||
var exp = {};
|
// exracts the skin url of a +profile+ object
|
||||||
|
// returns null when no url found (user has no skin)
|
||||||
// download the Mojang profile for +uuid+
|
function extract_skin_url(profile) {
|
||||||
// callback contains error, profile object
|
var url = null;
|
||||||
exp.get_profile = function(uuid, callback) {
|
if (profile && profile.properties) {
|
||||||
if (uuid.length <= 16) {
|
profile.properties.forEach(function(prop) {
|
||||||
callback(null, null);
|
if (prop.name == 'textures') {
|
||||||
return;
|
var json = Buffer(prop.value, 'base64').toString();
|
||||||
|
var props = JSON.parse(json);
|
||||||
|
url = props && props.textures && props.textures.SKIN && props.textures.SKIN.url || null;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a request to skins.miencraft.net
|
||||||
|
// the skin url is taken from the HTTP redirect
|
||||||
|
var get_username_url = function(name, callback) {
|
||||||
|
request.get({
|
||||||
|
url: skins_url + name + ".png",
|
||||||
|
timeout: config.http_timeout,
|
||||||
|
followRedirect: false
|
||||||
|
}, function(error, response, body) {
|
||||||
|
if (!error && response.statusCode == 301) {
|
||||||
|
// skin_url received successfully
|
||||||
|
logging.log(name + " skin url received");
|
||||||
|
callback(null, response.headers.location);
|
||||||
|
} else if (error) {
|
||||||
|
callback(error, null);
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
// skin doesn't exist
|
||||||
|
logging.log(name + " has no skin");
|
||||||
|
callback(0, null);
|
||||||
|
} else if (response.statusCode == 429) {
|
||||||
|
// Too Many Requests
|
||||||
|
// Never got this, seems like skins aren't limited
|
||||||
|
logging.warn(name + " Too many requests");
|
||||||
|
logging.warn(body);
|
||||||
|
callback(null, null);
|
||||||
|
} else {
|
||||||
|
logging.error(name + " Unknown error:");
|
||||||
|
logging.error(response);
|
||||||
|
logging.error(body);
|
||||||
|
callback(null, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// make a request to sessionserver
|
||||||
|
// the skin_url is taken from the profile
|
||||||
|
var get_uuid_url = function(uuid, callback) {
|
||||||
request.get({
|
request.get({
|
||||||
url: session_url + uuid,
|
url: session_url + uuid,
|
||||||
timeout: config.http_timeout // ms
|
timeout: config.http_timeout // ms
|
||||||
}, function (error, response, body) {
|
}, function (error, response, body) {
|
||||||
if (!error && response.statusCode == 200) {
|
if (!error && response.statusCode == 200) {
|
||||||
// profile downloaded successfully
|
// profile downloaded successfully
|
||||||
console.log(uuid + " profile downloaded");
|
logging.log(uuid + " profile downloaded");
|
||||||
callback(null, JSON.parse(body));
|
callback(null, extract_skin_url(JSON.parse(body)));
|
||||||
} else if (error) {
|
} else if (error) {
|
||||||
callback(error, null);
|
callback(error, null);
|
||||||
} else if (response.statusCode == 204 || response.statusCode == 404) {
|
} else if (response.statusCode == 204 || response.statusCode == 404) {
|
||||||
// we get 204 No Content when UUID doesn't exist (including 404 in case they change that)
|
// we get 204 No Content when UUID doesn't exist (including 404 in case they change that)
|
||||||
console.log(uuid + " uuid does not exist");
|
logging.log(uuid + " uuid does not exist");
|
||||||
callback(0, null);
|
callback(0, null);
|
||||||
} else if (response.statusCode == 429) {
|
} else if (response.statusCode == 429) {
|
||||||
// Too Many Requests
|
// Too Many Requests
|
||||||
console.warn(uuid + " Too many requests");
|
logging.warn(uuid + " Too many requests");
|
||||||
console.warn(body);
|
logging.warn(body);
|
||||||
callback(null, null);
|
callback(null, null);
|
||||||
} else {
|
} else {
|
||||||
console.error(uuid + " Unknown error:");
|
logging.error(uuid + " Unknown error:");
|
||||||
console.error(response);
|
logging.error(response);
|
||||||
console.error(body);
|
logging.error(body);
|
||||||
callback(null, null);
|
callback(null, null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var exp = {};
|
||||||
|
|
||||||
|
// download skin_url for +uuid+ (name or uuid)
|
||||||
|
// callback contains error, skin_url
|
||||||
|
exp.get_skin_url = function(uuid, callback) {
|
||||||
|
if (uuid.length <= 16) {
|
||||||
|
get_username_url(uuid, function(err, url) {
|
||||||
|
callback(err, url);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
get_uuid_url(uuid, function(err, url) {
|
||||||
|
callback(err, url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// downloads skin file from +url+
|
// downloads skin file from +url+
|
||||||
// stores face image as +facename+
|
// stores face image as +facename+
|
||||||
// stores helm image as +helmname+
|
// stores helm image as +helmname+
|
||||||
// callback contains error
|
// callback contains error
|
||||||
exp.skin_file = function(url, facename, helmname, callback) {
|
exp.skin_file = function(url, facename, helmname, callback) {
|
||||||
if (fs.existsSync(facename) && fs.existsSync(facename)) {
|
if (fs.existsSync(facename) && fs.existsSync(facename)) {
|
||||||
console.log("Images already exist, not downloading.");
|
logging.log("Images already exist, not downloading.");
|
||||||
callback(null);
|
callback(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -59,32 +120,32 @@ exp.skin_file = function(url, facename, helmname, callback) {
|
|||||||
}, function (error, response, body) {
|
}, function (error, response, body) {
|
||||||
if (!error && response.statusCode == 200) {
|
if (!error && response.statusCode == 200) {
|
||||||
// skin downloaded successfully
|
// skin downloaded successfully
|
||||||
console.log(url + " skin downloaded");
|
logging.log(url + " skin downloaded");
|
||||||
skins.extract_face(body, facename, function(err) {
|
skins.extract_face(body, facename, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
console.log(facename + " face extracted");
|
logging.log(facename + " face extracted");
|
||||||
skins.extract_helm(facename, body, helmname, function(err) {
|
skins.extract_helm(facename, body, helmname, function(err) {
|
||||||
console.log(helmname + " helm extracted.");
|
logging.log(helmname + " helm extracted.");
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error downloading '" + url + "': " + error);
|
logging.error("Error downloading '" + url + "': " + error);
|
||||||
} else if (response.statusCode == 404) {
|
} else if (response.statusCode == 404) {
|
||||||
console.warn("texture not found (404): " + url);
|
logging.warn("texture not found (404): " + url);
|
||||||
} else if (response.statusCode == 429) {
|
} else if (response.statusCode == 429) {
|
||||||
// Too Many Requests
|
// Too Many Requests
|
||||||
// Never got this, seems like textures aren't limited
|
// Never got this, seems like textures aren't limited
|
||||||
console.warn("too many requests for " + url);
|
logging.warn("too many requests for " + url);
|
||||||
console.warn(body);
|
logging.warn(body);
|
||||||
} else {
|
} else {
|
||||||
console.error("unknown error for " + url);
|
logging.error("unknown error for " + url);
|
||||||
console.error(response);
|
logging.error(response);
|
||||||
console.error(body);
|
logging.error(body);
|
||||||
error = "unknown error"; // Error needs to be set, otherwise null in callback
|
error = "unknown error"; // Error needs to be set, otherwise null in callback
|
||||||
}
|
}
|
||||||
callback(error);
|
callback(error);
|
||||||
|
|||||||
@ -13,6 +13,11 @@ a {
|
|||||||
color: #00B7FF;
|
color: #00B7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.anchor {
|
||||||
|
position: relative;
|
||||||
|
top: -50px;
|
||||||
|
}
|
||||||
|
|
||||||
a.forkme {
|
a.forkme {
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
var networking = require('../modules/networking');
|
||||||
|
var logging = require('../modules/logging');
|
||||||
var helpers = require('../modules/helpers');
|
var helpers = require('../modules/helpers');
|
||||||
var router = require('express').Router();
|
var router = require('express').Router();
|
||||||
var config = require('../modules/config');
|
var config = require('../modules/config');
|
||||||
@ -6,13 +8,51 @@ var skins = require('../modules/skins');
|
|||||||
var human_status = {
|
var human_status = {
|
||||||
0: "none",
|
0: "none",
|
||||||
1: "cached",
|
1: "cached",
|
||||||
2: "checked",
|
2: "downloaded",
|
||||||
3: "downloaded",
|
3: "checked",
|
||||||
"-1": "error"
|
"-1": "error"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
router.get('/skins/:uuid.:ext?', function(req, res) {
|
||||||
|
var uuid = req.params.uuid;
|
||||||
|
var start = new Date();
|
||||||
|
|
||||||
|
if (!helpers.uuid_valid(uuid)) {
|
||||||
|
res.status(422).send("422 Invalid UUID");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// strip dashes
|
||||||
|
uuid = uuid.replace(/-/g, "");
|
||||||
|
try {
|
||||||
|
helpers.get_image_hash(uuid, function(err, status, hash) {
|
||||||
|
if (hash) {
|
||||||
|
res.writeHead(301, {
|
||||||
|
'Location': "http://textures.minecraft.net/texture/" + hash,
|
||||||
|
'Cache-Control': 'max-age=' + config.browser_cache_time + ', public',
|
||||||
|
'Response-Time': new Date() - start,
|
||||||
|
'X-Storage-Type': human_status[status]
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
|
} else if (!err) {
|
||||||
|
res.writeHead(404, {
|
||||||
|
'Cache-Control': 'max-age=' + config.browser_cache_time + ', public',
|
||||||
|
'Response-Time': new Date() - start,
|
||||||
|
'X-Storage-Type': human_status[status]
|
||||||
|
});
|
||||||
|
res.end("404 Not found");
|
||||||
|
} else {
|
||||||
|
res.status(500).send("500 Internal server error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch(e) {
|
||||||
|
logging.error("Error!");
|
||||||
|
logging.error(e);
|
||||||
|
res.status(500).send("500 Internal server error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* GET avatar request. */
|
/* GET avatar request. */
|
||||||
router.get('/:uuid.:ext?', function(req, res) {
|
router.get('/avatars/:uuid.:ext?', function(req, res) {
|
||||||
var uuid = req.params.uuid;
|
var uuid = req.params.uuid;
|
||||||
var size = req.query.size || config.default_size;
|
var size = req.query.size || config.default_size;
|
||||||
var def = req.query.default;
|
var def = req.query.default;
|
||||||
@ -35,9 +75,9 @@ router.get('/:uuid.:ext?', function(req, res) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
helpers.get_avatar(uuid, helm, size, function(err, status, image) {
|
helpers.get_avatar(uuid, helm, size, function(err, status, image) {
|
||||||
console.log(uuid + " - " + human_status[status]);
|
logging.log(uuid + " - " + human_status[status]);
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
logging.error(err);
|
||||||
}
|
}
|
||||||
if (image) {
|
if (image) {
|
||||||
sendimage(err ? 503 : 200, status, image);
|
sendimage(err ? 503 : 200, status, image);
|
||||||
@ -46,18 +86,26 @@ router.get('/:uuid.:ext?', function(req, res) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error("Error!");
|
logging.error("Error!");
|
||||||
console.error(e);
|
logging.error(e);
|
||||||
handle_default(500, status);
|
handle_default(500, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handle_default(http_status, img_status) {
|
function handle_default(http_status, img_status) {
|
||||||
if (def != "steve" && def != "alex") {
|
if (def && def != "steve" && def != "alex") {
|
||||||
def = skins.default_skin(uuid);
|
res.writeHead(301, {
|
||||||
|
'Cache-Control': 'max-age=' + config.browser_cache_time + ', public',
|
||||||
|
'Response-Time': new Date() - start,
|
||||||
|
'X-Storage-Type': human_status[img_status],
|
||||||
|
'Location': def
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
|
} else {
|
||||||
|
def = def || skins.default_skin(uuid);
|
||||||
|
skins.resize_img("public/images/" + def + ".png", size, function(err, image) {
|
||||||
|
sendimage(http_status, img_status, image);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
skins.resize_img("public/images/" + def + ".png", size, function(err, image) {
|
|
||||||
sendimage(http_status, img_status, image);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendimage(http_status, img_status, image) {
|
function sendimage(http_status, img_status, image) {
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
var express = require('express');
|
var express = require('express');
|
||||||
|
var config = require('../modules/config');
|
||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
|
|
||||||
/* GET home page. */
|
/* GET home page. */
|
||||||
router.get('/', function(req, res) {
|
router.get('/', function(req, res) {
|
||||||
res.render('index', {
|
res.render('index', {
|
||||||
title: 'Crafatar',
|
title: 'Crafatar',
|
||||||
domain: "https://" + req.headers.host
|
domain: "https://" + req.headers.host,
|
||||||
|
config: config
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
77
test/test.js
77
test/test.js
@ -3,6 +3,7 @@ var fs = require('fs');
|
|||||||
|
|
||||||
var networking = require('../modules/networking');
|
var networking = require('../modules/networking');
|
||||||
var helpers = require('../modules/helpers');
|
var helpers = require('../modules/helpers');
|
||||||
|
var logging = require('../modules/logging');
|
||||||
var config = require('../modules/config');
|
var config = require('../modules/config');
|
||||||
var skins = require('../modules/skins');
|
var skins = require('../modules/skins');
|
||||||
var cache = require("../modules/cache");
|
var cache = require("../modules/cache");
|
||||||
@ -10,18 +11,21 @@ var cache = require("../modules/cache");
|
|||||||
// we don't want tests to fail because of slow internet
|
// we don't want tests to fail because of slow internet
|
||||||
config.http_timeout = 3000;
|
config.http_timeout = 3000;
|
||||||
|
|
||||||
|
// no spam
|
||||||
|
logging.log = function(){};
|
||||||
|
|
||||||
var uuids = fs.readFileSync('test/uuids.txt').toString().split("\n");
|
var uuids = fs.readFileSync('test/uuids.txt').toString().split("\n");
|
||||||
var usernames = fs.readFileSync('test/usernames.txt').toString().split("\n");
|
var usernames = fs.readFileSync('test/usernames.txt').toString().split("\n");
|
||||||
// Get a random UUID + username in order to prevent rate limiting
|
// Get a random UUID + username in order to prevent rate limiting
|
||||||
var uuid = uuids[Math.round(Math.random() * (uuids.length - 1))];
|
var uuid = uuids[Math.round(Math.random() * (uuids.length - 1))];
|
||||||
var username = usernames[Math.round(Math.random() * (usernames.length - 1))];
|
var username = usernames[Math.round(Math.random() * (usernames.length - 1))];
|
||||||
|
|
||||||
describe('UUID/username', function() {
|
describe('Crafatar', function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
cache.get_redis().flushall();
|
cache.get_redis().flushall();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('UUID', function() {
|
describe('UUID/username', function() {
|
||||||
it("should be an invalid uuid", function(done) {
|
it("should be an invalid uuid", function(done) {
|
||||||
assert.strictEqual(helpers.uuid_valid("g098cb60fa8e427cb299793cbd302c9a"), false);
|
assert.strictEqual(helpers.uuid_valid("g098cb60fa8e427cb299793cbd302c9a"), false);
|
||||||
done();
|
done();
|
||||||
@ -58,43 +62,51 @@ describe('UUID/username', function() {
|
|||||||
assert.strictEqual(helpers.uuid_valid("a"), true);
|
assert.strictEqual(helpers.uuid_valid("a"), true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
it("should not exist", function(done) {
|
it("should not exist (uuid)", function(done) {
|
||||||
networking.get_profile("00000000000000000000000000000000", function(err, profile) {
|
networking.get_skin_url("00000000000000000000000000000000", function(err, profile) {
|
||||||
|
assert.strictEqual(err, 0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("should not exist (username)", function(done) {
|
||||||
|
networking.get_skin_url("Steve", function(err, profile) {
|
||||||
assert.strictEqual(err, 0);
|
assert.strictEqual(err, 0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Avatar', function() {
|
describe('Networking: Avatar', function() {
|
||||||
it("should be downloaded (uuid)", function(done) {
|
it("should be downloaded (uuid)", function(done) {
|
||||||
helpers.get_avatar(uuid, false, 160, function(err, status, image) {
|
helpers.get_avatar(uuid, false, 160, function(err, status, image) {
|
||||||
assert.strictEqual(status, 2);
|
assert.strictEqual(status, 2);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should be local (uuid)", function(done) {
|
it("should be cached (uuid)", function(done) {
|
||||||
helpers.get_avatar(uuid, false, 160, function(err, status, image) {
|
helpers.get_avatar(uuid, false, 160, function(err, status, image) {
|
||||||
assert.strictEqual(status, 1);
|
assert.strictEqual(status, 1);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
/* We can't test this because of mojang's rate limits :(
|
||||||
it("should be checked (uuid)", function(done) {
|
it("should be checked (uuid)", function(done) {
|
||||||
var original_cache_time = config.local_cache_time;
|
var original_cache_time = config.local_cache_time;
|
||||||
config.local_cache_time = 0;
|
config.local_cache_time = 0;
|
||||||
helpers.get_avatar(uuid, false, 160, function(err, status, image) {
|
helpers.get_avatar(uuid, false, 160, function(err, status, image) {
|
||||||
assert.strictEqual(status, 2);
|
assert.strictEqual(status, 3);
|
||||||
config.local_cache_time = original_cache_time;
|
config.local_cache_time = original_cache_time;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
it("should be downloaded (username)", function(done) {
|
it("should be downloaded (username)", function(done) {
|
||||||
helpers.get_avatar(username, false, 160, function(err, status, image) {
|
helpers.get_avatar(username, false, 160, function(err, status, image) {
|
||||||
assert.strictEqual(status, 2);
|
assert.strictEqual(status, 2);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should be local (username)", function(done) {
|
it("should be cached (username)", function(done) {
|
||||||
helpers.get_avatar(username, false, 160, function(err, status, image) {
|
helpers.get_avatar(username, false, 160, function(err, status, image) {
|
||||||
assert.strictEqual(status, 1);
|
assert.strictEqual(status, 1);
|
||||||
done();
|
done();
|
||||||
@ -104,7 +116,7 @@ describe('UUID/username', function() {
|
|||||||
var original_cache_time = config.local_cache_time;
|
var original_cache_time = config.local_cache_time;
|
||||||
config.local_cache_time = 0;
|
config.local_cache_time = 0;
|
||||||
helpers.get_avatar(username, false, 160, function(err, status, image) {
|
helpers.get_avatar(username, false, 160, function(err, status, image) {
|
||||||
assert.strictEqual(status, 2);
|
assert.strictEqual(status, 3);
|
||||||
config.local_cache_time = original_cache_time;
|
config.local_cache_time = original_cache_time;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@ -116,6 +128,18 @@ describe('UUID/username', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it("should already have the files / not download", function(done) {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
fs.openSync("face.png", "w");
|
||||||
|
fs.openSync("helm.png", "w");
|
||||||
|
networking.skin_file("http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", "face.png", "helm.png", function(err) {
|
||||||
|
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
|
||||||
|
fs.unlinkSync("face.png");
|
||||||
|
fs.unlinkSync("helm.png");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
it("should default to Alex", function(done) {
|
it("should default to Alex", function(done) {
|
||||||
assert.strictEqual(skins.default_skin("ec561538f3fd461daff5086b22154bce"), "alex");
|
assert.strictEqual(skins.default_skin("ec561538f3fd461daff5086b22154bce"), "alex");
|
||||||
done();
|
done();
|
||||||
@ -126,7 +150,7 @@ describe('UUID/username', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Mojang Errors', function() {
|
describe('Errors', function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
cache.get_redis().flushall();
|
cache.get_redis().flushall();
|
||||||
});
|
});
|
||||||
@ -136,21 +160,46 @@ describe('UUID/username', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should time out on profile download", function(done) {
|
it("should time out on uuid info download", function(done) {
|
||||||
|
var original_timeout = config.http_timeout;
|
||||||
config.http_timeout = 1;
|
config.http_timeout = 1;
|
||||||
networking.get_profile("069a79f444e94726a5befca90e38aaf5", function(err, profile) {
|
networking.get_skin_url("069a79f444e94726a5befca90e38aaf5", function(err, skin_url) {
|
||||||
assert.strictEqual(err.code, "ETIMEDOUT");
|
assert.strictEqual(err.code, "ETIMEDOUT");
|
||||||
config.http_timeout = 3000;
|
config.http_timeout = original_timeout;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("should time out on username info download", function(done) {
|
||||||
|
var original_timeout = config.http_timeout;
|
||||||
|
config.http_timeout = 1;
|
||||||
|
networking.get_skin_url("redstone_sheep", function(err, skin_url) {
|
||||||
|
assert.strictEqual(err.code, "ETIMEDOUT");
|
||||||
|
config.http_timeout = original_timeout;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should time out on skin download", function(done) {
|
it("should time out on skin download", function(done) {
|
||||||
|
var original_timeout = config.http_timeout;
|
||||||
config.http_timeout = 1;
|
config.http_timeout = 1;
|
||||||
networking.skin_file("http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", "face.png", "helm.png", function(err) {
|
networking.skin_file("http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", "face.png", "helm.png", function(err) {
|
||||||
assert.strictEqual(err.code, "ETIMEDOUT");
|
assert.strictEqual(err.code, "ETIMEDOUT");
|
||||||
config.http_timeout = 3000;
|
config.http_timeout = original_timeout;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it("should not find the skin", function(done) {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
networking.skin_file("http://textures.minecraft.net/texture/this-does-not-exist", "face.png", "helm.png", function(err) {
|
||||||
|
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("should handle file updates on invalid files", function(done) {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
cache.update_timestamp("0123456789abcdef0123456789abcdef", "invalid-file.png");
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
115
views/index.jade
115
views/index.jade
@ -4,55 +4,128 @@ block content
|
|||||||
.container(style= "margin-top: 70px;")
|
.container(style= "margin-top: 70px;")
|
||||||
.row
|
.row
|
||||||
.col-md-10
|
.col-md-10
|
||||||
h1 Crafatar
|
a(id="crafatar", class="anchor")
|
||||||
|
a(href="#crafatar")
|
||||||
|
h1 Crafatar
|
||||||
hr
|
hr
|
||||||
p Welcome to Crafatar, an API for Minecraft's faces!
|
p Welcome to Crafatar, an API for Minecraft's faces!
|
||||||
|
|
||||||
hr
|
hr
|
||||||
h2 Documentation
|
a(id="documentation", class="anchor")
|
||||||
|
a(href="#documentation")
|
||||||
|
h2 Documentation
|
||||||
|
|
||||||
h3 Endpoint
|
a(id="avatars", class="anchor")
|
||||||
|
a(href="#avatars")
|
||||||
|
h3 Avatars
|
||||||
p
|
p
|
||||||
| Replace
|
| Replace
|
||||||
mark.green uuid
|
mark.green id
|
||||||
| with a Mojang UUID to get the related head. All images are PNGs.
|
| with a Mojang UUID or username to get the related head. All images are PNGs.
|
||||||
.code
|
.code
|
||||||
| <img src="#{domain}/avatars/
|
| <img src="#{domain}/avatars/
|
||||||
mark.green uuid
|
mark.green id
|
||||||
| ">
|
| ">
|
||||||
|
|
||||||
h3 Parameters
|
a(id="parameters", class="anchor")
|
||||||
h4 size
|
a(href="#parameters")
|
||||||
p The size of the image in pixels, 1 - 512. <br> Default is 160.
|
h3 Parameters
|
||||||
h4 default
|
a(id="size", class="anchor")
|
||||||
p The image to be returned when the uuid has no skin (404). <br> Valid options are
|
a(href="#size")
|
||||||
|
h4 size
|
||||||
|
p The size of the image in pixels, #{config.min_size} - #{config.max_size}. <br> Default is #{config.default_size}.
|
||||||
|
a(id="default", class="anchor")
|
||||||
|
a(href="#default")
|
||||||
|
h4 default
|
||||||
|
p
|
||||||
|
| The image to be returned when the id has no skin (404). <br> Valid options are
|
||||||
a(href="/avatars/00000000000000000000000000000000?default=steve") steve
|
a(href="/avatars/00000000000000000000000000000000?default=steve") steve
|
||||||
| or
|
| or
|
||||||
a(href="/avatars/00000000000000000000000000000000?default=alex") alex
|
a(href="/avatars/00000000000000000000000000000000?default=alex") alex
|
||||||
| .<br> The standard value is calculated based on the UUID (even = alex, odd = steve)
|
| .<br> A URL is also accepted. <br>
|
||||||
h4 helm
|
| The standard value is calculated based on the id (even = alex, odd = steve)
|
||||||
|
a(id="helm", class="anchor")
|
||||||
|
a(href="#helm")
|
||||||
|
h4 helm
|
||||||
p Get an avatar with the second (helmet) layer applied. <br> The content of this parameter is ignored
|
p Get an avatar with the second (helmet) layer applied. <br> The content of this parameter is ignored
|
||||||
|
|
||||||
h3 HTTP headers
|
a(id="skins", class="anchor")
|
||||||
p Images will come with these HTTP headers, useful for debugging.
|
a(href="#skins")
|
||||||
h4 Response-Time
|
h3 Skins
|
||||||
|
p
|
||||||
|
| You can also get the full skin file from name or id. <br>
|
||||||
|
| Replace
|
||||||
|
mark.green id
|
||||||
|
| with a Mojang UUID or username to get the related skin.
|
||||||
|
| You are redirected to the textures URL, or a 404 is returned.
|
||||||
|
.code
|
||||||
|
| <img src="#{domain}/skins/
|
||||||
|
mark.green id
|
||||||
|
| ">
|
||||||
|
|
||||||
|
a(id="http-headers", class="anchor")
|
||||||
|
a(href="#http-headers")
|
||||||
|
h3 HTTP headers
|
||||||
|
p Responses come with these HTTP headers, useful for debugging.
|
||||||
|
a(id="response-time", class="anchor")
|
||||||
|
a(href="#response-time")
|
||||||
|
h4 Response-Time
|
||||||
p The time, in milliseconds, it took Crafatar to process the request.
|
p The time, in milliseconds, it took Crafatar to process the request.
|
||||||
h4 X-Storage-Type
|
a(id="x-storage-type", class="anchor")
|
||||||
|
a(href="#x-storage-type")
|
||||||
|
h4 X-Storage-Type
|
||||||
ul
|
ul
|
||||||
li <b>none</b>: No external requests. Cached: User has no skin.
|
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>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>
|
li
|
||||||
| This happens either when the user has no skin or it didn't change.
|
| <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. Skin changed or unknown, downloaded.
|
li <b>downloaded</b>: 2 external requests. Skin changed or unknown, downloaded.
|
||||||
li <b>error</b>: This can happen, for example, when Mojang's servers are down. If possible, an outdated image will be served instead.
|
li
|
||||||
|
| <b>error</b>: This can happen, for example, when Mojang's servers are down.<br>
|
||||||
|
| If possible, an outdated image is be served instead.
|
||||||
|
|
||||||
h3 Examples
|
a(id="about-usernames", class="anchor")
|
||||||
|
a(href="#about-usernames")
|
||||||
|
h3 About usernames
|
||||||
|
p
|
||||||
|
| We strongly advise you to use UUIDs instead of usernames in production.
|
||||||
|
| Usernames are deprecated by Mojang and you should only use usernames for testing.<br>
|
||||||
|
| Invalid usernames are rejected and a 422 is returned.
|
||||||
|
|
||||||
|
a(id="about-uuids", class="anchor")
|
||||||
|
a(href="#about-uuids")
|
||||||
|
h3 About UUIDs
|
||||||
|
p
|
||||||
|
| UUIDs may use the raw or dashed format.<br>
|
||||||
|
| Invalid UUIDs are rejected and a 422 is returned.
|
||||||
|
|
||||||
|
a(id="about-caching", class="anchor")
|
||||||
|
a(href="#about-caching")
|
||||||
|
h3 About caching
|
||||||
|
p
|
||||||
|
| Crafatar caches keeps skins for #{config.local_cache_time} seconds until they are checked for changes.<br>
|
||||||
|
| Images should be cached in browsers for #{config.browser_cache_time} seconds until a new request to Crafatar is made.
|
||||||
|
|
||||||
|
a(id="examples", class="anchor")
|
||||||
|
a(href="#examples")
|
||||||
|
h3 Examples
|
||||||
p Get jeb_'s avatar, 160 × 160 pixels
|
p Get jeb_'s avatar, 160 × 160 pixels
|
||||||
.code <img src="#{domain}/avatars/853c80ef3c3749fdaa49938b674adae6">
|
.code <img src="#{domain}/avatars/853c80ef3c3749fdaa49938b674adae6">
|
||||||
p Get jeb_'s avatar, 64 × 64 pixels
|
p Get jeb_'s avatar, 64 × 64 pixels
|
||||||
.code <img src="#{domain}/avatars/853c80ef3c3749fdaa49938b674adae6?size=64">
|
.code <img src="#{domain}/avatars/853c80ef3c3749fdaa49938b674adae6?size=64">
|
||||||
p Get jeb_'s helmet avatar, 64 × 64 pixels
|
p Get jeb_'s helmet avatar, 64 × 64 pixels
|
||||||
.code <img src="#{domain}/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&helm">
|
.code <img src="#{domain}/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&helm">
|
||||||
|
p Get jeb_'s avatar or fall back to steve
|
||||||
|
.code <img src="#{domain}/avatars/853c80ef3c3749fdaa49938b674adae6?default=steve">
|
||||||
|
p Get jeb_'s avatar or fall back to a custom image
|
||||||
|
.code <img src="#{domain}/avatars/853c80ef3c3749fdaa49938b674adae6?default=https%3A%2F%2Fi.imgur.com%2FozszMZV.png">
|
||||||
|
p Get jeb_'s avatar by username, 160 x 160 pixels
|
||||||
|
.code <img src="#{domain}/avatars/jeb_">
|
||||||
|
p Get jeb_'s skin
|
||||||
|
.code <img src="#{domain}/skins/853c80ef3c3749fdaa49938b674adae6">
|
||||||
|
p Get jeb_'s skin by username
|
||||||
|
.code <img src="#{domain}/skins/jeb_">
|
||||||
.col-md-2.center
|
.col-md-2.center
|
||||||
.sideface.redstone_sheep(title="redstone_sheep")
|
.sideface.redstone_sheep(title="redstone_sheep")
|
||||||
.sideface.Jake0oo0(title="Jake0oo0")
|
.sideface.Jake0oo0(title="Jake0oo0")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user