mirror of
https://github.com/azures04/crafatar.git
synced 2026-03-21 23:41:18 +01:00
This reverts commit 6e03f0f55dd351b35bd58106f529a5e8e2bf6d39.
This commit is contained in:
parent
8ce8b9d0d9
commit
ebe88cdc48
@ -37,7 +37,7 @@ function connect_redis() {
|
||||
|
||||
// sets the date of the face file belonging to +hash+ to now
|
||||
// the helms file is ignored because we only need 1 file to read/write from
|
||||
function update_file_date(hash, id) {
|
||||
function update_file_date(hash, uuid) {
|
||||
if (hash) {
|
||||
var path = config.faces_dir + hash + ".png";
|
||||
fs.exists(path, function(exists) {
|
||||
@ -45,11 +45,11 @@ function update_file_date(hash, id) {
|
||||
var date = new Date();
|
||||
fs.utimes(path, date, date, function(err){
|
||||
if (err) {
|
||||
logging.error(id + " Error: " + err);
|
||||
logging.error(uuid + " Error: " + err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logging.error(id + " tried to update " + path + " date, but it does not exist");
|
||||
logging.error(uuid + " tried to update " + path + " date, but it does not exist");
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -92,41 +92,41 @@ exp.info = function(callback) {
|
||||
});
|
||||
};
|
||||
|
||||
// sets the timestamp for +id+ and its face file's date to now
|
||||
exp.update_timestamp = function(id, hash) {
|
||||
logging.log(id + " cache: updating timestamp");
|
||||
// sets the timestamp for +uuid+ and its face file's date to now
|
||||
exp.update_timestamp = function(uuid, hash) {
|
||||
logging.log(uuid + " cache: updating timestamp");
|
||||
var time = new Date().getTime();
|
||||
// store id in lower case if not null
|
||||
id = id && id.toLowerCase();
|
||||
redis.hmset(id, "t", time);
|
||||
update_file_date(hash, id);
|
||||
// store uuid in lower case if not null
|
||||
uuid = uuid && uuid.toLowerCase();
|
||||
redis.hmset(uuid, "t", time);
|
||||
update_file_date(hash, uuid);
|
||||
};
|
||||
|
||||
// create the key +id+, store +hash+ and time
|
||||
exp.save_hash = function(id, skin, cape) {
|
||||
logging.log(id + " cache: saving hash");
|
||||
// create the key +uuid+, store +hash+ and time
|
||||
exp.save_hash = function(uuid, skin, cape) {
|
||||
logging.log(uuid + " cache: saving hash");
|
||||
logging.log("skin:" + skin + " cape:" + cape);
|
||||
var time = new Date().getTime();
|
||||
// store shorter null byte instead of "null"
|
||||
skin = skin || ".";
|
||||
cape = cape || ".";
|
||||
// store id in lower case if not null
|
||||
id = id && id.toLowerCase();
|
||||
redis.hmset(id, "s", skin, "c", cape, "t", time);
|
||||
// store uuid in lower case if not null
|
||||
uuid = uuid && uuid.toLowerCase();
|
||||
redis.hmset(uuid, "s", skin, "c", cape, "t", time);
|
||||
};
|
||||
|
||||
exp.remove_hash = function(id) {
|
||||
logging.log(id + " cache: deleting hash");
|
||||
redis.del(id.toLowerCase(), "h", "t");
|
||||
exp.remove_hash = function(uuid) {
|
||||
logging.log(uuid + " cache: deleting hash");
|
||||
redis.del(uuid.toLowerCase(), "h", "t");
|
||||
};
|
||||
|
||||
// get a details object for +id+
|
||||
// get a details object for +uuid+
|
||||
// {skin: "0123456789abcdef", cape: "gs1gds1g5d1g5ds1", time: 1414881524512}
|
||||
// null when id unkown
|
||||
exp.get_details = function(id, callback) {
|
||||
// get id in lower case if not null
|
||||
id = id && id.toLowerCase();
|
||||
redis.hgetall(id, function(err, data) {
|
||||
// null when uuid unkown
|
||||
exp.get_details = function(uuid, callback) {
|
||||
// get uuid in lower case if not null
|
||||
uuid = uuid && uuid.toLowerCase();
|
||||
redis.hgetall(uuid, function(err, data) {
|
||||
var details = null;
|
||||
if (data) {
|
||||
details = {
|
||||
|
||||
@ -7,7 +7,7 @@ var renders = require("./renders");
|
||||
var fs = require("fs");
|
||||
|
||||
// 0098cb60-fa8e-427c-b299-793cbd302c9a
|
||||
var valid_id = /^([0-9a-f-A-F-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
|
||||
var valid_uuid = /^([0-9a-f-A-F-]{32,36}|[a-zA-Z0-9_]{1,16})$/; // uuid|username
|
||||
var hash_pattern = /[0-9a-f]+$/;
|
||||
|
||||
// gets the hash from the textures.minecraft.net +url+
|
||||
@ -15,20 +15,20 @@ function get_hash(url) {
|
||||
return hash_pattern.exec(url)[0].toLowerCase();
|
||||
}
|
||||
|
||||
function store_skin(id, profile, details, callback) {
|
||||
networking.get_skin_url(id, profile, function(url) {
|
||||
function store_skin(uuid, profile, details, callback) {
|
||||
networking.get_skin_url(uuid, profile, function(url) {
|
||||
if (url) {
|
||||
var hash = get_hash(url);
|
||||
if (details && details.skin === hash) {
|
||||
cache.update_timestamp(id, hash);
|
||||
cache.update_timestamp(uuid, hash);
|
||||
callback(null, hash);
|
||||
} else {
|
||||
logging.log(id + " new skin hash: " + hash);
|
||||
logging.log(uuid + " new skin hash: " + hash);
|
||||
var facepath = __dirname + "/../" + config.faces_dir + hash + ".png";
|
||||
var helmpath = __dirname + "/../" + config.helms_dir + hash + ".png";
|
||||
fs.exists(facepath, function(exists) {
|
||||
if (exists) {
|
||||
logging.log(id + " skin already exists, not downloading");
|
||||
logging.log(uuid + " skin already exists, not downloading");
|
||||
callback(null, hash);
|
||||
} else {
|
||||
networking.get_from(url, function(img, response, err) {
|
||||
@ -40,9 +40,9 @@ function store_skin(id, profile, details, callback) {
|
||||
logging.error(err);
|
||||
callback(err, null);
|
||||
} else {
|
||||
logging.log(id + " face extracted");
|
||||
skins.extract_helm(id, facepath, img, helmpath, function(err) {
|
||||
logging.log(id + " helm extracted");
|
||||
logging.log(uuid + " face extracted");
|
||||
skins.extract_helm(uuid, facepath, img, helmpath, function(err) {
|
||||
logging.log(uuid + " helm extracted");
|
||||
logging.debug(helmpath);
|
||||
callback(err, hash);
|
||||
});
|
||||
@ -59,19 +59,19 @@ function store_skin(id, profile, details, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function store_cape(id, profile, details, callback) {
|
||||
networking.get_cape_url(id, profile, function(url) {
|
||||
function store_cape(uuid, profile, details, callback) {
|
||||
networking.get_cape_url(uuid, profile, function(url) {
|
||||
if (url) {
|
||||
var hash = get_hash(url);
|
||||
if (details && details.cape === hash) {
|
||||
cache.update_timestamp(id, hash);
|
||||
cache.update_timestamp(uuid, hash);
|
||||
callback(null, hash);
|
||||
} else {
|
||||
logging.log(id + " new cape hash: " + hash);
|
||||
logging.log(uuid + " new cape hash: " + hash);
|
||||
var capepath = __dirname + "/../" + config.capes_dir + hash + ".png";
|
||||
fs.exists(capepath, function(exists) {
|
||||
if (exists) {
|
||||
logging.log(id + " cape already exists, not downloading");
|
||||
logging.log(uuid + " cape already exists, not downloading");
|
||||
callback(null, hash);
|
||||
} else {
|
||||
networking.get_from(url, function(img, response, err) {
|
||||
@ -80,7 +80,7 @@ function store_cape(id, profile, details, callback) {
|
||||
callback(err, null);
|
||||
} else {
|
||||
skins.save_image(img, capepath, function(err) {
|
||||
logging.log(id + " cape saved");
|
||||
logging.log(uuid + " cape saved");
|
||||
callback(err, hash);
|
||||
});
|
||||
}
|
||||
@ -94,16 +94,16 @@ function store_cape(id, profile, details, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
// downloads the images for +id+ while checking the cache
|
||||
// downloads the images for +uuid+ while checking the cache
|
||||
// status based on +details+. +type+ specifies which
|
||||
// image type should be called back on
|
||||
// +callback+ contains the error buffer and image hash
|
||||
var currently_running = [];
|
||||
function callback_for(id, type, err, hash) {
|
||||
function callback_for(uuid, type, err, hash) {
|
||||
for (var i = 0; i < currently_running.length; i++) {
|
||||
var current = currently_running[i];
|
||||
if (current.id === id && current.type === type) {
|
||||
logging.debug(id + " now completing queued " + type + " request");
|
||||
if (current.uuid === uuid && current.type === type) {
|
||||
logging.debug(uuid + " now completing queued " + type + " request");
|
||||
current.callback(err, hash);
|
||||
currently_running.splice(i, 1); // remove from array
|
||||
i--;
|
||||
@ -120,41 +120,41 @@ function array_has_obj(arr, property, value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function store_images(id, details, type, callback) {
|
||||
var is_uuid = id.length > 16;
|
||||
function store_images(uuid, details, type, callback) {
|
||||
var is_uuid = uuid.length > 16;
|
||||
var new_hash = {
|
||||
id: id,
|
||||
uuid: uuid,
|
||||
type: type,
|
||||
callback: callback
|
||||
};
|
||||
if (!array_has_obj(currently_running, "id", id)) {
|
||||
if (!array_has_obj(currently_running, "uuid", uuid)) {
|
||||
currently_running.push(new_hash);
|
||||
networking.get_profile((is_uuid ? id : null), function(err, profile) {
|
||||
networking.get_profile((is_uuid ? uuid : null), function(err, profile) {
|
||||
if (err || (is_uuid && !profile)) {
|
||||
callback_for(id, type, err, null);
|
||||
callback_for(uuid, type, err, null);
|
||||
} else {
|
||||
store_skin(id, profile, details, function(err, skin_hash) {
|
||||
cache.save_hash(id, skin_hash, null);
|
||||
callback_for(id, "skin", err, skin_hash);
|
||||
store_cape(id, profile, details, function(err, cape_hash) {
|
||||
cache.save_hash(id, skin_hash, cape_hash);
|
||||
callback_for(id, "cape", err, cape_hash);
|
||||
store_skin(uuid, profile, details, function(err, skin_hash) {
|
||||
cache.save_hash(uuid, skin_hash, null);
|
||||
callback_for(uuid, "skin", err, skin_hash);
|
||||
store_cape(uuid, profile, details, function(err, cape_hash) {
|
||||
cache.save_hash(uuid, skin_hash, cape_hash);
|
||||
callback_for(uuid, "cape", err, cape_hash);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logging.log(id + " ID already being processed, adding to queue");
|
||||
logging.log(uuid + " ID already being processed, adding to queue");
|
||||
currently_running.push(new_hash);
|
||||
}
|
||||
}
|
||||
|
||||
var exp = {};
|
||||
|
||||
// returns true if the +id+ is a valid uuid or username
|
||||
// the id may be not exist, however
|
||||
exp.id_valid = function(id) {
|
||||
return valid_id.test(id);
|
||||
// returns true if the +uuid+ is a valid uuid or username
|
||||
// the uuid may be not exist, however
|
||||
exp.uuid_valid = function(uuid) {
|
||||
return valid_uuid.test(uuid);
|
||||
};
|
||||
|
||||
// decides whether to get an image from disk or to download it
|
||||
@ -165,28 +165,28 @@ exp.id_valid = function(id) {
|
||||
// 1: "cached" - found on disk
|
||||
// 2: "downloaded" - profile downloaded, skin downloaded from mojang servers
|
||||
// 3: "checked" - profile re-downloaded (was too old), but it has either not changed or has no skin
|
||||
exp.get_image_hash = function(id, raw_type, callback) {
|
||||
cache.get_details(id, function(err, details) {
|
||||
exp.get_image_hash = function(uuid, raw_type, callback) {
|
||||
cache.get_details(uuid, function(err, details) {
|
||||
var type = (details !== null ? (raw_type === "skin" ? details.skin : details.cape) : null);
|
||||
if (err) {
|
||||
callback(err, -1, null);
|
||||
} else {
|
||||
if (details && details.time + config.local_cache_time * 1000 >= new Date().getTime()) {
|
||||
logging.log(id + " id cached & recently updated");
|
||||
logging.log(uuid + " uuid cached & recently updated");
|
||||
callback(null, (type ? 1 : 0), type);
|
||||
} else {
|
||||
if (details) {
|
||||
logging.log(id + " id cached, but too old");
|
||||
logging.log(uuid + " uuid cached, but too old");
|
||||
} else {
|
||||
logging.log(id + " id not cached");
|
||||
logging.log(uuid + " uuid not cached");
|
||||
}
|
||||
store_images(id, details, raw_type, function(err, hash) {
|
||||
store_images(uuid, details, raw_type, function(err, hash) {
|
||||
if (err) {
|
||||
callback(err, -1, details && type);
|
||||
} else {
|
||||
var status = details && (type === hash) ? 3 : 2;
|
||||
logging.debug(id + " old hash: " + (details && type));
|
||||
logging.log(id + " hash: " + hash);
|
||||
logging.debug(uuid + " old hash: " + (details && type));
|
||||
logging.log(uuid + " hash: " + hash);
|
||||
callback(null, status, hash);
|
||||
}
|
||||
});
|
||||
@ -196,13 +196,13 @@ exp.get_image_hash = function(id, raw_type, callback) {
|
||||
};
|
||||
|
||||
|
||||
// handles requests for +id+ avatars with +size+
|
||||
// handles requests for +uuid+ avatars with +size+
|
||||
// callback contains error, status, image buffer, hash
|
||||
// image is the user's face+helm when helm is true, or the face otherwise
|
||||
// for status, see get_image_hash
|
||||
exp.get_avatar = function(id, helm, size, callback) {
|
||||
logging.log("request: " + id);
|
||||
exp.get_image_hash(id, "skin", function(err, status, hash) {
|
||||
exp.get_avatar = function(uuid, helm, size, callback) {
|
||||
logging.log("request: " + uuid);
|
||||
exp.get_image_hash(uuid, "skin", function(err, status, hash) {
|
||||
if (hash) {
|
||||
var facepath = __dirname + "/../" + config.faces_dir + hash + ".png";
|
||||
var helmpath = __dirname + "/../" + config.helms_dir + hash + ".png";
|
||||
@ -222,26 +222,26 @@ exp.get_avatar = function(id, helm, size, callback) {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// hash is null when the id has no skin
|
||||
// hash is null when uuid has no skin
|
||||
callback(err, status, null, null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// handles requests for +id+ skins
|
||||
// handles requests for +uuid+ skins
|
||||
// callback contains error, hash, image buffer
|
||||
exp.get_skin = function(id, callback) {
|
||||
logging.log(id + " skin request");
|
||||
exp.get_image_hash(id, "skin", function(err, status, hash) {
|
||||
exp.get_skin = function(uuid, callback) {
|
||||
logging.log(uuid + " skin request");
|
||||
exp.get_image_hash(uuid, "skin", function(err, status, hash) {
|
||||
var skinpath = __dirname + "/../" + config.skins_dir + hash + ".png";
|
||||
fs.exists(skinpath, function (exists) {
|
||||
if (exists) {
|
||||
logging.log(id + " skin already exists, not downloading");
|
||||
skins.open_skin(id, skinpath, function(err, img) {
|
||||
logging.log(uuid + " skin already exists, not downloading");
|
||||
skins.open_skin(uuid, skinpath, function(err, img) {
|
||||
callback(err, hash, img);
|
||||
});
|
||||
} else {
|
||||
networking.save_texture(id, hash, skinpath, function(err, img) {
|
||||
networking.save_texture(uuid, hash, skinpath, function(err, img) {
|
||||
callback(err, hash, img);
|
||||
});
|
||||
}
|
||||
@ -256,8 +256,8 @@ function get_type(helm, body) {
|
||||
|
||||
// handles creations of skin renders
|
||||
// callback contanis error, hash, image buffer
|
||||
exp.get_render = function(id, scale, helm, body, callback) {
|
||||
exp.get_skin(id, function(err, hash, img) {
|
||||
exp.get_render = function(uuid, scale, helm, body, callback) {
|
||||
exp.get_skin(uuid, function(err, hash, img) {
|
||||
if (!hash) {
|
||||
callback(err, -1, hash, null);
|
||||
return;
|
||||
@ -265,7 +265,7 @@ exp.get_render = function(id, scale, helm, body, callback) {
|
||||
var renderpath = __dirname + "/../" + config.renders_dir + hash + "-" + scale + "-" + get_type(helm, body) + ".png";
|
||||
fs.exists(renderpath, function(exists) {
|
||||
if (exists) {
|
||||
renders.open_render(id, renderpath, function(err, img) {
|
||||
renders.open_render(uuid, renderpath, function(err, img) {
|
||||
callback(err, 1, hash, img);
|
||||
});
|
||||
return;
|
||||
@ -274,7 +274,7 @@ exp.get_render = function(id, scale, helm, body, callback) {
|
||||
callback(err, 0, hash, null);
|
||||
return;
|
||||
}
|
||||
renders.draw_model(id, img, scale, helm, body, function(err, img) {
|
||||
renders.draw_model(uuid, img, scale, helm, body, function(err, img) {
|
||||
if (err) {
|
||||
callback(err, -1, hash, null);
|
||||
} else if (!img) {
|
||||
@ -294,20 +294,20 @@ exp.get_render = function(id, scale, helm, body, callback) {
|
||||
};
|
||||
|
||||
|
||||
// handles requests for skin of the +id+
|
||||
// handles requests for +uuid+ skins
|
||||
// callback contains error, hash, image buffer
|
||||
exp.get_skin = function(id, callback) {
|
||||
logging.log(id + " skin request");
|
||||
exp.get_image_hash(id, "skin", function(err, status, hash) {
|
||||
exp.get_skin = function(uuid, callback) {
|
||||
logging.log(uuid + " skin request");
|
||||
exp.get_image_hash(uuid, "skin", function(err, status, hash) {
|
||||
var skinpath = __dirname + "/../" + config.skins_dir + hash + ".png";
|
||||
fs.exists(skinpath, function(exists) {
|
||||
if (exists) {
|
||||
logging.log("skin already exists, not downloading");
|
||||
skins.open_skin(id, skinpath, function(err, img) {
|
||||
skins.open_skin(uuid, skinpath, function(err, img) {
|
||||
callback(err, hash, img);
|
||||
});
|
||||
} else {
|
||||
networking.save_texture(id, hash, skinpath, function(err, response, img) {
|
||||
networking.save_texture(uuid, hash, skinpath, function(err, response, img) {
|
||||
callback(err, hash, img);
|
||||
});
|
||||
}
|
||||
@ -315,11 +315,11 @@ exp.get_skin = function(id, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
// handles requests for +id+ capes
|
||||
// handles requests for +uuid+ capes
|
||||
// callback contains error, hash, image buffer
|
||||
exp.get_cape = function(id, callback) {
|
||||
logging.log(id + " cape request");
|
||||
exp.get_image_hash(id, "cape", function(err, status, hash) {
|
||||
exp.get_cape = function(uuid, callback) {
|
||||
logging.log(uuid + " cape request");
|
||||
exp.get_image_hash(uuid, "cape", function(err, status, hash) {
|
||||
if (!hash) {
|
||||
callback(err, null, null);
|
||||
return;
|
||||
@ -328,11 +328,11 @@ exp.get_cape = function(id, callback) {
|
||||
fs.exists(capepath, function(exists) {
|
||||
if (exists) {
|
||||
logging.log("cape already exists, not downloading");
|
||||
skins.open_skin(id, capepath, function(err, img) {
|
||||
skins.open_skin(uuid, capepath, function(err, img) {
|
||||
callback(err, hash, img);
|
||||
});
|
||||
} else {
|
||||
networking.save_texture(id, hash, capepath, function(err, response, img) {
|
||||
networking.save_texture(uuid, hash, capepath, function(err, response, img) {
|
||||
if (response && response.statusCode === 404) {
|
||||
callback(err, hash, null);
|
||||
} else {
|
||||
|
||||
@ -124,28 +124,28 @@ exp.get_profile = function(uuid, callback) {
|
||||
}
|
||||
};
|
||||
|
||||
// +id+ is likely a username and if so
|
||||
// +id+ is used to get the url, otherwise
|
||||
// +uuid+ is likely a username and if so
|
||||
// +uuid+ is used to get the url, otherwise
|
||||
// +profile+ will be used to get the url
|
||||
exp.get_skin_url = function(id, profile, callback) {
|
||||
getUrl(id, profile, 1, function(url) {
|
||||
exp.get_skin_url = function(uuid, profile, callback) {
|
||||
getUrl(uuid, profile, 1, function(url) {
|
||||
callback(url);
|
||||
});
|
||||
};
|
||||
|
||||
// +id+ is likely a username and if so
|
||||
// +id+ is used to get the url, otherwise
|
||||
// +uuid+ is likely a username and if so
|
||||
// +uuid+ is used to get the url, otherwise
|
||||
// +profile+ will be used to get the url
|
||||
exp.get_cape_url = function(id, profile, callback) {
|
||||
getUrl(id, profile, 2, function(url) {
|
||||
exp.get_cape_url = function(uuid, profile, callback) {
|
||||
getUrl(uuid, profile, 2, function(url) {
|
||||
callback(url);
|
||||
});
|
||||
};
|
||||
|
||||
function getUrl(id, profile, type, callback) {
|
||||
if (id.length <= 16) {
|
||||
function getUrl(uuid, profile, type, callback) {
|
||||
if (uuid.length <= 16) {
|
||||
//username
|
||||
exp.get_username_url(id, type, function(err, url) {
|
||||
exp.get_username_url(uuid, type, function(err, url) {
|
||||
callback(url || null);
|
||||
});
|
||||
} else {
|
||||
@ -157,23 +157,23 @@ function getUrl(id, profile, type, callback) {
|
||||
|
||||
// downloads skin file from +url+
|
||||
// callback contains error, image
|
||||
exp.get_skin = function(url, id, callback) {
|
||||
exp.get_skin = function(url, uuid, callback) {
|
||||
exp.get_from(url, function(body, response, err) {
|
||||
callback(body, err);
|
||||
});
|
||||
};
|
||||
|
||||
exp.save_texture = function(id, hash, outpath, callback) {
|
||||
exp.save_texture = function(uuid, hash, outpath, callback) {
|
||||
if (hash) {
|
||||
var textureurl = "http://textures.minecraft.net/texture/" + hash;
|
||||
exp.get_from(textureurl, function(img, response, err) {
|
||||
if (err) {
|
||||
logging.error(id + "error while downloading texture");
|
||||
logging.error(uuid + "error while downloading texture");
|
||||
callback(err, response, null);
|
||||
} else {
|
||||
fs.writeFile(outpath, img, "binary", function(err) {
|
||||
if (err) {
|
||||
logging.log(id + " error: " + err);
|
||||
logging.log(uuid + " error: " + err);
|
||||
}
|
||||
callback(err, response, img);
|
||||
});
|
||||
|
||||
@ -42,9 +42,9 @@ exp.draw_head = function(skin_canvas, model_ctx, scale) {
|
||||
// draws the body on to the +skin_canvas+
|
||||
// using the skin from the +model_ctx+ at the +scale+
|
||||
// parts are labeled as if drawn from the skin's POV
|
||||
exp.draw_body = function(id, skin_canvas, model_ctx, scale) {
|
||||
exp.draw_body = function(uuid, skin_canvas, model_ctx, scale) {
|
||||
if (skin_canvas.height == 32 * scale) {
|
||||
logging.log(id + " old skin");
|
||||
logging.log(uuid + " old skin");
|
||||
//Left Leg
|
||||
//Left Leg - Front
|
||||
model_ctx.setTransform(1,-0.5,0,1.2,0,0);
|
||||
@ -85,7 +85,7 @@ exp.draw_body = function(id, skin_canvas, model_ctx, scale) {
|
||||
model_ctx.scale(-1,1);
|
||||
model_ctx.drawImage(skin_canvas, 44*scale, 16*scale, 4*scale, 4*scale, -16*scale, 16*scale, 4*scale, 4*scale);
|
||||
} else {
|
||||
logging.log(id + " new skin");
|
||||
logging.log(uuid + " new skin");
|
||||
//Left Leg
|
||||
//Left Leg - Front
|
||||
model_ctx.setTransform(1,-0.5,0,1.2,0,0);
|
||||
@ -127,14 +127,14 @@ exp.draw_body = function(id, skin_canvas, model_ctx, scale) {
|
||||
};
|
||||
|
||||
// sets up the necessary components to draw the skin model
|
||||
// uses the +img+ skin from the +id+ with options of drawing
|
||||
// uses the +img+ skin from the +uuid+ with options of drawing
|
||||
// the +helm+ and the +body+
|
||||
// callback contains error, image buffer
|
||||
exp.draw_model = function(id, img, scale, helm, body, callback) {
|
||||
exp.draw_model = function(uuid, img, scale, helm, body, callback) {
|
||||
var image = new Image();
|
||||
|
||||
image.onerror = function(err) {
|
||||
logging.error(id + " render error: " + err);
|
||||
logging.error(uuid + " render error: " + err);
|
||||
callback(err, null);
|
||||
};
|
||||
|
||||
@ -151,19 +151,19 @@ exp.draw_model = function(id, img, scale, helm, body, callback) {
|
||||
//Scale it
|
||||
scale_image(skin_ctx.getImageData(0,0,64,original_height), skin_ctx, 0, 0, scale);
|
||||
if (body) {
|
||||
logging.log(id + " drawing body");
|
||||
exp.draw_body(id, skin_canvas, model_ctx, scale);
|
||||
logging.log(uuid + " drawing body");
|
||||
exp.draw_body(uuid, skin_canvas, model_ctx, scale);
|
||||
}
|
||||
logging.log(id + " drawing head");
|
||||
logging.log(uuid + " drawing head");
|
||||
exp.draw_head(skin_canvas, model_ctx, scale);
|
||||
if (helm) {
|
||||
logging.log(id + " drawing helmet");
|
||||
logging.log(uuid + " drawing helmet");
|
||||
exp.draw_helmet(skin_canvas, model_ctx, scale);
|
||||
}
|
||||
|
||||
model_canvas.toBuffer(function(err, buf){
|
||||
if (err) {
|
||||
logging.log(id + " error creating buffer: " + err);
|
||||
logging.log(uuid + " error creating buffer: " + err);
|
||||
}
|
||||
callback(err, buf);
|
||||
});
|
||||
@ -174,10 +174,10 @@ exp.draw_model = function(id, img, scale, helm, body, callback) {
|
||||
|
||||
// helper method to open a render from +renderpath+
|
||||
// callback contains error, image buffer
|
||||
exp.open_render = function(id, renderpath, callback) {
|
||||
exp.open_render = function(uuid, renderpath, callback) {
|
||||
fs.readFile(renderpath, function (err, buf) {
|
||||
if (err) {
|
||||
logging.error(id + " error while opening skin file: " + err);
|
||||
logging.error(uuid + " error while opening skin file: " + err);
|
||||
}
|
||||
callback(err, buf);
|
||||
});
|
||||
@ -188,13 +188,13 @@ exp.open_render = function(id, renderpath, callback) {
|
||||
function scale_image(imageData, context, d_x, d_y, scale) {
|
||||
var width = imageData.width;
|
||||
var height = imageData.height;
|
||||
context.clearRect(0, 0, width, height); //Clear the spot where it originated from
|
||||
for (var y = 0; y < height; y++) { //height original
|
||||
for (var x = 0; x < width ; x++) { //width original
|
||||
context.clearRect(0,0,width,height); //Clear the spot where it originated from
|
||||
for(y=0; y<height; y++) { //height original
|
||||
for(x=0; x<width; x++) { //width original
|
||||
//Gets original colour, then makes a scaled square of the same colour
|
||||
var index = (x + y * width) * 4;
|
||||
context.fillStyle = "rgba(" + imageData.data[index + 0] + "," + imageData.data[index + 1] + "," + imageData.data[index + 2] + "," + imageData.data[index + 3] + ")";
|
||||
context.fillRect(d_x + x * scale, d_y + y * scale, scale, scale);
|
||||
context.fillStyle = "rgba(" + imageData.data[index+0] + "," + imageData.data[index+1] + "," + imageData.data[index+2] + "," + imageData.data[index+3] + ")";
|
||||
context.fillRect(d_x + x*scale, d_y + y*scale, scale, scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ exp.extract_face = function(buffer, outname, callback) {
|
||||
// +facefile+ is the filename of an image produced by extract_face
|
||||
// result is saved to a file called +outname+
|
||||
// +callback+ contains error
|
||||
exp.extract_helm = function(id, facefile, buffer, outname, callback) {
|
||||
exp.extract_helm = function(uuid, facefile, buffer, outname, callback) {
|
||||
lwip.open(buffer, "png", function(err, skin_img) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
@ -53,7 +53,7 @@ exp.extract_helm = function(id, facefile, buffer, outname, callback) {
|
||||
callback(err);
|
||||
});
|
||||
} else {
|
||||
logging.log(id + " helm image is the same as face image, not storing!");
|
||||
logging.log(uuid + " helm image is the same as face image, not storing!");
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
@ -84,10 +84,9 @@ exp.resize_img = function(inname, size, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
// returns "alex" or "steve" calculated by the +id+
|
||||
// this method is only accurate when +id+ is a uuid
|
||||
exp.default_skin = function(id) {
|
||||
if (Number("0x" + id[31]) % 2 === 0) {
|
||||
// returns "alex" or "steve" calculated by the +uuid+
|
||||
exp.default_skin = function(uuid) {
|
||||
if (Number("0x" + uuid[31]) % 2 === 0) {
|
||||
return "alex";
|
||||
} else {
|
||||
return "steve";
|
||||
@ -96,10 +95,10 @@ exp.default_skin = function(id) {
|
||||
|
||||
// helper method for opening a skin file from +skinpath+
|
||||
// callback contains error, image buffer
|
||||
exp.open_skin = function(id, skinpath, callback) {
|
||||
exp.open_skin = function(uuid, skinpath, callback) {
|
||||
fs.readFile(skinpath, function(err, buf) {
|
||||
if (err) {
|
||||
logging.error(id + " error while opening skin file: " + err);
|
||||
logging.error(uuid + " error while opening skin file: " + err);
|
||||
callback(err, null);
|
||||
} else {
|
||||
callback(null, buf);
|
||||
|
||||
@ -15,7 +15,7 @@ var human_status = {
|
||||
// GET avatar request
|
||||
module.exports = function(req, res) {
|
||||
var start = new Date();
|
||||
var id = (req.url.path_list[2] || "").split(".")[0];
|
||||
var uuid = (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");
|
||||
@ -31,25 +31,25 @@ module.exports = function(req, res) {
|
||||
});
|
||||
res.end("Invalid Size");
|
||||
return;
|
||||
} else if (!helpers.id_valid(id)) {
|
||||
} else if (!helpers.uuid_valid(uuid)) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("Invalid ID");
|
||||
res.end("Invalid UUID");
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
id = id.replace(/-/g, "");
|
||||
uuid = uuid.replace(/-/g, "");
|
||||
|
||||
try {
|
||||
helpers.get_avatar(id, helm, size, function(err, status, image, hash) {
|
||||
logging.log(id + " - " + human_status[status]);
|
||||
helpers.get_avatar(uuid, helm, size, function(err, status, image, hash) {
|
||||
logging.log(uuid + " - " + human_status[status]);
|
||||
if (err) {
|
||||
logging.error(id + " " + err);
|
||||
logging.error(uuid + " " + err);
|
||||
if (err.code === "ENOENT") {
|
||||
cache.remove_hash(id);
|
||||
cache.remove_hash(uuid);
|
||||
}
|
||||
}
|
||||
etag = image && hash && hash.substr(0, 32) || "none";
|
||||
@ -61,21 +61,21 @@ module.exports = function(req, res) {
|
||||
} else if (err) {
|
||||
http_status = 503;
|
||||
}
|
||||
logging.debug(id + " etag: " + req.headers["if-none-match"]);
|
||||
logging.debug(id + " matches: " + matches);
|
||||
sendimage(http_status, status, image, id);
|
||||
logging.debug(uuid + " etag: " + req.headers["if-none-match"]);
|
||||
logging.debug(uuid + " matches: " + matches);
|
||||
sendimage(http_status, status, image, uuid);
|
||||
} else {
|
||||
handle_default(404, status, id);
|
||||
handle_default(404, status, uuid);
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
logging.error(id + " error: " + e);
|
||||
handle_default(500, status, id);
|
||||
logging.error(uuid + " error: " + e);
|
||||
handle_default(500, status, uuid);
|
||||
}
|
||||
|
||||
function handle_default(http_status, img_status, id) {
|
||||
function handle_default(http_status, img_status, uuid) {
|
||||
if (def && def !== "steve" && def !== "alex") {
|
||||
logging.log(id + " status: 301");
|
||||
logging.log(uuid + " status: 301");
|
||||
res.writeHead(301, {
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
"Response-Time": new Date() - start,
|
||||
@ -85,15 +85,15 @@ module.exports = function(req, res) {
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
def = def || skins.default_skin(id);
|
||||
def = def || skins.default_skin(uuid);
|
||||
skins.resize_img("public/images/" + def + ".png", size, function(err, image) {
|
||||
sendimage(http_status, img_status, image, id);
|
||||
sendimage(http_status, img_status, image, uuid);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function sendimage(http_status, img_status, image, id) {
|
||||
logging.log(id + " status: " + http_status);
|
||||
function sendimage(http_status, img_status, image, uuid) {
|
||||
logging.log(uuid + " status: " + http_status);
|
||||
res.writeHead(http_status, {
|
||||
"Content-Type": "image/png",
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
|
||||
@ -13,10 +13,10 @@ var human_status = {
|
||||
// GET cape request
|
||||
module.exports = function(req, res) {
|
||||
var start = new Date();
|
||||
var id = (req.url.pathname.split("/")[2] || "").split(".")[0];
|
||||
var uuid = (req.url.pathname.split("/")[2] || "").split(".")[0];
|
||||
var etag = null;
|
||||
|
||||
if (!helpers.id_valid(id)) {
|
||||
if (!helpers.uuid_valid(uuid)) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
@ -26,13 +26,13 @@ module.exports = function(req, res) {
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
id = id.replace(/-/g, "");
|
||||
uuid = uuid.replace(/-/g, "");
|
||||
|
||||
try {
|
||||
helpers.get_cape(id, function(err, status, image, hash) {
|
||||
logging.log(id + " - " + human_status[status]);
|
||||
helpers.get_cape(uuid, function(err, status, image, hash) {
|
||||
logging.log(uuid + " - " + human_status[status]);
|
||||
if (err) {
|
||||
logging.error(id + " " + err);
|
||||
logging.error(uuid + " " + err);
|
||||
}
|
||||
etag = hash && hash.substr(0, 32) || "none";
|
||||
var matches = req.headers["if-none-match"] === '"' + etag + '"';
|
||||
@ -56,7 +56,7 @@ module.exports = function(req, res) {
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
logging.error(id + " error:");
|
||||
logging.error(uuid + " error:");
|
||||
logging.error(e);
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "text/plain",
|
||||
|
||||
@ -33,7 +33,7 @@ module.exports = function(req, res) {
|
||||
}
|
||||
|
||||
var body = raw_type === "body";
|
||||
var id = (req.url.path_list[3] || "").split(".")[0];
|
||||
var uuid = (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");
|
||||
@ -46,23 +46,23 @@ module.exports = function(req, res) {
|
||||
});
|
||||
res.end("422 Invalid Scale");
|
||||
return;
|
||||
} else if (!helpers.id_valid(id)) {
|
||||
} else if (!helpers.uuid_valid(uuid)) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("422 Invalid ID");
|
||||
res.end("422 Invalid UUID");
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
id = id.replace(/-/g, "");
|
||||
uuid = uuid.replace(/-/g, "");
|
||||
|
||||
try {
|
||||
helpers.get_render(id, scale, helm, body, function(err, status, hash, image) {
|
||||
logging.log(id + " - " + human_status[status]);
|
||||
helpers.get_render(uuid, scale, helm, body, function(err, status, hash, image) {
|
||||
logging.log(uuid + " - " + human_status[status]);
|
||||
if (err) {
|
||||
logging.error(id + " " + err);
|
||||
logging.error(uuid + " " + err);
|
||||
}
|
||||
etag = hash && hash.substr(0, 32) || "none";
|
||||
var matches = req.headers["if-none-match"] === '"' + etag + '"';
|
||||
@ -73,25 +73,25 @@ module.exports = function(req, res) {
|
||||
} else if (err) {
|
||||
http_status = 503;
|
||||
}
|
||||
logging.debug(id + " etag: " + req.headers["if-none-match"]);
|
||||
logging.debug(id + " matches: " + matches);
|
||||
logging.debug(uuid + " etag: " + req.headers["if-none-match"]);
|
||||
logging.debug(uuid + " matches: " + matches);
|
||||
sendimage(http_status, status, image, uuid);
|
||||
} else {
|
||||
logging.log(id + " image not found, using default.");
|
||||
logging.log(uuid + " image not found, using default.");
|
||||
handle_default(404, status, uuid);
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
logging.error(id + " error: " + e);
|
||||
logging.error(uuid + " error: " + e);
|
||||
handle_default(500, status, uuid);
|
||||
}
|
||||
|
||||
|
||||
// default alex/steve images can be rendered, but
|
||||
// custom images will not be
|
||||
function handle_default(http_status, img_status, id) {
|
||||
function handle_default(http_status, img_status, uuid) {
|
||||
if (def && def !== "steve" && def !== "alex") {
|
||||
logging.log(id + " status: 301");
|
||||
logging.log(uuid + " status: 301");
|
||||
res.writeHead(301, {
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
"Response-Time": new Date() - start,
|
||||
@ -101,25 +101,25 @@ module.exports = function(req, res) {
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
def = def || skins.default_skin(id);
|
||||
def = def || skins.default_skin(uuid);
|
||||
fs.readFile("public/images/" + def + "_skin.png", function (err, buf) {
|
||||
if (err) {
|
||||
// errored while loading the default image, continuing with null image
|
||||
logging.error(id + "error loading default render image: " + err);
|
||||
logging.error(uuid + "error loading default render image: " + err);
|
||||
}
|
||||
// we render the default skins, but not custom images
|
||||
renders.draw_model(id, buf, scale, helm, body, function(err, def_img) {
|
||||
renders.draw_model(uuid, buf, scale, helm, body, function(err, def_img) {
|
||||
if (err) {
|
||||
logging.log(id + "error while rendering default image: " + err);
|
||||
logging.log(uuid + "error while rendering default image: " + err);
|
||||
}
|
||||
sendimage(http_status, img_status, def_img, id);
|
||||
sendimage(http_status, img_status, def_img, uuid);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function sendimage(http_status, img_status, image, id) {
|
||||
logging.log(id + " status: " + http_status);
|
||||
function sendimage(http_status, img_status, image, uuid) {
|
||||
logging.log(uuid + " status: " + http_status);
|
||||
res.writeHead(http_status, {
|
||||
"Content-Type": "image/png",
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
|
||||
@ -7,27 +7,27 @@ var lwip = require("lwip");
|
||||
// GET skin request
|
||||
module.exports = function(req, res) {
|
||||
var start = new Date();
|
||||
var id = (req.url.path_list[2] || "").split(".")[0];
|
||||
var uuid = (req.url.path_list[2] || "").split(".")[0];
|
||||
var def = req.url.query.default;
|
||||
var etag = null;
|
||||
|
||||
if (!helpers.id_valid(id)) {
|
||||
if (!helpers.uuid_valid(uuid)) {
|
||||
res.writeHead(422, {
|
||||
"Content-Type": "text/plain",
|
||||
"Response-Time": new Date() - start
|
||||
});
|
||||
res.end("Invalid ID");
|
||||
res.end("Invalid UUID");
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
id = id.replace(/-/g, "");
|
||||
uuid = uuid.replace(/-/g, "");
|
||||
|
||||
try {
|
||||
helpers.get_skin(id, function(err, hash, image) {
|
||||
logging.log(id);
|
||||
helpers.get_skin(uuid, function(err, hash, image) {
|
||||
logging.log(uuid);
|
||||
if (err) {
|
||||
logging.error(id + " " + err);
|
||||
logging.error(uuid + " " + err);
|
||||
}
|
||||
etag = hash && hash.substr(0, 32) || "none";
|
||||
var matches = req.headers["if-none-match"] === '"' + etag + '"';
|
||||
@ -38,21 +38,21 @@ module.exports = function(req, res) {
|
||||
} else if (err) {
|
||||
http_status = 503;
|
||||
}
|
||||
logging.debug(id + " etag: " + req.headers["if-none-match"]);
|
||||
logging.debug(id + " matches: " + matches);
|
||||
sendimage(http_status, image, id);
|
||||
logging.debug(uuid + " etag: " + req.headers["if-none-match"]);
|
||||
logging.debug(uuid + " matches: " + matches);
|
||||
sendimage(http_status, image, uuid);
|
||||
} else {
|
||||
handle_default(404, id);
|
||||
handle_default(404, uuid);
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
logging.error(id + " error: " + e);
|
||||
handle_default(500, id);
|
||||
logging.error(uuid + " error: " + e);
|
||||
handle_default(500, uuid);
|
||||
}
|
||||
|
||||
function handle_default(http_status, id) {
|
||||
function handle_default(http_status, uuid) {
|
||||
if (def && def !== "steve" && def !== "alex") {
|
||||
logging.log(id + " status: 301");
|
||||
logging.log(uuid + " status: 301");
|
||||
res.writeHead(301, {
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
"Response-Time": new Date() - start,
|
||||
@ -62,17 +62,17 @@ module.exports = function(req, res) {
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
def = def || skins.default_skin(id);
|
||||
def = def || skins.default_skin(uuid);
|
||||
lwip.open("public/images/" + def + "_skin.png", function(err, image) {
|
||||
image.toBuffer("png", function(err, buffer) {
|
||||
sendimage(http_status, buffer, id);
|
||||
sendimage(http_status, buffer, uuid);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function sendimage(http_status, image, id) {
|
||||
logging.log(id + " status: " + http_status);
|
||||
function sendimage(http_status, image, uuid) {
|
||||
logging.log(uuid + " status: " + http_status);
|
||||
res.writeHead(http_status, {
|
||||
"Content-Type": "image/png",
|
||||
"Cache-Control": "max-age=" + config.browser_cache_time + ", public",
|
||||
|
||||
20
test/test.js
20
test/test.js
@ -45,43 +45,43 @@ describe("Crafatar", function() {
|
||||
|
||||
describe("UUID/username", function() {
|
||||
it("non-hex uuid is invalid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("g098cb60fa8e427cb299793cbd302c9a"), false);
|
||||
assert.strictEqual(helpers.uuid_valid("g098cb60fa8e427cb299793cbd302c9a"), false);
|
||||
done();
|
||||
});
|
||||
it("empty id is invalid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid(""), false);
|
||||
assert.strictEqual(helpers.uuid_valid(""), false);
|
||||
done();
|
||||
});
|
||||
it("non-alphanumeric username is invalid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("usernäme"), false);
|
||||
assert.strictEqual(helpers.uuid_valid("usernäme"), false);
|
||||
done();
|
||||
});
|
||||
it("dashed username is invalid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("user-name"), false);
|
||||
assert.strictEqual(helpers.uuid_valid("user-name"), false);
|
||||
done();
|
||||
});
|
||||
it(">16 length username is invalid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("ThisNameIsTooLong"), false);
|
||||
assert.strictEqual(helpers.uuid_valid("ThisNameIsTooLong"), false);
|
||||
done();
|
||||
});
|
||||
it("lowercase uuid is valid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("0098cb60fa8e427cb299793cbd302c9a"), true);
|
||||
assert.strictEqual(helpers.uuid_valid("0098cb60fa8e427cb299793cbd302c9a"), true);
|
||||
done();
|
||||
});
|
||||
it("uppercase uuid is valid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("1DCEF164FF0A47F2B9A691385C774EE7"), true);
|
||||
assert.strictEqual(helpers.uuid_valid("1DCEF164FF0A47F2B9A691385C774EE7"), true);
|
||||
done();
|
||||
});
|
||||
it("dashed uuid is valid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), true);
|
||||
assert.strictEqual(helpers.uuid_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), true);
|
||||
done();
|
||||
});
|
||||
it("16 chars, underscored, capital, numbered username is valid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("__niceUs3rname__"), true);
|
||||
assert.strictEqual(helpers.uuid_valid("__niceUs3rname__"), true);
|
||||
done();
|
||||
});
|
||||
it("1 char username is valid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("a"), true);
|
||||
assert.strictEqual(helpers.uuid_valid("a"), true);
|
||||
done();
|
||||
});
|
||||
it("should not exist (uuid)", function(done) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user