mirror of
https://github.com/azures04/crafatar.git
synced 2026-03-22 07:51:17 +01:00
Compare commits
No commits in common. "master" and "v2.1.0" have entirely different histories.
2
.buildpacks
Normal file
2
.buildpacks
Normal file
@ -0,0 +1,2 @@
|
||||
https://github.com/mojodna/heroku-buildpack-cairo.git
|
||||
https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
@ -1,6 +0,0 @@
|
||||
.*
|
||||
*.md
|
||||
Dockerfile
|
||||
LICENSE
|
||||
images/
|
||||
node_modules/
|
||||
21
.editorconfig
Normal file
21
.editorconfig
Normal file
@ -0,0 +1,21 @@
|
||||
# We use EditorConfig to standardize settings between contributors
|
||||
# See http://editorconfig.org for more info and plugin downloads
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js, json, yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[.gitignore]
|
||||
# echo "filename" >> .gitignorre
|
||||
insert_final_newline = true
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,3 +1,9 @@
|
||||
images/*/*.png
|
||||
node_modules/
|
||||
coverage/
|
||||
.DS_Store
|
||||
*.log
|
||||
*.rdb
|
||||
*.sublime-*
|
||||
config.js
|
||||
lib/public/images/sponsor.png
|
||||
|
||||
29
.travis.yml
Normal file
29
.travis.yml
Normal file
@ -0,0 +1,29 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 8.9.4
|
||||
sudo: false
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- libcairo2-dev
|
||||
- libjpeg8-dev
|
||||
- libpango1.0-dev
|
||||
- libgif-dev
|
||||
- build-essential
|
||||
- g++-4.8
|
||||
script:
|
||||
- npm run-script test-travis
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- irc.esper.net#crafatar
|
||||
skip_join: true
|
||||
env:
|
||||
- TRAVIS=true CXX=g++-4.8
|
||||
services:
|
||||
- redis-server
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
50
Dockerfile
50
Dockerfile
@ -1,35 +1,27 @@
|
||||
FROM node:12-alpine AS builder
|
||||
FROM node:12-alpine
|
||||
|
||||
RUN apk --no-cache add git python3 build-base redis cairo-dev pango-dev jpeg-dev giflib-dev
|
||||
ARG REDIS_URL
|
||||
ARG DEBUG
|
||||
ARG EPHEMERAL_STORAGE
|
||||
|
||||
RUN adduser -D app
|
||||
USER app
|
||||
RUN apk --no-cache --virtual .build-deps add git python build-base
|
||||
RUN apk --no-cache --virtual .canvas-deps add cairo-dev pango-dev jpeg-dev giflib-dev
|
||||
|
||||
RUN mkdir -p /crafatar/images/faces
|
||||
RUN mkdir -p /crafatar/images/helms
|
||||
RUN mkdir -p /crafatar/images/skins
|
||||
RUN mkdir -p /crafatar/images/renders
|
||||
RUN mkdir -p /crafatar/images/capes
|
||||
|
||||
VOLUME /crafatar/images
|
||||
|
||||
COPY package.json www.js crafatar/
|
||||
COPY config.example.js crafatar/config.js
|
||||
COPY lib/ crafatar/lib/
|
||||
|
||||
WORKDIR /crafatar
|
||||
|
||||
COPY --chown=app package.json package-lock.json /home/app/crafatar/
|
||||
WORKDIR /home/app/crafatar
|
||||
RUN npm install
|
||||
|
||||
COPY --chown=app . .
|
||||
RUN mkdir -p images/faces images/helms images/skins images/renders images/capes
|
||||
|
||||
ARG VERBOSE_TEST
|
||||
ARG DEBUG
|
||||
RUN nohup redis-server & npm test
|
||||
|
||||
|
||||
FROM node:12-alpine
|
||||
RUN apk --no-cache add cairo pango jpeg giflib
|
||||
RUN adduser -D app
|
||||
USER app
|
||||
RUN mkdir /home/app/crafatar
|
||||
WORKDIR /home/app/crafatar
|
||||
RUN mkdir -p images/faces images/helms images/skins images/renders images/capes
|
||||
|
||||
COPY --chown=app --from=builder /home/app/crafatar/node_modules/ node_modules/
|
||||
COPY --chown=app package.json www.js config.js ./
|
||||
COPY --chown=app lib/ lib/
|
||||
|
||||
VOLUME /home/app/crafatar/images
|
||||
ENV NODE_ENV production
|
||||
ENTRYPOINT ["npm", "start"]
|
||||
EXPOSE 3000
|
||||
ENTRYPOINT npm start
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Crafatar Team
|
||||
Copyright (c) 2016 Crafatar Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
43
README.md
43
README.md
@ -1,8 +1,8 @@
|
||||
# Crafatar
|
||||
<img alt="logo" src="lib/public/logo.png" align="right" width="128px" height="128px">
|
||||
# Crafatar [](https://travis-ci.org/crafatar/crafatar/) [](https://coveralls.io/r/crafatar/crafatar) [](https://codeclimate.com/github/crafatar/crafatar)
|
||||
[](https://david-dm.org/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar#info=devDependencies) [](https://inch-ci.org/github/crafatar/crafatar)
|
||||
|
||||
[](https://travis-ci.org/crafatar/crafatar/) [](https://coveralls.io/r/crafatar/crafatar) [](https://codeclimate.com/github/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar) [](https://david-dm.org/crafatar/crafatar#info=devDependencies) [](https://inch-ci.org/github/crafatar/crafatar)
|
||||
|
||||
<img alt="logo" src="lib/public/logo.png" align="right">
|
||||
<a href="https://crafatar.com">Crafatar</a> serves Minecraft avatars based on the skin for use in external applications.
|
||||
Inspired by <a href="https://gravatar.com">Gravatar</a> (hence the name) and <a href="https://minotar.net">Minotar</a>.
|
||||
|
||||
@ -34,14 +34,6 @@ Please [visit the website](https://crafatar.com) for details.
|
||||
|
||||
# Installation
|
||||
|
||||
## Docker
|
||||
|
||||
```sh
|
||||
docker network create crafatar
|
||||
docker run --net crafatar -d --name redis redis
|
||||
docker run --net crafatar -v crafatar-images:/home/app/crafatar/images -e REDIS_URL=redis://redis -p 3000:3000 crafatar/crafatar
|
||||
```
|
||||
|
||||
## Manual
|
||||
|
||||
- Install [nodejs](https://nodejs.org/) 12 (LTS)
|
||||
@ -52,21 +44,32 @@ docker run --net crafatar -v crafatar-images:/home/app/crafatar/images -e REDIS_
|
||||
|
||||
Crafatar is now available at http://0.0.0.0:3000.
|
||||
|
||||
## Configration / Environment variables
|
||||
## Docker
|
||||
|
||||
See the `config.js` file.
|
||||
Download the docker image from [releases](https://github.com/crafatar/crafatar/releases) (docker hub coming soon™️).
|
||||
|
||||
# Operational notes
|
||||
```sh
|
||||
docker load -i crafatar-docker.tar
|
||||
mkdir /path/to/crafatar-images
|
||||
```
|
||||
|
||||
## inodes
|
||||
```sh
|
||||
docker network create crafatar
|
||||
docker run --net crafatar -d --name redis redis
|
||||
docker run --net crafatar -v /path/to/crafatar-images:/crafatar/images -e REDIS_URL=redis://redis -p 3000:3000 crafatar:2.1.0
|
||||
```
|
||||
|
||||
Crafatar stores a lot of images on disk. For avatars, these are 8×8 px PNG images with an average file size of \~90 bytes. This can lead to issues on file systems such as ext4, which (by default) has a bytes-per-inode ratio of 16Kb. With thousands of files with an average file size below this ratio, you will run out of available inodes before running out of disk space. (Note that this will still be reported as `ENOSPC: no space left on device`).
|
||||
## Environment variables
|
||||
|
||||
Consider using a different file system, changing the inode ratio, or deleting files before the inode limit is reached.
|
||||
| Variable | Default | Description |
|
||||
| :- | :- | :- |
|
||||
| `BIND` | `0.0.0.0` | Hostname to listen on |
|
||||
| `PORT` | `3000` | Port to listen on |
|
||||
| `DEBUG` | `false` | Enable verbose debug logging |
|
||||
| `REDIS_URL` | `redis://127.0.0.1:6379` | URI of the redis server |
|
||||
| `EPHEMERAL_STORAGE` | | If set, redis is flushed on start* |
|
||||
|
||||
## disk space and memory usage
|
||||
|
||||
Eventually you will run out of disk space and/or redis will be out of memory. Make sure to delete image files and/or flush redis before this happens.
|
||||
\* Use this to avoid issues when you have a persistent redis database but an ephemeral storage
|
||||
|
||||
# Tests
|
||||
```sh
|
||||
|
||||
30
app.json
Normal file
30
app.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "Crafatar",
|
||||
"description": "A blazing fast API for Minecraft faces!",
|
||||
"repository": "https://github.com/crafatar/crafatar",
|
||||
"keywords": [
|
||||
"node",
|
||||
"minecraft",
|
||||
"avatar",
|
||||
"redis"
|
||||
],
|
||||
"website": "https://crafatar.com/",
|
||||
"env": {
|
||||
"EPHEMERAL_STORAGE": {
|
||||
"description": "Set to true if your storage is gone after deploying",
|
||||
"required": false,
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"addons": [
|
||||
"rediscloud"
|
||||
],
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "https://github.com/mojodna/heroku-buildpack-cairo.git"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/heroku/heroku-buildpack-nodejs.git"
|
||||
}
|
||||
]
|
||||
}
|
||||
40
config.example.js
Normal file
40
config.example.js
Normal file
@ -0,0 +1,40 @@
|
||||
var config = {
|
||||
avatars: {
|
||||
min_size: 1, // for avatars
|
||||
max_size: 512, // for avatars; too big values might lead to slow response time or DoS
|
||||
default_size: 160 // for avatars; size to be used when no size given
|
||||
},
|
||||
renders: {
|
||||
min_scale: 1, // for 3D rendered skins
|
||||
max_scale: 10, // for 3D rendered skins; too big values might lead to slow response time or DoS
|
||||
default_scale: 6 // for 3D rendered skins; scale to be used when no scale given
|
||||
},
|
||||
cleaner: {
|
||||
interval: 600, // interval seconds to check limits
|
||||
disk_limit: 524288, // min allowed free KB on disk to trigger image deletion
|
||||
redis_limit: 24576, // max allowed used KB on redis to trigger redis flush
|
||||
amount: 50000 // amount of skins for which all image types are deleted
|
||||
},
|
||||
directories: {
|
||||
faces: "./images/faces/", // directory where faces are kept. must have trailing "/"
|
||||
helms: "./images/helms/", // directory where helms are kept. must have trailing "/"
|
||||
skins: "./images/skins/", // directory where skins are kept. must have trailing "/"
|
||||
renders: "./images/renders/", // directory where rendered skins are kept. must have trailing "/"
|
||||
capes: "./images/capes/" // directory where capes are kept. must have trailing "/"
|
||||
},
|
||||
caching: {
|
||||
local: 1200, // seconds until we will check if user's skin changed. should be > 60 to comply with Mojang's rate limit
|
||||
browser: 3600 // seconds until browser will request image again
|
||||
},
|
||||
server: {
|
||||
http_timeout: 2000, // ms until connection to Mojang is dropped
|
||||
debug_enabled: false, // enables logging.debug & editing index page
|
||||
log_time: true // set to false if you use an external logger that provides timestamps
|
||||
},
|
||||
sponsor: {
|
||||
sidebar: '<hr><div class="list-group"><a class="list-group-item sponsor-item" href="https://akliz.net/crafatar" target="_blank" title="Applies to all modpacks and plans for the first billing cycle only.">Save 20% on a Minecraft server with Akliz.</a></div>',
|
||||
top_right: '<a href="https://akliz.net/crafatar" target="_blank" title="Crafatar is sponsored by Akliz" class="sponsor"><img src="/images/sponsor.png" alt="Akliz"></a>'
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
69
config.js
69
config.js
@ -1,69 +0,0 @@
|
||||
var config = {
|
||||
avatars: {
|
||||
// for avatars
|
||||
min_size: parseInt(process.env.AVATAR_MIN) || 1,
|
||||
// for avatars; large values might lead to slow response time or DoS
|
||||
max_size: parseInt(process.env.AVATAR_MAX) || 512,
|
||||
// for avatars; size to be used when no size given
|
||||
default_size: parseInt(process.env.AVATAR_DEFAULT) || 160
|
||||
},
|
||||
renders: {
|
||||
// for 3D rendered skins
|
||||
min_scale: parseInt(process.env.RENDER_MIN) || 1,
|
||||
// for 3D rendered skins; large values might lead to slow response time or DoS
|
||||
max_scale: parseInt(process.env.RENDER_MAX) || 10,
|
||||
// for 3D rendered skins; scale to be used when no scale given
|
||||
default_scale: parseInt(process.env.RENDER_DEFAULT) || 6
|
||||
},
|
||||
directories: {
|
||||
// directory where faces are kept. must have trailing "/"
|
||||
faces: process.env.FACE_DIR || "./images/faces/",
|
||||
// directory where helms are kept. must have trailing "/"
|
||||
helms: process.env.HELM_DIR || "./images/helms/",
|
||||
// directory where skins are kept. must have trailing "/"
|
||||
skins: process.env.SKIN_DIR || "./images/skins/",
|
||||
// directory where rendered skins are kept. must have trailing "/"
|
||||
renders: process.env.RENDER_DIR || "./images/renders/",
|
||||
// directory where capes are kept. must have trailing "/"
|
||||
capes: process.env.CAPE_DIR || "./images/capes/"
|
||||
},
|
||||
caching: {
|
||||
// seconds until we will check if user's skin changed.
|
||||
// Should be > 60 to comply with Mojang's rate limit
|
||||
local: parseInt(process.env.CACHE_LOCAL) || 1200,
|
||||
// seconds until browser will request image again
|
||||
browser: parseInt(process.env.CACHE_BROWSER) || 3600,
|
||||
// If true, redis is flushed on start.
|
||||
// Use this to avoid issues when you have a persistent redis database but an ephemeral storage
|
||||
ephemeral: process.env.EPHEMERAL_STORAGE === "true",
|
||||
// Used for information on the front page
|
||||
cloudflare: process.env.CLOUDFLARE === "true"
|
||||
},
|
||||
// URL of your redis server
|
||||
redis: process.env.REDIS_URL || 'redis://localhost:6379',
|
||||
server: {
|
||||
// port to listen on
|
||||
port: parseInt(process.env.PORT) || 3000,
|
||||
// IP address to listen on
|
||||
bind: process.env.BIND || "0.0.0.0",
|
||||
// ms until connection to Mojang is dropped
|
||||
http_timeout: parseInt(process.env.EXTERNAL_HTTP_TIMEOUT) || 2000,
|
||||
// enables logging.debug & editing index page
|
||||
debug_enabled: process.env.DEBUG === "true",
|
||||
// set to false if you use an external logger that provides timestamps,
|
||||
log_time: process.env.LOG_TIME === "true",
|
||||
// rate limit per second for outgoing requests to the Mojang session server
|
||||
// requests exceeding this limit are skipped and considered failed
|
||||
sessions_rate_limit: parseInt(process.env.SESSIONS_RATE_LIMIT)
|
||||
},
|
||||
sponsor: {
|
||||
sidebar: process.env.SPONSOR_SIDE,
|
||||
top_right: process.env.SPONSOR_TOP_RIGHT
|
||||
},
|
||||
endpoints: {
|
||||
textures_url: process.env.TEXTURES_ENDPOINT || "https://textures.minecraft.net/texture/",
|
||||
session_url: process.env.SESSION_ENDPOINT || "https://sessionserver.mojang.com/session/minecraft/profile/"
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
43
lib/cache.js
43
lib/cache.js
@ -1,6 +1,7 @@
|
||||
var logging = require("./logging");
|
||||
var node_redis = require("redis");
|
||||
var config = require("../config");
|
||||
var url = require("url");
|
||||
|
||||
var redis = null;
|
||||
|
||||
@ -8,10 +9,19 @@ var redis = null;
|
||||
// flushes redis when using ephemeral storage (e.g. Heroku)
|
||||
function connect_redis() {
|
||||
logging.log("connecting to redis...");
|
||||
redis = node_redis.createClient(config.redis);
|
||||
// parse redis env
|
||||
var redis_env = process.env.REDISCLOUD_URL || process.env.REDIS_URL;
|
||||
var redis_url = redis_env ? url.parse(redis_env) : {};
|
||||
redis_url.port = redis_url.port || 6379;
|
||||
redis_url.hostname = redis_url.hostname || "localhost";
|
||||
// connect to redis
|
||||
redis = node_redis.createClient(redis_url.port, redis_url.hostname);
|
||||
if (redis_url.auth) {
|
||||
redis.auth(redis_url.auth.split(":")[1]);
|
||||
}
|
||||
redis.on("ready", function() {
|
||||
logging.log("Redis connection established.");
|
||||
if (config.caching.ephemeral) {
|
||||
if (process.env.EPHEMERAL_STORAGE) {
|
||||
logging.log("Storage is ephemeral, flushing redis");
|
||||
redis.flushall();
|
||||
}
|
||||
@ -31,6 +41,35 @@ exp.get_redis = function() {
|
||||
return redis;
|
||||
};
|
||||
|
||||
|
||||
// updates the redis instance's server_info object
|
||||
// callback: error, info object
|
||||
exp.info = function(callback) {
|
||||
redis.info(function(err, res) {
|
||||
// parse the info command and store it in redis.server_info
|
||||
|
||||
// this code block was taken from mranney/node_redis#on_info_cmd
|
||||
// http://git.io/LBUNbg
|
||||
var lines = res.toString().split("\r\n");
|
||||
var obj = {};
|
||||
lines.forEach(function(line) {
|
||||
var parts = line.split(":");
|
||||
if (parts[1]) {
|
||||
obj[parts[0]] = parts[1];
|
||||
}
|
||||
});
|
||||
obj.versions = [];
|
||||
if (obj.redis_version) {
|
||||
obj.redis_version.split(".").forEach(function(num) {
|
||||
obj.versions.push(+num);
|
||||
});
|
||||
}
|
||||
redis.server_info = obj;
|
||||
|
||||
callback(err, redis.server_info);
|
||||
});
|
||||
};
|
||||
|
||||
// set model type to value of *slim*
|
||||
exp.set_slim = function(rid, userId, slim, callback) {
|
||||
logging.debug(rid, "setting slim for", userId, "to " + slim);
|
||||
|
||||
126
lib/cleaner.js
Normal file
126
lib/cleaner.js
Normal file
@ -0,0 +1,126 @@
|
||||
var logging = require("./logging");
|
||||
var config = require("../config");
|
||||
var cache = require("./cache");
|
||||
var path = require("path");
|
||||
var df = require("node-df");
|
||||
var fs = require("fs");
|
||||
|
||||
var redis = cache.get_redis();
|
||||
var exp = {};
|
||||
|
||||
// does nothing
|
||||
function nil() {}
|
||||
|
||||
// compares redis' used_memory with cleaning_redis_limit
|
||||
// callback: error, true|false
|
||||
function should_clean_redis(callback) {
|
||||
cache.info(function(err, info) {
|
||||
if (err) {
|
||||
callback(err, false);
|
||||
} else {
|
||||
try {
|
||||
// logging.debug(info.toString());
|
||||
var used = parseInt(info.used_memory) / 1024;
|
||||
var result = used >= config.cleaner.redis_limit;
|
||||
var msg = "RedisCleaner: " + used + "KB used";
|
||||
(result ? logging.log : logging.debug)(msg);
|
||||
callback(err, result);
|
||||
} catch(e) {
|
||||
callback(e, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// uses `df` to get the available fisk space
|
||||
// callback: error, true|false
|
||||
function should_clean_disk(callback) {
|
||||
df({
|
||||
file: config.directories.faces,
|
||||
prefixMultiplier: "KiB",
|
||||
isDisplayPrefixMultiplier: false,
|
||||
precision: 2
|
||||
}, function(err, response) {
|
||||
if (err) {
|
||||
callback(err, false);
|
||||
} else {
|
||||
var available = response[0].available;
|
||||
var result = available < config.cleaner.disk_limit;
|
||||
var msg = "DiskCleaner: " + available + "KB available";
|
||||
(result ? logging.log : logging.debug)(msg);
|
||||
callback(err, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// check if redis limit reached, then flush redis
|
||||
// check if disk limit reached, then delete images
|
||||
exp.run = function() {
|
||||
should_clean_redis(function(err, clean) {
|
||||
if (err) {
|
||||
logging.error("Failed to run RedisCleaner");
|
||||
logging.error(err);
|
||||
} else if (clean) {
|
||||
logging.warn("RedisCleaner: Redis limit reached! flushing now");
|
||||
redis.flushall();
|
||||
} else {
|
||||
logging.log("RedisCleaner: Nothing to clean");
|
||||
}
|
||||
});
|
||||
|
||||
should_clean_disk(function(err, clean) {
|
||||
if (err) {
|
||||
logging.error("Failed to run DiskCleaner");
|
||||
logging.error(err);
|
||||
} else if (clean) {
|
||||
logging.warn("DiskCleaner: Disk limit reached! Cleaning images now");
|
||||
|
||||
// hotfix for #139 | FIXME
|
||||
logging.warn("DiskCleaner: Flushing Redis to prevent ENOENT");
|
||||
redis.flushall();
|
||||
// end hotfix
|
||||
|
||||
var skinsdir = config.directories.skins;
|
||||
var capesdir = config.directories.capes;
|
||||
var facesdir = config.directories.faces;
|
||||
var helmsdir = config.directories.helms;
|
||||
var rendersdir = config.directories.renders;
|
||||
fs.readdir(skinsdir, function(readerr, files) {
|
||||
if (!readerr) {
|
||||
for (var i = 0, l = Math.min(files.length, config.cleaner.amount); i < l; i++) {
|
||||
var filename = files[i];
|
||||
if (filename[0] !== ".") {
|
||||
fs.unlink(path.join(facesdir, filename), nil);
|
||||
fs.unlink(path.join(helmsdir, filename), nil);
|
||||
fs.unlink(path.join(skinsdir, filename), nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
fs.readdir(rendersdir, function(readerr, files) {
|
||||
if (!readerr) {
|
||||
for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
|
||||
var filename = files[j];
|
||||
if (filename[0] !== ".") {
|
||||
fs.unlink(rendersdir + filename, nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
fs.readdir(capesdir, function(readerr, files) {
|
||||
if (!readerr) {
|
||||
for (var j = 0, l = Math.min(files.length, config.cleaner.amount); j < l; j++) {
|
||||
var filename = files[j];
|
||||
if (filename[0] !== ".") {
|
||||
fs.unlink(capesdir + filename, nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logging.log("DiskCleaner: Nothing to clean");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = exp;
|
||||
@ -7,8 +7,8 @@ var skins = require("./skins");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
|
||||
// 0098cb60fa8e427cb299793cbd302c9a
|
||||
var valid_user_id = /^[0-9a-fA-F]{32}$/; // uuid
|
||||
// 0098cb60-fa8e-427c-b299-793cbd302c9a
|
||||
var valid_user_id = /^[0-9a-f-A-F-]{32,36}$/; // uuid
|
||||
var hash_pattern = /[0-9a-f]+$/;
|
||||
|
||||
// gets the hash from the textures.minecraft.net +url+
|
||||
@ -122,22 +122,13 @@ var requests = {
|
||||
cape: {}
|
||||
};
|
||||
|
||||
var loginterval = setInterval(function(){
|
||||
var skinreqs = Object.keys(requests.skin).length;
|
||||
var capereqs = Object.keys(requests.cape).length;
|
||||
if (skinreqs || capereqs) {
|
||||
logging.log("Currently waiting for " + skinreqs + " skin requests and " + capereqs + " cape requests.");
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// add a request for +userId+ and +type+ to the queue
|
||||
function push_request(userId, type, callback) {
|
||||
function push_request(userId, type, fun) {
|
||||
// avoid special properties (e.g. 'constructor')
|
||||
var userId_safe = "!" + userId;
|
||||
if (!requests[type][userId_safe]) {
|
||||
requests[type][userId_safe] = [];
|
||||
}
|
||||
requests[type][userId_safe].push(callback);
|
||||
requests[type][userId_safe].push(fun);
|
||||
}
|
||||
|
||||
// calls back all queued requests that match userId and type
|
||||
@ -171,6 +162,7 @@ function store_images(rid, userId, cache_details, type, callback) {
|
||||
logging.debug(rid, "adding to request queue");
|
||||
push_request(userId, type, callback);
|
||||
} else {
|
||||
// add request to the queue
|
||||
push_request(userId, type, callback);
|
||||
|
||||
networking.get_profile(rid, userId, function(err, profile) {
|
||||
@ -184,7 +176,7 @@ function store_images(rid, userId, cache_details, type, callback) {
|
||||
resume(userId, "cape", cache_err, null, false);
|
||||
});
|
||||
} else {
|
||||
// an error occured, not caching. we can try again in 60 seconds
|
||||
// an error occured, not caching. we can try in 60 seconds
|
||||
resume(userId, type, err, null, false);
|
||||
}
|
||||
} else {
|
||||
@ -248,13 +240,10 @@ exp.get_image_hash = function(rid, userId, type, callback) {
|
||||
}
|
||||
store_images(rid, userId, cache_details, type, function(store_err, new_hash, slim) {
|
||||
if (store_err) {
|
||||
// an error occured, but we have a cached hash
|
||||
// we might have a cached hash although an error occured
|
||||
// (e.g. Mojang servers not reachable, using outdated hash)
|
||||
|
||||
// bump the TTL after hitting the rate limit
|
||||
var ratelimited = store_err.code === "RATELIMIT";
|
||||
cache.update_timestamp(rid, userId, !ratelimited, function(err2) {
|
||||
callback(err2 || store_err, 4, cache_details && cached_hash, slim);
|
||||
cache.update_timestamp(rid, userId, true, function(err2) {
|
||||
callback(err2 || store_err, -1, cache_details && cached_hash, slim);
|
||||
});
|
||||
} else {
|
||||
var status = cache_details && (cached_hash === new_hash) ? 3 : 2;
|
||||
@ -332,7 +321,7 @@ function get_type(overlay, body) {
|
||||
}
|
||||
|
||||
// handles creations of 3D renders
|
||||
// callback: error, status, skin hash, image buffer
|
||||
// callback: error, skin hash, image buffer
|
||||
exp.get_render = function(rid, userId, scale, overlay, body, callback) {
|
||||
exp.get_skin(rid, userId, function(err, skin_hash, status, img, slim) {
|
||||
if (!skin_hash) {
|
||||
@ -358,7 +347,7 @@ exp.get_render = function(rid, userId, scale, overlay, body, callback) {
|
||||
callback(null, 0, skin_hash, null);
|
||||
} else {
|
||||
fs.writeFile(renderpath, drawn_img, "binary", function(write_err) {
|
||||
callback(write_err, status, skin_hash, drawn_img);
|
||||
callback(write_err, 2, skin_hash, drawn_img);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -395,8 +384,4 @@ exp.get_cape = function(rid, userId, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
exp.stoplog = function() {
|
||||
clearInterval(loginterval);
|
||||
}
|
||||
|
||||
module.exports = exp;
|
||||
@ -23,20 +23,16 @@ function log(level, args, logger) {
|
||||
}
|
||||
}
|
||||
|
||||
// log with INFO level
|
||||
exp.log = function() {
|
||||
log(" INFO", arguments);
|
||||
};
|
||||
// log with WARN level
|
||||
exp.warn = function() {
|
||||
log(" WARN", arguments, console.warn);
|
||||
};
|
||||
// log with ERROR level
|
||||
exp.error = function() {
|
||||
log("ERROR", arguments, console.error);
|
||||
};
|
||||
// log with DEBUG level if debug logging is enabled
|
||||
if (config.server.debug_enabled) {
|
||||
if (config.server.debug_enabled || process.env.DEBUG === "true") {
|
||||
exp.debug = function() {
|
||||
log("DEBUG", arguments);
|
||||
};
|
||||
|
||||
@ -1,121 +1,80 @@
|
||||
var http_code = require("http").STATUS_CODES;
|
||||
var logging = require("./logging");
|
||||
var request = require("request");
|
||||
var config = require("../config");
|
||||
var skins = require("./skins");
|
||||
var http = require("http");
|
||||
require("./object-patch");
|
||||
|
||||
var session_url = config.endpoints.session_url;
|
||||
var textures_url = config.endpoints.textures_url;
|
||||
|
||||
// count requests made to session_url in the last 1000ms
|
||||
var session_requests = [];
|
||||
var session_url = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
||||
var textures_url = "http://textures.minecraft.net/texture/";
|
||||
|
||||
var exp = {};
|
||||
|
||||
// returns the amount of outgoing session requests made in the last 1000ms
|
||||
function req_count() {
|
||||
var index = session_requests.findIndex((i) => i >= Date.now() - 1000);
|
||||
if (index >= 0) {
|
||||
return session_requests.length - index;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// deletes all entries in session_requests, should be called every 1000ms
|
||||
exp.resetCounter = function() {
|
||||
var count = req_count();
|
||||
if (count) {
|
||||
var logfunc = count >= config.server.sessions_rate_limit ? logging.warn : logging.debug;
|
||||
logfunc('Clearing old session requests (count was ' + count + ')');
|
||||
session_requests.splice(0, session_requests.length - count);
|
||||
} else {
|
||||
session_requests = []
|
||||
}
|
||||
}
|
||||
|
||||
// performs a GET request to the +url+
|
||||
// +options+ object includes these options:
|
||||
// encoding (string), default is to return a buffer
|
||||
// callback: the body, response,
|
||||
// and error buffer. get_from helper method is available
|
||||
exp.get_from_options = function(rid, url, options, callback) {
|
||||
var is_session_req = config.server.sessions_rate_limit && url.startsWith(session_url);
|
||||
request.get({
|
||||
url: url,
|
||||
headers: {
|
||||
"User-Agent": "Crafatar (+https://crafatar.com)"
|
||||
},
|
||||
timeout: config.server.http_timeout,
|
||||
followRedirect: false,
|
||||
encoding: options.encoding || null,
|
||||
}, function(error, response, body) {
|
||||
// log url + code + description
|
||||
var code = response && response.statusCode;
|
||||
|
||||
// This is to prevent being blocked by CloudFront for exceeding the rate limit
|
||||
if (is_session_req && req_count() >= config.server.sessions_rate_limit) {
|
||||
var e = new Error("Skipped, rate limit exceeded");
|
||||
var logfunc = code && code < 405 ? logging.debug : logging.warn;
|
||||
logfunc(rid, url, code || error && error.code, http_code[code]);
|
||||
|
||||
// not necessarily used
|
||||
var e = new Error(code);
|
||||
e.name = "HTTP";
|
||||
e.code = "RATELIMIT";
|
||||
e.code = "HTTPERROR";
|
||||
|
||||
var response = new http.IncomingMessage();
|
||||
response.statusCode = 403;
|
||||
|
||||
callback(null, response, e);
|
||||
} else {
|
||||
is_session_req && session_requests.push(Date.now());
|
||||
request.get({
|
||||
url: url,
|
||||
headers: {
|
||||
"User-Agent": "Crafatar (+https://crafatar.com)"
|
||||
},
|
||||
timeout: config.server.http_timeout,
|
||||
followRedirect: false,
|
||||
encoding: options.encoding || null,
|
||||
}, function(error, response, body) {
|
||||
// log url + code + description
|
||||
var code = response && response.statusCode;
|
||||
|
||||
var logfunc = code && (code < 400 || code === 404) ? logging.debug : logging.warn;
|
||||
logfunc(rid, url, code || error && error.code, http.STATUS_CODES[code]);
|
||||
|
||||
// not necessarily used
|
||||
var e = new Error(code);
|
||||
e.name = "HTTP";
|
||||
e.code = "HTTPERROR";
|
||||
|
||||
switch (code) {
|
||||
case 200:
|
||||
case 301:
|
||||
case 302: // never seen, but mojang might use it in future
|
||||
case 307: // never seen, but mojang might use it in future
|
||||
case 308: // never seen, but mojang might use it in future
|
||||
// these are okay
|
||||
break;
|
||||
case 204: // no content, used like 404 by mojang. making sure it really has no content
|
||||
case 404:
|
||||
// can be cached as null
|
||||
body = null;
|
||||
break;
|
||||
case 403: // Blocked by CloudFront :(
|
||||
case 429: // this shouldn't usually happen, but occasionally does
|
||||
case 500:
|
||||
case 502: // CloudFront can't reach mojang origin
|
||||
case 503:
|
||||
case 504:
|
||||
// we don't want to cache this
|
||||
error = error || e;
|
||||
body = null;
|
||||
break;
|
||||
default:
|
||||
if (!error) {
|
||||
// Probably 500 or the likes
|
||||
logging.error(rid, "Unexpected response:", code, body);
|
||||
}
|
||||
error = error || e;
|
||||
body = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (body && !body.length) {
|
||||
// empty response
|
||||
switch (code) {
|
||||
case 200:
|
||||
case 301:
|
||||
case 302: // never seen, but mojang might use it in future
|
||||
case 307: // never seen, but mojang might use it in future
|
||||
case 308: // never seen, but mojang might use it in future
|
||||
// these are okay
|
||||
break;
|
||||
case 204: // no content, used like 404 by mojang. making sure it really has no content
|
||||
case 404:
|
||||
// can be cached as null
|
||||
body = null;
|
||||
}
|
||||
break;
|
||||
case 429: // this shouldn't usually happen, but occasionally does
|
||||
case 500:
|
||||
case 502: // CloudFront can't reach mojang origin
|
||||
case 503:
|
||||
case 504:
|
||||
// we don't want to cache this
|
||||
error = error || e;
|
||||
body = null;
|
||||
break;
|
||||
default:
|
||||
if (!error) {
|
||||
// Probably 500 or the likes
|
||||
logging.error(rid, "Unexpected response:", code, body);
|
||||
}
|
||||
error = error || e;
|
||||
body = null;
|
||||
break;
|
||||
}
|
||||
|
||||
callback(body, response, error);
|
||||
});
|
||||
}
|
||||
if (body && !body.length) {
|
||||
// empty response
|
||||
body = null;
|
||||
}
|
||||
|
||||
callback(body, response, error);
|
||||
});
|
||||
};
|
||||
|
||||
// helper method for get_from_options, no options required
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 428 B |
@ -1,64 +1,34 @@
|
||||
var valid_user_id = /^[0-9a-f-A-F-]{32,36}$/; // uuid
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
var quotes = [
|
||||
["Crafatar is the best at what it does.", "Shotbow Network", "https://twitter.com/ShotbowNetwork/status/565201303555829762"],
|
||||
["Crafatar seems to stand out from others", "Dabsunter", "https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar"],
|
||||
["I can’t tell you how much Crafatar helped me along the way! You guys do some amazing work.", "Luke Chatton", "https://github.com/lukechatton"],
|
||||
["It's just awesome! Keep up the good work", "Dannyps", "https://forums.spongepowered.org/t/title-cant-be-empty/4964/22"],
|
||||
["It's one of the few services that actually does HTTP header caching correctly", "confuser", "https://github.com/BanManagement/BanManager-WebUI/issues/16#issuecomment-73230674"],
|
||||
["It's so beautiful. <3", "FerusGrim", "https://twitter.com/FerusGrim/status/642824817683656704"],
|
||||
["Love it! It's great!", "Reddit User", "https://reddit.com/comments/2nth0j/-/cmh5771"],
|
||||
["Such a useful service!", "Tim Z, NameMC", "https://twitter.com/CoderTimZ/status/602682146793349120"],
|
||||
["Thanks for providing us with such a reliable service :)", "BeanBlockz", "https://twitter.com/BeanBlockz/status/743927789422845952"],
|
||||
["This is excellent for my website! Good work.", "cyanide43", "https://reddit.com/comments/2nth0j/-/cmgpq85"],
|
||||
["This is really cool!", "AlexWebber", "https://forums.spongepowered.org/t/crafatar-a-new-minecraft-avatar-service/4964/19"],
|
||||
["This really is looking amazing. Absolutely love it!", "Enter_", "https://forums.spongepowered.org/t/crafatar-a-new-minecraft-avatar-service/4964/21"],
|
||||
["We couldn't believe how flawless your API is, Good job!", "SenceServers", "https://twitter.com/SenceServers/status/697132506626265089"],
|
||||
["WOW, Crafatar is FAST", "Rileriscool", "https://twitter.com/rileriscool/status/562057234986065921"],
|
||||
["You deserve way more popularity", "McSlushie", "https://github.com/crafatar/crafatar/wiki/Credit/a8f37373531b1d2c2cb3557ba809542a2ed81626"],
|
||||
["You do excellent work on Crafatar and are awesome! A very polished, concise & clean project.", "DrCorporate", "https://reddit.com/comments/2r1ns6/-/cnbq5f1"]
|
||||
];
|
||||
// shuffle quotes
|
||||
for (i = quotes.length -1; i > 0; i--) {
|
||||
var a = Math.floor(Math.random() * i);
|
||||
var b = quotes[i];
|
||||
quotes[i] = quotes[a];
|
||||
quotes[a] = b;
|
||||
}
|
||||
xhr.onload = function() {
|
||||
var response = JSON.parse(xhr.responseText);
|
||||
var status = {};
|
||||
response.map(function(elem) {
|
||||
var key = Object.keys(elem)[0];
|
||||
status[key] = elem[key];
|
||||
});
|
||||
|
||||
var current_quote = 0;
|
||||
|
||||
function changeQuote() {
|
||||
var elem = document.querySelector("#quote");
|
||||
var quote = quotes[current_quote];
|
||||
elem.innerHTML = "<b>“" + quote[0] + "”</b><br>― <i>" + quote[1] + "</i>";
|
||||
elem.href = quote[2];
|
||||
current_quote = (current_quote + 1) % quotes.length;
|
||||
}
|
||||
|
||||
fetch('https://mc-heads.net/json/mc_status').then(r => r.json()).then(data => {
|
||||
var textures_err = data.report.skins.status !== "up";
|
||||
var session_err = data.report.session.status !== "up";
|
||||
var textures_err = status["textures.minecraft.net"] !== "green";
|
||||
var session_err = status["sessionserver.mojang.com"] !== "green";
|
||||
|
||||
if (textures_err || session_err) {
|
||||
var warn = document.createElement("div");
|
||||
warn.setAttribute("class", "alert alert-warning");
|
||||
warn.setAttribute("role", "alert");
|
||||
warn.innerHTML = "<h5>Mojang issues</h5> Mojang's servers are having trouble <i>right now</i>, this may affect requests at Crafatar. <small><a href=\"https://mc-heads.net/mcstatus\" target=\"_blank\">check status</a>";
|
||||
warn.innerHTML = "<h5>Mojang issues</h5> Mojang's servers are having trouble <i>right now</i>, this may affect requests at Crafatar. <small><a href=\"https://help.mojang.com\" target=\"_blank\">check status</a>";
|
||||
document.querySelector("#alerts").appendChild(warn);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
var avatars = document.querySelector("#avatar-wrapper");
|
||||
// shuffle avatars
|
||||
for (var i = 0; i < avatars.children.length; i++) {
|
||||
// shake 'em on down!
|
||||
// https://stackoverflow.com/a/11972692/2517068
|
||||
avatars.appendChild(avatars.children[Math.random() * i | 0]);
|
||||
}
|
||||
|
||||
setInterval(changeQuote, 5000);
|
||||
changeQuote();
|
||||
|
||||
var tryit = document.querySelector("#tryit");
|
||||
var tryname = document.querySelector("#tryname");
|
||||
var images = document.querySelectorAll(".tryit");
|
||||
@ -74,4 +44,7 @@ document.addEventListener("DOMContentLoaded", function(event) {
|
||||
images[j].src = images[j].dataset.src.replace("$", value);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.open("GET", "https://status.mojang.com/check", true);
|
||||
xhr.send();
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 691 B |
@ -35,6 +35,7 @@ a.forkme:hover {
|
||||
|
||||
a.sponsor {
|
||||
position: fixed;
|
||||
z-index: 1041;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
right: 0px;
|
||||
@ -42,11 +43,6 @@ a.sponsor {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.sponsor img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a.sponsor-item {
|
||||
color: #aa7100 !important;
|
||||
font-weight: initial;
|
||||
@ -58,22 +54,6 @@ a.sponsor-item {
|
||||
background: #fff8ec !important;
|
||||
}
|
||||
|
||||
#quote-wrapper {
|
||||
line-height: 9.5em;
|
||||
}
|
||||
|
||||
#quote {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: initial;
|
||||
background: #d4e7ff;
|
||||
border-color: #94cbfc;
|
||||
}
|
||||
|
||||
#quote:hover {
|
||||
background: #dcedff;
|
||||
}
|
||||
|
||||
.alert {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
@ -3,18 +3,17 @@ var config = require("../config");
|
||||
var crc = require("crc").crc32;
|
||||
|
||||
var human_status = {
|
||||
"-2": "user error", // e.g. invalid size
|
||||
"-1": "server error", // e.g. mojang/network issues
|
||||
0: "none", // cached as null (user has no skin)
|
||||
1: "cached", // found on disk
|
||||
2: "downloaded", // profile downloaded, skin downloaded from mojang servers
|
||||
3: "checked", // profile re-downloaded (was too old), has no skin or skin cached
|
||||
4: "server error;cached" // tried to check but ran into server error, using cached version
|
||||
"-2": "user error", // e.g. invalid size
|
||||
"-1": "server error", // e.g. mojang/network issues
|
||||
0: "none", // cached as null (user has no skin)
|
||||
1: "cached", // found on disk
|
||||
2: "downloaded", // profile downloaded, skin downloaded from mojang servers
|
||||
3: "checked", // profile re-downloaded (was too old), has no skin or skin cached
|
||||
};
|
||||
|
||||
|
||||
// print these, but without stacktrace
|
||||
var silent_errors = ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ECONNREFUSED", "HTTPERROR", "RATELIMIT"];
|
||||
var silent_errors = ["ETIMEDOUT", "ESOCKETTIMEDOUT", "ECONNRESET", "EHOSTUNREACH", "ECONNREFUSED", "HTTPERROR"];
|
||||
|
||||
// handles HTTP responses
|
||||
// +request+ a http.IncomingMessage
|
||||
@ -31,13 +30,16 @@ module.exports = function(request, response, result) {
|
||||
// These headers are the same for every response
|
||||
var headers = {
|
||||
"Content-Type": result.body && result.type || "text/plain",
|
||||
"Content-Length": Buffer.from(result.body || "").length,
|
||||
"Cache-Control": "max-age=" + config.caching.browser,
|
||||
"Response-Time": Date.now() - request.start,
|
||||
"X-Request-ID": request.id,
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
};
|
||||
|
||||
response.on("close", function() {
|
||||
logging.warn(request.id, "Connection closed");
|
||||
});
|
||||
|
||||
response.on("finish", function() {
|
||||
logging.log(request.id, request.method, request.url.href, response.statusCode, headers["Response-Time"] + "ms", "(" + (human_status[result.status] || "-") + ")");
|
||||
});
|
||||
@ -85,30 +87,13 @@ module.exports = function(request, response, result) {
|
||||
if (result.status === -2) {
|
||||
response.writeHead(result.code || 422, headers);
|
||||
} else if (result.status === -1) {
|
||||
// server errors shouldn't be cached
|
||||
headers["Cache-Control"] = "no-cache, max-age=0";
|
||||
if (result.body && result.hash && !result.hash.startsWith("mhf_")) {
|
||||
headers["Warning"] = '110 Crafatar "Response is Stale"'
|
||||
headers["Etag"] = etag;
|
||||
result.code = result.code || 200;
|
||||
}
|
||||
if (result.err && result.err.code === "ENOENT") {
|
||||
result.code = result.code || 500;
|
||||
}
|
||||
if (!result.code) {
|
||||
// Don't use 502 on Cloudflare
|
||||
// As they will show their own error page instead
|
||||
// https://support.cloudflare.com/hc/en-us/articles/200172706
|
||||
result.code = config.caching.cloudflare ? 500 : 502;
|
||||
}
|
||||
response.writeHead(result.code, headers);
|
||||
// 500 responses shouldn't be cached
|
||||
headers["Cache-Control"] = "private, max-age=0, no-cache";
|
||||
response.writeHead(result.code || 500, headers);
|
||||
} else {
|
||||
if (result.body) {
|
||||
if (result.status === 4) {
|
||||
headers["Warning"] = '111 Crafatar "Revalidation Failed"'
|
||||
}
|
||||
headers["Etag"] = etag;
|
||||
response.writeHead(200, headers);
|
||||
headers.Etag = etag;
|
||||
response.writeHead(result.status === 2 ? 201 : 200, headers);
|
||||
} else {
|
||||
response.writeHead(404, headers);
|
||||
}
|
||||
|
||||
@ -14,10 +14,12 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
|
||||
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
||||
if (helpers.id_valid(def)) {
|
||||
// clean up the old URL to match new image
|
||||
req.url.searchParams.delete('default');
|
||||
req.url.path_list[1] = def;
|
||||
req.url.pathname = req.url.path_list.join('/');
|
||||
var newUrl = req.url.toString();
|
||||
var parsed = req.url;
|
||||
delete parsed.query.default;
|
||||
delete parsed.search;
|
||||
parsed.path_list[1] = def;
|
||||
parsed.pathname = "/" + parsed.path_list.join("/");
|
||||
var newUrl = url.format(parsed);
|
||||
callback({
|
||||
status: img_status,
|
||||
redirect: newUrl,
|
||||
@ -51,9 +53,9 @@ function handle_default(img_status, userId, size, def, req, err, callback) {
|
||||
// GET avatar request
|
||||
module.exports = function(req, callback) {
|
||||
var userId = (req.url.path_list[1] || "").split(".")[0];
|
||||
var size = parseInt(req.url.searchParams.get("size")) || config.avatars.default_size;
|
||||
var def = req.url.searchParams.get("default");
|
||||
var overlay = req.url.searchParams.has("overlay") || req.url.searchParams.has("helm");
|
||||
var size = parseInt(req.url.query.size) || config.avatars.default_size;
|
||||
var def = req.url.query.default;
|
||||
var overlay = Object.prototype.hasOwnProperty.call(req.url.query, "overlay") || Object.prototype.hasOwnProperty.call(req.url.query, "helm");
|
||||
|
||||
// check for extra paths
|
||||
if (req.url.path_list.length > 2) {
|
||||
@ -65,9 +67,6 @@ module.exports = function(req, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
|
||||
// Prevent app from crashing/freezing
|
||||
if (size < config.avatars.min_size || size > config.avatars.max_size) {
|
||||
// "Unprocessable Entity", valid request, but semantically erroneous:
|
||||
@ -85,6 +84,9 @@ module.exports = function(req, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
|
||||
try {
|
||||
helpers.get_avatar(req.id, userId, overlay, size, function(err, status, image, hash) {
|
||||
if (err) {
|
||||
|
||||
@ -4,7 +4,7 @@ var cache = require("../cache");
|
||||
// GET cape request
|
||||
module.exports = function(req, callback) {
|
||||
var userId = (req.url.path_list[1] || "").split(".")[0];
|
||||
var def = req.url.searchParams.get('default');
|
||||
var def = req.url.query.default;
|
||||
var rid = req.id;
|
||||
|
||||
// check for extra paths
|
||||
@ -17,8 +17,6 @@ module.exports = function(req, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
if (!helpers.id_valid(userId)) {
|
||||
callback({
|
||||
status: -2,
|
||||
@ -27,6 +25,9 @@ module.exports = function(req, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
|
||||
try {
|
||||
helpers.get_cape(rid, userId, function(err, hash, status, image) {
|
||||
if (err) {
|
||||
|
||||
@ -7,7 +7,6 @@ var ejs = require("ejs");
|
||||
var str;
|
||||
var index;
|
||||
|
||||
// pre-compile the index page
|
||||
function compile() {
|
||||
logging.log("Compiling index page");
|
||||
str = read(path.join(__dirname, "..", "views", "index.html.ejs"), "utf-8");
|
||||
|
||||
@ -17,10 +17,12 @@ function handle_default(rid, scale, overlay, body, img_status, userId, size, def
|
||||
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
||||
if (helpers.id_valid(def)) {
|
||||
// clean up the old URL to match new image
|
||||
req.url.searchParams.delete('default');
|
||||
req.url.path_list[2] = def;
|
||||
req.url.pathname = req.url.path_list.join('/');
|
||||
var newUrl = req.url.toString();
|
||||
var parsed = req.url;
|
||||
delete parsed.query.default;
|
||||
delete parsed.search;
|
||||
parsed.path_list[2] = def;
|
||||
parsed.pathname = "/" + parsed.path_list.join("/");
|
||||
var newUrl = url.format(parsed);
|
||||
callback({
|
||||
status: img_status,
|
||||
redirect: newUrl,
|
||||
@ -60,9 +62,9 @@ module.exports = function(req, callback) {
|
||||
var rid = req.id;
|
||||
var body = raw_type === "body";
|
||||
var userId = (req.url.path_list[2] || "").split(".")[0];
|
||||
var def = req.url.searchParams.get("default");
|
||||
var scale = parseInt(req.url.searchParams.get("scale")) || config.renders.default_scale;
|
||||
var overlay = req.url.searchParams.has("overlay") || req.url.searchParams.has("helm");
|
||||
var def = req.url.query.default;
|
||||
var scale = parseInt(req.url.query.scale) || config.renders.default_scale;
|
||||
var overlay = Object.prototype.hasOwnProperty.call(req.url.query, "overlay") || Object.prototype.hasOwnProperty.call(req.url.query, "helm");
|
||||
|
||||
// check for extra paths
|
||||
if (req.url.path_list.length > 3) {
|
||||
@ -83,9 +85,6 @@ module.exports = function(req, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
|
||||
if (scale < config.renders.min_scale || scale > config.renders.max_scale) {
|
||||
callback({
|
||||
status: -2,
|
||||
@ -100,6 +99,9 @@ module.exports = function(req, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
|
||||
try {
|
||||
helpers.get_render(rid, userId, scale, overlay, body, function(err, status, hash, image) {
|
||||
if (err) {
|
||||
|
||||
@ -14,10 +14,12 @@ function handle_default(img_status, userId, def, req, err, callback) {
|
||||
if (defname !== "steve" && defname !== "mhf_steve" && defname !== "alex" && defname !== "mhf_alex") {
|
||||
if (helpers.id_valid(def)) {
|
||||
// clean up the old URL to match new image
|
||||
req.url.searchParams.delete('default');
|
||||
req.url.path_list[1] = def;
|
||||
req.url.pathname = req.url.path_list.join('/');
|
||||
var newUrl = req.url.toString();
|
||||
var parsed = req.url;
|
||||
delete parsed.query.default;
|
||||
delete parsed.search;
|
||||
parsed.path_list[1] = def;
|
||||
parsed.pathname = "/" + parsed.path_list.join("/");
|
||||
var newUrl = url.format(parsed);
|
||||
callback({
|
||||
status: img_status,
|
||||
redirect: newUrl,
|
||||
@ -60,7 +62,7 @@ function handle_default(img_status, userId, def, req, err, callback) {
|
||||
// GET skin request
|
||||
module.exports = function(req, callback) {
|
||||
var userId = (req.url.path_list[1] || "").split(".")[0];
|
||||
var def = req.url.searchParams.get("default");
|
||||
var def = req.url.query.default;
|
||||
var rid = req.id;
|
||||
|
||||
// check for extra paths
|
||||
@ -73,8 +75,6 @@ module.exports = function(req, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
if (!helpers.id_valid(userId)) {
|
||||
callback({
|
||||
status: -2,
|
||||
@ -83,6 +83,9 @@ module.exports = function(req, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// strip dashes
|
||||
userId = userId.replace(/-/g, "");
|
||||
|
||||
try {
|
||||
helpers.get_skin(rid, userId, function(err, hash, status, image, slim) {
|
||||
if (err) {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
var querystring = require("querystring");
|
||||
var response = require("./response");
|
||||
var helpers = require("./helpers.js");
|
||||
var toobusy = require("toobusy-js");
|
||||
var logging = require("./logging");
|
||||
var config = require("../config");
|
||||
@ -22,33 +21,24 @@ var routes = {
|
||||
|
||||
// serves assets from lib/public
|
||||
function asset_request(req, callback) {
|
||||
const filename = path.join(__dirname, "public", ...req.url.path_list);
|
||||
const relative = path.relative(path.join(__dirname, "public"), filename);
|
||||
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
|
||||
fs.access(filename, function(fs_err) {
|
||||
if (!fs_err) {
|
||||
fs.readFile(filename, function(err, data) {
|
||||
callback({
|
||||
body: data,
|
||||
type: mime.getType(filename),
|
||||
err: err,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
var filename = path.join(__dirname, "public", req.url.path_list.join("/"));
|
||||
fs.access(filename, function(fs_err) {
|
||||
if (!fs_err) {
|
||||
fs.readFile(filename, function(err, data) {
|
||||
callback({
|
||||
body: "Not found",
|
||||
status: -2,
|
||||
code: 404,
|
||||
body: data,
|
||||
type: mime.getType(filename),
|
||||
err: err,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback({
|
||||
body: "Forbidden",
|
||||
status: -2,
|
||||
code: 403,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
callback({
|
||||
body: "Not found",
|
||||
status: -2,
|
||||
code: 404,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// generates a 12 character random string
|
||||
@ -56,18 +46,26 @@ function request_id() {
|
||||
return Math.random().toString(36).substring(2, 14);
|
||||
}
|
||||
|
||||
// splits decoded URL path into an Array
|
||||
// splits a URL path into an Array
|
||||
// the path is resolved and decoded
|
||||
function path_list(pathname) {
|
||||
// remove double and trailing slashes
|
||||
pathname = pathname.replace(/\/\/+/g, "/").replace(/(.)\/$/, "$1");
|
||||
var list = pathname.split("/");
|
||||
list.shift();
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
// URL decode
|
||||
list[i] = querystring.unescape(list[i]);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// handles the +req+ by routing to the request to the appropriate module
|
||||
function requestHandler(req, res) {
|
||||
req.url = new URL(decodeURI(req.url), 'http://' + req.headers.host);
|
||||
req.url.pathname = path.resolve('/', req.url.pathname);
|
||||
req.url = url.parse(req.url, true);
|
||||
req.url.query = req.url.query || {};
|
||||
req.url.path_list = path_list(req.url.pathname);
|
||||
|
||||
req.id = request_id();
|
||||
req.start = Date.now();
|
||||
|
||||
@ -137,10 +135,9 @@ function requestHandler(req, res) {
|
||||
|
||||
var exp = {};
|
||||
|
||||
// Start the server
|
||||
exp.boot = function(callback) {
|
||||
var port = config.server.port;
|
||||
var bind_ip = config.server.bind;
|
||||
var port = process.env.PORT || 3000;
|
||||
var bind_ip = process.env.BIND || "0.0.0.0";
|
||||
server = http.createServer(requestHandler).listen(port, bind_ip, function() {
|
||||
logging.log("Server running on http://" + bind_ip + ":" + port + "/");
|
||||
if (callback) {
|
||||
@ -152,7 +149,7 @@ exp.boot = function(callback) {
|
||||
// wait for established connections to finish (30s max),
|
||||
// then exit
|
||||
process.on("SIGTERM", function() {
|
||||
logging.warn("Got SIGTERM, no longer accepting new connections!");
|
||||
logging.warn("Got SIGTERM, no longer accepting connections!");
|
||||
|
||||
setTimeout(function() {
|
||||
logging.error("Dropping connections after 30s. Force quit.");
|
||||
@ -166,9 +163,7 @@ exp.boot = function(callback) {
|
||||
});
|
||||
};
|
||||
|
||||
// Close the server
|
||||
exp.close = function(callback) {
|
||||
helpers.stoplog();
|
||||
server.close(callback);
|
||||
};
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<title>Crafatar – A blazing fast API for Minecraft faces!</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
<link rel="icon" sizes="16x16" type="image/png" href="/favicon.png">
|
||||
<link rel="stylesheet" href="/stylesheets/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/stylesheets/style.css">
|
||||
<meta name="description" content="A blazing fast API for Minecraft faces with support for avatars, skins, and 3D renders!">
|
||||
@ -61,7 +61,11 @@
|
||||
<div class="col-md-9">
|
||||
<section id="documentation">
|
||||
<div id="alerts">
|
||||
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<h5>Crafatar is rate limited by Mojang!</h5>
|
||||
Please consider installing your own instance of Crafatar!<br>
|
||||
For more information visit <a href="https://github.com/crafatar/crafatar/issues/260">our issue tracker</a>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section id="try">
|
||||
@ -76,7 +80,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p>You can use <a rel="nofollow" target="_blank" href="https://minecraftuuid.com">minecraftuuid.com</a> to find the UUID of a username.</p>
|
||||
</section>
|
||||
|
||||
<section id="avatars">
|
||||
@ -211,12 +214,10 @@
|
||||
<h3><a href="#meta-caching">About Caching</a></h3>
|
||||
<p>
|
||||
Crafatar checks for skin updates every <%= config.caching.local / 60 %> minutes.<br>
|
||||
Images are also cached in your browser for <%= config.caching.browser / 60 %> minutes unless you clear your browser cache.
|
||||
<% if (config.caching.cloudflare) { %>
|
||||
<br>In addition, <span title="A CDN and caching proxy">Cloudflare</span> may cache images as long as your browser would.
|
||||
<% } %>
|
||||
Images are cached in your browser for <%= config.caching.browser / 60 %> minutes until a new request to Crafatar is made.<br>
|
||||
In addition, <span title="A CDN and caching proxy">CloudFlare</span> caches up to 2 hours on a per-url basis.
|
||||
</p>
|
||||
<p>After changing your Minecraft skin, you can try clearing your browser cache to see the change faster.</p>
|
||||
<p>When you changed your skin you can try clearing your browser cache to see the change faster.</p>
|
||||
</section>
|
||||
|
||||
<section id="meta-cors">
|
||||
@ -227,30 +228,11 @@
|
||||
<section id="meta-http-headers">
|
||||
<h3><a href="#meta-http-headers">HTTP Headers</a></h3>
|
||||
<p>
|
||||
Crafatar always replies with a <code>200 OK</code> status code when the requested user's skin/cape was found. This is also used in some rare cases when Mojang servers are having issues and the image couldn't be checked for changes, but Crafatar still had a cached version.
|
||||
<% if (config.caching.cloudflare) { %>
|
||||
<code>500 Server Error</code> is used when no skin/cape was found because of Mojang or Crafatar server issues.
|
||||
<% } else { %>
|
||||
<code>502 Bad Gateway</code> and <code>500 Server Error</code> are used when no skin/cape was found because of Mojang or Crafatar server issues.
|
||||
<% } %>
|
||||
</p>
|
||||
<p>
|
||||
Note that requests are usually answered with an image (with Steve/Alex skin), even if an error occured!
|
||||
</p>
|
||||
<p>
|
||||
Responses come with some HTTP headers that are useful for debugging.
|
||||
<% if (config.caching.cloudflare) { %>
|
||||
<br>Please note that these headers may be cached by <span title="A CDN and caching proxy">Cloudflare</span>.
|
||||
<% } %>
|
||||
Responses come with some custom HTTP headers, useful for debugging.<br>
|
||||
Please note that these headers may be cached by <span title="A CDN and caching proxy">CloudFlare</span>.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<b>Warning</b>: When using a cached image after an error occured. One of:
|
||||
<ul>
|
||||
<li><code>110 Crafatar "Response is Stale"</code></li>
|
||||
<li><code>111 Crafatar "Revalidation Failed"</code></li>
|
||||
</ul>
|
||||
<li>
|
||||
<b>X-Storage-Type</b>: Details about how the requested image was stored on the server
|
||||
<ul>
|
||||
@ -259,56 +241,52 @@
|
||||
<li><b>checked</b>: Requested skin details, skin cached. (1 external request)<br>
|
||||
This happens either when the user removed their skin or when it didn't change.</li>
|
||||
<li><b>downloaded</b>: Requested skin details, skin downloaded. (2 external requests)</li>
|
||||
<li><b>server error</b>: This can happen, for example, when Mojang's servers are down.</li>
|
||||
<li><b>server error;cached</b>: Same as server error, but a cached skin was available.</li>
|
||||
<li><b>server error</b>: This can happen, for example, when Mojang's servers are down.<br>
|
||||
If possible, a cached image is served instead.</li>
|
||||
<li><b>user error</b>: You have done something wrong, such as requesting a malformed uuid.<br>
|
||||
Check the response body for details.</li>
|
||||
</ul>
|
||||
<li>
|
||||
<b>X-Request-ID</b>: The internal ID assigned to this request.<br>
|
||||
If you think something is wrong with your request, please contact us and provide this ID.
|
||||
<li>
|
||||
<b>Response-Time</b>: How long it took Crafatar to answer the request, in ms.
|
||||
If you think something is wrong with your request, please <a href="#contact">contact us</a> and provide this ID.
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section id="contact">
|
||||
<h2><a href="#contact">Contact</a></h2>
|
||||
<ul>
|
||||
<li>Follow us on twitter <a href="https://twitter.com/crafatar" target="_blank">@crafatar</a></li>
|
||||
<li>Open an issue <a href="https://github.com/crafatar/crafatar/issues" target="_blank">on GitHub</a></li>
|
||||
<li><a href="https://webchat.esper.net/?channels=crafatar" target="_blank">Join us</a> in <a href="irc://irc.esper.net/crafatar">#crafatar</a> on irc.esper.net</li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<h4>Popular Crafatar users</h4>
|
||||
<div class="list-group">
|
||||
<a rel="nofollow" href="http://technicpack.net" target="_blank" class="list-group-item">Technic</a>
|
||||
<a rel="nofollow" href="https://hypixel.net" target="_blank" class="list-group-item">Hypixel</a>
|
||||
<a rel="nofollow" href="https://mineplex.com" target="_blank" class="list-group-item">Mineplex</a>
|
||||
<a rel="nofollow" href="https://hivemc.com" target="_blank" class="list-group-item">The Hive</a>
|
||||
<a rel="nofollow" href="https://www.technicpack.net" target="_blank" class="list-group-item">Technic Pack</a>
|
||||
<a rel="nofollow" href="https://namemc.com" target="_blank" class="list-group-item">NameMC</a>
|
||||
<a rel="nofollow" href="https://www.minecraft-index.com" target="_blank" class="list-group-item">MC Index</a>
|
||||
<a rel="nofollow" href="https://www.minehq.com/hcteams/leaderboards" target="_blank" class="list-group-item">MineHQ</a>
|
||||
<a rel="nofollow" href="https://shotbow.net" target="_blank" class="list-group-item">Shotbow</a>
|
||||
<a rel="nofollow" href="https://mcuuid.net/" target="_blank" class="list-group-item">MCUUID</a>
|
||||
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F" target="_blank" class="list-group-item">and many more…</a>
|
||||
</div>
|
||||
<hr>
|
||||
<h4>Quotes</h4>
|
||||
<div id="quote-wrapper" class="list-group">
|
||||
<a id="quote" rel="nofollow" target="_blank" class="list-group-item"></a>
|
||||
</div>
|
||||
<p>See <a rel="nofollow" href="https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar" target="_blank">all quotes</a>.</p>
|
||||
<p>See also: <a rel="nofollow" href="https://github.com/crafatar/crafatar/wiki/What-people-say-about-Crafatar" target="_blank">what users say</a> about Crafatar</p>
|
||||
<hr>
|
||||
<h4>Crafatar Tools & Plugins</h4>
|
||||
<div class="list-group">
|
||||
<a rel="nofollow" href="https://github.com/DiscordSRV/DiscordSRV#readme" target="_blank" class="list-group-item">DiscordSRV</a>
|
||||
<a rel="nofollow" href="https://github.com/the-obsidian/discourse-minecraft-avatar" target="_blank" class="list-group-item">Discourse Minecraft Avatar</a>
|
||||
<a rel="nofollow" href="https://xenforo.com/community/resources/associationmc.3232/" target="_blank" class="list-group-item">AssociationMc <i>(XenForo)</i></a>
|
||||
<a rel="nofollow" href="https://open.vanillaforums.com/addon/crafatar-plugin" target="_blank" class="list-group-item">Crafatar Avatars <i>(Vanilla)</i></a>
|
||||
<a rel="nofollow" href="https://www.spigotmc.org/resources/picture-login.4514/" target="_blank" class="list-group-item">Picture Login <i>(Spigot/Bukkit)</i></a>
|
||||
<a rel="nofollow" href="https://github.com/yeahwhat-mc/discourse-yeahwhat" target="_blank" class="list-group-item">Minecraft Heads <i>(Discourse)</i></a>
|
||||
<a rel="nofollow" href="http://vanillaforums.org/addon/crafatar-plugin" target="_blank" class="list-group-item">Crafatar Avatars <i>(Vanilla)</i></a>
|
||||
<a rel="nofollow" href="https://www.spigotmc.org/resources/picture-login.4514/" target="_blank" class="list-group-item">Picture Login <i>(Bukkit)</i></a>
|
||||
<a rel="nofollow" href="https://github.com/sk89q/Plumeria" target="_blank" class="list-group-item">Plumeria <i>(Discord)</i></a>
|
||||
<a href="https://github.com/crafatar/crafatar/wiki/Who-uses-crafatar%3F#other-services-using-crafatar" target="_blank" class="list-group-item">and many more…</a>
|
||||
</div>
|
||||
<hr>
|
||||
<h4>Contact</h4>
|
||||
<div class="list-group">
|
||||
<a class="list-group-item" href="https://twitter.com/crafatar" target="_blank">@crafatar on Twitter</a>
|
||||
<a class="list-group-item" href="https://github.com/crafatar/crafatar/issues" target="_blank">Issue tracker</a>
|
||||
</div>
|
||||
<% if (config.sponsor.sidebar) { %>
|
||||
<hr>
|
||||
<%- config.sponsor.sidebar %>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
2430
package-lock.json
generated
2430
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
42
package.json
42
package.json
@ -1,25 +1,53 @@
|
||||
{
|
||||
"name": "crafatar",
|
||||
"version": "2.1.5",
|
||||
"version": "2.1.0",
|
||||
"private": true,
|
||||
"description": "A blazing fast API for Minecraft faces!",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "jomo",
|
||||
"url": "https://github.com/jomo"
|
||||
},
|
||||
{
|
||||
"name": "Jake",
|
||||
"url": "https://github.com/Jake0oo0"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/crafatar/crafatar"
|
||||
},
|
||||
"issues": {
|
||||
"url": "https://github.com/crafatar/crafatar/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"minecraft",
|
||||
"avatar"
|
||||
],
|
||||
"scripts": {
|
||||
"postinstall": "cp 'config.example.js' 'config.js'",
|
||||
"start": "node www.js",
|
||||
"test": "mocha"
|
||||
"test": "mocha",
|
||||
"test-travis": "istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage"
|
||||
},
|
||||
"engines": {
|
||||
"node": "12.16.1"
|
||||
"node": "10.19.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@randy.tarampi/lwip": "^1.3.1",
|
||||
"canvas": "^2.6.1",
|
||||
"crc": "^3.8.0",
|
||||
"ejs": "^3.1.5",
|
||||
"mime": "^2.4.6",
|
||||
"ejs": "^3.0.1",
|
||||
"@randy.tarampi/lwip": "^1.1.0",
|
||||
"mime": "^2.4.4",
|
||||
"node-df": "crafatar/node-df",
|
||||
"redis": "^3.0.2",
|
||||
"request": "^2.88.2",
|
||||
"toobusy-js": "^0.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^7.2.0"
|
||||
"coveralls": "^3.0.11",
|
||||
"istanbul": "^0.4.5",
|
||||
"mocha": "^7.1.1",
|
||||
"mocha-lcov-reporter": "^1.3.0"
|
||||
}
|
||||
}
|
||||
61
test/bulk.sh
61
test/bulk.sh
@ -1,55 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
hostname="crafatar.com"
|
||||
async="true"
|
||||
random="false"
|
||||
interval="0.1"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [-s | -r | -i <interval> | -h <hostname>]... <host uri>" >&2
|
||||
if [ "$1" = "-s" ]; then
|
||||
async=""
|
||||
shift
|
||||
elif [ "$1" = "-i" ]; then
|
||||
interval="$2"
|
||||
shift 2
|
||||
fi
|
||||
host="$1"
|
||||
shift
|
||||
if [ -z "$host" ] || [ ! -z "$@" ]; then
|
||||
echo "Usage: $0 [-s | -i <interval>] <host uri>"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
get_ids() {
|
||||
local shuf
|
||||
if [ "$random" = "true" ]; then
|
||||
while true; do uuid -v 4; done
|
||||
else
|
||||
# `brew install coreutils` on OS X for gshuf
|
||||
shuf=$(command -v shuf gshuf)
|
||||
# randomize ids
|
||||
$shuf < uuids.txt
|
||||
fi
|
||||
}
|
||||
# insert newline after uuids
|
||||
ids="$(cat 'uuids.txt')"
|
||||
# `brew install coreutils` on OS X
|
||||
ids="$(shuf <<< "$ids" 2>/dev/null || gshuf <<< "$ids")"
|
||||
|
||||
bulk() {
|
||||
trap return INT # return from this function on Ctrl+C
|
||||
get_ids | while read id; do
|
||||
if [ "$async" = "false" ]; then
|
||||
curl -H "Host: $hostname" -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay"
|
||||
trap return INT
|
||||
echo "$ids" | while read id; do
|
||||
if [ -z "$async" ]; then
|
||||
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay"
|
||||
else
|
||||
curl -H "Host: $hostname" -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay" &
|
||||
curl -sSL -o /dev/null -w "%{url_effective} %{http_code} %{time_total}s\\n" -- "$host/avatars/$id?overlay" &
|
||||
sleep "$interval"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
while [ $# != 0 ]; do
|
||||
case "$1" in
|
||||
-s)
|
||||
async="false";;
|
||||
-r)
|
||||
random="true";;
|
||||
-i)
|
||||
interval="$2"
|
||||
shift;;
|
||||
*)
|
||||
[ -n "$host" ] && usage
|
||||
host="$1";;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[ -z "$host" ] && usage
|
||||
|
||||
time bulk
|
||||
122
test/test.js
122
test/test.js
@ -3,12 +3,13 @@
|
||||
|
||||
// no spam
|
||||
var logging = require("../lib/logging");
|
||||
if (process.env.VERBOSE_TEST !== "true") {
|
||||
if (process.env.VERBOSE_TEST !== "true" && process.env.TRAVIS !== "true") {
|
||||
logging.log = logging.debug = logging.warn = logging.error = function() {};
|
||||
}
|
||||
|
||||
var networking = require("../lib/networking");
|
||||
var helpers = require("../lib/helpers");
|
||||
var cleaner = require("../lib/cleaner");
|
||||
var request = require("request");
|
||||
var config = require("../config");
|
||||
var server = require("../lib/server");
|
||||
@ -67,6 +68,10 @@ describe("Crafatar", function() {
|
||||
console.log("Flushing and waiting for redis ...");
|
||||
cache.get_redis().flushall(function() {
|
||||
console.log("Redis flushed!");
|
||||
// cause I don't know how big hard drives are these days
|
||||
config.cleaner.disk_limit = Infinity;
|
||||
config.cleaner.redis_limit = Infinity;
|
||||
cleaner.run();
|
||||
done();
|
||||
});
|
||||
});
|
||||
@ -88,8 +93,8 @@ describe("Crafatar", function() {
|
||||
assert.strictEqual(helpers.id_valid("1DCEF164FF0A47F2B9A691385C774EE7"), true);
|
||||
done();
|
||||
});
|
||||
it("dashed uuid is not valid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), false);
|
||||
it("dashed uuid is valid", function(done) {
|
||||
assert.strictEqual(helpers.id_valid("0098cb60-fa8e-427c-b299-793cbd302c9a"), true);
|
||||
done();
|
||||
});
|
||||
it("username is invalid", function(done) {
|
||||
@ -158,7 +163,7 @@ describe("Crafatar", function() {
|
||||
it("should time out on skin download", function(done) {
|
||||
var original_timeout = config.http_timeout;
|
||||
config.server.http_timeout = 1;
|
||||
networking.get_from(rid(), config.endpoints.textures_url + "477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
|
||||
networking.get_from(rid(), "http://textures.minecraft.net/texture/477be35554684c28bdeee4cf11c591d3c88afb77e0b98da893fd7bc318c65184", function(body, res, error) {
|
||||
assert.notStrictEqual(["ETIMEDOUT", "ESOCKETTIMEDOUT"].indexOf(error.code), -1);
|
||||
config.server.http_timeout = original_timeout;
|
||||
done();
|
||||
@ -166,7 +171,7 @@ describe("Crafatar", function() {
|
||||
});
|
||||
it("should not find the skin", function(done) {
|
||||
assert.doesNotThrow(function() {
|
||||
networking.get_from(rid(), config.endpoints.textures_url + "this-does-not-exist", function(img, response, err) {
|
||||
networking.get_from(rid(), "http://textures.minecraft.net/texture/this-does-not-exist", function(img, response, err) {
|
||||
assert.strictEqual(err, null); // no error here, but it shouldn't throw exceptions
|
||||
done();
|
||||
});
|
||||
@ -258,7 +263,7 @@ describe("Crafatar", function() {
|
||||
var url = "http://localhost:3000/%61%76%61%74%61%72%73/%61%65%37%39%35%61%61%38%36%33%32%37%34%30%38%65%39%32%61%62%32%35%63%38%61%35%39%66%33%62%61%31"; // avatars/ae795aa86327408e92ab25c8a59f3ba1
|
||||
request.get(url, function(error, res, body) {
|
||||
assert.ifError(error);
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
assert.strictEqual(res.statusCode, 201);
|
||||
assert_headers(res);
|
||||
assert(res.headers.etag);
|
||||
assert.strictEqual(res.headers["content-type"], "image/png");
|
||||
@ -281,7 +286,7 @@ describe("Crafatar", function() {
|
||||
function req() {
|
||||
request.get(url, function(error, res, body) {
|
||||
assert.ifError(error);
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
assert.strictEqual(res.statusCode === 201 || res.statusCode === 200, true);
|
||||
assert_headers(res);
|
||||
assert(res.headers.etag);
|
||||
assert.strictEqual(res.headers["content-type"], "image/png");
|
||||
@ -298,24 +303,20 @@ describe("Crafatar", function() {
|
||||
var server_tests = {
|
||||
"avatar with existing uuid": {
|
||||
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||
crc32: [4264176600],
|
||||
},
|
||||
"avatar with existing dashed uuid": {
|
||||
url: "http://localhost:3000/avatars/853c80ef-3c37-49fd-aa49938b674adae6?size=16",
|
||||
crc32: [4264176600],
|
||||
crc32: [3337292777],
|
||||
},
|
||||
"avatar with non-existent uuid": {
|
||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16",
|
||||
crc32: [3348154329],
|
||||
crc32: [2416827277, 1243826040],
|
||||
},
|
||||
"avatar with non-existent uuid defaulting to mhf_alex": {
|
||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=mhf_alex",
|
||||
crc32: [73899130],
|
||||
crc32: [862751081, 809395677],
|
||||
},
|
||||
"avatar with non-existent uuid defaulting to uuid": {
|
||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
||||
crc32: [0],
|
||||
redirect: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||
},
|
||||
"avatar with non-existent uuid defaulting to url": {
|
||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||
@ -324,20 +325,20 @@ describe("Crafatar", function() {
|
||||
},
|
||||
"overlay avatar with existing uuid": {
|
||||
url: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16&overlay",
|
||||
crc32: [575355728],
|
||||
crc32: [1710265722],
|
||||
},
|
||||
"overlay avatar with non-existent uuid": {
|
||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay",
|
||||
crc32: [3348154329],
|
||||
crc32: [2416827277, 1243826040],
|
||||
},
|
||||
"overlay avatar with non-existent uuid defaulting to mhf_alex": {
|
||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=mhf_alex",
|
||||
crc32: [73899130],
|
||||
crc32: [862751081, 809395677],
|
||||
},
|
||||
"overlay avatar with non-existent uuid defaulting to uuid": {
|
||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
||||
crc32: [0],
|
||||
redirect: "http://localhost:3000/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||
redirect: "/avatars/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||
},
|
||||
"overlay avatar with non-existent uuid defaulting to url": {
|
||||
url: "http://localhost:3000/avatars/00000000000000000000000000000000?size=16&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||
@ -346,7 +347,7 @@ describe("Crafatar", function() {
|
||||
},
|
||||
"cape with existing uuid": {
|
||||
url: "http://localhost:3000/capes/853c80ef3c3749fdaa49938b674adae6",
|
||||
crc32: [985789174, 2099310578],
|
||||
crc32: [2556702429],
|
||||
},
|
||||
"cape with non-existent uuid": {
|
||||
url: "http://localhost:3000/capes/00000000000000000000000000000000",
|
||||
@ -359,20 +360,20 @@ describe("Crafatar", function() {
|
||||
},
|
||||
"skin with existing uuid": {
|
||||
url: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6",
|
||||
crc32: [1759176487],
|
||||
crc32: [26500336],
|
||||
},
|
||||
"skin with non-existent uuid": {
|
||||
url: "http://localhost:3000/skins/00000000000000000000000000000000",
|
||||
crc32: [1853029228],
|
||||
crc32: [981937087],
|
||||
},
|
||||
"skin with non-existent uuid defaulting to mhf_alex": {
|
||||
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=mhf_alex",
|
||||
crc32: [427506205],
|
||||
crc32: [2298915739],
|
||||
},
|
||||
"skin with non-existent uuid defaulting to uuid": {
|
||||
url: "http://localhost:3000/skins/00000000000000000000000000000000?size=16&default=853c80ef3c3749fdaa49938b674adae6",
|
||||
crc32: [0],
|
||||
redirect: "http://localhost:3000/skins/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||
redirect: "/skins/853c80ef3c3749fdaa49938b674adae6?size=16",
|
||||
},
|
||||
"skin with non-existent uuid defaulting to url": {
|
||||
url: "http://localhost:3000/skins/00000000000000000000000000000000?default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||
@ -394,7 +395,7 @@ describe("Crafatar", function() {
|
||||
"head render with non-existent uuid defaulting to uuid": {
|
||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
|
||||
crc32: [0],
|
||||
redirect: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||
},
|
||||
"head render with non-existent uuid defaulting to url": {
|
||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||
@ -416,7 +417,7 @@ describe("Crafatar", function() {
|
||||
"overlay head with non-existent uuid defaulting to uuid": {
|
||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=853c80ef3c3749fdaa49938b674adae6",
|
||||
crc32: [0],
|
||||
redirect: "http://localhost:3000/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
|
||||
redirect: "/renders/head/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay=",
|
||||
},
|
||||
"overlay head render with non-existent uuid defaulting to url": {
|
||||
url: "http://localhost:3000/renders/head/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||
@ -425,7 +426,7 @@ describe("Crafatar", function() {
|
||||
},
|
||||
"body render with existing uuid": {
|
||||
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||
crc32: [1144887125],
|
||||
crc32: [2745192436],
|
||||
},
|
||||
"body render with non-existent uuid": {
|
||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2",
|
||||
@ -433,12 +434,12 @@ describe("Crafatar", function() {
|
||||
},
|
||||
"body render with non-existent uuid defaulting to mhf_alex": {
|
||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=mhf_alex",
|
||||
crc32: [4280894468],
|
||||
crc32: [1255106465],
|
||||
},
|
||||
"body render with non-existent uuid defaulting to uuid": {
|
||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=853c80ef3c3749fdaa49938b674adae6",
|
||||
crc32: [0],
|
||||
redirect: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||
redirect: "/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2",
|
||||
},
|
||||
"body render with non-existent uuid defaulting to url": {
|
||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||
@ -447,7 +448,7 @@ describe("Crafatar", function() {
|
||||
},
|
||||
"overlay body render with existing uuid": {
|
||||
url: "http://localhost:3000/renders/body/853c80ef3c3749fdaa49938b674adae6?scale=2&overlay",
|
||||
crc32: [1107696668],
|
||||
crc32: [2441671793],
|
||||
},
|
||||
"overlay body render with non-existent uuid": {
|
||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay",
|
||||
@ -455,7 +456,7 @@ describe("Crafatar", function() {
|
||||
},
|
||||
"overlay body render with non-existent uuid defaulting to mhf_alex": {
|
||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=mhf_alex",
|
||||
crc32: [4280894468],
|
||||
crc32: [1255106465],
|
||||
},
|
||||
"overlay body render with non-existent uuid defaulting to url": {
|
||||
url: "http://localhost:3000/renders/body/00000000000000000000000000000000?scale=2&overlay&default=http%3A%2F%2Fexample.com%2FCaseSensitive",
|
||||
@ -493,7 +494,7 @@ describe("Crafatar", function() {
|
||||
done();
|
||||
} else {
|
||||
assert.strictEqual(res.headers["content-type"], "image/png");
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
assert.strictEqual(res.statusCode, res.headers["x-storage-type"] === "downloaded" ? 201 : 200);
|
||||
assert(res.headers.etag);
|
||||
assert.strictEqual(res.headers.etag, '"' + hash + '"');
|
||||
assert_cache(location.url, res.headers.etag, function() {
|
||||
@ -539,7 +540,7 @@ describe("Crafatar", function() {
|
||||
});
|
||||
|
||||
it("should return a 422 (invalid render type)", function(done) {
|
||||
request.get("http://localhost:3000/renders/invalid/2d5aa9cdaeb049189930461fc9b91cc5", function(error, res, body) {
|
||||
request.get("http://localhost:3000/renders/invalid/Jake_0", function(error, res, body) {
|
||||
assert.ifError(error);
|
||||
assert.strictEqual(res.statusCode, 422);
|
||||
done();
|
||||
@ -568,30 +569,6 @@ describe("Crafatar", function() {
|
||||
});
|
||||
}(loc));
|
||||
}
|
||||
|
||||
it("should return /public resources", function(done) {
|
||||
request.get("http://localhost:3000/javascript/crafatar.js", function(error, res, body) {
|
||||
assert.ifError(error);
|
||||
assert.strictEqual(res.statusCode, 200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not allow path traversal on /public", function(done) {
|
||||
request.get("http://localhost:3000/../server.js", function(error, res, body) {
|
||||
assert.ifError(error);
|
||||
assert.strictEqual(res.statusCode, 404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not allow encoded path traversal on /public", function(done) {
|
||||
request.get("http://localhost:3000/%2E%2E/server.js", function(error, res, body) {
|
||||
assert.ifError(error);
|
||||
assert.strictEqual(res.statusCode, 404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// we have to make sure that we test both a 32x64 and 64x64 skin
|
||||
@ -612,7 +589,7 @@ describe("Crafatar", function() {
|
||||
|
||||
describe("Networking: Cape", function() {
|
||||
it("should not fail (guaranteed cape)", function(done) {
|
||||
helpers.get_cape(rid(), "61699b2ed3274a019f1e0ea8c3f06bc6", function(err, hash, status, img) {
|
||||
helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
|
||||
assert.strictEqual(err, null);
|
||||
done();
|
||||
});
|
||||
@ -621,13 +598,13 @@ describe("Crafatar", function() {
|
||||
before(function() {
|
||||
cache.get_redis().flushall();
|
||||
});
|
||||
helpers.get_cape(rid(), "61699b2ed3274a019f1e0ea8c3f06bc6", function(err, hash, status, img) {
|
||||
helpers.get_cape(rid(), "Dinnerbone", function(err, hash, status, img) {
|
||||
assert.strictEqual(err, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it("should not be found", function(done) {
|
||||
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
|
||||
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
||||
assert.ifError(err);
|
||||
assert.strictEqual(img, null);
|
||||
done();
|
||||
@ -637,7 +614,7 @@ describe("Crafatar", function() {
|
||||
|
||||
describe("Networking: Skin", function() {
|
||||
it("should not fail", function(done) {
|
||||
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
|
||||
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
||||
assert.strictEqual(err, null);
|
||||
done();
|
||||
});
|
||||
@ -646,7 +623,7 @@ describe("Crafatar", function() {
|
||||
before(function() {
|
||||
cache.get_redis().flushall();
|
||||
});
|
||||
helpers.get_cape(rid(), "2d5aa9cdaeb049189930461fc9b91cc5", function(err, hash, status, img) {
|
||||
helpers.get_cape(rid(), "Jake_0", function(err, hash, status, img) {
|
||||
assert.strictEqual(err, null);
|
||||
done();
|
||||
});
|
||||
@ -710,28 +687,15 @@ describe("Crafatar", function() {
|
||||
|
||||
|
||||
describe("Errors", function() {
|
||||
before(function() {
|
||||
before(function() {
|
||||
cache.get_redis().flushall();
|
||||
});
|
||||
|
||||
// Mojang has changed its rate limiting, so we no longer expect to hit the rate limit
|
||||
// it("uuid SHOULD be rate limited", function(done) {
|
||||
// networking.get_profile(rid(), uuid, function() {
|
||||
// networking.get_profile(rid(), uuid, function(err, profile) {
|
||||
// assert.strictEqual(err.toString(), "HTTP: 429");
|
||||
// assert.strictEqual(profile, null);
|
||||
// done();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
it("CloudFront rate limit is handled", function(done) {
|
||||
var original_rate_limit = config.server.sessions_rate_limit;
|
||||
config.server.sessions_rate_limit = 1;
|
||||
it("uuid SHOULD be rate limited", function(done) {
|
||||
networking.get_profile(rid(), uuid, function() {
|
||||
networking.get_profile(rid(), uuid, function(err, profile) {
|
||||
assert.strictEqual(err.code, "RATELIMIT");
|
||||
config.server.sessions_rate_limit = original_rate_limit;
|
||||
assert.strictEqual(err.toString(), "HTTP: 429");
|
||||
assert.strictEqual(profile, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
4
www.js
4
www.js
@ -1,5 +1,5 @@
|
||||
var networking = require("./lib/networking");
|
||||
var logging = require("./lib/logging");
|
||||
var cleaner = require("./lib/cleaner");
|
||||
var config = require("./config");
|
||||
|
||||
process.on("uncaughtException", function(err) {
|
||||
@ -7,6 +7,6 @@ process.on("uncaughtException", function(err) {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
setInterval(networking.resetCounter, 1000);
|
||||
setInterval(cleaner.run, config.cleaner.interval * 1000);
|
||||
|
||||
require("./lib/server.js").boot();
|
||||
Loading…
x
Reference in New Issue
Block a user