mirror of
https://github.com/azures04/crafatar.git
synced 2026-03-21 23:41:18 +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",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "~4.9.0",
|
||||
|
||||
@ -1,78 +1,60 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var skins = require('../skins');
|
||||
var networking = require('../modules/networking');
|
||||
var helpers = require('../modules/helpers');
|
||||
var router = require('express').Router();
|
||||
var config = require('../modules/config');
|
||||
var skins = require('../modules/skins');
|
||||
var fs = require('fs');
|
||||
|
||||
var valid_uuid = /^[0-9a-f]{32}$/;
|
||||
|
||||
/* GET home page. */
|
||||
/* GET avatar request. */
|
||||
router.get('/:uuid/:size?', function(req, res) {
|
||||
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 start = new Date();
|
||||
|
||||
// Prevent app from crashing/freezing
|
||||
if (size <= 0 || size > 512) size = 180;
|
||||
if (valid_uuid.test(uuid)) {
|
||||
var filename = uuid + ".png";
|
||||
if (fs.existsSync("skins/" + filename)) {
|
||||
console.log('found ' + filename);
|
||||
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': 'local'});
|
||||
res.end(data);
|
||||
if (size <= config.min_size || size > config.max_size) {
|
||||
// "Unprocessable Entity", valid request, but semantically erroneous:
|
||||
// https://tools.ietf.org/html/rfc4918#page-78
|
||||
res.status(422).send("422 Invalid size");
|
||||
return;
|
||||
} else if (!helpers.uuid_valid(uuid)) {
|
||||
res.status(422).send("422 Invalid UUID");
|
||||
return;
|
||||
}
|
||||
|
||||
helpers.get_avatar(uuid, size, function(err, status, image) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else if (status == 1 || status == 2) {
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
res.status(404).send('404 Not found');
|
||||
}
|
||||
} else {
|
||||
res.status(422) // "Unprocessable Entity", valid request, but semantically erroneous: https://tools.ietf.org/html/rfc4918#page-78
|
||||
.send("422 Invalid UUID");
|
||||
}
|
||||
|
||||
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;
|
||||
@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
var debug = require('debug')('crafatar');
|
||||
var app = require('../app');
|
||||
var app = require('./app');
|
||||
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
|
||||
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