restructure directories
www.js is our 'main' file, it's now at the project's root instead of server.js routes, views, assets are now in lib, too
@@ -1,6 +1,7 @@
|
||||
var logging = require("./logging");
|
||||
var node_redis = require("redis");
|
||||
var config = require("./config");
|
||||
var path = require("path");
|
||||
var url = require("url");
|
||||
var fs = require("fs");
|
||||
|
||||
@@ -39,17 +40,17 @@ function connect_redis() {
|
||||
// the helms file is ignored because we only need 1 file to read/write from
|
||||
function update_file_date(rid, skin_hash) {
|
||||
if (skin_hash) {
|
||||
var path = config.faces_dir + skin_hash + ".png";
|
||||
fs.exists(path, function(exists) {
|
||||
var face_path = path.join(__dirname, "..", config.faces_dir, skin_hash + ".png");
|
||||
fs.exists(face_path, function(exists) {
|
||||
if (exists) {
|
||||
var date = new Date();
|
||||
fs.utimes(path, date, date, function(err) {
|
||||
fs.utimes(face_path, date, date, function(err) {
|
||||
if (err) {
|
||||
logging.error(rid, "Error:", err.stack);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logging.error(rid, "tried to update", path + " date, but it does not exist");
|
||||
logging.error(rid, "tried to update", face_path + " date, but it does not exist");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BIN
lib/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
lib/public/favicon.png
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
lib/public/images/akliz.png
Normal file
|
After Width: | Height: | Size: 830 B |
BIN
lib/public/images/alex.png
Normal file
|
After Width: | Height: | Size: 150 B |
BIN
lib/public/images/alex_skin.png
Normal file
|
After Width: | Height: | Size: 997 B |
BIN
lib/public/images/steve.png
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
lib/public/images/steve_skin.png
Normal file
|
After Width: | Height: | Size: 835 B |
BIN
lib/public/images/twitter.png
Normal file
|
After Width: | Height: | Size: 371 B |
BIN
lib/public/logo.png
Normal file
|
After Width: | Height: | Size: 691 B |
295
lib/public/stylesheets/style.css
Normal file
@@ -0,0 +1,295 @@
|
||||
body {
|
||||
font-size: 14px;
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
font-weight: 300;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
||||
|
||||
a.anchor {
|
||||
position: relative;
|
||||
top: -50px;
|
||||
}
|
||||
|
||||
a.forkme {
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1040;
|
||||
position: fixed;
|
||||
display: inline-block;
|
||||
background: #008000;
|
||||
box-shadow: 0 0 5px #000;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
padding: 3px 40px;
|
||||
border: 2px solid #006400;
|
||||
-webkit-transform: rotate(45deg) translate(65px);
|
||||
transform: rotate(45deg) translate(65px);
|
||||
}
|
||||
a.forkme:hover {
|
||||
color: #ddd;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.sponsor {
|
||||
position: fixed;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
z-index: 1041;
|
||||
margin: 5px 10px;
|
||||
}
|
||||
|
||||
.container > .navbar-header {
|
||||
display: inline-block;
|
||||
margin: inherit;
|
||||
}
|
||||
|
||||
a.navbar-brand.twitter {
|
||||
color: #55acee;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a.navbar-brand.twitter:before {
|
||||
content: "";
|
||||
background: url("/images/twitter.png");
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
mark.green {
|
||||
background: inherit;
|
||||
color: #008000;
|
||||
font-weight: bold;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
thead {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
color: #333;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 2em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.code {
|
||||
display: block;
|
||||
font-family: monospace;
|
||||
word-wrap: break-word;
|
||||
min-height: 20px;
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 20px;
|
||||
background: #fafafa;
|
||||
border-left: 3px solid;
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
box-shadow: 0 0 1px inset;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.code .example {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.code .example:hover {
|
||||
color: #000;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.preview-background {
|
||||
background: #eee;
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.code .example-wrapper .preview, .code .preview-placeholder {
|
||||
display: none;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
bottom: -260px;
|
||||
padding-left: 10px;
|
||||
height: 220px;
|
||||
background-position: 10px center;
|
||||
background-repeat: no-repeat;
|
||||
font-size: 14px;
|
||||
font-family: "Helvetica Neue", Arial, sans-serif;
|
||||
font-weight: 300;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.code .preview-placeholder {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
line-height: 200px;
|
||||
}
|
||||
|
||||
.code .preview-placeholder:hover {
|
||||
/* fixes glitchy blinking */
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.code:hover .preview-placeholder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.code .example-wrapper .preview i {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.code .example-wrapper:hover .preview {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#avatar-example-1:hover .preview {
|
||||
background-image: url("/avatars/jeb_");
|
||||
}
|
||||
#avatar-example-2:hover .preview {
|
||||
background-image: url("/avatars/jeb_?helm");
|
||||
}
|
||||
#avatar-example-3:hover .preview {
|
||||
background-image: url("/avatars/jeb_?size=128");
|
||||
}
|
||||
#avatar-example-4:hover .preview {
|
||||
background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6");
|
||||
}
|
||||
#avatar-example-5:hover .preview {
|
||||
background-image: url("/avatars/0?default=alex");
|
||||
}
|
||||
#avatar-example-6:hover .preview {
|
||||
background-image: url("/avatars/0?default=https%3A%2F%2Fi.imgur.com%2FocJVWAc.png");
|
||||
}
|
||||
|
||||
#render-example-1:hover .preview {
|
||||
background-image: url("/renders/body/jeb_?helm&scale=4");
|
||||
}
|
||||
#render-example-2:hover .preview {
|
||||
background-image: url("/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8");
|
||||
}
|
||||
|
||||
#skin-example-1:hover .preview {
|
||||
background-image: url("/skins/jeb_");
|
||||
}
|
||||
#skin-example-2:hover .preview {
|
||||
background-image: url("/skins/0?default=alex");
|
||||
}
|
||||
|
||||
#cape-example-1:hover .preview {
|
||||
background-image: url("/capes/Dinnerbone");
|
||||
}
|
||||
#cape-example-2:hover .preview {
|
||||
background-image: url("/capes/md_5");
|
||||
}
|
||||
|
||||
img.preload {
|
||||
/*
|
||||
preload hover images
|
||||
browsers don't load 0x0 images
|
||||
*/
|
||||
position: fixed;
|
||||
top: -9999px;
|
||||
left: -9999px;
|
||||
}
|
||||
|
||||
.jumbotron img {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.avatar-wrapper {
|
||||
height: 64px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.avatar.jomo {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64")}
|
||||
.avatar.jomo:hover {background-image: url("/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64&helm")}
|
||||
|
||||
.avatar.jake_0 {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64")}
|
||||
.avatar.jake_0:hover {background-image: url("/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64&helm")}
|
||||
|
||||
.avatar.sk89q {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64")}
|
||||
.avatar.sk89q:hover {background-image: url("/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64&helm")}
|
||||
|
||||
.avatar.md_5 {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64")}
|
||||
.avatar.md_5:hover {background-image: url("/avatars/af74a02d19cb445bb07f6866a861f783?size=64&helm")}
|
||||
|
||||
.avatar.jeb {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64")}
|
||||
.avatar.jeb:hover {background-image: url("/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&helm")}
|
||||
|
||||
.avatar.notch {background-image: url("/avatars/069a79f444e94726a5befca90e38aaf5?size=64")}
|
||||
/* Notch fucked up his helm */
|
||||
|
||||
.avatar.dinnerbone {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64")}
|
||||
.avatar.dinnerbone:hover {background-image: url("/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64&helm")}
|
||||
|
||||
.avatar.ez {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64")}
|
||||
.avatar.ez:hover {background-image: url("/avatars/7d043c7389524696bfba571c05b6aec0?size=64&helm")}
|
||||
|
||||
.avatar.grumm {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64")}
|
||||
.avatar.grumm:hover {background-image: url("/avatars/e6b5c088068044df9e1b9bf11792291b?size=64&helm")}
|
||||
|
||||
.avatar.themogmimer {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64")}
|
||||
.avatar.themogmimer:hover {background-image: url("/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64&helm")}
|
||||
|
||||
.avatar.marc {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64")}
|
||||
.avatar.marc:hover {background-image: url("/avatars/b05881186e75410db2db4d3066b223f7?size=64&helm")}
|
||||
|
||||
.avatar.searge {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64")}
|
||||
.avatar.searge:hover {background-image: url("/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64&helm")}
|
||||
|
||||
.avatar.xlson {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64")}
|
||||
.avatar.xlson:hover {background-image: url("/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64&helm")}
|
||||
|
||||
.avatar.minecraftchick {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64")}
|
||||
.avatar.minecraftchick:hover {background-image: url("/avatars/c9b54008fd8047428b238787b5f2401c?size=64&helm")}
|
||||
|
||||
.avatar.kappe {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64")}
|
||||
.avatar.kappe:hover {background-image: url("/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64&helm")}
|
||||
|
||||
.avatar.krisjelbring {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64")}
|
||||
.avatar.krisjelbring:hover {background-image: url("/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64&helm")}
|
||||
|
||||
.avatar.thinkofdeath {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64")}
|
||||
.avatar.thinkofdeath:hover {background-image: url("/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64&helm")}
|
||||
|
||||
.avatar.evilseph {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64")}
|
||||
.avatar.evilseph:hover {background-image: url("/avatars/020242a17b9441799eff511eea1221da?size=64&helm")}
|
||||
|
||||
.avatar.mollstam {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64")}
|
||||
.avatar.mollstam:hover {background-image: url("/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64&helm")}
|
||||
|
||||
.avatar.mollstam {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64")}
|
||||
.avatar.mollstam:hover {background-image: url("/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64&helm")}
|
||||
|
||||
.avatar.flipped {
|
||||
-webkit-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
111
lib/routes/avatars.js
Normal file
@@ -0,0 +1,111 @@
|
||||
var logging = require("../logging");
|
||||
var helpers = require("../helpers");
|
||||
var config = require("../config");
|
||||
var skins = require("../skins");
|
||||
var cache = require("../cache");
|
||||
|
||||
var human_status = {
|
||||
0: "none",
|
||||
1: "cached",
|
||||
2: "downloaded",
|
||||
3: "checked",
|
||||
"-1": "error"
|
||||
};
|
||||
|
||||
// GET avatar request
|
||||
module.exports = function(req, res) {
|
||||
var start = new Date();
|
||||
var userId = (req.url.path_list[2] || "").split(".")[0];
|
||||
var size = parseInt(req.url.query.size) || config.default_size;
|
||||
var def = req.url.query.default;
|
||||
var helm = req.url.query.hasOwnProperty("helm");
|
||||
var etag = null;
|
||||
var rid = req.id;
|
||||
|
||||
function sendimage(rid, http_status, img_status, image) {
|
||||
logging.log(rid, "status:", http_status);
|
||||
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": human_status[img_status],
|
||||
"X-Request-ID": rid,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Etag": '"' + etag + '"'
|
||||
});
|
||||
res.end(http_status === 304 ? null : image);
|
||||
}
|
||||
|
||||
function handle_default(rid, http_status, img_status, userId) {
|
||||
if (def && def !== "steve" && def !== "alex") {
|
||||
logging.log(rid, "status: 301");
|
||||
res.writeHead(301, {
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
"Response-Time": new Date() - start,
|
||||
"X-Storage-Type": human_status[img_status],
|
||||
"X-Request-ID": rid,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Location": def
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
def = def || skins.default_skin(userId);
|
||||
skins.resize_img("public/images/" + def + ".png", size, function(err, image) {
|
||||
sendimage(rid, http_status, img_status, image);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent app from crashing/freezing
|
||||
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.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("Invalid Size");
|
||||
return;
|
||||
} else if (!helpers.id_valid(userId)) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("Invalid ID");
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
logging.log(rid, "userid:", userId);
|
||||
|
||||
|
||||
try {
|
||||
helpers.get_avatar(rid, userId, helm, size, function(err, status, image, hash) {
|
||||
logging.log(rid, "storage type:", human_status[status]);
|
||||
if (err) {
|
||||
logging.error(rid, err);
|
||||
if (err.code === "ENOENT") {
|
||||
// no such file
|
||||
cache.remove_hash(rid, userId);
|
||||
}
|
||||
}
|
||||
etag = image && hash && hash.substr(0, 32) || "none";
|
||||
var matches = req.headers["if-none-match"] === '"' + etag + '"';
|
||||
if (image) {
|
||||
var http_status = 200;
|
||||
if (err) {
|
||||
http_status = 503;
|
||||
}
|
||||
logging.debug(rid, "etag:", req.headers["if-none-match"]);
|
||||
logging.debug(rid, "matches:", matches);
|
||||
sendimage(rid, matches ? 304 : http_status, status, image);
|
||||
} else {
|
||||
handle_default(rid, matches ? 304 : 200, status, userId);
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
logging.error(rid, "error:", e.stack);
|
||||
handle_default(rid, 500, -1, userId);
|
||||
}
|
||||
};
|
||||
91
lib/routes/capes.js
Normal file
@@ -0,0 +1,91 @@
|
||||
var logging = require("../logging");
|
||||
var helpers = require("../helpers");
|
||||
var config = require("../config");
|
||||
var cache = require("../cache");
|
||||
|
||||
var human_status = {
|
||||
0: "none",
|
||||
1: "cached",
|
||||
2: "downloaded",
|
||||
3: "checked",
|
||||
"-1": "error"
|
||||
};
|
||||
|
||||
// GET cape request
|
||||
module.exports = function(req, res) {
|
||||
var start = new Date();
|
||||
var userId = (req.url.pathname.split("/")[2] || "").split(".")[0];
|
||||
var etag = null;
|
||||
var rid = req.id;
|
||||
|
||||
function sendimage(rid, http_status, img_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": human_status[img_status],
|
||||
"X-Request-ID": rid,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Etag": '"' + etag + '"'
|
||||
});
|
||||
res.end(http_status === 304 ? null : image);
|
||||
}
|
||||
|
||||
if (!helpers.id_valid(userId)) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("Invalid ID");
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
logging.log(rid, "userid:", userId);
|
||||
|
||||
try {
|
||||
helpers.get_cape(rid, userId, function(err, status, image, hash) {
|
||||
logging.log(rid, "storage type:", human_status[status]);
|
||||
if (err) {
|
||||
logging.error(rid, err);
|
||||
if (err.code === "ENOENT") {
|
||||
// no such file
|
||||
cache.remove_hash(rid, userId);
|
||||
}
|
||||
}
|
||||
etag = hash && hash.substr(0, 32) || "none";
|
||||
var matches = req.headers["if-none-match"] === '"' + etag + '"';
|
||||
if (image) {
|
||||
var http_status = 200;
|
||||
if (err) {
|
||||
http_status = 503;
|
||||
}
|
||||
logging.debug(rid, "etag:", req.headers["if-none-match"]);
|
||||
logging.debug(rid, "matches:", matches);
|
||||
logging.log(rid, "status:", http_status);
|
||||
sendimage(rid, matches ? 304 : http_status, status, image);
|
||||
} else if (matches) {
|
||||
res.writeHead(304, {
|
||||
"Etag": '"' + etag + '"',
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
res.writeHead(404, {
|
||||
"Content-Type": "text/plain",
|
||||
"Etag": '"' + etag + '"',
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("404 not found");
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
logging.error(rid, "error:" + e.stack);
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("500 server error");
|
||||
}
|
||||
};
|
||||
19
lib/routes/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
var config = require("../config");
|
||||
var path = require("path");
|
||||
var jade = require("jade");
|
||||
|
||||
// compile jade
|
||||
var index = jade.compileFile(path.join(__dirname, "../views/index.jade"));
|
||||
|
||||
module.exports = function(req, res) {
|
||||
var html = index({
|
||||
title: "Crafatar",
|
||||
domain: "https://" + req.headers.host,
|
||||
config: config
|
||||
});
|
||||
res.writeHead(200, {
|
||||
"Content-Length": Buffer.byteLength(html, "UTF-8"),
|
||||
"Content-Type": "text/html; charset=utf-8"
|
||||
});
|
||||
res.end(html);
|
||||
};
|
||||
139
lib/routes/renders.js
Normal file
@@ -0,0 +1,139 @@
|
||||
var logging = require("../logging");
|
||||
var helpers = require("../helpers");
|
||||
var config = require("../config");
|
||||
var cache = require("../cache");
|
||||
var skins = require("../skins");
|
||||
var renders = require("../renders");
|
||||
var fs = require("fs");
|
||||
|
||||
var human_status = {
|
||||
0: "none",
|
||||
1: "cached",
|
||||
2: "downloaded",
|
||||
3: "checked",
|
||||
"-1": "error"
|
||||
};
|
||||
|
||||
// valid types: head, body
|
||||
// helmet is query param
|
||||
// TODO: The Type logic should be two separate GET functions once response methods are extracted
|
||||
|
||||
// GET render request
|
||||
module.exports = function(req, res) {
|
||||
var start = new Date();
|
||||
var raw_type = (req.url.path_list[2] || "");
|
||||
var rid = req.id;
|
||||
|
||||
// validate type
|
||||
if (raw_type !== "body" && raw_type !== "head") {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("Invalid Render Type");
|
||||
return;
|
||||
}
|
||||
|
||||
var body = raw_type === "body";
|
||||
var userId = (req.url.path_list[3] || "").split(".")[0];
|
||||
var def = req.url.query.default;
|
||||
var scale = parseInt(req.url.query.scale) || config.default_scale;
|
||||
var helm = req.url.query.hasOwnProperty("helm");
|
||||
var etag = null;
|
||||
|
||||
function sendimage(rid, http_status, img_status, image) {
|
||||
logging.log(rid, "status:", http_status);
|
||||
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": human_status[img_status],
|
||||
"X-Request-ID": rid,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Etag": '"' + etag + '"'
|
||||
});
|
||||
res.end(http_status === 304 ? null : image);
|
||||
}
|
||||
|
||||
// default alex/steve images can be rendered, but
|
||||
// custom images will not be
|
||||
function handle_default(rid, http_status, img_status, userId) {
|
||||
if (def && def !== "steve" && def !== "alex") {
|
||||
logging.log(rid, "status: 301");
|
||||
res.writeHead(301, {
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
"Response-Time": new Date() - start,
|
||||
"X-Storage-Type": human_status[img_status],
|
||||
"X-Request-ID": rid,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Location": def
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
def = def || skins.default_skin(userId);
|
||||
fs.readFile("public/images/" + def + "_skin.png", function (err, buf) {
|
||||
if (err) {
|
||||
// errored while loading the default image, continuing with null image
|
||||
logging.error(rid, "error loading default render image:", err);
|
||||
}
|
||||
// we render the default skins, but not custom images
|
||||
renders.draw_model(rid, buf, scale, helm, body, function(render_err, def_img) {
|
||||
if (render_err) {
|
||||
logging.error(rid, "error while rendering default image:", render_err);
|
||||
}
|
||||
sendimage(rid, http_status, img_status, def_img);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (scale < config.min_scale || scale > config.max_scale) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("422 Invalid Scale");
|
||||
return;
|
||||
} else if (!helpers.id_valid(userId)) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("422 Invalid ID");
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
logging.log(rid, "userId:", userId);
|
||||
|
||||
try {
|
||||
helpers.get_render(rid, userId, scale, helm, body, function(err, status, hash, image) {
|
||||
logging.log(rid, "storage type:", human_status[status]);
|
||||
if (err) {
|
||||
logging.error(rid, err);
|
||||
if (err.code === "ENOENT") {
|
||||
// no such file
|
||||
cache.remove_hash(rid, userId);
|
||||
}
|
||||
}
|
||||
etag = hash && hash.substr(0, 32) || "none";
|
||||
var matches = req.headers["if-none-match"] === '"' + etag + '"';
|
||||
if (image) {
|
||||
var http_status = 200;
|
||||
if (err) {
|
||||
http_status = 503;
|
||||
}
|
||||
logging.debug(rid, "etag:", req.headers["if-none-match"]);
|
||||
logging.debug(rid, "matches:", matches);
|
||||
sendimage(rid, matches ? 304 : http_status, status, image);
|
||||
} else {
|
||||
logging.log(rid, "image not found, using default.");
|
||||
handle_default(rid, matches ? 304 : 200, status, userId);
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
logging.error(rid, "error:", e.stack);
|
||||
handle_default(rid, 500, -1, userId);
|
||||
}
|
||||
};
|
||||
90
lib/routes/skins.js
Normal file
@@ -0,0 +1,90 @@
|
||||
var logging = require("../logging");
|
||||
var helpers = require("../helpers");
|
||||
var config = require("../config");
|
||||
var skins = require("../skins");
|
||||
var path = require("path");
|
||||
var lwip = require("lwip");
|
||||
|
||||
// GET skin request
|
||||
module.exports = function(req, res) {
|
||||
var start = new Date();
|
||||
var userId = (req.url.path_list[2] || "").split(".")[0];
|
||||
var def = req.url.query.default;
|
||||
var etag = null;
|
||||
var rid = req.id;
|
||||
|
||||
function sendimage(rid, http_status, image) {
|
||||
logging.log(rid, "status:", http_status);
|
||||
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",
|
||||
"X-Request-ID": rid,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Etag": '"' + etag + '"'
|
||||
});
|
||||
res.end(http_status === 304 ? null : image);
|
||||
}
|
||||
|
||||
function handle_default(rid, http_status, userId) {
|
||||
if (def && def !== "steve" && def !== "alex") {
|
||||
logging.log(rid, "status: 301");
|
||||
res.writeHead(301, {
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
"Response-Time": new Date() - start,
|
||||
"X-Storage-Type": "downloaded",
|
||||
"X-Request-ID": rid,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Location": def
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
def = def || skins.default_skin(userId);
|
||||
lwip.open(path.join(__dirname, "..", "public", "images", def + "_skin.png"), function(err, image) {
|
||||
// FIXME: err is not handled
|
||||
image.toBuffer("png", function(buf_err, buffer) {
|
||||
// FIXME: buf_err is not handled
|
||||
sendimage(rid, http_status, buffer);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!helpers.id_valid(userId)) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("Invalid ID");
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
logging.log(rid, "userid:", userId);
|
||||
|
||||
try {
|
||||
helpers.get_skin(rid, userId, function(err, hash, image) {
|
||||
if (err) {
|
||||
logging.error(rid, err);
|
||||
}
|
||||
etag = hash && hash.substr(0, 32) || "none";
|
||||
var matches = req.headers["if-none-match"] === '"' + etag + '"';
|
||||
if (image) {
|
||||
var http_status = 200;
|
||||
if (err) {
|
||||
http_status = 503;
|
||||
}
|
||||
logging.debug(rid, "etag:", req.headers["if-none-match"]);
|
||||
logging.debug(rid, "matches:", matches);
|
||||
sendimage(rid, matches ? 304 : http_status, image);
|
||||
} else {
|
||||
handle_default(rid, 200, userId);
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
logging.error(rid, "error:", e.stack);
|
||||
handle_default(rid, 500, userId);
|
||||
}
|
||||
};
|
||||
114
lib/server.js
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env node
|
||||
var logging = require("./logging");
|
||||
var querystring = require("querystring");
|
||||
var config = require("./config");
|
||||
var http = require("http");
|
||||
var mime = require("mime");
|
||||
var path = require("path");
|
||||
var url = require("url");
|
||||
var fs = require("fs");
|
||||
var server = null;
|
||||
|
||||
var routes = {
|
||||
index: require("./routes/index"),
|
||||
avatars: require("./routes/avatars"),
|
||||
skins: require("./routes/skins"),
|
||||
renders: require("./routes/renders"),
|
||||
capes: require("./routes/capes")
|
||||
};
|
||||
|
||||
function asset_request(req, res) {
|
||||
var filename = path.join(__dirname, "public", req.url.path_list.join("/"));
|
||||
fs.exists(filename, function(exists) {
|
||||
if (exists) {
|
||||
res.writeHead(200, { "Content-type": mime.lookup(filename) });
|
||||
fs.createReadStream(filename).pipe(res);
|
||||
} else {
|
||||
res.writeHead(404, {
|
||||
"Content-type": "text/plain"
|
||||
});
|
||||
res.end("Not Found");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function requestHandler(req, res) {
|
||||
var request = req;
|
||||
request.url = url.parse(req.url, true);
|
||||
request.url.query = request.url.query || {};
|
||||
|
||||
// remove trailing and double slashes + other junk
|
||||
var path_list = request.url.pathname.split("/");
|
||||
for (var i = 0; i < path_list.length; i++) {
|
||||
// URL decode
|
||||
path_list[i] = querystring.unescape(path_list[i]);
|
||||
}
|
||||
request.url.path_list = path_list;
|
||||
|
||||
// generate 12 character random string
|
||||
request.id = Math.random().toString(36).substring(2, 14);
|
||||
|
||||
var local_path = request.url.path_list[1];
|
||||
logging.log(request.id + request.method, request.url.href);
|
||||
if (request.method === "GET" || request.method === "HEAD") {
|
||||
try {
|
||||
switch (local_path) {
|
||||
case "":
|
||||
routes.index(request, res);
|
||||
break;
|
||||
case "avatars":
|
||||
routes.avatars(request, res);
|
||||
break;
|
||||
case "skins":
|
||||
routes.skins(request, res);
|
||||
break;
|
||||
case "renders":
|
||||
routes.renders(request, res);
|
||||
break;
|
||||
case "capes":
|
||||
routes.capes(request, res);
|
||||
break;
|
||||
default:
|
||||
asset_request(request, res);
|
||||
}
|
||||
} catch(e) {
|
||||
var error = JSON.stringify(req.headers) + "\n" + e.stack;
|
||||
logging.error(request.id + "Error:", error);
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
res.end(config.debug_enabled ? error : "Internal Server Error");
|
||||
}
|
||||
} else {
|
||||
res.writeHead(405, {
|
||||
"Content-Type": "text/plain"
|
||||
});
|
||||
res.end("Method Not Allowed");
|
||||
}
|
||||
}
|
||||
|
||||
var exp = {};
|
||||
|
||||
exp.boot = function(callback) {
|
||||
var port = process.env.PORT || 3000;
|
||||
var bind_ip = process.env.BIND || "0.0.0.0";
|
||||
logging.log("Server running on http://" + bind_ip + ":" + port + "/");
|
||||
server = http.createServer(requestHandler).listen(port, bind_ip, function() {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exp.close = function(callback) {
|
||||
server.close(function() {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = exp;
|
||||
|
||||
if (require.main === module) {
|
||||
logging.error("Please use 'npm start' or 'lib/www.js'");
|
||||
process.exit(1);
|
||||
}
|
||||
391
lib/views/index.jade
Normal file
@@ -0,0 +1,391 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
.jumbotron
|
||||
.container
|
||||
h1 Crafatar
|
||||
p A blazing fast API for Minecraft faces!
|
||||
.avatar-wrapper
|
||||
.avatar.jomo(title="jomo's avatar")
|
||||
.avatar.jake_0(title="jake_0's avatar")
|
||||
.avatar.sk89q(title="sk89q's avatar")
|
||||
.avatar.md_5(title="md_5's avatar")
|
||||
.avatar.notch(title="notch's avatar")
|
||||
.avatar.jeb(title="jeb's avatar")
|
||||
.avatar.dinnerbone.flipped(title="dinnerbone's avatar")
|
||||
.avatar.ez(title="ez' avatar")
|
||||
.avatar.grumm.flipped(title="grumm's avatar")
|
||||
.avatar.themogmimer(title="themogmimer's avatar")
|
||||
.avatar.searge(title="searge's avatar")
|
||||
.avatar.xlson(title="xlson's avatar")
|
||||
.avatar.krisjelbring(title="krisjelbring's avatar")
|
||||
.avatar.minecraftchick(title="minecraftchick's avatar")
|
||||
.avatar.kappe(title="kappe's avatar")
|
||||
.avatar.marc(title="marc's avatar")
|
||||
.avatar.mollstam(title="mollstam's avatar")
|
||||
.avatar.evilseph(title="evilseph's avatar")
|
||||
.avatar.thinkofdeath(title="thinkofdeath's avatar")
|
||||
|
||||
.container
|
||||
section(id="documentation")
|
||||
h2 Documentation
|
||||
.row
|
||||
section
|
||||
a(id="avatars", class="anchor")
|
||||
a(href="#avatars")
|
||||
h3 Avatars
|
||||
| Replace
|
||||
mark.green userid
|
||||
| with a Mojang <b>UUID</b> or <b>username</b> to get the related head. All images are PNGs.
|
||||
.code
|
||||
| #{domain}/avatars/
|
||||
mark.green userid
|
||||
|
||||
section
|
||||
a(id="avatar-parameters" class="anchor")
|
||||
a(href="#avatar-parameters")
|
||||
h4 Avatar Parameters
|
||||
table(class="table table-striped")
|
||||
thead
|
||||
tr
|
||||
td parameter
|
||||
td type
|
||||
td default
|
||||
td description
|
||||
tbody
|
||||
tr
|
||||
td size
|
||||
td integer
|
||||
td #{config.default_size}
|
||||
td The size of the image in pixels, #{config.min_size} - #{config.max_size}.
|
||||
tr
|
||||
td default
|
||||
td string
|
||||
td
|
||||
| The standard value is calculated based on the UUID (even = alex, odd = steve).<br>
|
||||
| Usernames always default to steve.
|
||||
td
|
||||
| The image to be served when the userid has no skin.<br>
|
||||
| Valid options are
|
||||
a(href="/avatars/0?default=steve") steve
|
||||
| ,
|
||||
a(href="/avatars/0?default=alex") alex
|
||||
| , or a custom URL.
|
||||
tr
|
||||
td helm
|
||||
td null
|
||||
td
|
||||
td Apply the "second" layer (hat) to the avatar.
|
||||
|
||||
section
|
||||
a(id="avatar-examples", class="anchor")
|
||||
a(href="#avatar-examples")
|
||||
h4 Avatar Examples
|
||||
.code
|
||||
#avatar-example-1.example-wrapper
|
||||
.example #{domain}/avatars/jeb_
|
||||
p.preview Jeb's avatar
|
||||
#avatar-example-2.example-wrapper
|
||||
.example #{domain}/avatars/jeb_?helm
|
||||
p.preview Jeb's avatar with helm
|
||||
#avatar-example-3.example-wrapper
|
||||
.example #{domain}/avatars/jeb_?size=128
|
||||
p.preview Jeb's avatar, 128 × 128
|
||||
#avatar-example-4.example-wrapper
|
||||
.example #{domain}/avatars/853c80ef3c3749fdaa49938b674adae6
|
||||
p.preview Jeb's avatar by UUID
|
||||
#avatar-example-5.example-wrapper
|
||||
.example #{domain}/avatars/jeb_?default=alex
|
||||
p.preview Jeb's avatar, or fall back to alex <i>(this example assumes jeb_ does not exist)</i>
|
||||
#avatar-example-6.example-wrapper
|
||||
.example #{domain}/avatars/jeb_?default=https://i.imgur.com/ocJVWAc.png
|
||||
p.preview
|
||||
| Jeb's avatar, or fall back to a custom image <i>(this example assumes jeb_ does not exist)</i>
|
||||
p.preview-placeholder
|
||||
| Hover over the examples for a preview!
|
||||
.preview-background
|
||||
|
||||
|
||||
section
|
||||
a(id="renders" class="anchor")
|
||||
a(href="#renders")
|
||||
h3 3D Renders
|
||||
p
|
||||
| Crafatar also provides support for 3D renders of Minecraft skins.<br>
|
||||
| Please note that <b>this feature is currently beta</b>!<br>
|
||||
| Replace
|
||||
mark.green userid
|
||||
| with a Mojang <b>UUID</b> or <b>username</b> to get a render of the skin.
|
||||
| The <b>head</b> render type returns a render of the skin's head.
|
||||
span.code
|
||||
| #{domain}/renders/head/
|
||||
mark.green userid
|
||||
| The <b>body</b> render returns a render of the entire skin.
|
||||
span.code
|
||||
| #{domain}/renders/body/
|
||||
mark.green userid
|
||||
|
||||
section
|
||||
a(id="render-parameters" class="anchor")
|
||||
a(href="#render-parameters")
|
||||
h4 Render Parameters
|
||||
table(class="table table-striped")
|
||||
thead
|
||||
tr
|
||||
td parameter
|
||||
td type
|
||||
td default
|
||||
td description
|
||||
tbody
|
||||
tr
|
||||
td scale
|
||||
td integer
|
||||
td #{config.default_scale}. The actual size differs between the type of render.
|
||||
td The scale factor of the image #{config.min_scale} - #{config.max_scale}.
|
||||
tr
|
||||
td helm
|
||||
td null
|
||||
td
|
||||
td Apply the "second" layer (hat) to the avatar.
|
||||
|
||||
section
|
||||
a(id="render-examples", class="anchor")
|
||||
a(href="#render-examples")
|
||||
h4 Render Examples
|
||||
.code
|
||||
#render-example-1.example-wrapper
|
||||
.example #{domain}/renders/body/jeb_?helm&scale=4
|
||||
p.preview Jeb's body, with helmet, scale 4
|
||||
#render-example-2.example-wrapper
|
||||
.example #{domain}/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8
|
||||
p.preview Jeb's head, by UUID, scale 8
|
||||
p.preview-placeholder
|
||||
| Hover over the examples for a preview!
|
||||
.preview-background
|
||||
|
||||
|
||||
section
|
||||
a(id="skins" class="anchor")
|
||||
a(href="#skins")
|
||||
h3 Skins
|
||||
p
|
||||
| You can also get the full skin file of a player.<br>
|
||||
| Replace
|
||||
mark.green userid
|
||||
| with a Mojang <b>UUID</b> or <b>username</b> to get the related skin.<br>
|
||||
| The user's skin is returned, or the default image is served.<br>
|
||||
| You can use the default parameter here as well.
|
||||
span.code
|
||||
| #{domain}/skins/
|
||||
mark.green userid
|
||||
|
||||
section
|
||||
a(id="skin-parameters" class="anchor")
|
||||
a(href="#skin-parameters")
|
||||
h4 Skin Parameters
|
||||
table(class="table table-striped")
|
||||
thead
|
||||
tr
|
||||
td parameter
|
||||
td type
|
||||
td default
|
||||
td description
|
||||
tbody
|
||||
tr
|
||||
td default
|
||||
td string
|
||||
td
|
||||
| The standard value is calculated based on the UUID (even = alex, odd = steve).<br>
|
||||
| Usernames always default to steve.
|
||||
td
|
||||
| The image to be served when the userid has no skin.<br>
|
||||
| Valid options are
|
||||
a(href="/skins/0?default=steve") steve
|
||||
| ,
|
||||
a(href="/skins/0?default=alex") alex
|
||||
| , or a custom URL.
|
||||
|
||||
section
|
||||
a(id="skin-examples", class="anchor")
|
||||
a(href="#skin-examples")
|
||||
h4 Skin Examples
|
||||
.code
|
||||
#skin-example-1.example-wrapper
|
||||
.example #{domain}/skins/jeb_
|
||||
p.preview Jeb's skin
|
||||
#skin-example-2.example-wrapper
|
||||
.example #{domain}/skins/jeb_?default=alex
|
||||
p.preview Jeb's skin, or fall back to alex <i>(this example assumes jeb_ does not exist)</i>
|
||||
p.preview-placeholder
|
||||
| Hover over the examples for a preview!
|
||||
.preview-background
|
||||
|
||||
section
|
||||
a(id="capes" class="anchor")
|
||||
a(href="#capes")
|
||||
h3 Capes
|
||||
p
|
||||
| A cape endpoint is also available to get the active cape of a user.<br>
|
||||
| Replace
|
||||
mark.green userid
|
||||
| with a Mojang <b>UUID</b> or <b>username</b> to get the related cape.<br>
|
||||
| The user's cape is returned, otherwise a 404 is returned.<br>
|
||||
.code
|
||||
| #{domain}/capes/
|
||||
mark.green userid
|
||||
|
||||
section
|
||||
a(id="cape-examples", class="anchor")
|
||||
a(href="#cape-examples")
|
||||
h4 Cape Examples
|
||||
.code
|
||||
#cape-example-1.example-wrapper
|
||||
.example #{domain}/capes/Dinnerbone
|
||||
p.preview Dinnerbone's Cape <i>Mojang capes are not transparent...</i>
|
||||
#cape-example-2.example-wrapper
|
||||
.example #{domain}/capes/md_5
|
||||
p.preview md_5's Cape
|
||||
p.preview-placeholder
|
||||
| Hover over the examples for a preview!
|
||||
.preview-background
|
||||
|
||||
section
|
||||
a(id="meta" class="anchor")
|
||||
a(href="#meta")
|
||||
h2 Meta
|
||||
|
||||
section
|
||||
a(id="meta-cors" class="anchor")
|
||||
a(href="#meta-cors")
|
||||
h3 CORS
|
||||
p
|
||||
| Crafatar supports CORS so you can make AJAX request from within the browser!
|
||||
|
||||
section
|
||||
a(id="meta-http-headers" class="anchor")
|
||||
a(href="#meta-http-headers")
|
||||
h3 HTTP Headers
|
||||
p
|
||||
| Responses come with these HTTP headers, useful for debugging.<br>
|
||||
| Please note that these headers are cached by CloudFlare <small>(CF-Cache-Status: HIT)</small>.
|
||||
|
||||
section
|
||||
a(id="meta-response-time" class="anchor")
|
||||
a(href="#meta-response-time")
|
||||
h4 Response-Time
|
||||
p The time, in milliseconds, it took Crafatar to process the request.
|
||||
|
||||
section
|
||||
a(id="meta-x-storage-type" class="anchor")
|
||||
a(href="#meta-x-storage-type")
|
||||
h4 X-Storage-Type
|
||||
ul
|
||||
li <b>none</b>: No external requests. Cached: User has no skin.
|
||||
li <b>cached</b>: No external requests. Skin cached and stored locally.
|
||||
li
|
||||
| <b>checked</b>: 1 external request. Skin cached, checked for updates, no skin downloaded.<br>
|
||||
| This happens either when the user removed their skin or when it didn't change.
|
||||
li <b>downloaded</b>: 2 external requests. Skin changed or unknown, downloaded.
|
||||
li
|
||||
| <b>error</b>: This can happen, for example, when Mojang's servers are down.<br>
|
||||
| If possible, an outdated image is served instead.
|
||||
section
|
||||
a(id="meta-x-request-id" class="anchor")
|
||||
a(href="#meta-x-request-id")
|
||||
h4 X-Request-ID
|
||||
p
|
||||
| The internal ID assigned to this request.<br>
|
||||
| If you think something is wrong with your request, please <a href="#contact">contact us</a> and provide this ID.
|
||||
|
||||
section
|
||||
a(id="meta-about-usernames" class="anchor")
|
||||
a(href="#meta-about-usernames")
|
||||
h3 About Usernames
|
||||
p
|
||||
| We strongly advise you to use UUIDs instead of usernames in production.<br>
|
||||
| Usernames are deprecated by Mojang and you should only use usernames for testing.<br>
|
||||
| You don't have to change anything when using UUIDs and someone changes their Username.<br>
|
||||
| Malformed usernames are rejected.
|
||||
|
||||
section
|
||||
a(id="meta-about-uuids" class="anchor")
|
||||
a(href="#meta-about-uuids")
|
||||
h3 About UUIDs
|
||||
p
|
||||
| UUIDs may use the blank or dashed format.<br>
|
||||
| Malformed UUIDs are rejected.
|
||||
|
||||
section
|
||||
a(id="meta-about-caching" class="anchor")
|
||||
a(href="#meta-about-caching")
|
||||
h3 About Caching
|
||||
p
|
||||
| Crafatar caches skins for #{config.local_cache_time/60} minutes before checking for skin changes.<br>
|
||||
| Images are cached in your browser for #{config.browser_cache_time/60} minutes until a new request to Crafatar is made.<br>
|
||||
| When you changed your skin you can try clearing your browser cache to see the change faster.
|
||||
|
||||
|
||||
section
|
||||
a(id="contact" class="anchor")
|
||||
a(href="#contact")
|
||||
h2 Contact
|
||||
ul
|
||||
li Follow us on twitter <a href="https://twitter.com/crafatar" target="_blank">@crafatar</a>
|
||||
li Open an issue <a href="https://github.com/crafatar/crafatar/issues" target="_blank">on GitHub</a>
|
||||
li <a href="https://webchat.esper.net/?channels=crafatar" target="_blank">Join us</a> in #crafatar on irc.esper.net
|
||||
|
||||
footer
|
||||
hr
|
||||
p(class="pull-right") Copyright Crafatar #{new Date().getFullYear()}
|
||||
|
||||
|
||||
// preload hover images
|
||||
img.preload(src="/avatars/jeb_", alt="preloaded image")
|
||||
img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6", alt="preloaded image")
|
||||
img.preload(src="/avatars/0?default=alex", alt="preloaded image")
|
||||
img.preload(src="/avatars/0?default=https%3A%2F%2Fi.imgur.com%2FocJVWAc.png", alt="preloaded image")
|
||||
img.preload(src="/skins/0?default=alex", alt="preloaded image")
|
||||
img.preload(src="/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=8", alt="preloaded image")
|
||||
img.preload(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/af74a02d19cb445bb07f6866a861f783?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/069a79f444e94726a5befca90e38aaf5?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/7d043c7389524696bfba571c05b6aec0?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/e6b5c088068044df9e1b9bf11792291b?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/b05881186e75410db2db4d3066b223f7?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/jeb_?size=128", alt="preloaded image")
|
||||
img.preload(src="/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/c9b54008fd8047428b238787b5f2401c?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/020242a17b9441799eff511eea1221da?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64", alt="preloaded image")
|
||||
img.preload(src="/avatars/jeb_?helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/ae795aa86327408e92ab25c8a59f3ba1?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/2d5aa9cdaeb049189930461fc9b91cc5?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/0ea8eca3dbf647cc9d1ac64551ca975c?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/af74a02d19cb445bb07f6866a861f783?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/853c80ef3c3749fdaa49938b674adae6?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/61699b2ed3274a019f1e0ea8c3f06bc6?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/7d043c7389524696bfba571c05b6aec0?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/e6b5c088068044df9e1b9bf11792291b?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/renders/body/jeb_?helm&scale=4", alt="preloaded image")
|
||||
img.preload(src="/avatars/1c1bd09a6a0f4928a7914102a35d2670?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/b05881186e75410db2db4d3066b223f7?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/696a82ce41f44b51aa31b8709b8686f0?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/b9583ca43e64488a9c8c4ab27e482255?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/c9b54008fd8047428b238787b5f2401c?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/d8f9a4340f2d415f9acfcd70341c75ec?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/7125ba8b1c864508b92bb5c042ccfe2b?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/4566e69fc90748ee8d71d7ba5aa00d20?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/020242a17b9441799eff511eea1221da?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/9769ecf6331448f3ace67ae06cec64a3?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/avatars/f8cdb6839e9043eea81939f85d9c5d69?size=64&helm", alt="preloaded image")
|
||||
img.preload(src="/skins/jeb_", alt="preloaded image")
|
||||
21
lib/views/layout.jade
Normal file
@@ -0,0 +1,21 @@
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
link(rel="icon", sizes="16x16", type="image/png", href="/favicon.png")
|
||||
link(href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.1/css/bootstrap.min.css", rel="stylesheet")
|
||||
link(rel="stylesheet", href="/stylesheets/style.css")
|
||||
meta(name="description", content="Crafatar is a blazing fast Minecraft avatar API with support for avatars, 1.8 skins, and even 3D renders!")
|
||||
meta(name="keywords", content="minecraft, avatar, renders, skins, uuid, username")
|
||||
meta(name="viewport", content="initial-scale=1,maximum-scale=1")
|
||||
body
|
||||
a.forkme(href="https://github.com/crafatar/crafatar", target="_blank") Fork me on GitHub
|
||||
a.sponsor(href="https://akliz.net/crafatar", target="_blank", title="Crafatar is sponsored by Akliz")
|
||||
img(src="/images/akliz.png", alt="Akliz")
|
||||
.navbar.navbar-default.navbar-fixed-top
|
||||
.container
|
||||
.navbar-header
|
||||
a.navbar-brand(href="/") Crafatar
|
||||
.navbar-header
|
||||
a.navbar-brand.twitter(href="https://twitter.com/Crafatar", target="_blank") @crafatar
|
||||
block content
|
||||
21
lib/www.js
@@ -1,21 +0,0 @@
|
||||
var logging = require("../lib/logging");
|
||||
var cleaner = require("../lib/cleaner");
|
||||
var config = require("../lib/config");
|
||||
var cluster = require("cluster");
|
||||
|
||||
if (cluster.isMaster) {
|
||||
var cores = config.clusters || require("os").cpus().length;
|
||||
logging.log("Starting", cores + " workers");
|
||||
for (var i = 0; i < cores; i++) {
|
||||
cluster.fork();
|
||||
}
|
||||
|
||||
cluster.on("exit", function (worker) {
|
||||
logging.error("Worker #" + worker.id + " died. Rebooting a new one.");
|
||||
setTimeout(cluster.fork, 100);
|
||||
});
|
||||
|
||||
setInterval(cleaner.run, config.cleaning_interval * 1000);
|
||||
} else {
|
||||
require("../server.js").boot();
|
||||
}
|
||||