mirror of
https://github.com/azures04/crafatar.git
synced 2026-03-21 23:41:18 +01:00
- minor change to customise textures & session server host for custom yggdrasil server
201 lines
6.1 KiB
JavaScript
201 lines
6.1 KiB
JavaScript
var logging = require("./logging");
|
|
var request = require("request");
|
|
var config = require("../config");
|
|
var skins = require("./skins");
|
|
var http = require("http");
|
|
require("./object-patch");
|
|
|
|
var session_url = config.endpoints.session_url;
|
|
var textures_url = config.endpoints.textures_url;
|
|
|
|
// count requests made to session_url in the last 1000ms
|
|
var session_requests = [];
|
|
|
|
var exp = {};
|
|
|
|
// returns the amount of outgoing session requests made in the last 1000ms
|
|
function req_count() {
|
|
var index = session_requests.findIndex((i) => i >= Date.now() - 1000);
|
|
if (index >= 0) {
|
|
return session_requests.length - index;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// deletes all entries in session_requests, should be called every 1000ms
|
|
exp.resetCounter = function() {
|
|
var count = req_count();
|
|
if (count) {
|
|
var logfunc = count >= config.server.sessions_rate_limit ? logging.warn : logging.debug;
|
|
logfunc('Clearing old session requests (count was ' + count + ')');
|
|
session_requests.splice(0, session_requests.length - count);
|
|
} else {
|
|
session_requests = []
|
|
}
|
|
}
|
|
|
|
// performs a GET request to the +url+
|
|
// +options+ object includes these options:
|
|
// encoding (string), default is to return a buffer
|
|
// callback: the body, response,
|
|
// and error buffer. get_from helper method is available
|
|
exp.get_from_options = function(rid, url, options, callback) {
|
|
var is_session_req = config.server.sessions_rate_limit && url.startsWith(session_url);
|
|
|
|
// This is to prevent being blocked by CloudFront for exceeding the rate limit
|
|
if (is_session_req && req_count() >= config.server.sessions_rate_limit) {
|
|
var e = new Error("Skipped, rate limit exceeded");
|
|
e.name = "HTTP";
|
|
e.code = "RATELIMIT";
|
|
|
|
var response = new http.IncomingMessage();
|
|
response.statusCode = 403;
|
|
|
|
callback(null, response, e);
|
|
} else {
|
|
is_session_req && session_requests.push(Date.now());
|
|
request.get({
|
|
url: url,
|
|
headers: {
|
|
"User-Agent": "Crafatar (+https://crafatar.com)"
|
|
},
|
|
timeout: config.server.http_timeout,
|
|
followRedirect: false,
|
|
encoding: options.encoding || null,
|
|
}, function(error, response, body) {
|
|
// log url + code + description
|
|
var code = response && response.statusCode;
|
|
|
|
var logfunc = code && (code < 400 || code === 404) ? logging.debug : logging.warn;
|
|
logfunc(rid, url, code || error && error.code, http.STATUS_CODES[code]);
|
|
|
|
// not necessarily used
|
|
var e = new Error(code);
|
|
e.name = "HTTP";
|
|
e.code = "HTTPERROR";
|
|
|
|
switch (code) {
|
|
case 200:
|
|
case 301:
|
|
case 302: // never seen, but mojang might use it in future
|
|
case 307: // never seen, but mojang might use it in future
|
|
case 308: // never seen, but mojang might use it in future
|
|
// these are okay
|
|
break;
|
|
case 204: // no content, used like 404 by mojang. making sure it really has no content
|
|
case 404:
|
|
// can be cached as null
|
|
body = null;
|
|
break;
|
|
case 403: // Blocked by CloudFront :(
|
|
case 429: // this shouldn't usually happen, but occasionally does
|
|
case 500:
|
|
case 502: // CloudFront can't reach mojang origin
|
|
case 503:
|
|
case 504:
|
|
// we don't want to cache this
|
|
error = error || e;
|
|
body = null;
|
|
break;
|
|
default:
|
|
if (!error) {
|
|
// Probably 500 or the likes
|
|
logging.error(rid, "Unexpected response:", code, body);
|
|
}
|
|
error = error || e;
|
|
body = null;
|
|
break;
|
|
}
|
|
|
|
if (body && !body.length) {
|
|
// empty response
|
|
body = null;
|
|
}
|
|
|
|
callback(body, response, error);
|
|
});
|
|
}
|
|
};
|
|
|
|
// helper method for get_from_options, no options required
|
|
exp.get_from = function(rid, url, callback) {
|
|
exp.get_from_options(rid, url, {}, function(body, response, err) {
|
|
callback(body, response, err);
|
|
});
|
|
};
|
|
|
|
// gets the URL for a skin/cape from the 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.from(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(null, url || null, !!slim);
|
|
};
|
|
|
|
// make a request to sessionserver for +uuid+
|
|
// callback: error, profile
|
|
exp.get_profile = function(rid, uuid, callback) {
|
|
exp.get_from_options(rid, session_url + uuid, { encoding: "utf8" }, function(body, response, err) {
|
|
try {
|
|
body = body ? JSON.parse(body) : null;
|
|
callback(err || null, body);
|
|
} catch(e) {
|
|
if (e instanceof SyntaxError) {
|
|
logging.warn(rid, "Failed to parse JSON", e);
|
|
logging.debug(rid, body);
|
|
callback(err || null, null);
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
// get the skin URL and type for +userId+
|
|
// +profile+ is used if +userId+ is a uuid
|
|
// callback: error, url, slim
|
|
exp.get_skin_info = function(rid, userId, profile, callback) {
|
|
exp.get_uuid_info(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) {
|
|
exp.get_uuid_info(profile, "CAPE", callback);
|
|
};
|
|
|
|
// download the +tex_hash+ image from the texture server
|
|
// and save it in the +outpath+ file
|
|
// callback: error, response, image buffer
|
|
exp.save_texture = function(rid, tex_hash, outpath, callback) {
|
|
if (tex_hash) {
|
|
var textureurl = textures_url + tex_hash;
|
|
exp.get_from(rid, textureurl, function(img, response, err) {
|
|
if (err) {
|
|
callback(err, response, null);
|
|
} else {
|
|
skins.save_image(img, outpath, function(img_err) {
|
|
callback(img_err, response, img);
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
callback(null, null, null);
|
|
}
|
|
};
|
|
|
|
module.exports = exp; |