From 1606fac89bc091562c5a0295de7ce5bec0488e05 Mon Sep 17 00:00:00 2001 From: jomo Date: Sat, 1 Nov 2014 21:03:42 +0100 Subject: [PATCH] add support for helmets (?helm), closes #2. changed '/:uuid/123' to '/:uuid?size=123', closes #10. Bugfixes --- modules/config.js | 13 ++++++----- modules/helpers.js | 17 +++++++++----- modules/networking.js | 20 +++++++++++++--- modules/skins.js | 47 ++++++++++++++++++++++++++++++++++---- routes/avatars.js | 19 ++++++++------- skins/{ => faces}/.gitkeep | 0 skins/helms/.gitkeep | 0 views/index.jade | 10 ++++---- 8 files changed, 92 insertions(+), 34 deletions(-) rename skins/{ => faces}/.gitkeep (100%) create mode 100644 skins/helms/.gitkeep diff --git a/modules/config.js b/modules/config.js index 3d53f99..b1f9caa 100644 --- a/modules/config.js +++ b/modules/config.js @@ -1,10 +1,11 @@ var config = { - min_size: 0, // < 0 will (obviously) cause crash - 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 - browser_cache_time: 3600,// seconds until browser will request image again - http_timeout: 1000, // ms until connection to mojang is dropped - skins_dir: 'skins/' // directory where skins are kept. should have trailing '/' + min_size: 0, // < 0 will (obviously) cause crash + 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 + 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 '/' }; module.exports = config; \ No newline at end of file diff --git a/modules/helpers.js b/modules/helpers.js index 35990da..aa3ea90 100644 --- a/modules/helpers.js +++ b/modules/helpers.js @@ -8,7 +8,7 @@ var valid_uuid = /^[0-9a-f]{32}$/; var exp = {}; // 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) { var url = null; if (profile && profile.properties) { @@ -16,7 +16,7 @@ exp.skin_url = function(profile) { 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; + 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+ -// // callback is a function with 3 parameters: // 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 // -1: error // 1: found on disk // 2: profile requested/found, skin downloaded from mojang servers // 3: profile requested/found, but it has no skin -exp.get_avatar = function(uuid, size, callback) { - var filepath = config.skins_dir + uuid + ".png"; +exp.get_avatar = function(uuid, helm, size, callback) { + var facepath = config.faces_dir + uuid + ".png"; + var helmpath = config.helms_dir + uuid + ".png"; + var filepath = helm ? helmpath : facepath; + if (fs.existsSync(filepath)) { + // file found on disk skins.resize_img(filepath, size, function(err, result) { callback(err, 1, result); }); } else { + // download skin networking.get_profile(uuid, function(err, profile) { if (err) { callback(err, -1, profile); @@ -55,7 +60,7 @@ exp.get_avatar = function(uuid, size, callback) { var skinurl = exp.skin_url(profile); if (skinurl) { - networking.skin_file(skinurl, filepath, function(err) { + networking.skin_file(skinurl, facepath, helmpath, function(err) { if (err) { callback(err, -1, null); } else { diff --git a/modules/networking.js b/modules/networking.js index 17d0690..88eb5b9 100644 --- a/modules/networking.js +++ b/modules/networking.js @@ -6,12 +6,15 @@ var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/"; var exp = {}; +// download the Mojang profile for +uuid+ +// callback contains error, profile object exp.get_profile = function(uuid, callback) { request.get({ url: session_url + uuid, timeout: config.http_timeout // ms }, function (error, response, body) { if (!error && response.statusCode == 200) { + // profile downloaded successfully callback(null, JSON.parse(body)); } else { 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({ url: url, encoding: null, // encoding must be null so we get a buffer timeout: config.http_timeout // ms }, function (error, response, body) { if (!error && response.statusCode == 200) { - skins.extract_face(body, outname, function(err) { - callback(err); + // skin downloaded successfully + skins.extract_face(body, facename, function(err) { + if (err) { + callback(err); + } else { + skins.extract_helm(facename, body, helmname, function(err) { + callback(err); + }); + } }); } else { if (error) { diff --git a/modules/skins.js b/modules/skins.js index 8917a22..774ffe3 100644 --- a/modules/skins.js +++ b/modules/skins.js @@ -3,15 +3,15 @@ var lwip = require('lwip'); var exp = {}; // extracts the face from an image +buffer+ -// save it to a file called +outname+ -// callback has an error parameter which can be null +// result is saved to a file called +outname+ +// +callback+ contains error exp.extract_face = function(buffer, outname, callback) { lwip.open(buffer, "png", function(err, image) { if (err) { callback(err); } else { image.batch() - .crop(8, 8, 15, 15) + .crop(8, 8, 15, 15) // face .writeFile(outname, function(err) { if (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 -// +callback+ is a buffer of the resized image +// +callback+ contains error, image buffer exp.resize_img = function(inname, size, callback) { lwip.open(inname, function(err, image) { if (err) { diff --git a/routes/avatars.js b/routes/avatars.js index a4693ff..691c6bf 100644 --- a/routes/avatars.js +++ b/routes/avatars.js @@ -6,10 +6,11 @@ var skins = require('../modules/skins'); var fs = require('fs'); /* GET avatar request. */ -router.get('/:uuid/:size?', function(req, res) { - var uuid = req.param('uuid'); - var size = req.param('size') || config.default_size; +router.get('/:uuid', function(req, res) { + var uuid = req.params.uuid; + var size = req.query.size || config.default_size; var def = req.query.default; + var helm = req.query.hasOwnProperty('helm'); var start = new Date(); // Prevent app from crashing/freezing @@ -24,13 +25,12 @@ router.get('/:uuid/:size?', function(req, res) { } try { - helpers.get_avatar(uuid, size, function(err, status, image) { + helpers.get_avatar(uuid, helm, size, function(err, status, image) { if (err) { console.error(err); handle_404(def); } else if (status == 1 || status == 2) { - var time = new Date() - start; - sendimage(200, time, image); + sendimage(200, image); } else if (status == 3) { handle_404(def); } @@ -44,19 +44,18 @@ router.get('/:uuid/:size?', function(req, res) { function handle_404(def) { if (def == "alex" || def == "steve") { skins.resize_img("public/images/" + def + ".png", size, function(image) { - var time = new Date() - start; - sendimage(404, time, image); + sendimage(404, image); }); } else { res.status(404).send('404 Not found'); } } - function sendimage(status, time, image) { + function sendimage(status, image) { res.writeHead(status, { 'Content-Type': 'image/png', 'Cache-Control': 'max-age=' + config.browser_cache_time + ', public', - 'Response-Time': time, + 'Response-Time': new Date() - start, 'X-Storage-Type': 'local' }); res.end(image); diff --git a/skins/.gitkeep b/skins/faces/.gitkeep similarity index 100% rename from skins/.gitkeep rename to skins/faces/.gitkeep diff --git a/skins/helms/.gitkeep b/skins/helms/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/views/index.jade b/views/index.jade index 9abf1bd..6f4ef24 100644 --- a/views/index.jade +++ b/views/index.jade @@ -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: .well <img src="#{domain}/avatars/ae795aa86327408e92ab25c8a59f3ba1/250?default=alex"> .col-md-2 - img(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?default=steve", title="Jake0oo0") - img(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?default=steve", title="redstone_sheep") - img(src="/avatars/069a79f444e94726a5befca90e38aaf5?default=steve", title="Notch") - img(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?default=steve", title="sk89q") - img(src="/avatars/af74a02d19cb445bb07f6866a861f783?default=steve", title="md_5") + img(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?default=alex&helm", title="Jake0oo0") + img(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?default=alex&helm", title="redstone_sheep") + img(src="/avatars/069a79f444e94726a5befca90e38aaf5?default=alex&helm", title="Notch") + img(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?default=alex&helm", title="sk89q") + img(src="/avatars/af74a02d19cb445bb07f6866a861f783?default=alex&helm", title="md_5")