diff --git a/lib/cache.js b/lib/cache.js index b089589..7f81cd7 100644 --- a/lib/cache.js +++ b/lib/cache.js @@ -70,6 +70,15 @@ exp.info = function(callback) { }); }; +// set model type to value of *slim* +exp.set_slim = function(rid, userId, slim, callback) { + logging.debug(rid, "setting slim for ", userId, "to " + slim); + // store userId in lower case if not null + userId = userId && userId.toLowerCase(); + + redis.hmset(userId, ["a", Number(slim)], callback); +}; + // sets the timestamp for +userId+ // 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 @@ -85,31 +94,34 @@ exp.update_timestamp = function(rid, userId, temp, callback) { }); }; -// create the key +userId+, store +skin_hash+, +cape_hash+ and time -// if either +skin_hash+ or +cape_hash+ are undefined, they will not be stored -// this feature can be used to write both cape and skin at separate times +// create the key +userId+, store +skin_hash+, +cape_hash+, +slim+ and current time +// if +skin_hash+ or +cape_hash+ are undefined, they aren't stored +// this is useful to store cape and skin at separate times, without overwriting the other +// +slim+ can be true (alex) or false (steve) // +callback+ contans error -exp.save_hash = function(rid, userId, skin_hash, cape_hash, callback) { - logging.debug(rid, "caching skin:" + skin_hash + " cape:" + cape_hash); - var time = Date.now(); - // store shorter null byte instead of "null" +exp.save_hash = function(rid, userId, skin_hash, cape_hash, slim, callback) { + logging.debug(rid, "caching skin:" + skin_hash + " cape:" + cape_hash + " slim:" + slim); + // store shorter null value instead of "null" string skin_hash = skin_hash === null ? "" : skin_hash; cape_hash = cape_hash === null ? "" : cape_hash; // store userId in lower case if not null userId = userId && userId.toLowerCase(); - if (skin_hash === undefined) { - redis.hmset(userId, "c", cape_hash, "t", time, function(err) { - callback(err); - }); - } else if (cape_hash === undefined) { - redis.hmset(userId, "s", skin_hash, "t", time, function(err) { - callback(err); - }); - } else { - redis.hmset(userId, "s", skin_hash, "c", cape_hash, "t", time, function(err) { - callback(err); - }); + + var args = []; + if (cape_hash !== undefined) { + args.push("c", cape_hash); } + if (skin_hash !== undefined) { + args.push("s", skin_hash); + } + if (slim !== undefined) { + args.push("a", Number(!!slim)); + } + args.push("t", Date.now()); + + redis.hmset(userId, args, function(err) { + callback(err); + }); }; // removes the hash for +userId+ from the cache @@ -131,6 +143,7 @@ exp.get_details = function(userId, callback) { details = { skin: data.s === "" ? null : data.s, cape: data.c === "" ? null : data.c, + slim: data.a === "1", time: Number(data.t) }; } diff --git a/lib/helpers.js b/lib/helpers.js index f2a8d0c..bb14c21 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -19,14 +19,19 @@ function get_hash(url) { // gets the skin for +userId+ with +profile+ // uses +cache_details+ to determine if the skin needs to be downloaded or can be taken from cache // face and face+helm images are extracted and stored to files -// callback: error, skin hash +// callback: error, skin hash, callback function store_skin(rid, userId, profile, cache_details, callback) { - networking.get_skin_url(rid, userId, profile, function(err, url) { + networking.get_skin_info(rid, userId, profile, function(err, url, slim) { + if (!err && userId.length > 16) { + // updating username with model info from uuid details + cache.set_slim(rid, profile.name, slim); + } + if (!err && url) { var skin_hash = get_hash(url); if (cache_details && cache_details.skin === skin_hash) { cache.update_timestamp(rid, userId, false, function(cache_err) { - callback(cache_err, skin_hash); + callback(cache_err, skin_hash, slim); }); } else { logging.debug(rid, "new skin hash:", skin_hash); @@ -36,25 +41,25 @@ function store_skin(rid, userId, profile, cache_details, callback) { fs.exists(facepath, function(exists) { if (exists) { logging.debug(rid, "skin already exists, not downloading"); - callback(null, skin_hash); + callback(null, skin_hash, slim); } else { networking.get_from(rid, url, function(img, response, err1) { if (err1 || !img) { - callback(err1, null); + callback(err1, null, slim); } else { skins.save_image(img, skinpath, function(skin_err, skin_img) { if (skin_err) { - callback(skin_err, null); + callback(skin_err, null, slim); } else { skins.extract_face(img, facepath, function(err2) { if (err2) { - callback(err2, null); + callback(err2, null, slim); } else { logging.debug(rid, "face extracted"); skins.extract_helm(rid, facepath, img, helmpath, function(err3) { logging.debug(rid, "helm extracted"); logging.debug(rid, helmpath); - callback(err3, skin_hash); + callback(err3, skin_hash, slim); }); } }); @@ -128,7 +133,7 @@ function push_request(userId, type, fun) { } // calls back all queued requests that match userId and type -function resume(userId, type, err, hash) { +function resume(userId, type, err, hash, slim) { var userId_safe = "!" + userId; var callbacks = requests[type][userId_safe]; if (callbacks) { @@ -138,7 +143,7 @@ function resume(userId, type, err, hash) { for (var i = 0; i < callbacks.length; i++) { // continue the request - callbacks[i](err, hash); + callbacks[i](err, hash, slim); // remove from array callbacks.splice(i, 1); i--; @@ -152,7 +157,7 @@ function resume(userId, type, err, hash) { // downloads the images for +userId+ while checking the cache // status based on +cache_details+. +type+ specifies which // 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) { var is_uuid = userId.length > 16; if (requests[type]["!" + userId]) { @@ -167,34 +172,34 @@ function store_images(rid, userId, cache_details, type, callback) { // error or uuid without profile if (!err && !profile) { // no error, but uuid without profile - cache.save_hash(rid, userId, null, null, function(cache_err) { + cache.save_hash(rid, userId, null, null, undefined, function(cache_err) { // we have no profile, so we have neither skin nor cape - resume(userId, "skin", cache_err, null); - resume(userId, "cape", cache_err, null); + resume(userId, "skin", cache_err, null, false); + resume(userId, "cape", cache_err, null, false); }); } else { // an error occured, not caching. we can try in 60 seconds - resume(userId, type, err, null); + resume(userId, type, err, null, false); } } else { // no error and we have a profile (if it's a uuid) - store_skin(rid, userId, profile, cache_details, function(store_err, skin_hash) { + store_skin(rid, userId, profile, cache_details, function(store_err, skin_hash, slim) { if (store_err && !skin_hash) { // an error occured, not caching. we can try in 60 seconds - resume(userId, "skin", store_err, null); + resume(userId, "skin", store_err, null, slim); } else { - cache.save_hash(rid, userId, skin_hash, undefined, function(cache_err) { - resume(userId, "skin", (store_err || cache_err), skin_hash); + cache.save_hash(rid, userId, skin_hash, undefined, slim, function(cache_err) { + resume(userId, "skin", (store_err || cache_err), skin_hash, slim); }); } }); store_cape(rid, userId, profile, cache_details, function(store_err, cape_hash) { if (store_err && !cape_hash) { // an error occured, not caching. we can try in 60 seconds - resume(userId, "cape", (store_err), cape_hash); + resume(userId, "cape", (store_err), cape_hash, false); } else { - cache.save_hash(rid, userId, undefined, cape_hash, function(cache_err) { - resume(userId, "cape", (store_err || cache_err), cape_hash); + cache.save_hash(rid, userId, undefined, cape_hash, undefined, function(cache_err) { + resume(userId, "cape", (store_err || cache_err), cape_hash, false); }); } }); @@ -212,7 +217,7 @@ exp.id_valid = function(userId) { }; // decides whether to get a +type+ image for +userId+ from disk or to download it -// callback: error, status, hash +// callback: error, status, hash, slim // for status, see response.js exp.get_image_hash = function(rid, userId, type, callback) { cache.get_details(userId, function(err, cache_details) { @@ -221,31 +226,32 @@ exp.get_image_hash = function(rid, userId, type, callback) { cached_hash = type === "skin" ? cache_details.skin : cache_details.cape; } if (err) { - callback(err, -1, null); + callback(err, -1, null, false); } else { if (cache_details && cache_details[type] !== undefined && cache_details.time + config.caching.local * 1000 >= Date.now()) { // use cached image logging.debug(rid, "userId cached & recently updated"); - callback(null, (cached_hash ? 1 : 0), cached_hash); + callback(null, (cached_hash ? 1 : 0), cached_hash, cache_details.slim); } else { // download image - if (cache_details) { + if (cache_details && cache_details[type] !== undefined) { logging.debug(rid, "userId cached, but too old"); + logging.debug(rid, JSON.stringify(cache_details)); } else { logging.debug(rid, "userId not cached"); } - store_images(rid, userId, cache_details, type, function(store_err, new_hash) { + store_images(rid, userId, cache_details, type, function(store_err, new_hash, slim) { if (store_err) { // we might have a cached hash although an error occured // (e.g. Mojang servers not reachable, using outdated hash) cache.update_timestamp(rid, userId, true, function(err2) { - callback(err2 || store_err, -1, cache_details && cached_hash); + callback(err2 || store_err, -1, cache_details && cached_hash, slim); }); } else { var status = cache_details && (cached_hash === new_hash) ? 3 : 2; logging.debug(rid, "cached hash:", (cache_details && cached_hash)); logging.debug(rid, "new hash:", new_hash); - callback(null, status, new_hash); + callback(null, status, new_hash, slim); } }); } @@ -259,7 +265,7 @@ exp.get_image_hash = function(rid, userId, type, callback) { // image is the user's face+overlay when overlay is true, or the face otherwise // for status, see get_image_hash exp.get_avatar = function(rid, userId, overlay, size, callback) { - exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash) { + exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash, slim) { if (skin_hash) { var facepath = path.join(config.directories.faces, skin_hash + ".png"); var helmpath = path.join(config.directories.helms, skin_hash + ".png"); @@ -284,25 +290,25 @@ exp.get_avatar = function(rid, userId, overlay, size, callback) { }; // 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_image_hash(rid, userId, "skin", function(err, status, skin_hash) { + exp.get_image_hash(rid, userId, "skin", function(err, status, skin_hash, slim) { if (skin_hash) { var skinpath = path.join(config.directories.skins, skin_hash + ".png"); fs.exists(skinpath, function(exists) { if (exists) { logging.debug(rid, "skin already exists, not downloading"); skins.open_skin(rid, skinpath, function(skin_err, img) { - callback(skin_err || err, skin_hash, status, img); + callback(skin_err || err, skin_hash, status, img, slim); }); } else { networking.save_texture(rid, skin_hash, skinpath, function(net_err, response, img) { - callback(net_err || err, skin_hash, status, img); + callback(net_err || err, skin_hash, status, img, slim); }); } }); } else { - callback(err, null, status, null); + callback(err, null, status, null, slim); } }); }; @@ -318,12 +324,12 @@ function get_type(overlay, body) { // handles creations of 3D renders // callback: error, skin hash, image buffer exp.get_render = function(rid, userId, scale, overlay, body, callback) { - exp.get_skin(rid, userId, function(err, skin_hash, status, img) { + exp.get_skin(rid, userId, function(err, skin_hash, status, img, slim) { if (!skin_hash) { callback(err, status, skin_hash, null); return; } - var renderpath = path.join(config.directories.renders, [skin_hash, scale, get_type(overlay, body)].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) { if (exists) { renders.open_render(rid, renderpath, function(render_err, rendered_img) { @@ -335,7 +341,7 @@ exp.get_render = function(rid, userId, scale, overlay, body, callback) { callback(err, 0, skin_hash, null); return; } - renders.draw_model(rid, img, scale, overlay, body, function(draw_err, drawn_img) { + renders.draw_model(rid, img, scale, overlay, body, slim || userId.toLowerCase() === "mhf_alex", function(draw_err, drawn_img) { if (draw_err) { callback(draw_err, -1, skin_hash, null); } else if (!drawn_img) { @@ -354,7 +360,7 @@ exp.get_render = function(rid, userId, scale, overlay, body, callback) { // handles requests for +userId+ capes // callback: error, cape hash, status, image buffer exp.get_cape = function(rid, userId, callback) { - exp.get_image_hash(rid, userId, "cape", function(err, status, cape_hash) { + exp.get_image_hash(rid, userId, "cape", function(err, status, cape_hash, slim) { if (!cape_hash) { callback(err, null, status, null); return; diff --git a/lib/networking.js b/lib/networking.js index 704f590..8cb5e4a 100644 --- a/lib/networking.js +++ b/lib/networking.js @@ -3,6 +3,7 @@ var logging = require("./logging"); var request = require("request"); var config = require("../config"); var skins = require("./skins"); +require("./object-patch"); var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/"; var skins_url = "https://skins.minecraft.net/MinecraftSkins/"; @@ -12,50 +13,22 @@ var mojang_urls = [skins_url, capes_url]; var exp = {}; -// extracts the +type+ [SKIN|CAPE] URL -// from the nested & encoded +profile+ object -// returns the URL or null if not present -function extract_url(profile, type) { - var url = null; - if (profile && profile.properties) { - profile.properties.forEach(function(prop) { - if (prop.name === "textures") { - var json = new Buffer(prop.value, "base64").toString(); - var props = JSON.parse(json); - url = props && props.textures && props.textures[type] && props.textures[type].url || null; - } - }); - } - return url; -} - -// helper method that calls `get_username_url` or `get_uuid_url` based on the +usedId+ +// helper method that calls `get_username_url` or `get_uuid_info` based on the +usedId+ // +userId+ is used for usernames, while +profile+ is used for UUIDs -function get_url(rid, userId, profile, type, callback) { +// callback: error, url, slim +function get_info(rid, userId, profile, type, callback) { if (userId.length <= 16) { // username exp.get_username_url(rid, userId, type, function(err, url) { - callback(err, url || null); + callback(err, url || null, false); }); } else { - exp.get_uuid_url(profile, type, function(url) { - callback(null, url || null); + exp.get_uuid_info(profile, type, function(url, slim) { + callback(null, url || null, slim); }); } } -// exracts the skin URL of a +profile+ object -// returns null when no URL found (user has no skin) -exp.extract_skin_url = function(profile) { - return extract_url(profile, "SKIN"); -}; - -// exracts the cape URL of a +profile+ object -// returns null when no URL found (user has no cape) -exp.extract_cape_url = function(profile) { - return extract_url(profile, "CAPE"); -}; - // performs a GET request to the +url+ // +options+ object includes these options: // encoding (string), default is to return a buffer @@ -133,6 +106,7 @@ exp.get_from = function(rid, url, callback) { // the skin url is taken from the HTTP redirect // type reference is above exp.get_username_url = function(rid, name, type, callback) { + type = Number(type === "CAPE"); exp.get_from(rid, mojang_urls[type] + name + ".png", function(body, response, err) { if (!err) { if (response) { @@ -147,15 +121,24 @@ exp.get_username_url = function(rid, name, type, callback) { }; // gets the URL for a skin/cape from the profile -// +type+ specifies which to retrieve -exp.get_uuid_url = function(profile, type, callback) { - var url = null; - if (type === 0) { - url = exp.extract_skin_url(profile); - } else if (type === 1) { - url = exp.extract_cape_url(profile); +// +type+ "SKIN" or "CAPE", specifies which to retrieve +// callback: url, slim +exp.get_uuid_info = function(profile, type, callback) { + var properties = Object.get(profile, "properties") || []; + properties.forEach(function(prop) { + if (prop.name === "textures") { + var json = new Buffer(prop.value, "base64").toString(); + profile = JSON.parse(json); + } + }); + + var url = Object.get(profile, "textures." + type + ".url"); + var slim; + if (type === "SKIN") { + slim = Object.get(profile, "textures.SKIN.metadata.model") === "slim"; } - callback(url || null); + + callback(url || null, !!slim); }; // make a request to sessionserver for +uuid+ @@ -181,20 +164,17 @@ exp.get_profile = function(rid, uuid, callback) { } }; -// get the skin URL for +userId+ +// get the skin URL and type for +userId+ // +profile+ is used if +userId+ is a uuid -exp.get_skin_url = function(rid, userId, profile, callback) { - get_url(rid, userId, profile, 0, function(err, url) { - callback(err, url); - }); +// callback: error, url, slim +exp.get_skin_info = function(rid, userId, profile, callback) { + get_info(rid, userId, profile, "SKIN", callback); }; // get the cape URL for +userId+ // +profile+ is used if +userId+ is a uuid exp.get_cape_url = function(rid, userId, profile, callback) { - get_url(rid, userId, profile, 1, function(err, url) { - callback(err, url); - }); + get_info(rid, userId, profile, "CAPE", callback); }; // download the +tex_hash+ image from the texture server diff --git a/lib/object-patch.js b/lib/object-patch.js new file mode 100644 index 0000000..261460e --- /dev/null +++ b/lib/object-patch.js @@ -0,0 +1,22 @@ +// Adds Object.get function +// +pathstr+ is a string of dot-separated nested properties on +ojb+ +// returns undefined if any of the properties do not exist +// returns the value of the last property otherwise +// +// Object.get({"foo": {"bar": 123}}, "foo.bar"); // 123 +// Object.get({"foo": {"bar": 123}}, "bar.foo"); // undefined +Object.get = function(obj, pathstr) { + var path = pathstr.split("."); + var result = obj; + + for (var i = 0; i < path.length; i++) { + var key = path[i]; + if (!result || !result.hasOwnProperty(key)) { + return undefined; + } else { + result = result[key]; + } + } + + return result; +}; \ No newline at end of file diff --git a/lib/renders.js b/lib/renders.js index 7073c6f..9fe1b9c 100644 --- a/lib/renders.js +++ b/lib/renders.js @@ -8,200 +8,246 @@ var Canvas = require("canvas"); var Image = Canvas.Image; var exp = {}; -// scales an image from the +imagedata+ onto the +context+ -// scaled by a factor of +scale+ with options +d_x+ and +d_y+ -function scale_image(imageData, context, d_x, d_y, scale) { - var width = imageData.width; - var height = imageData.height; - context.clearRect(0, 0, width, height); // Clear the spot where it originated from - for (var y = 0; y < height; y++) { // height original - for (var x = 0; x < width; x++) { // width original - // Gets original colour, then makes a scaled square of the same colour - var index = (x + y * width) * 4; - context.fillStyle = "rgba(" + imageData.data[index + 0] + ", " + imageData.data[index + 1] + ", " + imageData.data[index + 2] + ", " + imageData.data[index + 3] + ")"; - context.fillRect(d_x + x * scale, d_y + y * scale, scale, scale); - } - } -} - -// makes images less worse by using binary transparency -function enhance(context) { - var imagedata = context.getImageData(0, 0, context.canvas.width, context.canvas.height); +// set alpha values to 255 +function removeTransparency(canvas) { + var ctx = canvas.getContext("2d"); + var imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height); var data = imagedata.data; // data is [r,g,b,a, r,g,b,a, *] - for (var i = 3; i < data.length; i += 4) { - // round to 0 or 255 - data[i] = Math.round(data[i] / 255) * 255; + for (var i = 0; i < data.length; i += 4) { + // usually we would have to check for alpha = 0 + // and set color to black here + // but node-canvas already does that for us + + // remove transparency + data[i + 3] = 255; } - context.putImageData(imagedata, 0, 0); + ctx.putImageData(imagedata, 0, 0); + return canvas; } -// draws the helmet on to the +skin_canvas+ -// using the skin from the +model_ctx+ at the +scale+ -exp.draw_helmet = function(skin_canvas, model_ctx, scale) { - // Helmet - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 40 * scale, 8 * scale, 8 * scale, 8 * scale, 10 * scale, 13 / 1.2 * scale, 8 * scale, 8 * scale); - // Helmet - Right - model_ctx.setTransform(1, 0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 32 * scale, 8 * scale, 8 * scale, 8 * scale, 2 * scale, 3 / 1.2 * scale, 8 * scale, 8 * scale); - // Helmet - Top - model_ctx.setTransform(-1, 0.5, 1, 0.5, 0, 0); - model_ctx.scale(-1, 1); - model_ctx.drawImage(skin_canvas, 40 * scale, 0, 8 * scale, 8 * scale, -3 * scale, 5 * scale, 8 * scale, 8 * scale); -}; - -// draws the head on to the +skin_canvas+ -// using the skin from the +model_ctx+ at the +scale+ -exp.draw_head = function(skin_canvas, model_ctx, scale) { - // Head - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 8 * scale, 8 * scale, 8 * scale, 8 * scale, 10 * scale, 13 / 1.2 * scale, 8 * scale, 8 * scale); - // Head - Right - model_ctx.setTransform(1, 0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 0, 8 * scale, 8 * scale, 8 * scale, 2 * scale, 3 / 1.2 * scale, 8 * scale, 8 * scale); - // Head - Top - model_ctx.setTransform(-1, 0.5, 1, 0.5, 0, 0); - model_ctx.scale(-1, 1); - model_ctx.drawImage(skin_canvas, 8 * scale, 0, 8 * scale, 8 * scale, -3 * scale, 5 * scale, 8 * scale, 8 * scale); -}; - -// draws the body on to the +skin_canvas+ -// using the skin from the +model_ctx+ at the +scale+ -// parts are labeled as if drawn from the skin's POV -exp.draw_body = function(rid, skin_canvas, model_ctx, scale) { - if (skin_canvas.height === 32 * scale) { - logging.debug(rid, "uses old skin format"); - // Left Leg - // Left Leg - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.scale(-1, 1); - model_ctx.drawImage(skin_canvas, 4 * scale, 20 * scale, 4 * scale, 12 * scale, -16 * scale, 34.4 / 1.2 * scale, 4 * scale, 12 * scale); - - // Right Leg - // Right Leg - Right - model_ctx.setTransform(1, 0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 0 * scale, 20 * scale, 4 * scale, 12 * scale, 4 * scale, 26.4 / 1.2 * scale, 4 * scale, 12 * scale); - // Right Leg - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 4 * scale, 20 * scale, 4 * scale, 12 * scale, 8 * scale, 34.4 / 1.2 * scale, 4 * scale, 12 * scale); - - // Arm Left - // Arm Left - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.scale(-1, 1); - model_ctx.drawImage(skin_canvas, 44 * scale, 20 * scale, 4 * scale, 12 * scale, -20 * scale, 20 / 1.2 * scale, 4 * scale, 12 * scale); - // Arm Left - Top - model_ctx.setTransform(-1, 0.5, 1, 0.5, 0, 0); - model_ctx.drawImage(skin_canvas, 44 * scale, 16 * scale, 4 * scale, 4 * scale, 0, 16 * scale, 4 * scale, 4 * scale); - - // Body - // Body - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 20 * scale, 20 * scale, 8 * scale, 12 * scale, 8 * scale, 20 / 1.2 * scale, 8 * scale, 12 * scale); - - // Arm Right - // Arm Right - Right - model_ctx.setTransform(1, 0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 40 * scale, 20 * scale, 4 * scale, 12 * scale, 0, 16 / 1.2 * scale, 4 * scale, 12 * scale); - // Arm Right - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 44 * scale, 20 * scale, 4 * scale, 12 * scale, 4 * scale, 20 / 1.2 * scale, 4 * scale, 12 * scale); - // Arm Right - Top - model_ctx.setTransform(-1, 0.5, 1, 0.5, 0, 0); - model_ctx.scale(-1, 1); - model_ctx.drawImage(skin_canvas, 44 * scale, 16 * scale, 4 * scale, 4 * scale, -16 * scale, 16 * scale, 4 * scale, 4 * scale); - } else { - logging.debug(rid, "uses new skin format"); - // Left Leg - // Left Leg - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 20 * scale, 52 * scale, 4 * scale, 12 * scale, 12 * scale, 34.4 / 1.2 * scale, 4 * scale, 12 * scale); - - // Right Leg - // Right Leg - Right - model_ctx.setTransform(1, 0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 0, 20 * scale, 4 * scale, 12 * scale, 4 * scale, 26.4 / 1.2 * scale, 4 * scale, 12 * scale); - // Right Leg - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 4 * scale, 20 * scale, 4 * scale, 12 * scale, 8 * scale, 34.4 / 1.2 * scale, 4 * scale, 12 * scale); - - // Arm Left - // Arm Left - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 36 * scale, 52 * scale, 4 * scale, 12 * scale, 16 * scale, 20 / 1.2 * scale, 4 * scale, 12 * scale); - // Arm Left - Top - model_ctx.setTransform(-1, 0.5, 1, 0.5, 0, 0); - model_ctx.drawImage(skin_canvas, 36 * scale, 48 * scale, 4 * scale, 4 * scale, 0, 16 * scale, 4 * scale, 4 * scale); - - // Body - // Body - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 20 * scale, 20 * scale, 8 * scale, 12 * scale, 8 * scale, 20 / 1.2 * scale, 8 * scale, 12 * scale); - - // Arm Right - // Arm Right - Right - model_ctx.setTransform(1, 0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 40 * scale, 20 * scale, 4 * scale, 12 * scale, 0, 16 / 1.2 * scale, 4 * scale, 12 * scale); - // Arm Right - Front - model_ctx.setTransform(1, -0.5, 0, 1.2, 0, 0); - model_ctx.drawImage(skin_canvas, 44 * scale, 20 * scale, 4 * scale, 12 * scale, 4 * scale, 20 / 1.2 * scale, 4 * scale, 12 * scale); - // Arm Right - Top - model_ctx.setTransform(-1, 0.5, 1, 0.5, 0, 0); - model_ctx.scale(-1, 1); - model_ctx.drawImage(skin_canvas, 44 * scale, 16 * scale, 4 * scale, 4 * scale, -16 * scale, 16 * scale, 4 * scale, 4 * scale); +function hasTransparency(canvas) { + var ctx = canvas.getContext("2d"); + var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data; + for (var i = 3; i < imageData.length; i += 4) { + if (imageData[i] < 255) { + // found pixel with translucent alpha value + return true; + } } -}; + return false; +} -// sets up the necessary components to draw the skin model -// uses the +img+ skin with options of drawing -// the +overlay+ and the +body+ -// callback: error, image buffer -exp.draw_model = function(rid, img, scale, overlay, body, callback) { - var image = new Image(); +function resize(src, scale) { + var dst = new Canvas(); + dst.width = scale * src.width; + dst.height = scale * src.height; + var context = dst.getContext("2d"); - image.onerror = function(err) { - callback(err, null); - }; + // don't blur on resize + context.patternQuality = "fast"; - image.onload = function() { - var width = 64 * scale; - var original_height = image.height === 32 ? 32 : 64; - var height = original_height * scale; - var model_canvas = new Canvas(20 * scale, (body ? 44.8 : 17.6) * scale); - var skin_canvas = new Canvas(width, height); - var model_ctx = model_canvas.getContext("2d"); - var skin_ctx = skin_canvas.getContext("2d"); + context.drawImage(src, 0, 0, src.width * scale, src.height * scale); + return dst; +} + +function getPart(src, x, y, width, height, scale) { + var dst = new Canvas(); + dst.width = scale * width; + dst.height = scale * height; + var context = dst.getContext("2d"); + + // don't blur on resize + context.patternQuality = "fast"; + + context.drawImage(src, x, y, width, height, 0, 0, width * scale, height * scale); + return dst; +} + +function flip(src) { + var dst = new Canvas(); + dst.width = src.width; + dst.height = src.height; + var context = dst.getContext("2d"); + context.scale(-1, 1); + context.drawImage(src, -src.width, 0); + return dst; +} + +// skew for isometric perspective +var skew_a = 26 / 45; // 0.57777777 +var skew_b = skew_a * 2; // 1.15555555 + +exp.draw_model = function(rid, img, scale, overlay, is_body, slim, callback) { + var canvas = new Canvas(); + canvas.width = scale * 20; + canvas.height = scale * (is_body ? 45.1 : 18.5); + + var ctx = canvas.getContext("2d"); + var skin = new Image(); + + skin.onload = function() { + var old_skin = skin.height === 32; + var arm_width = slim ? 3 : 4; + + /* eslint-disable no-multi-spaces */ + var head_top = resize(removeTransparency(getPart(skin, 8, 0, 8, 8, 1)), scale); + var head_front = resize(removeTransparency(getPart(skin, 8, 8, 8, 8, 1)), scale); + var head_right = resize(removeTransparency(getPart(skin, 0, 8, 8, 8, 1)), scale); + + var arm_right_top = resize(removeTransparency(getPart(skin, 44, 16, arm_width, 4, 1)), scale); + var arm_right_front = resize(removeTransparency(getPart(skin, 44, 20, arm_width, 12, 1)), scale); + var arm_right_side = resize(removeTransparency(getPart(skin, 40, 20, 4, 12, 1)), scale); + + var arm_left_top = old_skin ? flip(arm_right_top) : resize(removeTransparency(getPart(skin, 36, 48, arm_width, 4, 1)), scale); + var arm_left_front = old_skin ? flip(arm_right_front) : resize(removeTransparency(getPart(skin, 36, 52, arm_width, 12, 1)), scale); + + var leg_right_front = resize(removeTransparency(getPart(skin, 4, 20, 4, 12, 1)), scale); + var leg_right_side = resize(removeTransparency(getPart(skin, 0, 20, 4, 12, 1)), scale); + + var leg_left_front = old_skin ? flip(leg_right_front) : resize(removeTransparency(getPart(skin, 20, 52, 4, 12, 1)), scale); + + var body_front = resize(removeTransparency(getPart(skin, 20, 20, 8, 12, 1)), scale); + /* eslint-enable no-multi-spaces */ - skin_ctx.drawImage(image, 0, 0, 64, original_height); - // Scale it - scale_image(skin_ctx.getImageData(0, 0, 64, original_height), skin_ctx, 0, 0, scale); - if (body) { - exp.draw_body(rid, skin_canvas, model_ctx, scale); - } - exp.draw_head(skin_canvas, model_ctx, scale); if (overlay) { - exp.draw_helmet(skin_canvas, model_ctx, scale); + if (hasTransparency(getPart(skin, 32, 0, 32, 32, 1))) { + // render head overlay + head_top.getContext("2d").drawImage(getPart(skin, 40, 0, 8, 8, scale), 0, 0); + head_front.getContext("2d").drawImage(getPart(skin, 40, 8, 8, 8, scale), 0, 0); + head_right.getContext("2d").drawImage(getPart(skin, 32, 8, 8, 8, scale), 0, 0); + } + + if (!old_skin) { + // See #117 + // if MC-89760 gets fixed, we can (probably) simply check the whole skin for transparency + + /* eslint-disable no-multi-spaces */ + var body_region = getPart(skin, 16, 32, 32, 16, 1); + var right_arm_region = getPart(skin, 48, 48, 16, 16, 1); + var left_arm_region = getPart(skin, 40, 32, 16, 16, 1); + var right_leg_region = getPart(skin, 0, 32, 16, 16, 1); + var left_leg_region = getPart(skin, 0, 48, 16, 16, 1); + /* eslint-enable no-multi-spaces */ + + if (hasTransparency(body_region)) { + // render body overlay + body_front.getContext("2d").drawImage(getPart(skin, 20, 36, 8, 12, scale), 0, 0); + } + + if (hasTransparency(right_arm_region)) { + // render right arm overlay + arm_right_top.getContext("2d").drawImage(getPart(skin, 44, 32, arm_width, 4, scale), 0, 0); + arm_right_front.getContext("2d").drawImage(getPart(skin, 44, 36, arm_width, 12, scale), 0, 0); + arm_right_side.getContext("2d").drawImage(getPart(skin, 40, 36, 4, 12, scale), 0, 0); + } + + if (hasTransparency(left_arm_region)) { + // render left arm overlay + arm_left_top.getContext("2d").drawImage(getPart(skin, 36 + 16, 48, arm_width, 4, scale), 0, 0); + arm_left_front.getContext("2d").drawImage(getPart(skin, 36 + 16, 52, arm_width, 12, scale), 0, 0); + } + + if (hasTransparency(right_leg_region)) { + // render right leg overlay + leg_right_front.getContext("2d").drawImage(getPart(skin, 4, 36, 4, 12, scale), 0, 0); + leg_right_side.getContext("2d").drawImage(getPart(skin, 0, 36, 4, 12, scale), 0, 0); + } + + if (hasTransparency(left_leg_region)) { + // render left leg overlay + leg_left_front.getContext("2d").drawImage(getPart(skin, 4, 52, 4, 12, scale), 0, 0); + } + } } - // FIXME: This is a temporary fix for #32 - enhance(model_ctx); + var x = 0; + var y = 0; + var z = 0; - model_canvas.toBuffer(function(err, buf) { + var z_offset = scale * 3; + var x_offset = scale * 2; + + if (is_body) { + // pre-render front onto separate canvas + var front = new Canvas(); + front.width = scale * 16; + front.height = scale * 24; + var frontc = front.getContext("2d"); + frontc.patternQuality = "fast"; + + frontc.drawImage(arm_right_front, (4 - arm_width) * scale, 0 * scale, arm_width * scale, 12 * scale); + frontc.drawImage(arm_left_front, 12 * scale, 0 * scale, arm_width * scale, 12 * scale); + frontc.drawImage(body_front, 4 * scale, 0 * scale, 8 * scale, 12 * scale); + frontc.drawImage(leg_right_front, 4 * scale, 12 * scale, 4 * scale, 12 * scale); + frontc.drawImage(leg_left_front, 8 * scale, 12 * scale, 4 * scale, 12 * scale); + + + // top + x = x_offset + scale * 2; + y = scale * -arm_width; + z = z_offset + scale * 8; + ctx.setTransform(1, -skew_a, 1, skew_a, 0, 0); + ctx.drawImage(arm_right_top, y - z - 0.5, x + z, arm_right_top.width + 1, arm_right_top.height + 1); + + y = scale * 8; + ctx.drawImage(arm_left_top, y - z, x + z, arm_left_top.width, arm_left_top.height + 1); + + // right side + ctx.setTransform(1, skew_a, 0, skew_b, 0, 0); + x = x_offset + scale * 2; + y = 0; + z = z_offset + scale * 20; + ctx.drawImage(leg_right_side, x + y, z - y, leg_right_side.width, leg_right_side.height); + + x = x_offset + scale * 2; + y = scale * -arm_width; + z = z_offset + scale * 8; + ctx.drawImage(arm_right_side, x + y, z - y - 0.5, arm_right_side.width, arm_right_side.height + 1); + + // front + z = z_offset + scale * 12; + y = 0; + ctx.setTransform(1, -skew_a, 0, skew_b, 0, skew_a); + ctx.drawImage(front, y + x, x + z - 0.5, front.width, front.height); + } + + // head top + x = x_offset; + y = -0.5; + z = z_offset; + ctx.setTransform(1, -skew_a, 1, skew_a, 0, 0); + ctx.drawImage(head_top, y - z, x + z, head_top.width, head_top.height + 1); + + // head front + x = x_offset + 8 * scale; + y = 0; + z = z_offset - 0.5; + ctx.setTransform(1, -skew_a, 0, skew_b, 0, skew_a); + ctx.drawImage(head_front, y + x, x + z, head_front.width, head_front.height); + + // head right + x = x_offset; + y = 0; + z = z_offset; + ctx.setTransform(1, skew_a, 0, skew_b, 0, 0); + ctx.drawImage(head_right, x + y, z - y - 0.5, head_right.width, head_right.height + 1); + + canvas.toBuffer(function(err, buf) { + if (err) { + logging.error(rid, "error creating buffer:", err); + } callback(err, buf); }); }; - image.src = img; + skin.src = img; }; // helper method to open a render from +renderpath+ // callback: error, image buffer exp.open_render = function(rid, renderpath, callback) { - fs.readFile(renderpath, function(err, buf) { - callback(err, buf); - }); + fs.readFile(renderpath, callback); }; - module.exports = exp; \ No newline at end of file diff --git a/lib/response.js b/lib/response.js index 561d58f..fbaf8f2 100644 --- a/lib/response.js +++ b/lib/response.js @@ -63,19 +63,15 @@ module.exports = function(request, response, result) { headers["X-Storage-Type"] = human_status[result.status]; } - if (result.body) { - // use Mojang's image hash if available - // use crc32 as a hash function otherwise - var etag = result.hash && result.hash.substr(0, 10) || crc(result.body); - headers.Etag = "\"" + etag + "\""; + // use crc32 as a hash function for Etag + var etag = "\"" + crc(result.body || "") + "\""; - // handle etag caching - var incoming_etag = request.headers["if-none-match"]; - if (incoming_etag && incoming_etag === headers.Etag) { - response.writeHead(304, headers); - response.end(); - return; - } + // handle etag caching + var incoming_etag = request.headers["if-none-match"]; + if (incoming_etag && incoming_etag === etag) { + response.writeHead(304, headers); + response.end(); + return; } if (result.redirect) { @@ -87,12 +83,16 @@ module.exports = function(request, response, result) { if (result.status === -2) { response.writeHead(result.code || 422, headers); - response.end(result.body); } else if (result.status === -1) { response.writeHead(500, headers); - response.end(result.body); } else { - response.writeHead(result.body ? 200 : 404, headers); - response.end(result.body); + if (result.body) { + headers.Etag = etag; + response.writeHead(200, headers); + } else { + response.writeHead(404, headers); + } } + + response.end(result.body); }; \ No newline at end of file diff --git a/lib/routes/avatars.js b/lib/routes/avatars.js index 5146638..24d6497 100644 --- a/lib/routes/avatars.js +++ b/lib/routes/avatars.js @@ -7,7 +7,8 @@ var url = require("url"); function handle_default(img_status, userId, size, def, req, err, callback) { def = def || skins.default_skin(userId); - if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") { + var defname = def.toLowerCase(); + if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") { if (helpers.id_valid(def)) { // clean up the old URL to match new image var parsed = req.url; @@ -29,7 +30,7 @@ function handle_default(img_status, userId, size, def, req, err, callback) { } } else { // handle steve and alex - def = def.toLowerCase(); + def = defname; if (def.substr(0, 4) !== "mhf_") { def = "mhf_" + def; } diff --git a/lib/routes/renders.js b/lib/routes/renders.js index 3ef4724..90ca267 100644 --- a/lib/routes/renders.js +++ b/lib/routes/renders.js @@ -12,7 +12,8 @@ var fs = require("fs"); // overlay is query param function handle_default(rid, scale, overlay, body, img_status, userId, size, def, req, err, callback) { def = def || skins.default_skin(userId); - if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") { + var defname = def.toLowerCase(); + if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") { if (helpers.id_valid(def)) { // clean up the old URL to match new image var parsed = req.url; @@ -34,13 +35,13 @@ function handle_default(rid, scale, overlay, body, img_status, userId, size, def } } else { // handle steve and alex - def = def.toLowerCase(); + def = defname; 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 - renders.draw_model(rid, buf, scale, overlay, body, function(render_err, def_img) { + renders.draw_model(rid, buf, scale, overlay, body, def === "mhf_alex", function(render_err, def_img) { callback({ status: img_status, body: def_img, @@ -55,7 +56,7 @@ function handle_default(rid, scale, overlay, body, img_status, userId, size, def // GET render request module.exports = function(req, callback) { - var raw_type = (req.url.path_list[1] || ""); + var raw_type = req.url.path_list[1] || ""; var rid = req.id; var body = raw_type === "body"; var userId = (req.url.path_list[2] || "").split(".")[0]; diff --git a/lib/routes/skins.js b/lib/routes/skins.js index 310b4bd..f71991a 100644 --- a/lib/routes/skins.js +++ b/lib/routes/skins.js @@ -8,7 +8,8 @@ var url = require("url"); function handle_default(img_status, userId, def, req, err, callback) { def = def || skins.default_skin(userId); - if (def !== "steve" && def !== "mhf_steve" && def !== "alex" && def !== "mhf_alex") { + var defname = def.toLowerCase(); + if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") { if (helpers.id_valid(def)) { // clean up the old URL to match new image var parsed = req.url; @@ -30,7 +31,7 @@ function handle_default(img_status, userId, def, req, err, callback) { } } else { // handle steve and alex - def = def.toLowerCase(); + def = defname; if (def.substr(0, 4) !== "mhf_") { def = "mhf_" + def; } @@ -83,7 +84,7 @@ module.exports = function(req, callback) { userId = userId.replace(/-/g, ""); 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.code === "ENOENT") { // no such file diff --git a/lib/skins.js b/lib/skins.js index a6e1637..d2c7cda 100644 --- a/lib/skins.js +++ b/lib/skins.js @@ -104,8 +104,12 @@ exp.resize_img = function(inname, size, callback) { // returns "mhf_alex" or "mhf_steve" calculated by the +uuid+ exp.default_skin = function(uuid) { if (uuid.length <= 16) { - // we can't get the skin type by username - return "mhf_steve"; + if (uuid.toLowerCase() === "mhf_alex") { + return uuid; + } else { + // we can't get the skin type by username + return "mhf_steve"; + } } else { // great thanks to Minecrell for research into Minecraft and Java's UUID hashing! // https://git.io/xJpV diff --git a/package.json b/package.json index 8bb2adb..fad407f 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "iojs": "2.0.x" }, "dependencies": { - "canvas": "^1.2.9", + "canvas": "^1.3.4", "crc": "~3.3.0", "ejs": "^2.3.4", "lwip": "~0.0.7", diff --git a/test/test.js b/test/test.js index 3b04a8d..d8ae20c 100644 --- a/test/test.js +++ b/test/test.js @@ -17,7 +17,7 @@ var fs = require("fs"); config.server.http_timeout *= 3; // no spam -if (process.env.VERBOSE_TEST !== "true") { +if (process.env.VERBOSE_TEST !== "true" && process.env.TRAVIS !== "true") { logging.log = logging.debug = logging.warn = logging.error = function() {}; } @@ -52,7 +52,10 @@ var alex_ids = [ "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) { return Math.floor(Math.random() * (max - min + 1)) + min; @@ -120,14 +123,14 @@ describe("Crafatar", function() { }); it("should not exist (uuid)", function(done) { 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.strictEqual(profile, null); 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); done(); }); @@ -136,10 +139,10 @@ describe("Crafatar", function() { describe("Avatar", function() { it("uuid's account should exist, but skin should not", function(done) { // 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.notStrictEqual(profile, null); - networking.get_uuid_url(profile, 1, function(url) { + networking.get_uuid_info(profile, "CAPE", function(url) { assert.strictEqual(url, null); done(); }); @@ -172,7 +175,7 @@ describe("Crafatar", function() { it("should time out on uuid info download", function(done) { var original_timeout = config.server.http_timeout; 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"); config.server.http_timeout = original_timeout; done(); @@ -181,7 +184,7 @@ describe("Crafatar", function() { it("should time out on username info download", function(done) { var original_timeout = config.server.http_timeout; 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"); config.server.http_timeout = original_timeout; done(); @@ -190,7 +193,7 @@ describe("Crafatar", function() { it("should time out on skin download", function(done) { var original_timeout = config.http_timeout; 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"); config.server.http_timeout = original_timeout; done(); @@ -198,14 +201,14 @@ describe("Crafatar", function() { }); it("should not find the skin", function(done) { 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 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); done(); }); @@ -233,7 +236,6 @@ describe("Crafatar", function() { assert.ifError(error); assert.ifError(body); assert.equal(res.statusCode, 304); - assert(res.headers.etag); assert_headers(res); callback(); }); @@ -338,440 +340,396 @@ describe("Crafatar", function() { var server_tests = { "avatar with existing username": { url: "http://localhost:3000/avatars/jeb_?size=16", - etag: '"a846b82963"', - crc32: 1623808067 + crc32: [1623808067] }, "avatar with non-existent username": { url: "http://localhost:3000/avatars/0?size=16", - etag: '"mhf_steve"', 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=mhf_alex", - etag: '"mhf_alex"', crc32: [862751081, 809395677] }, "avatar with non-existent username defaulting to username": { url: "http://localhost:3000/avatars/0?size=16&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/avatars/jeb_?size=16" }, "avatar with non-existent username defaulting to uuid": { url: "http://localhost:3000/avatars/0?size=16&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16" }, "avatar with non-existent username defaulting to url": { url: "http://localhost:3000/avatars/0?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "overlay avatar with existing username": { url: "http://localhost:3000/avatars/jeb_?size=16&overlay", - etag: '"a846b82963"', - crc32: 646871998 + crc32: [646871998] }, "overlay avatar with non-existent username": { url: "http://localhost:3000/avatars/0?size=16&overlay", - etag: '"mhf_steve"', crc32: [2416827277, 1243826040] }, - "overlay 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&overlay&default=mhf_alex", - etag: '"mhf_alex"', crc32: [862751081, 809395677] }, "overlay avatar with non-existent username defaulting to username": { url: "http://localhost:3000/avatars/0?size=16&overlay&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/avatars/jeb_?size=16&overlay=" }, "overlay avatar with non-existent username defaulting to uuid": { url: "http://localhost:3000/avatars/0?size=16&overlay&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay=" }, "overlay avatar with non-existent username defaulting to url": { url: "http://localhost:3000/avatars/0?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "avatar with existing uuid": { url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16", - etag: '"a846b82963"', - crc32: 1623808067 + crc32: [1623808067] }, "avatar with non-existent uuid": { url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16", - etag: '"mhf_steve"', 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=mhf_alex", - etag: '"mhf_alex"', crc32: [862751081, 809395677] }, "avatar with non-existent uuid defaulting to username": { url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/avatars/jeb_?size=16" }, "avatar with non-existent uuid defaulting to uuid": { url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16" }, "avatar with non-existent uuid defaulting to url": { url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "overlay avatar with existing uuid": { url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay", - etag: '"a846b82963"', - crc32: 646871998 + crc32: [646871998] }, "overlay avatar with non-existent uuid": { url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay", - etag: '"mhf_steve"', crc32: [2416827277, 1243826040] }, - "overlay 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&overlay&default=mhf_alex", - etag: '"mhf_alex"', crc32: [862751081, 809395677] }, "overlay avatar with non-existent uuid defaulting to username": { url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/avatars/jeb_?size=16" }, "overlay avatar with non-existent uuid defaulting to uuid": { url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16" }, "overlay avatar with non-existent uuid defaulting to url": { url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "cape with existing username": { url: "http://localhost:3000/capes/jeb_", - etag: '"3f688e0e69"', crc32: [989800403, 1901140141] }, "cape with non-existent username": { url: "http://localhost:3000/capes/0", - crc32: 0 + crc32: [0] }, "cape with non-existent username defaulting to url": { url: "http://localhost:3000/capes/0?default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "cape with existing uuid": { url: "http://localhost:3000/capes/853c80ef3c3749fdaa49938b674adae6", - etag: '"3f688e0e69"', crc32: [989800403, 1901140141] }, "cape with non-existent uuid": { url: "http://localhost:3000/capes/00000000000000000000000000000000", - crc32: 0 + crc32: [0] }, "cape with non-existent uuid defaulting to url": { url: "http://localhost:3000/capes/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "skin with existing username": { url: "http://localhost:3000/skins/jeb_", - etag: '"a846b82963"', - crc32: 26500336 + crc32: [26500336] }, "skin with non-existent username": { url: "http://localhost:3000/skins/0", - 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=mhf_alex", - etag: '"mhf_alex"', - crc32: 2298915739 + crc32: [2298915739] }, "skin with non-existent username defaulting to username": { url: "http://localhost:3000/skins/0?size=16&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/skins/jeb_?size=16" }, "skin with non-existent username defaulting to uuid": { url: "http://localhost:3000/skins/0?size=16&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16" }, "skin with non-existent username defaulting to url": { url: "http://localhost:3000/skins/0?default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "skin with existing uuid": { url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6", - etag: '"a846b82963"', - crc32: 26500336 + crc32: [26500336] }, "skin with non-existent uuid": { url: "http://localhost:3000/skins/00000000000000000000000000000000", - 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=mhf_alex", - etag: '"mhf_alex"', - crc32: 2298915739 + crc32: [2298915739] }, "skin with non-existent uuid defaulting to username": { url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/skins/jeb_?size=16" }, "skin with non-existent uuid defaulting to uuid": { url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16" }, "skin with non-existent uuid defaulting to url": { url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "head render with existing username": { url: "http://localhost:3000/renders/head/jeb_?scale=2", - etag: '"a846b82963"', - crc32: [1743362302, 208074514, 2506366593] + crc32: [3487896679, 3001090792] }, "head render with non-existent username": { url: "http://localhost:3000/renders/head/0?scale=2", - etag: '"mhf_steve"', - crc32: [897270661, 1026982335, 1726107733] + 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=mhf_alex", - etag: '"mhf_alex"', - crc32: [2357619670, 3172866498, 2214491831] + crc32: [263450586, 3116770561] }, "head render with non-existent username defaulting to username": { url: "http://localhost:3000/avatars/0?scale=2&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/avatars/jeb_?scale=2" }, "head render with non-existent username defaulting to uuid": { url: "http://localhost:3000/avatars/0?scale=2&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?scale=2" }, "head render with non-existent username defaulting to url": { url: "http://localhost:3000/renders/head/0?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "overlay head render with existing username": { url: "http://localhost:3000/renders/head/jeb_?scale=2&overlay", - etag: '"a846b82963"', - crc32: [4178514320, 2340078566, 3980890516] + crc32: [762377383, 1726474987] }, "overlay head render with non-existent username": { url: "http://localhost:3000/renders/head/0?scale=2&overlay", - etag: '"mhf_steve"', - crc32: [507497693, 3868868707, 7372195] + crc32: [3257141069, 214248305] }, - "overlay 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&overlay&default=mhf_alex", - etag: '"mhf_alex"', - crc32: [891113664, 1785326216, 622500655] + crc32: [263450586, 3116770561] }, "overlay head render with non-existent username defaulting to username": { url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/renders/head/jeb_?scale=2&overlay=" }, "overlay head render with non-existent username defaulting to uuid": { url: "http://localhost:3000/renders/head/0?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=" }, "overlay head render with non-existent username defaulting to url": { 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/CaseSensitive" }, "head render with existing uuid": { url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2", - etag: '"a846b82963"', - crc32: [1743362302, 208074514, 2506366593] + crc32: [3487896679, 3001090792] }, "head render with non-existent uuid": { url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2", - etag: '"mhf_steve"', - crc32: [897270661, 1026982335, 1726107733] + 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=mhf_alex", - etag: '"mhf_alex"', - crc32: [2357619670, 3172866498, 2214491831] + crc32: [263450586, 3116770561] }, "head render with non-existent uuid defaulting to username": { url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/renders/head/jeb_?scale=2" }, "head render with non-existent uuid defaulting to uuid": { url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2" }, "head render with non-existent uuid defaulting to url": { url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "overlay head render with existing uuid": { url: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay", - etag: '"a846b82963"', - crc32: [4178514320, 2340078566, 3980890516] + crc32: [762377383, 1726474987] }, "overlay head render with non-existent uuid": { url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay", - etag: '"mhf_steve"', - crc32: [507497693, 3868868707, 7372195] + crc32: [3257141069, 214248305] }, - "overlay 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&overlay&default=mhf_alex", - etag: '"mhf_alex"', - crc32: [891113664, 1785326216, 622500655] + crc32: [263450586, 3116770561] }, "overlay head with non-existent uuid defaulting to username": { url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/renders/head/jeb_?scale=2&overlay=" }, "overlay head with non-existent uuid defaulting to uuid": { url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=" }, "overlay head render with non-existent uuid defaulting to url": { 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/CaseSensitive" }, "body render with existing username": { url: "http://localhost:3000/renders/body/jeb_?scale=2", - etag: '"a846b82963"', - crc32: [1023392610, 4127764743, 3884408742] + crc32: [3127075871, 2595192206] }, "body render with non-existent username": { url: "http://localhost:3000/renders/body/0?scale=2", - etag: '"mhf_steve"', - crc32: [3559591930, 3663447404, 1521463481] + 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=mhf_alex", - etag: '"mhf_alex"', - crc32: [470529151, 1823026927, 2079926997] + crc32: [549240598, 3952648540] }, "body render with non-existent username defaulting to username": { url: "http://localhost:3000/renders/body/0?scale=2&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/renders/body/jeb_?scale=2" }, "body render with non-existent username defaulting to uuid": { url: "http://localhost:3000/renders/body/0?scale=2&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2" }, "body render with non-existent username defaulting to url": { url: "http://localhost:3000/renders/body/0?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "overlay body render with existing username": { url: "http://localhost:3000/renders/body/jeb_?scale=2&overlay", - etag: '"a846b82963"', - crc32: [3476579592, 97705180, 3086172613] + crc32: [699892097, 2732138694] }, "overlay body render with non-existent username": { url: "http://localhost:3000/renders/body/0?scale=2&overlay", - etag: '"mhf_steve"', - crc32: [3992841063, 1025743887, 1906839968] + crc32: [1046655221, 1620063267] }, - "overlay 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&overlay&default=mhf_alex", - etag: '"mhf_alex"', - crc32: [3317518715, 3621585514, 294661951] + crc32: [549240598, 3952648540] }, "overlay body render with non-existent username defaulting to username": { url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/renders/body/jeb_?scale=2&overlay=" }, "overlay body render with non-existent username defaulting to uuid": { url: "http://localhost:3000/renders/body/0?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=" }, "overlay body render with non-existent username defaulting to url": { 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/CaseSensitive" }, "body render with existing uuid": { url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2", - etag: '"a846b82963"', - crc32: [1023392610, 4127764743, 3884408742] + crc32: [3127075871, 2595192206] }, "body render with non-existent uuid": { url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2", - etag: '"mhf_steve"', - crc32: [3559591930, 3663447404, 1521463481] + 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=mhf_alex", - etag: '"mhf_alex"', - crc32: [470529151, 1823026927, 2079926997] + crc32: [549240598, 3952648540] }, "body render with non-existent uuid defaulting to username": { url: "http://localhost:3000/renders/body/0?scale=2&default=jeb_", - crc32: 0, + crc32: [0], redirect: "/renders/body/jeb_?scale=2" }, "body render with non-existent uuid defaulting to uuid": { url: "http://localhost:3000/renders/body/0?scale=2&default=853c80ef3c3749fdaa49938b674adae6", - crc32: 0, + crc32: [0], redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2" }, "body render with non-existent uuid defaulting to url": { url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive", - crc32: 0, + crc32: [0], redirect: "http://example.com/CaseSensitive" }, "overlay body render with existing uuid": { url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay", - etag: '"a846b82963"', - crc32: [3476579592, 97705180, 3086172613] + crc32: [699892097, 2732138694] }, "overlay body render with non-existent uuid": { url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay", - etag: '"mhf_steve"', - crc32: [3992841063, 1025743887, 1906839968] + crc32: [1046655221, 1620063267] }, - "overlay 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&overlay&default=mhf_alex", - etag: '"mhf_alex"', - crc32: [3317518715, 3621585514, 294661951] + crc32: [549240598, 3952648540] }, "overlay body render with non-existent uuid defaulting to url": { 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/CaseSensitive" }, }; @@ -784,32 +742,30 @@ describe("Crafatar", function() { assert.ifError(error); assert_headers(res); assert(res.headers["x-storage-type"]); - assert.strictEqual(res.headers.etag, location.etag); + var hash = crc(body); var matches = false; - if (location.crc32 instanceof Array) { - for (var i = 0; i < location.crc32.length; i++) { - if (location.crc32[i] === crc(body)) { - matches = true; - break; - } + for (var c = 0; c < location.crc32.length; c++) { + if (location.crc32[c] === hash) { + matches = true; + break; } - } else { - matches = location.crc32 === crc(body); } try { - assert.ok(matches); + assert(matches); } catch(e) { - throw new Error(crc(body) + " != " + location.crc32 + " | " + body.toString("base64")); + throw new Error(hash + " != " + location.crc32 + " | " + body.toString("base64")); } assert.strictEqual(res.headers.location, location.redirect); - if (location.etag === undefined) { + if (location.crc32[0] === 0) { assert.strictEqual(res.statusCode, location.redirect ? 307 : 404); + assert.ifError(res.headers.etag); // etag must not be present on non-200 assert.strictEqual(res.headers["content-type"], "text/plain"); done(); } else { - assert(res.headers.etag); assert.strictEqual(res.headers["content-type"], "image/png"); assert.strictEqual(res.statusCode, 200); + assert(res.headers.etag); + assert.strictEqual(res.headers.etag, '"' + hash + '"'); assert_cache(location.url, res.headers.etag, function() { done(); }); @@ -819,6 +775,22 @@ describe("Crafatar", function() { }(loc)); } + it("should update the username skin type on uuid request", function(done) { + /*eslint-disable handle-callback-err */ + request.get("http://localhost:3000/renders/body/mhf_alex", function(error, res, body) { + cache.get_details("mhf_alex", function(err, details) { + assert.strictEqual(details.slim, false); + request.get("http://localhost:3000/renders/body/6ab4317889fd490597f60f67d9d76fd9", function(uerror, ures, ubody) { + cache.get_details("mhf_alex", function(cerr, cdetails) { + /*eslint-enable handle-callback-err */ + assert.strictEqual(cdetails.slim, true); + done(); + }); + }); + }); + }); + }); + it("should return a 422 (invalid size)", function(done) { var size = config.avatars.max_size + 1; request.get("http://localhost:3000/avatars/Jake_0?size=" + size, function(error, res, body) { @@ -878,13 +850,13 @@ describe("Crafatar", function() { // we have to make sure that we test both a 32x64 and 64x64 skin describe("Networking: Render", function() { 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); 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); done(); }); @@ -893,7 +865,7 @@ describe("Crafatar", function() { describe("Networking: Cape", function() { 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); done(); }); @@ -902,13 +874,13 @@ describe("Crafatar", function() { before(function() { 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); 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.strictEqual(img, null); done(); @@ -918,7 +890,7 @@ describe("Crafatar", function() { describe("Networking: Skin", function() { 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); done(); }); @@ -927,7 +899,7 @@ describe("Crafatar", function() { before(function() { 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); done(); }); @@ -948,14 +920,14 @@ describe("Crafatar", function() { }); 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.strictEqual(status, 2); 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.strictEqual(status === 0 || status === 1, true); done(); @@ -967,7 +939,7 @@ describe("Crafatar", function() { it("should be checked", function(done) { var original_cache_time = config.caching.local; 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.strictEqual(status, 3); config.caching.local = original_cache_time; @@ -979,7 +951,7 @@ describe("Crafatar", function() { describe("Networking: Skin", function() { 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); done(); }); @@ -988,13 +960,13 @@ describe("Crafatar", function() { describe("Networking: Render", function() { 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); 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); done(); }); @@ -1003,7 +975,7 @@ describe("Crafatar", function() { describe("Networking: Cape", function() { 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); done(); }); @@ -1018,8 +990,8 @@ describe("Crafatar", function() { if (id_type === "uuid") { it("uuid should be rate limited", function(done) { - networking.get_profile(rid, id, function() { - networking.get_profile(rid, id, function(err, profile) { + networking.get_profile(rid(), id, function() { + networking.get_profile(rid(), id, function(err, profile) { assert.strictEqual(err.toString(), "HTTP: 429"); assert.strictEqual(profile, null); done(); @@ -1028,8 +1000,8 @@ describe("Crafatar", function() { }); } else { 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(err, status, image) { + helpers.get_avatar(rid(), id, false, 160, function() { + helpers.get_avatar(rid(), id, false, 160, function(err, status, image) { assert.strictEqual(err, null); done(); });