123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- 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 = "https://sessionserver.mojang.com/session/minecraft/profile/";
- var textures_url = "https://textures.minecraft.net/texture/";
- // 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.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;
|