mirror of
https://github.com/azures04/crafatar.git
synced 2026-03-22 07:51:17 +01:00
split code into more modules, fixes #8
This commit is contained in:
parent
e2348bbb9d
commit
cc159d3620
8
modules/config.js
Normal file
8
modules/config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
72
modules/helpers.js
Normal file
72
modules/helpers.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
var networking = require('./networking');
|
||||||
|
var config = require('./config');
|
||||||
|
var skins = require('./skins');
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var valid_uuid = /^[0-9a-f]{32}$/;
|
||||||
|
var skins_dir = config.skins_dir;
|
||||||
|
|
||||||
|
var exp = {};
|
||||||
|
|
||||||
|
// exracts the skin url of a +profile+ object
|
||||||
|
// returns null when no url found
|
||||||
|
exp.skin_url = function(profile) {
|
||||||
|
var url = null;
|
||||||
|
if (profile && profile.properties) {
|
||||||
|
profile.properties.forEach(function(prop) {
|
||||||
|
if (prop.name == 'textures') {
|
||||||
|
var json = Buffer(prop.value, 'base64').toString();
|
||||||
|
var props = JSON.parse(json);
|
||||||
|
url = props && props.textures && props.textures.SKIN && props.textures.SKIN.url;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// returns true if the +uuid+ is a valid uuid
|
||||||
|
// the uuid may be not exist, however
|
||||||
|
exp.uuid_valid = function(uuid) {
|
||||||
|
return valid_uuid.test(uuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
// handles requests for +uuid+ images with +size+
|
||||||
|
//
|
||||||
|
// callback is a function with 3 parameters:
|
||||||
|
// error, status, image buffer
|
||||||
|
//
|
||||||
|
// the status gives information about how the image was received
|
||||||
|
// -1: profile requested, but it was not found
|
||||||
|
// 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 = skins_dir + uuid + ".png";
|
||||||
|
if (fs.existsSync(filepath)) {
|
||||||
|
skins.resize_img(filepath, size, function(result) {
|
||||||
|
callback(null, 1, result);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
networking.get_profile(uuid, function(err, profile) {
|
||||||
|
if (err) {
|
||||||
|
callback(err, -1, profile);
|
||||||
|
}
|
||||||
|
var skinurl = exp.skin_url(profile);
|
||||||
|
|
||||||
|
if (skinurl) {
|
||||||
|
networking.skin_file(skinurl, filepath, function() {
|
||||||
|
console.log('got skin');
|
||||||
|
skins.resize_img(filepath, size, function(result) {
|
||||||
|
callback(null, 2, result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// profile found, but has no skin
|
||||||
|
callback(null, 3, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = exp;
|
||||||
65
modules/networking.js
Normal file
65
modules/networking.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
var request = require('request');
|
||||||
|
var skins = require('./skins');
|
||||||
|
|
||||||
|
var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
||||||
|
|
||||||
|
var exp = {};
|
||||||
|
|
||||||
|
exp.get_profile = function(uuid, callback) {
|
||||||
|
request.get({
|
||||||
|
url: session_url + uuid,
|
||||||
|
timeout: 1000 // ms
|
||||||
|
}, function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 200) {
|
||||||
|
callback(null, JSON.parse(body));
|
||||||
|
} else {
|
||||||
|
if (error) {
|
||||||
|
callback(error, null);
|
||||||
|
return;
|
||||||
|
} else if (response.statusCode == 204 || response.statusCode == 404) {
|
||||||
|
// we get 204 No Content when UUID doesn't exist (including 404 in case they change that)
|
||||||
|
} else if (response.statusCode == 429) {
|
||||||
|
// Too Many Requests
|
||||||
|
console.warn("Too many requests for " + uuid);
|
||||||
|
console.warn(body);
|
||||||
|
} else {
|
||||||
|
console.error("Unknown error:");
|
||||||
|
console.error(response);
|
||||||
|
console.error(body);
|
||||||
|
}
|
||||||
|
callback(null, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exp.skin_file = function(url, outname, callback) {
|
||||||
|
request.get({
|
||||||
|
url: url,
|
||||||
|
encoding: null, // encoding must be null so we get a buffer
|
||||||
|
timeout: 1000 // ms
|
||||||
|
}, function (error, response, body) {
|
||||||
|
if (!error && response.statusCode == 200) {
|
||||||
|
skins.extract_face(body, outname, function() {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
console.warn("Texture not found: " + url);
|
||||||
|
} else if (response.statusCode == 429) {
|
||||||
|
// Too Many Requests
|
||||||
|
// Never got this, seems like textures aren't limited
|
||||||
|
console.warn("Too many requests for " + url);
|
||||||
|
console.warn(body);
|
||||||
|
} else {
|
||||||
|
console.error("Unknown error:");
|
||||||
|
console.error(response);
|
||||||
|
console.error(body);
|
||||||
|
}
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = exp;
|
||||||
32
modules/skins.js
Normal file
32
modules/skins.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
var lwip = require('lwip');
|
||||||
|
|
||||||
|
var exp = {};
|
||||||
|
|
||||||
|
// extracts the face from an image +buffer+
|
||||||
|
// save it to a file called +outname+
|
||||||
|
exp.extract_face = function(buffer, outname, callback) {
|
||||||
|
lwip.open(buffer, "png", function(err, image) {
|
||||||
|
if (err) throw err;
|
||||||
|
image.batch()
|
||||||
|
.crop(8, 8, 15, 15)
|
||||||
|
.writeFile(outname, function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// resizes the image file +inname+ to +size+ by +size+ pixels
|
||||||
|
// +callback+ is a buffer of the resized image
|
||||||
|
exp.resize_img = function(inname, size, callback) {
|
||||||
|
lwip.open(inname, function(err, image) {
|
||||||
|
if (err) throw err;
|
||||||
|
image.batch()
|
||||||
|
.resize(size, size, "nearest-neighbor") // nearest-neighbor doesn't blur
|
||||||
|
.toBuffer('png', function(err, buffer) {
|
||||||
|
callback(buffer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = exp;
|
||||||
@ -3,7 +3,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./bin/www"
|
"start": "node server.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "~4.9.0",
|
"express": "~4.9.0",
|
||||||
|
|||||||
@ -1,78 +1,60 @@
|
|||||||
var express = require('express');
|
var networking = require('../modules/networking');
|
||||||
var router = express.Router();
|
var helpers = require('../modules/helpers');
|
||||||
var skins = require('../skins');
|
var router = require('express').Router();
|
||||||
|
var config = require('../modules/config');
|
||||||
|
var skins = require('../modules/skins');
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
|
||||||
var valid_uuid = /^[0-9a-f]{32}$/;
|
/* GET avatar request. */
|
||||||
|
|
||||||
/* GET home page. */
|
|
||||||
router.get('/:uuid/:size?', function(req, res) {
|
router.get('/:uuid/:size?', function(req, res) {
|
||||||
var uuid = req.param('uuid');
|
var uuid = req.param('uuid');
|
||||||
var size = req.param('size') || 180;
|
var size = req.param('size') || config.default_size;
|
||||||
var def = req.query.default;
|
var def = req.query.default;
|
||||||
var start = new Date();
|
var start = new Date();
|
||||||
|
|
||||||
// Prevent app from crashing/freezing
|
// Prevent app from crashing/freezing
|
||||||
if (size <= 0 || size > 512) size = 180;
|
if (size <= config.min_size || size > config.max_size) {
|
||||||
if (valid_uuid.test(uuid)) {
|
// "Unprocessable Entity", valid request, but semantically erroneous:
|
||||||
var filename = uuid + ".png";
|
// https://tools.ietf.org/html/rfc4918#page-78
|
||||||
if (fs.existsSync("skins/" + filename)) {
|
res.status(422).send("422 Invalid size");
|
||||||
console.log('found ' + filename);
|
return;
|
||||||
skins.resize_img("skins/" + filename, size, function(data) {
|
} else if (!helpers.uuid_valid(uuid)) {
|
||||||
// tell browser to cache image locally for 10 minutes
|
res.status(422).send("422 Invalid UUID");
|
||||||
var end = new Date() - start;
|
return;
|
||||||
res.writeHead(200, {'Content-Type': 'image/png', 'Cache-Control': 'max-age=600, public', 'Response-Time': end, 'Storage-Type': 'local'});
|
|
||||||
res.end(data);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log(filename + ' not found, downloading profile..');
|
|
||||||
skins.get_profile(uuid, function(profile) {
|
|
||||||
var skinurl = skins.skin_url(profile);
|
|
||||||
if (skinurl) {
|
|
||||||
console.log('got profile, skin url is "' + skinurl + '" downloading..');
|
|
||||||
skins.skin_file(skinurl, "skins/" + filename, function() {
|
|
||||||
console.log('got skin');
|
|
||||||
skins.resize_img("skins/" + filename, size, function(data) {
|
|
||||||
// tell browser to cache image locally for 10 minutes
|
|
||||||
var end = new Date() - start;
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Type': 'image/png',
|
|
||||||
'Cache-Control': 'max-age=600, public',
|
|
||||||
'Response-Time': end,
|
|
||||||
'Storage-Type': 'downloaded'
|
|
||||||
});
|
|
||||||
res.end(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('no skin url found');
|
|
||||||
switch (def) {
|
|
||||||
case "alex":
|
|
||||||
skins.resize_img("public/images/alex.png", size, function(data) {
|
|
||||||
// tell browser to cache image locally for 10 minutes
|
|
||||||
var end = new Date() - start;
|
|
||||||
res.writeHead(404, {'Content-Type': 'image/png', 'Cache-Control': 'max-age=600, public', 'Response-Time': end, 'Storage-Type': 'local'});
|
|
||||||
res.end(data);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "steve":
|
|
||||||
skins.resize_img("public/images/steve.png", size, function(data) {
|
|
||||||
// tell browser to cache image locally for 10 minutes
|
|
||||||
var end = new Date() - start;
|
|
||||||
res.writeHead(404, {'Content-Type': 'image/png', 'Cache-Control': 'max-age=600, public', 'Response-Time': end, 'Storage-Type': 'local'});
|
|
||||||
res.end(data);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
res.status(404).send('404 Not found');
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
helpers.get_avatar(uuid, size, function(err, status, image) {
|
||||||
}
|
if (err) {
|
||||||
} else {
|
throw err;
|
||||||
res.status(422) // "Unprocessable Entity", valid request, but semantically erroneous: https://tools.ietf.org/html/rfc4918#page-78
|
} else if (status == 1 || status == 2) {
|
||||||
.send("422 Invalid UUID");
|
var time = new Date() - start;
|
||||||
|
sendimage(200, time, image);
|
||||||
|
} else if (status == 3) {
|
||||||
|
handle_404(def);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(404).send('404 Not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendimage(status, time, image) {
|
||||||
|
res.writeHead(status, {
|
||||||
|
'Content-Type': 'image/png',
|
||||||
|
'Cache-Control': 'max-age=' + config.browser_cache_time + ', public',
|
||||||
|
'Response-Time': time,
|
||||||
|
'X-Storage-Type': 'local'
|
||||||
|
});
|
||||||
|
res.end(image);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
var debug = require('debug')('crafatar');
|
var debug = require('debug')('crafatar');
|
||||||
var app = require('../app');
|
var app = require('./app');
|
||||||
|
|
||||||
app.set('port', process.env.PORT || 3000);
|
app.set('port', process.env.PORT || 3000);
|
||||||
|
|
||||||
var server = app.listen(app.get('port'), function() {
|
var server = app.listen(app.get('port'), function() {
|
||||||
debug('Express server listening on port ' + server.address().port);
|
debug('Crafatar server listening on port ' + server.address().port);
|
||||||
});
|
});
|
||||||
105
skins.js
105
skins.js
@ -1,105 +0,0 @@
|
|||||||
var request = require('request');
|
|
||||||
var lwip = require('lwip');
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Skin retrieval methods are based on @jomo's CLI Crafatar implementation.
|
|
||||||
* https://github.com/jomo/Crafatar
|
|
||||||
*/
|
|
||||||
|
|
||||||
function extract_face(buffer, outname, callback) {
|
|
||||||
lwip.open(buffer, "png", function(err, image) {
|
|
||||||
if (err) {
|
|
||||||
console.log('c ' + buffer.length);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
image.batch()
|
|
||||||
.crop(8, 8, 15, 15)
|
|
||||||
.writeFile(outname, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
get_profile: function(uuid, callback) {
|
|
||||||
request.get({
|
|
||||||
url: "https://sessionserver.mojang.com/session/minecraft/profile/" + uuid,
|
|
||||||
timeout: 1000 // ms
|
|
||||||
}, function (error, response, body) {
|
|
||||||
if (!error && response.statusCode == 200) {
|
|
||||||
callback(JSON.parse(body));
|
|
||||||
} else {
|
|
||||||
if (error) {
|
|
||||||
console.error(error);
|
|
||||||
} else if (response.statusCode == 204 || response.statusCode == 404) {
|
|
||||||
// we get 204 No Content when UUID doesn't exist (including 404 in case they change that)
|
|
||||||
} else if (response.statusCode == 429) {
|
|
||||||
// Too Many Requests
|
|
||||||
console.warn("Too many requests for " + uuid);
|
|
||||||
console.warn(body);
|
|
||||||
} else {
|
|
||||||
console.error("Unknown error:");
|
|
||||||
console.error(response);
|
|
||||||
console.error(body);
|
|
||||||
}
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
skin_url: function(profile) {
|
|
||||||
var url = null;
|
|
||||||
if (profile && profile.properties) {
|
|
||||||
profile.properties.forEach(function(prop) {
|
|
||||||
if (prop.name == 'textures') {
|
|
||||||
var json = Buffer(prop.value, 'base64').toString();
|
|
||||||
var props = JSON.parse(json);
|
|
||||||
url = props && props.textures && props.textures.SKIN && props.textures.SKIN.url;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
},
|
|
||||||
|
|
||||||
skin_file: function(url, outname, callback) {
|
|
||||||
request.get({
|
|
||||||
url: url,
|
|
||||||
encoding: null, // encoding must be null so we get a buffer
|
|
||||||
timeout: 1000 // ms
|
|
||||||
}, function (error, response, body) {
|
|
||||||
if (!error && response.statusCode == 200) {
|
|
||||||
extract_face(body, outname, function() {
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (error) {
|
|
||||||
console.error(error);
|
|
||||||
} else if (response.statusCode == 404) {
|
|
||||||
console.warn("Texture not found: " + url);
|
|
||||||
} else if (response.statusCode == 429) {
|
|
||||||
// Too Many Requests
|
|
||||||
// Never got this, seems like textures aren't limited
|
|
||||||
console.warn("Too many requests for " + url);
|
|
||||||
console.warn(body);
|
|
||||||
} else {
|
|
||||||
console.error("Unknown error:");
|
|
||||||
console.error(response);
|
|
||||||
console.error(body);
|
|
||||||
}
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
resize_img: function(inname, size, callback) {
|
|
||||||
lwip.open(inname, function(err, image) {
|
|
||||||
if (err) throw err;
|
|
||||||
image.batch()
|
|
||||||
.resize(size, size, "nearest-neighbor") // nearest-neighbor doesn't blur
|
|
||||||
.toBuffer('png', function(err, buffer) {
|
|
||||||
callback(buffer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Loading…
x
Reference in New Issue
Block a user