mirror of
https://github.com/azures04/crafatar.git
synced 2026-03-22 07:51:17 +01:00
parent
ebd8e18b29
commit
e6481e3c73
@ -4,7 +4,7 @@ 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 '/'
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,7 @@ 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,28 +14,15 @@ 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) {
|
||||||
|
console.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");
|
console.log(uuid + " hash has not changed");
|
||||||
@ -47,7 +34,7 @@ function store_images(uuid, details, callback) {
|
|||||||
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,30 +52,14 @@ 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
|
// decides whether to get an image from disk or to download it
|
||||||
// callback contains error, status, hash
|
// callback contains error, status, hash
|
||||||
// the status gives information about how the image was received
|
// the status gives information about how the image was received
|
||||||
// -1: error
|
// -1: "error"
|
||||||
// 0: cached as null
|
// 0: "none" - cached as null
|
||||||
// 1: found on disk
|
// 1: "cached" - found on disk
|
||||||
// 2: profile requested/found, skin downloaded from mojang servers
|
// 2: "downloaded" - profile downloaded, skin downloaded from mojang servers
|
||||||
// 3: profile requested/found, but it has not changed or no skin
|
// 3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
|
||||||
function get_image_hash(uuid, callback) {
|
function get_image_hash(uuid, callback) {
|
||||||
cache.get_details(uuid, function(err, details) {
|
cache.get_details(uuid, function(err, details) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -100,12 +71,17 @@ function get_image_hash(uuid, callback) {
|
|||||||
callback(null, (details.hash ? 1 : 0), details.hash);
|
callback(null, (details.hash ? 1 : 0), details.hash);
|
||||||
} else {
|
} else {
|
||||||
console.log(uuid + " uuid not known or too old");
|
console.log(uuid + " uuid not known or too old");
|
||||||
|
console.log("details:");
|
||||||
|
console.log(details);
|
||||||
|
console.log("/details");
|
||||||
store_images(uuid, details, function(err, hash) {
|
store_images(uuid, details, function(err, hash) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err, -1, details && details.hash);
|
callback(err, -1, details && details.hash);
|
||||||
} else {
|
} else {
|
||||||
console.log(uuid + " hash: " + hash);
|
console.log(uuid + " hash: " + hash);
|
||||||
callback(null, (hash != (details && details.hash) ? 2 : 3), hash);
|
var oldhash = details && details.hash;
|
||||||
|
var status = hash !== oldhash ? 2 : 3;
|
||||||
|
callback(null, status, hash);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,16 +4,60 @@ 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
|
||||||
|
console.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
|
||||||
|
console.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
|
||||||
|
console.warn(name + " Too many requests");
|
||||||
|
console.warn(body);
|
||||||
|
callback(null, null);
|
||||||
|
} else {
|
||||||
|
console.error(name + " Unknown error:");
|
||||||
|
console.error(response);
|
||||||
|
console.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
|
||||||
@ -21,7 +65,7 @@ exp.get_profile = function(uuid, callback) {
|
|||||||
if (!error && response.statusCode == 200) {
|
if (!error && response.statusCode == 200) {
|
||||||
// profile downloaded successfully
|
// profile downloaded successfully
|
||||||
console.log(uuid + " profile downloaded");
|
console.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) {
|
||||||
@ -42,6 +86,22 @@ exp.get_profile = function(uuid, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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+
|
||||||
|
|||||||
@ -6,8 +6,8 @@ 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"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
34
test/test.js
34
test/test.js
@ -16,12 +16,12 @@ var usernames = fs.readFileSync('test/usernames.txt').toString().split("\n");
|
|||||||
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 +58,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 +112,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();
|
||||||
});
|
});
|
||||||
@ -137,18 +145,20 @@ describe('UUID/username', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("should time out on profile download", function(done) {
|
it("should time out on profile 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, profile) {
|
||||||
assert.strictEqual(err.code, "ETIMEDOUT");
|
assert.strictEqual(err.code, "ETIMEDOUT");
|
||||||
config.http_timeout = 3000;
|
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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user