add support for helmets (?helm), closes #2. changed '/:uuid/123' to '/:uuid?size=123', closes #10. Bugfixes

This commit is contained in:
jomo 2014-11-01 21:03:42 +01:00
parent a61ac9b6a4
commit 1606fac89b
8 changed files with 92 additions and 34 deletions

View File

@ -1,10 +1,11 @@
var config = { var config = {
min_size: 0, // < 0 will (obviously) cause crash min_size: 0, // < 0 will (obviously) cause crash
max_size: 512, // too big values might lead to slow response time or DoS max_size: 512, // too big values might lead to slow response time or DoS
default_size: 180, // size to be used when no size given default_size: 180, // size to be used when no size given
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: 1000, // ms until connection to mojang is dropped
skins_dir: 'skins/' // directory where skins 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 '/'
}; };
module.exports = config; module.exports = config;

View File

@ -8,7 +8,7 @@ var valid_uuid = /^[0-9a-f]{32}$/;
var exp = {}; var exp = {};
// exracts the skin url of a +profile+ object // exracts the skin url of a +profile+ object
// returns null when no url found // returns null when no url found (user has no skin)
exp.skin_url = function(profile) { exp.skin_url = function(profile) {
var url = null; var url = null;
if (profile && profile.properties) { if (profile && profile.properties) {
@ -16,7 +16,7 @@ exp.skin_url = function(profile) {
if (prop.name == 'textures') { if (prop.name == 'textures') {
var json = Buffer(prop.value, 'base64').toString(); var json = Buffer(prop.value, 'base64').toString();
var props = JSON.parse(json); var props = JSON.parse(json);
url = props && props.textures && props.textures.SKIN && props.textures.SKIN.url; url = props && props.textures && props.textures.SKIN && props.textures.SKIN.url || null;
} }
}); });
} }
@ -31,22 +31,27 @@ exp.uuid_valid = function(uuid) {
}; };
// handles requests for +uuid+ images with +size+ // handles requests for +uuid+ images with +size+
//
// callback is a function with 3 parameters: // callback is a function with 3 parameters:
// error, status, image buffer // error, status, image buffer
// image is the user's face+helm when helm is true, or the face otherwise
// //
// the status gives information about how the image was received // the status gives information about how the image was received
// -1: error // -1: error
// 1: found on disk // 1: found on disk
// 2: profile requested/found, skin downloaded from mojang servers // 2: profile requested/found, skin downloaded from mojang servers
// 3: profile requested/found, but it has no skin // 3: profile requested/found, but it has no skin
exp.get_avatar = function(uuid, size, callback) { exp.get_avatar = function(uuid, helm, size, callback) {
var filepath = config.skins_dir + uuid + ".png"; var facepath = config.faces_dir + uuid + ".png";
var helmpath = config.helms_dir + uuid + ".png";
var filepath = helm ? helmpath : facepath;
if (fs.existsSync(filepath)) { if (fs.existsSync(filepath)) {
// file found on disk
skins.resize_img(filepath, size, function(err, result) { skins.resize_img(filepath, size, function(err, result) {
callback(err, 1, result); callback(err, 1, result);
}); });
} else { } else {
// download skin
networking.get_profile(uuid, function(err, profile) { networking.get_profile(uuid, function(err, profile) {
if (err) { if (err) {
callback(err, -1, profile); callback(err, -1, profile);
@ -55,7 +60,7 @@ exp.get_avatar = function(uuid, size, callback) {
var skinurl = exp.skin_url(profile); var skinurl = exp.skin_url(profile);
if (skinurl) { if (skinurl) {
networking.skin_file(skinurl, filepath, function(err) { networking.skin_file(skinurl, facepath, helmpath, function(err) {
if (err) { if (err) {
callback(err, -1, null); callback(err, -1, null);
} else { } else {

View File

@ -6,12 +6,15 @@ var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
var exp = {}; var exp = {};
// download the Mojang profile for +uuid+
// callback contains error, profile object
exp.get_profile = function(uuid, callback) { exp.get_profile = 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
callback(null, JSON.parse(body)); callback(null, JSON.parse(body));
} else { } else {
if (error) { if (error) {
@ -33,15 +36,26 @@ exp.get_profile = function(uuid, callback) {
}); });
}; };
exp.skin_file = function(url, outname, callback) { // downloads skin file from +url+
// stores face image as +facename+
// stores helm image as +helmname+
// callback is forwarded from skins/extract_face or skins/extract_helm
exp.skin_file = function(url, facename, helmname, callback) {
request.get({ request.get({
url: url, url: url,
encoding: null, // encoding must be null so we get a buffer encoding: null, // encoding must be null so we get a buffer
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) {
skins.extract_face(body, outname, function(err) { // skin downloaded successfully
callback(err); skins.extract_face(body, facename, function(err) {
if (err) {
callback(err);
} else {
skins.extract_helm(facename, body, helmname, function(err) {
callback(err);
});
}
}); });
} else { } else {
if (error) { if (error) {

View File

@ -3,15 +3,15 @@ var lwip = require('lwip');
var exp = {}; var exp = {};
// extracts the face from an image +buffer+ // extracts the face from an image +buffer+
// save it to a file called +outname+ // result is saved to a file called +outname+
// callback has an error parameter which can be null // +callback+ contains error
exp.extract_face = function(buffer, outname, callback) { exp.extract_face = function(buffer, outname, callback) {
lwip.open(buffer, "png", function(err, image) { lwip.open(buffer, "png", function(err, image) {
if (err) { if (err) {
callback(err); callback(err);
} else { } else {
image.batch() image.batch()
.crop(8, 8, 15, 15) .crop(8, 8, 15, 15) // face
.writeFile(outname, function(err) { .writeFile(outname, function(err) {
if (err) { if (err) {
callback(err); callback(err);
@ -23,8 +23,47 @@ exp.extract_face = function(buffer, outname, callback) {
}); });
}; };
// extracts the helm from an image +buffer+ and lays it over a +facefile+
// +facefile+ is the filename of an image produced by extract_face
// result is saved to a file called +outname+
// +callback+ contains error
exp.extract_helm = function(facefile, buffer, outname, callback) {
lwip.open(buffer, "png", function(err, skin) {
if (err) {
callback(err);
} else {
lwip.open(facefile, function(err, face_img) {
if (err) {
callback(err);
} else {
skin.crop(42, 8, 49, 15, function(err, helm_img) {
if (err) {
callback(err);
} else {
face_img.paste(0, 0, helm_img, function(err, face_helm_img) {
if (err) {
callback(err);
} else {
face_helm_img.writeFile(outname, function(err) {
if (err) {
callback(err);
} else {
callback(null);
// JavaScript callback hell <3
}
});
}
});
}
});
}
});
}
});
};
// resizes the image file +inname+ to +size+ by +size+ pixels // resizes the image file +inname+ to +size+ by +size+ pixels
// +callback+ is a buffer of the resized image // +callback+ contains error, image buffer
exp.resize_img = function(inname, size, callback) { exp.resize_img = function(inname, size, callback) {
lwip.open(inname, function(err, image) { lwip.open(inname, function(err, image) {
if (err) { if (err) {

View File

@ -6,10 +6,11 @@ var skins = require('../modules/skins');
var fs = require('fs'); var fs = require('fs');
/* GET avatar request. */ /* GET avatar request. */
router.get('/:uuid/:size?', function(req, res) { router.get('/:uuid', function(req, res) {
var uuid = req.param('uuid'); var uuid = req.params.uuid;
var size = req.param('size') || config.default_size; var size = req.query.size || config.default_size;
var def = req.query.default; var def = req.query.default;
var helm = req.query.hasOwnProperty('helm');
var start = new Date(); var start = new Date();
// Prevent app from crashing/freezing // Prevent app from crashing/freezing
@ -24,13 +25,12 @@ router.get('/:uuid/:size?', function(req, res) {
} }
try { try {
helpers.get_avatar(uuid, size, function(err, status, image) { helpers.get_avatar(uuid, helm, size, function(err, status, image) {
if (err) { if (err) {
console.error(err); console.error(err);
handle_404(def); handle_404(def);
} else if (status == 1 || status == 2) { } else if (status == 1 || status == 2) {
var time = new Date() - start; sendimage(200, image);
sendimage(200, time, image);
} else if (status == 3) { } else if (status == 3) {
handle_404(def); handle_404(def);
} }
@ -44,19 +44,18 @@ router.get('/:uuid/:size?', function(req, res) {
function handle_404(def) { function handle_404(def) {
if (def == "alex" || def == "steve") { if (def == "alex" || def == "steve") {
skins.resize_img("public/images/" + def + ".png", size, function(image) { skins.resize_img("public/images/" + def + ".png", size, function(image) {
var time = new Date() - start; sendimage(404, image);
sendimage(404, time, image);
}); });
} else { } else {
res.status(404).send('404 Not found'); res.status(404).send('404 Not found');
} }
} }
function sendimage(status, time, image) { function sendimage(status, image) {
res.writeHead(status, { res.writeHead(status, {
'Content-Type': 'image/png', 'Content-Type': 'image/png',
'Cache-Control': 'max-age=' + config.browser_cache_time + ', public', 'Cache-Control': 'max-age=' + config.browser_cache_time + ', public',
'Response-Time': time, 'Response-Time': new Date() - start,
'X-Storage-Type': 'local' 'X-Storage-Type': 'local'
}); });
res.end(image); res.end(image);

0
skins/helms/.gitkeep Normal file
View File

View File

@ -32,8 +32,8 @@ block content
p(style="margin-top: 10px;") By default, a 404 text is returned when the avatar was not found. You can change that to the avatar of steve or alex: p(style="margin-top: 10px;") By default, a 404 text is returned when the avatar was not found. You can change that to the avatar of steve or alex:
.well &lt;img src="#{domain}/avatars/ae795aa86327408e92ab25c8a59f3ba1/250?default=alex"&gt; .well &lt;img src="#{domain}/avatars/ae795aa86327408e92ab25c8a59f3ba1/250?default=alex"&gt;
.col-md-2 .col-md-2
img(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?default=steve", title="Jake0oo0") img(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?default=alex&helm", title="Jake0oo0")
img(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?default=steve", title="redstone_sheep") img(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?default=alex&helm", title="redstone_sheep")
img(src="/avatars/069a79f444e94726a5befca90e38aaf5?default=steve", title="Notch") img(src="/avatars/069a79f444e94726a5befca90e38aaf5?default=alex&helm", title="Notch")
img(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?default=steve", title="sk89q") img(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?default=alex&helm", title="sk89q")
img(src="/avatars/af74a02d19cb445bb07f6866a861f783?default=steve", title="md_5") img(src="/avatars/af74a02d19cb445bb07f6866a861f783?default=alex&helm", title="md_5")