Implement Skeleton for Returning Capes (Currently returns skins)

Align capes.js codestyle to the rest of the project

get_cape_hash helper (not working - wip)

clean up capes.js
This commit is contained in:
Navarr Barnier 2014-12-12 10:16:40 -05:00 committed by Jake
parent 5929461df8
commit dd7e46f377
9 changed files with 265 additions and 4 deletions

63
app.js Normal file
View File

@ -0,0 +1,63 @@
var express = require("express");
var path = require("path");
var logger = require("morgan");
var cookieParser = require("cookie-parser");
var bodyParser = require("body-parser");
var routes = require("./routes/index");
var avatars = require("./routes/avatars");
var skins = require("./routes/skins");
var renders = require('./routes/renders');
var capes = require("./routes/capes");
var app = express();
// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");
app.use(logger("dev"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));
app.use('/', routes);
app.use('/avatars', avatars);
app.use('/skins', skins);
app.use('/renders', renders);
app.use("/capes", capes);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error("Not Found");
err.status = 404;
next(err);
});
// error handlers
// development error handler
// will print stacktrace
if (app.get("env") === "development") {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render("error", {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render("error", {
message: err.message,
error: {}
});
});
module.exports = app;

View File

@ -13,6 +13,7 @@ var config = {
helms_dir: "images/helms/", // directory where helms are kept. should have trailing "/" helms_dir: "images/helms/", // directory where helms are kept. should have trailing "/"
skins_dir: "images/skins/", // directory where skins are kept. should have trailing "/" skins_dir: "images/skins/", // directory where skins are kept. should have trailing "/"
renders_dir: "images/renders/",// Directory where rendered skins are kept. should have trailing "/" renders_dir: "images/renders/",// Directory where rendered skins are kept. should have trailing "/"
capes_dir: "images/capes/", // directory where capes are kept. should have trailing "/"
debug_enabled: false, // enables logging.debug debug_enabled: false, // enables logging.debug
min_scale: 1, // for renders min_scale: 1, // for renders
max_scale: 10, // for renders; too big values might lead to slow response time or DoS max_scale: 10, // for renders; too big values might lead to slow response time or DoS

View File

@ -75,6 +75,45 @@ function store_images(uuid, details, callback) {
} }
} }
}); });
networking.get_cape_url(uuid, function(err, cape_url) {
if (err) {
callback(err, null);
} else {
if (cape_url) {
logging.log(uuid + " " + cape_url);
// set file paths
var hash = get_hash(cape_url);
if (details && details.hash == hash) {
// hash hasn't changed
logging.log(uuid + " hash has not changed");
cache.update_timestamp(uuid, hash);
callback(null, hash);
} else {
// hash has changed
logging.log(uuid + " new hash: " + hash);
var capepath = __dirname + "/../" + config.capes_dir + hash + ".png";
if (fs.existsSync(capepath)) {
logging.log(uuid + " Cape already exists, not downloading");
cache.save_hash(uuid, hash);
callback(null, hash);
} else {
// download cape
networking.get_cape(cape_url, function(err, img) {
if (err || !img) {
callback(err, null);
}
});
}
}
} else {
// profile found, but has no cape
cache.save_hash(uuid, null);
callback(null, null);
}
}
});
} }
@ -127,6 +166,19 @@ exp.get_image_hash = function(uuid, callback) {
}); });
}; };
exp.get_cape_hash = function(uuid, callback) {
cache.get_details(uuid, function(err, details) {
if (err) {
callback(err, -1, null);
} else {
if (details && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
logging.log(uuid + " uuid cached & recently updated");
}
}
})
};
// handles requests for +uuid+ avatars with +size+ // handles requests for +uuid+ avatars with +size+
// callback contains error, status, image buffer, hash // callback contains error, status, image buffer, hash
@ -224,4 +276,23 @@ exp.get_render = function(uuid, scale, helm, body, callback) {
}); });
}; };
exp.get_cape = function(uuid, callback) {
logging.log(uuid + " cape request");
exp.get_image_hash(uuid, function(err, status, hash) {
if (hash) {
var capeurl = "http://textures.minecraft.net/texture/" + hash;
networking.get_cape(capeurl, function(err, img) {
if (err) {
logging.error("error while downloading cape");
callback(err, hash, null);
} else {
callback(null, hash, img);
}
});
} else {
callback(err, null, null);
}
});
};
module.exports = exp; module.exports = exp;

View File

@ -6,6 +6,7 @@ var fs = require("fs");
var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/"; var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
var skins_url = "https://skins.minecraft.net/MinecraftSkins/"; var skins_url = "https://skins.minecraft.net/MinecraftSkins/";
var capes_url = "https://skins.minecraft.net/MinecraftCloaks/";
// exracts the skin url of a +profile+ object // exracts the skin url of a +profile+ object
// returns null when no url found (user has no skin) // returns null when no url found (user has no skin)
@ -23,6 +24,20 @@ function extract_skin_url(profile) {
return url; return url;
} }
function extract_cape_url(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.CAPE && props.textures.CAPE.url || null;
}
});
}
return url;
}
// make a request to skins.miencraft.net // make a request to skins.miencraft.net
// the skin url is taken from the HTTP redirect // the skin url is taken from the HTTP redirect
var get_username_url = function(name, callback) { var get_username_url = function(name, callback) {
@ -104,6 +119,18 @@ exp.get_skin_url = function(uuid, callback) {
} }
}; };
exp.get_cape_url = function(uuid, callback) {
if (uuid.length <= 16) {
get_username_url(uuid, function(err, url) {
callback(err, url);
});
} else {
get_uuid_url(uuid, function(err, url) {
callback(err, url);
});
}
};
// downloads skin file from +url+ // downloads skin file from +url+
// callback contains error, image // callback contains error, image
exp.get_skin = function(url, uuid, callback) { exp.get_skin = function(url, uuid, callback) {
@ -162,4 +189,37 @@ exp.save_skin = function(uuid, hash, outpath, callback) {
} }
}; };
exp.get_cape = function(url, callback) {
request.get({
url: url,
headers: {
"User-Agent": "https://crafatar.com"
},
encoding: null, // encoding must be null so we get a buffer
timeout: config.http_timeout // ms
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
// cape downloaded successfully
logging.log("downloaded cape");
logging.debug(url);
callback(null, body);
} else {
if (error) {
logging.error("Error downloading '" + url + "': " + error);
} else if (response.statusCode == 404) {
logging.warn("texture not found (404): " + url);
} else if (response.statusCode == 429) {
logging.warn("too many requests for " + url);
logging.warn(body);
} else {
logging.error("unknown error for " + url);
logging.error(response);
logging.error(body);
error = "unknown error"; // Error needs to be set, otherwise null in callback
}
callback(error, null);
}
});
};
module.exports = exp; module.exports = exp;

66
routes/capes.js Normal file
View File

@ -0,0 +1,66 @@
var logging = require("../modules/logging");
var helpers = require("../modules/helpers");
var config = require("../modules/config");
var router = require("express").Router();
var lwip = require("lwip");
/* GET skin request. */
router.get("/:uuid.:ext?", function (req, res) {
var uuid = (req.params.uuid || "");
var def = req.query.default;
var start = new Date();
var etag = null;
if (!helpers.uuid_valid(uuid)) {
res.status(422).send("422 Invalid UUID");
return;
}
// strip dashes
uuid = uuid.replace(/-/g, "");
try {
helpers.get_cape(uuid, function (err, hash, image) {
logging.log(uuid);
if (err) {
logging.error(err);
}
etag = hash && hash.substr(0, 32) || "none";
var matches = req.get("If-None-Match") == "\"" + etag + "\"";
if (image) {
var http_status = 200;
if (matches) {
http_status = 304;
} else if (err) {
http_status = 503;
}
logging.debug("Etag: " + req.get("If-None-Match"));
logging.debug("matches: " + matches);
logging.log("status: " + http_status);
sendimage(http_status, image);
} else {
res.status(404).send("404 not found");
}
});
} catch (e) {
logging.error("Error!");
logging.error(e);
res.status(500).send("500 error while retrieving cape");
}
function sendimage(http_status, image) {
res.writeHead(http_status, {
"Content-Type": "image/png",
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
"Response-Time": new Date() - start,
"X-Storage-Type": "downloaded",
"Access-Control-Allow-Origin": "*",
"Etag": "\"" + etag + "\""
});
res.end(http_status == 304 ? null : image);
}
});
module.exports = router;

0
skins/capes/.gitkeep Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 907 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B