@20syldev/api 3.5.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +7 -0
- package/README.md +32 -33
- package/dist/app.js +36 -0
- package/dist/app.js.map +1 -0
- package/dist/config/env.js +73 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/versions.js +72 -0
- package/dist/config/versions.js.map +1 -0
- package/dist/constants.js +42 -0
- package/dist/constants.js.map +1 -0
- package/dist/middleware/cors.js +15 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/error.js +6 -0
- package/dist/middleware/error.js.map +1 -0
- package/dist/middleware/json.js +8 -0
- package/dist/middleware/json.js.map +1 -0
- package/dist/middleware/logger.js +26 -0
- package/dist/middleware/logger.js.map +1 -0
- package/dist/middleware/ratelimit.js +58 -0
- package/dist/middleware/ratelimit.js.map +1 -0
- package/dist/middleware/version.js +30 -0
- package/dist/middleware/version.js.map +1 -0
- package/dist/modules/v3/algorithms.js +221 -0
- package/dist/modules/v3/algorithms.js.map +1 -0
- package/{modules → dist/modules}/v3/captcha.js +4 -13
- package/dist/modules/v3/captcha.js.map +1 -0
- package/dist/modules/v3/chat.js +103 -0
- package/dist/modules/v3/chat.js.map +1 -0
- package/{modules → dist/modules}/v3/color.js +12 -13
- package/dist/modules/v3/color.js.map +1 -0
- package/dist/modules/v3/convert.js +41 -0
- package/dist/modules/v3/convert.js.map +1 -0
- package/dist/modules/v3/domain.js +83 -0
- package/dist/modules/v3/domain.js.map +1 -0
- package/dist/modules/v3/hash.js +16 -0
- package/dist/modules/v3/hash.js.map +1 -0
- package/dist/modules/v3/hyperplanning.js +50 -0
- package/dist/modules/v3/hyperplanning.js.map +1 -0
- package/dist/modules/v3/levenshtein.js +38 -0
- package/dist/modules/v3/levenshtein.js.map +1 -0
- package/dist/modules/v3/personal.js +129 -0
- package/dist/modules/v3/personal.js.map +1 -0
- package/dist/modules/v3/qrcode.js +21 -0
- package/dist/modules/v3/qrcode.js.map +1 -0
- package/dist/modules/v3/tic_tac_toe.js +221 -0
- package/dist/modules/v3/tic_tac_toe.js.map +1 -0
- package/dist/modules/v3/time.js +88 -0
- package/dist/modules/v3/time.js.map +1 -0
- package/{modules → dist/modules}/v3/token.js +10 -11
- package/dist/modules/v3/token.js.map +1 -0
- package/dist/modules/v3/username.js +92 -0
- package/dist/modules/v3/username.js.map +1 -0
- package/dist/modules/v3/utils.js +70 -0
- package/dist/modules/v3/utils.js.map +1 -0
- package/dist/modules/v3.js +16 -0
- package/dist/modules/v3.js.map +1 -0
- package/dist/modules/v4/algorithms.js +200 -0
- package/dist/modules/v4/algorithms.js.map +1 -0
- package/dist/modules/v4/captcha.js +35 -0
- package/dist/modules/v4/captcha.js.map +1 -0
- package/dist/modules/v4/chat.js +92 -0
- package/dist/modules/v4/chat.js.map +1 -0
- package/dist/modules/v4/color.js +43 -0
- package/dist/modules/v4/color.js.map +1 -0
- package/dist/modules/v4/convert.js +33 -0
- package/dist/modules/v4/convert.js.map +1 -0
- package/dist/modules/v4/dice.js +30 -0
- package/dist/modules/v4/dice.js.map +1 -0
- package/dist/modules/v4/domain.js +78 -0
- package/dist/modules/v4/domain.js.map +1 -0
- package/dist/modules/v4/encode.js +165 -0
- package/dist/modules/v4/encode.js.map +1 -0
- package/dist/modules/v4/geo.js +41 -0
- package/dist/modules/v4/geo.js.map +1 -0
- package/dist/modules/v4/hash.js +9 -0
- package/dist/modules/v4/hash.js.map +1 -0
- package/dist/modules/v4/hyperplanning.js +42 -0
- package/dist/modules/v4/hyperplanning.js.map +1 -0
- package/dist/modules/v4/levenshtein.js +29 -0
- package/dist/modules/v4/levenshtein.js.map +1 -0
- package/dist/modules/v4/palette.js +89 -0
- package/dist/modules/v4/palette.js.map +1 -0
- package/dist/modules/v4/personal.js +125 -0
- package/dist/modules/v4/personal.js.map +1 -0
- package/dist/modules/v4/placeholder.js +96 -0
- package/dist/modules/v4/placeholder.js.map +1 -0
- package/dist/modules/v4/qrcode.js +12 -0
- package/dist/modules/v4/qrcode.js.map +1 -0
- package/dist/modules/v4/statistics.js +41 -0
- package/dist/modules/v4/statistics.js.map +1 -0
- package/dist/modules/v4/text.js +293 -0
- package/dist/modules/v4/text.js.map +1 -0
- package/dist/modules/v4/tic_tac_toe.js +206 -0
- package/dist/modules/v4/tic_tac_toe.js.map +1 -0
- package/dist/modules/v4/time.js +73 -0
- package/dist/modules/v4/time.js.map +1 -0
- package/dist/modules/v4/token.js +36 -0
- package/dist/modules/v4/token.js.map +1 -0
- package/dist/modules/v4/username.js +87 -0
- package/dist/modules/v4/username.js.map +1 -0
- package/dist/modules/v4/utils.js +26 -0
- package/dist/modules/v4/utils.js.map +1 -0
- package/dist/modules/v4/validate.js +54 -0
- package/dist/modules/v4/validate.js.map +1 -0
- package/dist/modules/v4.js +18 -0
- package/dist/modules/v4.js.map +1 -0
- package/dist/routes/delete.js +65 -0
- package/dist/routes/delete.js.map +1 -0
- package/dist/routes/get.js +441 -0
- package/dist/routes/get.js.map +1 -0
- package/dist/routes/index.js +50 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/patch.js +41 -0
- package/dist/routes/patch.js.map +1 -0
- package/dist/routes/post.js +185 -0
- package/dist/routes/post.js.map +1 -0
- package/dist/storage/index.js +13 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types/storage.js +2 -0
- package/dist/types/storage.js.map +1 -0
- package/dist/utils/response.js +10 -0
- package/dist/utils/response.js.map +1 -0
- package/dist/utils/version.js +3 -0
- package/dist/utils/version.js.map +1 -0
- package/docs/changelog.md +333 -0
- package/eslint.config.js +20 -0
- package/package.json +31 -15
- package/src/app.ts +48 -0
- package/src/config/env.ts +81 -0
- package/src/config/versions.ts +92 -0
- package/src/constants.ts +47 -0
- package/src/middleware/cors.ts +17 -0
- package/src/middleware/error.ts +7 -0
- package/src/middleware/json.ts +9 -0
- package/src/middleware/logger.ts +31 -0
- package/src/middleware/ratelimit.ts +66 -0
- package/src/middleware/version.ts +39 -0
- package/{modules → src/modules}/v3/algorithms.js +1 -1
- package/src/modules/v3/captcha.js +60 -0
- package/{modules → src/modules}/v3/chat.js +6 -4
- package/src/modules/v3/color.js +60 -0
- package/{modules → src/modules}/v3/convert.js +5 -5
- package/{modules → src/modules}/v3/domain.js +51 -4
- package/{modules → src/modules}/v3/hash.js +1 -1
- package/{modules → src/modules}/v3/hyperplanning.js +11 -10
- package/{modules → src/modules}/v3/levenshtein.js +3 -3
- package/{modules → src/modules}/v3/personal.js +20 -8
- package/{modules → src/modules}/v3/qrcode.js +1 -1
- package/{modules → src/modules}/v3/tic_tac_toe.js +24 -18
- package/{modules → src/modules}/v3/time.js +22 -11
- package/src/modules/v3/token.js +54 -0
- package/src/modules/v3/username.js +94 -0
- package/{modules → src/modules}/v3/utils.js +2 -2
- package/{modules → src/modules}/v3.js +1 -1
- package/src/modules/v4/algorithms.ts +197 -0
- package/src/modules/v4/captcha.ts +49 -0
- package/src/modules/v4/chat.ts +122 -0
- package/src/modules/v4/color.ts +64 -0
- package/src/modules/v4/convert.ts +38 -0
- package/src/modules/v4/dice.ts +39 -0
- package/src/modules/v4/domain.ts +81 -0
- package/src/modules/v4/encode.ts +170 -0
- package/src/modules/v4/geo.ts +53 -0
- package/src/modules/v4/hash.ts +9 -0
- package/src/modules/v4/hyperplanning.ts +58 -0
- package/src/modules/v4/levenshtein.ts +38 -0
- package/src/modules/v4/palette.ts +103 -0
- package/src/modules/v4/personal.ts +147 -0
- package/src/modules/v4/placeholder.ts +122 -0
- package/src/modules/v4/qrcode.ts +11 -0
- package/src/modules/v4/statistics.ts +55 -0
- package/src/modules/v4/text.ts +300 -0
- package/src/modules/v4/tic_tac_toe.ts +255 -0
- package/src/modules/v4/time.ts +92 -0
- package/src/modules/v4/token.ts +43 -0
- package/src/modules/v4/username.ts +89 -0
- package/src/modules/v4/utils.ts +38 -0
- package/src/modules/v4/validate.ts +55 -0
- package/src/modules/v4.ts +17 -0
- package/src/routes/delete.ts +72 -0
- package/src/routes/get.ts +508 -0
- package/src/routes/index.ts +58 -0
- package/src/routes/patch.ts +45 -0
- package/src/routes/post.ts +200 -0
- package/src/storage/index.ts +16 -0
- package/src/types/express.d.ts +17 -0
- package/src/types/storage.ts +48 -0
- package/src/utils/response.ts +11 -0
- package/src/utils/version.ts +5 -0
- package/tests/integration/api.test.ts +395 -0
- package/tests/tsconfig.json +3 -0
- package/tests/unit/algorithms.test.ts +120 -0
- package/tests/unit/color.test.ts +33 -0
- package/tests/unit/convert.test.ts +42 -0
- package/tests/unit/dice.test.ts +57 -0
- package/tests/unit/domain.test.ts +47 -0
- package/tests/unit/encode.test.ts +108 -0
- package/tests/unit/geo.test.ts +45 -0
- package/tests/unit/hash.test.ts +37 -0
- package/tests/unit/hyperplanning.test.ts +77 -0
- package/tests/unit/levenshtein.test.ts +34 -0
- package/tests/unit/palette.test.ts +53 -0
- package/tests/unit/personal.test.ts +65 -0
- package/tests/unit/statistics.test.ts +57 -0
- package/tests/unit/text.test.ts +107 -0
- package/tests/unit/time.test.ts +67 -0
- package/tests/unit/token.test.ts +61 -0
- package/tests/unit/username.test.ts +34 -0
- package/tests/unit/validate.test.ts +71 -0
- package/tsconfig.json +17 -0
- package/tsconfig.test.json +9 -0
- package/app.js +0 -865
- package/modules/v3/username.js +0 -32
package/src/app.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { env } from './config/env.js';
|
|
3
|
+
import { setupCors } from './middleware/cors.js';
|
|
4
|
+
import { jsonResponseMiddleware } from './middleware/json.js';
|
|
5
|
+
import { rateLimitMiddleware } from './middleware/ratelimit.js';
|
|
6
|
+
import { loggerMiddleware } from './middleware/logger.js';
|
|
7
|
+
import { errorHandler } from './middleware/error.js';
|
|
8
|
+
import { versionCheckMiddleware, endpointCheckMiddleware } from './middleware/version.js';
|
|
9
|
+
import indexRoutes from './routes/index.js';
|
|
10
|
+
import getRoutes from './routes/get.js';
|
|
11
|
+
import postRoutes from './routes/post.js';
|
|
12
|
+
|
|
13
|
+
const app = express();
|
|
14
|
+
|
|
15
|
+
// Setup CORS, body parsing, static files
|
|
16
|
+
setupCors(app);
|
|
17
|
+
|
|
18
|
+
// Custom JSON response
|
|
19
|
+
app.use(jsonResponseMiddleware);
|
|
20
|
+
|
|
21
|
+
// Rate limiting
|
|
22
|
+
app.use(rateLimitMiddleware);
|
|
23
|
+
|
|
24
|
+
// Request logging
|
|
25
|
+
app.use(loggerMiddleware);
|
|
26
|
+
|
|
27
|
+
// Error handling
|
|
28
|
+
app.use(errorHandler);
|
|
29
|
+
|
|
30
|
+
// Root routes (before version check)
|
|
31
|
+
app.use(indexRoutes);
|
|
32
|
+
|
|
33
|
+
// Version & endpoint validation
|
|
34
|
+
app.use('/:version', versionCheckMiddleware);
|
|
35
|
+
app.use('/:version/:endpoint', endpointCheckMiddleware);
|
|
36
|
+
|
|
37
|
+
// Versioned routes
|
|
38
|
+
app.use(getRoutes);
|
|
39
|
+
app.use(postRoutes);
|
|
40
|
+
|
|
41
|
+
// Start server
|
|
42
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
43
|
+
app.listen(env.PORT, () =>
|
|
44
|
+
console.log(`API is running on\n - http://127.0.0.1:${env.PORT}\n - http://localhost:${env.PORT}`),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default app;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import dotenv from 'dotenv';
|
|
2
|
+
import { DEFAULT_PORT } from '../constants.js';
|
|
3
|
+
|
|
4
|
+
dotenv.config();
|
|
5
|
+
|
|
6
|
+
function envList(key: string): string[] | null {
|
|
7
|
+
const v = process.env[key];
|
|
8
|
+
return v && v !== 'undefined' ? v.split(' ') : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function envNumber(key: string, fallback: number): number {
|
|
12
|
+
const v = parseInt(process.env[key] ?? '', 10);
|
|
13
|
+
return isNaN(v) ? fallback : v;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const env = {
|
|
17
|
+
PORT: envNumber('PORT', DEFAULT_PORT),
|
|
18
|
+
GITHUB_TOKEN: process.env.GITHUB_TOKEN ?? '',
|
|
19
|
+
|
|
20
|
+
DEFAULT_LIMIT: envNumber('DEFAULT_LIMIT', 2000),
|
|
21
|
+
PRO_LIMIT: envNumber('PRO_LIMIT', 6000),
|
|
22
|
+
ADVANCED_LIMIT: envNumber('ADVANCED_LIMIT', 4000),
|
|
23
|
+
BUSINESS_LIMIT: envNumber('BUSINESS_LIMIT', 10000),
|
|
24
|
+
GLOBAL_LIMIT: envNumber('GLOBAL_LIMIT', 50000),
|
|
25
|
+
|
|
26
|
+
BUSINESS_TOKEN_LIST: envList('BUSINESS_TOKEN_LIST') ?? [],
|
|
27
|
+
PRO_TOKEN_LIST: envList('PRO_TOKEN_LIST') ?? [],
|
|
28
|
+
ADVANCED_TOKEN_LIST: envList('ADVANCED_TOKEN_LIST') ?? [],
|
|
29
|
+
|
|
30
|
+
// Website endpoint env vars
|
|
31
|
+
G_2048: process.env.G_2048,
|
|
32
|
+
API: process.env.API,
|
|
33
|
+
CDN: process.env.CDN,
|
|
34
|
+
COOP_API: process.env.COOP_API,
|
|
35
|
+
COOP_STATUS: process.env.COOP_STATUS,
|
|
36
|
+
CHAT: process.env.CHAT,
|
|
37
|
+
DIGIT: process.env.DIGIT,
|
|
38
|
+
DOC_COOPBOT: process.env.DOC_COOPBOT,
|
|
39
|
+
DOCS: process.env.DOCS,
|
|
40
|
+
DONUT: process.env.DONUT,
|
|
41
|
+
DRAWIO_PLUGIN: process.env.DRAWIO_PLUGIN,
|
|
42
|
+
FLOWERS: process.env.FLOWERS,
|
|
43
|
+
GEMSYNC: process.env.GEMSYNC,
|
|
44
|
+
GFT: process.env.GFT,
|
|
45
|
+
GITSITE: process.env.GITSITE,
|
|
46
|
+
LEBONCHAR: process.env.LEBONCHAR,
|
|
47
|
+
LOGGER: process.env.LOGGER,
|
|
48
|
+
LOGS: process.env.LOGS,
|
|
49
|
+
LOGVAULT: process.env.LOGVAULT,
|
|
50
|
+
LYAH: process.env.LYAH,
|
|
51
|
+
MINIFY: process.env.MINIFY,
|
|
52
|
+
MN: process.env.MN,
|
|
53
|
+
MONITORING: process.env.MONITORING,
|
|
54
|
+
MORPION: process.env.MORPION,
|
|
55
|
+
NITROGEN: process.env.NITROGEN,
|
|
56
|
+
OLD_DATABASE: process.env.OLD_DATABASE,
|
|
57
|
+
PASSWORD: process.env.PASSWORD,
|
|
58
|
+
PHP: process.env.PHP,
|
|
59
|
+
PLANNING: process.env.PLANNING,
|
|
60
|
+
PING: process.env.PING,
|
|
61
|
+
PORTFOLIO: process.env.PORTFOLIO,
|
|
62
|
+
PYTHON_API: process.env.PYTHON_API,
|
|
63
|
+
README: process.env.README,
|
|
64
|
+
TIMESTAMP: process.env.TIMESTAMP,
|
|
65
|
+
TERMINAL: process.env.TERMINAL,
|
|
66
|
+
VALENTINE: process.env.VALENTINE,
|
|
67
|
+
WRKIT: process.env.WRKIT,
|
|
68
|
+
ZPKI: process.env.ZPKI,
|
|
69
|
+
|
|
70
|
+
PATCH: envList('PATCH'),
|
|
71
|
+
RECENT: envList('RECENT'),
|
|
72
|
+
NEW: envList('NEW'),
|
|
73
|
+
DOMAINS: envList('DOMAINS'),
|
|
74
|
+
|
|
75
|
+
STATS1: process.env.STATS1,
|
|
76
|
+
STATS2: process.env.STATS2,
|
|
77
|
+
STATS3: process.env.STATS3,
|
|
78
|
+
STATS4: process.env.STATS4,
|
|
79
|
+
TAG: process.env.TAG,
|
|
80
|
+
ACTIVE: process.env.ACTIVE === 'true',
|
|
81
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as apiv3 from '../modules/v3.js';
|
|
2
|
+
import * as apiv4 from '../modules/v4.js';
|
|
3
|
+
|
|
4
|
+
export interface Endpoint {
|
|
5
|
+
name: string;
|
|
6
|
+
path?: string;
|
|
7
|
+
children?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface VersionConfig {
|
|
11
|
+
endpoints: {
|
|
12
|
+
get: Endpoint[];
|
|
13
|
+
post: Endpoint[];
|
|
14
|
+
patch?: Endpoint[];
|
|
15
|
+
delete?: Endpoint[];
|
|
16
|
+
};
|
|
17
|
+
modules: typeof apiv3 | typeof apiv4;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Merges a base list of endpoints with additions, deduplicating by `name`.
|
|
22
|
+
* If an addition has the same name as a base entry, it overrides it.
|
|
23
|
+
*/
|
|
24
|
+
const merge = (base: Endpoint[], additions: Endpoint[]): Endpoint[] => {
|
|
25
|
+
const map = new Map(base.map((e) => [e.name, e]));
|
|
26
|
+
for (const e of additions) map.set(e.name, e);
|
|
27
|
+
return [...map.values()];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const v1 = {
|
|
31
|
+
get: [
|
|
32
|
+
{ name: 'algorithms', path: '/algorithms?method={algorithm}&value={value}(&value2={value2})' },
|
|
33
|
+
{ name: 'captcha', path: '/captcha?text={text}' },
|
|
34
|
+
{ name: 'color', path: '/color' },
|
|
35
|
+
{ name: 'convert', path: '/convert?value={value}&from={unit}&to={unit}' },
|
|
36
|
+
{ name: 'domain', path: '/domain' },
|
|
37
|
+
{ name: 'infos', path: '/infos' },
|
|
38
|
+
{ name: 'personal', path: '/personal' },
|
|
39
|
+
{ name: 'qrcode', path: '/qrcode?url={URL}' },
|
|
40
|
+
{ name: 'username', path: '/username' },
|
|
41
|
+
{ name: 'website', path: '/website' },
|
|
42
|
+
],
|
|
43
|
+
post: [{ name: 'token', path: '/token' }],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const v2 = {
|
|
47
|
+
get: merge(v1.get, [{ name: 'chat', path: '/chat' }]),
|
|
48
|
+
post: merge(v1.post, [
|
|
49
|
+
{
|
|
50
|
+
name: 'chat',
|
|
51
|
+
children: {
|
|
52
|
+
chat: '/chat',
|
|
53
|
+
private: '/chat/private',
|
|
54
|
+
} as Record<string, string>,
|
|
55
|
+
},
|
|
56
|
+
{ name: 'hash', path: '/hash' },
|
|
57
|
+
{
|
|
58
|
+
name: 'tic_tac_toe',
|
|
59
|
+
children: {
|
|
60
|
+
tic_tac_toe: '/tic-tac-toe',
|
|
61
|
+
fetch: '/tic-tac-toe/fetch',
|
|
62
|
+
list: '/tic-tac-toe/list',
|
|
63
|
+
} as Record<string, string>,
|
|
64
|
+
},
|
|
65
|
+
]),
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const v3 = {
|
|
69
|
+
get: merge(v2.get, [
|
|
70
|
+
{ name: 'levenshtein', path: '/levenshtein?str1={string}&str2={string}' },
|
|
71
|
+
{
|
|
72
|
+
name: 'time',
|
|
73
|
+
path: '/time(?type={type}&start={timestamp}&end={timestamp}&format={format}&timezone={timezone})',
|
|
74
|
+
},
|
|
75
|
+
]),
|
|
76
|
+
post: merge(v2.post, [{ name: 'hyperplanning', path: '/hyperplanning' }]),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const v4 = {
|
|
80
|
+
get: merge(v3.get, [
|
|
81
|
+
{ name: 'dice', path: '/dice?roll={NdX+M}' },
|
|
82
|
+
{ name: 'statistics', path: '/statistics?values={n1,n2,n3,...}' },
|
|
83
|
+
]),
|
|
84
|
+
post: [...v3.post],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const versions: Record<string, VersionConfig> = {
|
|
88
|
+
v1: { endpoints: v1, modules: apiv3 },
|
|
89
|
+
v2: { endpoints: v2, modules: apiv3 },
|
|
90
|
+
v3: { endpoints: v3, modules: apiv3 },
|
|
91
|
+
v4: { endpoints: v4, modules: apiv4 },
|
|
92
|
+
};
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
2
|
+
|
|
3
|
+
export const APP_VERSION = pkg.version;
|
|
4
|
+
export const MAX_LOG_ENTRIES = 1000;
|
|
5
|
+
export const RATE_LIMIT_WINDOW = 10_000;
|
|
6
|
+
export const RATE_LIMIT_MAX = 50;
|
|
7
|
+
export const SESSION_TTL = 3_600_000;
|
|
8
|
+
export const GAME_CLEANUP_TTL = 600_000;
|
|
9
|
+
export const GITHUB_CACHE_TTL = 600_000;
|
|
10
|
+
export const MIN_TOKEN_LENGTH = 12;
|
|
11
|
+
export const MAX_TOKEN_LENGTH = 4096;
|
|
12
|
+
export const MAX_FACTORIAL = 170;
|
|
13
|
+
export const MAX_GCD_VALUE = 100_000;
|
|
14
|
+
export const MAX_PRIME_LIST = 10_000;
|
|
15
|
+
export const MAX_LEVENSHTEIN_LENGTH = 1000;
|
|
16
|
+
export const MAX_STRING_LENGTH = 1000;
|
|
17
|
+
export const MIN_TEMPERATURE = -273.15;
|
|
18
|
+
export const MAX_TEMPERATURE = 1_000_000;
|
|
19
|
+
export const DOCS_URL = 'https://docs.sylvain.sh';
|
|
20
|
+
export const DEFAULT_PORT = 3000;
|
|
21
|
+
|
|
22
|
+
export const STATUS_MESSAGES: Record<number, string> = {
|
|
23
|
+
400: 'Bad Request',
|
|
24
|
+
401: 'Unauthorized',
|
|
25
|
+
404: 'Not Found',
|
|
26
|
+
405: 'Method Not Allowed',
|
|
27
|
+
429: 'Too Many Requests',
|
|
28
|
+
500: 'Internal Server Error',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const START_TIME = Date.now();
|
|
32
|
+
|
|
33
|
+
export const ROMAN_VALUES: [number, string][] = [
|
|
34
|
+
[1000, 'M'],
|
|
35
|
+
[900, 'CM'],
|
|
36
|
+
[500, 'D'],
|
|
37
|
+
[400, 'CD'],
|
|
38
|
+
[100, 'C'],
|
|
39
|
+
[90, 'XC'],
|
|
40
|
+
[50, 'L'],
|
|
41
|
+
[40, 'XL'],
|
|
42
|
+
[10, 'X'],
|
|
43
|
+
[9, 'IX'],
|
|
44
|
+
[5, 'V'],
|
|
45
|
+
[4, 'IV'],
|
|
46
|
+
[1, 'I'],
|
|
47
|
+
];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import cors from 'cors';
|
|
2
|
+
import express, { type Express } from 'express';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
export function setupCors(app: Express): void {
|
|
10
|
+
app.set('trust proxy', 1);
|
|
11
|
+
app.use(cors({ methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'] }));
|
|
12
|
+
app.use(express.urlencoded({ extended: true }));
|
|
13
|
+
app.use(express.json());
|
|
14
|
+
|
|
15
|
+
app.use('/favicon.ico', express.static(join(__dirname, '..', '..', 'src', 'favicon.ico')));
|
|
16
|
+
app.use('/robots.txt', express.static(join(__dirname, '..', '..', 'robots.txt')));
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { error } from '../utils/response.js';
|
|
3
|
+
|
|
4
|
+
export function errorHandler(err: Error, _req: Request, res: Response, _next: NextFunction): void {
|
|
5
|
+
console.error(err.stack);
|
|
6
|
+
error(res, 500, err.message);
|
|
7
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
+
|
|
3
|
+
export function jsonResponseMiddleware(_req: Request, res: Response, next: NextFunction): void {
|
|
4
|
+
res.setHeader('Content-Type', 'application/json');
|
|
5
|
+
res.jsonResponse = (data: unknown) => {
|
|
6
|
+
res.send(JSON.stringify(data, null, 2));
|
|
7
|
+
};
|
|
8
|
+
next();
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createLogger } from '@20syldev/logger.ts';
|
|
2
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
3
|
+
import { MAX_LOG_ENTRIES } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
export const logger = createLogger({
|
|
6
|
+
maxEntries: MAX_LOG_ENTRIES,
|
|
7
|
+
console: true,
|
|
8
|
+
theme: 'colored',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export function loggerMiddleware(req: Request, res: Response, next: NextFunction): void {
|
|
12
|
+
if (req.method === 'HEAD') {
|
|
13
|
+
next();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (req.originalUrl === '/logs') {
|
|
17
|
+
next();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const startTime = Date.now();
|
|
22
|
+
const platform = (req.headers['sec-ch-ua-platform'] as string)?.replace(/"/g, '');
|
|
23
|
+
|
|
24
|
+
res.on('finish', () => {
|
|
25
|
+
const status = res.statusCode === 304 ? 200 : res.statusCode;
|
|
26
|
+
const duration = `${Date.now() - startTime}ms`;
|
|
27
|
+
|
|
28
|
+
logger.log({ method: req.method, url: req.originalUrl, status, duration, platform });
|
|
29
|
+
});
|
|
30
|
+
next();
|
|
31
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { env } from '../config/env.js';
|
|
3
|
+
import { ipLimits } from '../storage/index.js';
|
|
4
|
+
import { SESSION_TTL } from '../constants.js';
|
|
5
|
+
import { error } from '../utils/response.js';
|
|
6
|
+
|
|
7
|
+
let requests = 0;
|
|
8
|
+
let resetTime = Date.now() + SESSION_TTL;
|
|
9
|
+
|
|
10
|
+
export function rateLimitMiddleware(req: Request, res: Response, next: NextFunction): void {
|
|
11
|
+
const ip = (req.headers['cf-connecting-ip'] as string) || req.socket.remoteAddress || '';
|
|
12
|
+
const token = req.headers.authorization?.split(' ')[1] || '';
|
|
13
|
+
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
const minute = String(Math.floor(now / 60000) % 60);
|
|
16
|
+
const hour = String(Math.floor(now / 3600000) % 24);
|
|
17
|
+
|
|
18
|
+
if (
|
|
19
|
+
(token && ![...env.BUSINESS_TOKEN_LIST, ...env.PRO_TOKEN_LIST, ...env.ADVANCED_TOKEN_LIST].includes(token)) ||
|
|
20
|
+
token === 'undefined'
|
|
21
|
+
) {
|
|
22
|
+
error(res, 401, 'Invalid token.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let requestLimit: number;
|
|
27
|
+
if (env.BUSINESS_TOKEN_LIST.includes(token) && !env.BUSINESS_TOKEN_LIST.includes('undefined')) {
|
|
28
|
+
requestLimit = env.BUSINESS_LIMIT;
|
|
29
|
+
} else if (env.PRO_TOKEN_LIST.includes(token) && !env.PRO_TOKEN_LIST.includes('undefined')) {
|
|
30
|
+
requestLimit = env.PRO_LIMIT;
|
|
31
|
+
} else if (env.ADVANCED_TOKEN_LIST.includes(token) && !env.ADVANCED_TOKEN_LIST.includes('undefined')) {
|
|
32
|
+
requestLimit = env.ADVANCED_LIMIT;
|
|
33
|
+
} else {
|
|
34
|
+
requestLimit = env.DEFAULT_LIMIT;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (now > resetTime) {
|
|
38
|
+
requests = 0;
|
|
39
|
+
resetTime = now + SESSION_TTL;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (++requests > Math.max(env.GLOBAL_LIMIT, requestLimit)) {
|
|
43
|
+
error(res, 429, 'Global rate limit exceeded.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (['__proto__', 'constructor', 'prototype'].includes(ip)) {
|
|
48
|
+
error(res, 400, 'Invalid IP address.');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!ipLimits[ip]) ipLimits[ip] = {};
|
|
53
|
+
if (!ipLimits[ip]![hour]) ipLimits[ip]![hour] = {};
|
|
54
|
+
ipLimits[ip]![hour]![minute] = (ipLimits[ip]![hour]![minute] ?? 0) + 1;
|
|
55
|
+
|
|
56
|
+
if (ipLimits[ip]![hour]![minute]! > requestLimit) {
|
|
57
|
+
error(res, 429, `You have exceeded the limit of ${requestLimit} requests per hour.`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
Object.keys(ipLimits[ip]!).forEach((h) => {
|
|
62
|
+
if (h != hour) delete ipLimits[ip]![h];
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
next();
|
|
66
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import { versions } from '../config/versions.js';
|
|
3
|
+
import { error } from '../utils/response.js';
|
|
4
|
+
|
|
5
|
+
export function versionCheckMiddleware(req: Request, res: Response, next: NextFunction): void {
|
|
6
|
+
const version = req.params.version as string;
|
|
7
|
+
const latest = Object.keys(versions).pop()!;
|
|
8
|
+
|
|
9
|
+
req.version = version;
|
|
10
|
+
req.latest = latest;
|
|
11
|
+
|
|
12
|
+
if (!versions[version]) {
|
|
13
|
+
error(res, 404, `Invalid API version (${version}).`, latest);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
req.module = versions[version]!.modules;
|
|
18
|
+
|
|
19
|
+
if (!req.module) {
|
|
20
|
+
error(res, 404, `Module not found for version ${version}.`, latest);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
next();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function endpointCheckMiddleware(req: Request, res: Response, next: NextFunction): void {
|
|
28
|
+
const version = req.params.version as string;
|
|
29
|
+
const endpoint = req.params.endpoint as string;
|
|
30
|
+
|
|
31
|
+
req.version = version;
|
|
32
|
+
req.endpoint = endpoint;
|
|
33
|
+
|
|
34
|
+
if (!endpoint) {
|
|
35
|
+
error(res, 404, `Endpoint '${endpoint}' does not exist in ${req.version}.`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
next();
|
|
39
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createCanvas } from 'canvas';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate a captcha image from the provided text
|
|
5
|
+
*
|
|
6
|
+
* @param {string} text - Text to render in the captcha image
|
|
7
|
+
* @returns {Buffer} - PNG image buffer of the rendered captcha
|
|
8
|
+
* @throws {Error} - If text is not provided
|
|
9
|
+
*/
|
|
10
|
+
export default function captcha(text) {
|
|
11
|
+
if (!text) throw new Error('Text is required to generate a captcha.');
|
|
12
|
+
|
|
13
|
+
const size = 60,
|
|
14
|
+
font = '60px Comic Sans Ms',
|
|
15
|
+
width = text.length * size,
|
|
16
|
+
height = 120;
|
|
17
|
+
const canvas = createCanvas(width, height),
|
|
18
|
+
ctx = canvas.getContext('2d');
|
|
19
|
+
|
|
20
|
+
// Set white background
|
|
21
|
+
ctx.fillStyle = 'white';
|
|
22
|
+
ctx.fillRect(0, 0, width, height);
|
|
23
|
+
|
|
24
|
+
// Add random lines for noise
|
|
25
|
+
for (let i = 0; i < 20; i++) {
|
|
26
|
+
ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
|
|
27
|
+
ctx.beginPath();
|
|
28
|
+
ctx.moveTo(Math.random() * width, Math.random() * height);
|
|
29
|
+
ctx.lineTo(Math.random() * width, Math.random() * height);
|
|
30
|
+
ctx.lineWidth = Math.random() * 2;
|
|
31
|
+
ctx.stroke();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let x = (canvas.width + 20 - width) / 2;
|
|
35
|
+
|
|
36
|
+
// Draw each character with random rotation and color
|
|
37
|
+
for (let i = 0; i < text.length; i++) {
|
|
38
|
+
const offsetX = Math.cos(i * 0.3) * 10,
|
|
39
|
+
y = height / 2.5 + Math.floor(Math.random() * (height / 2));
|
|
40
|
+
|
|
41
|
+
ctx.font = font;
|
|
42
|
+
ctx.fillStyle = `rgb(${Math.floor(Math.random() * 192)}, ${Math.floor(Math.random() * 192)}, ${Math.floor(Math.random() * 192)})`;
|
|
43
|
+
|
|
44
|
+
ctx.save();
|
|
45
|
+
ctx.translate(x + size / 2, y);
|
|
46
|
+
ctx.rotate((Math.random() - 0.5) * 0.5);
|
|
47
|
+
ctx.fillText(text[i], -size / 2 + offsetX, 0);
|
|
48
|
+
ctx.restore();
|
|
49
|
+
|
|
50
|
+
x += size;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Add noise dots
|
|
54
|
+
for (let i = 0; i < 200; i++) {
|
|
55
|
+
ctx.fillStyle = 'black';
|
|
56
|
+
ctx.fillRect(Math.floor(Math.random() * width), Math.floor(Math.random() * height), 1.2, 1.2);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return canvas.toBuffer('image/png');
|
|
60
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkRateLimit } from
|
|
1
|
+
import { checkRateLimit } from './utils.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Manages a chat system with public and private messages
|
|
@@ -61,14 +61,16 @@ function sendMessage(params, messages, privateChats, sessions, u, now) {
|
|
|
61
61
|
const msg = {
|
|
62
62
|
username: params.username,
|
|
63
63
|
message,
|
|
64
|
-
timestamp: params.timestamp || new Date().toISOString()
|
|
64
|
+
timestamp: params.timestamp || new Date().toISOString(),
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
// Store message based on whether it's private or public
|
|
68
68
|
if (token) {
|
|
69
69
|
privateChats[token] = privateChats[token] || [];
|
|
70
70
|
privateChats[token].push(msg);
|
|
71
|
-
setTimeout(() => {
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
delete privateChats[token];
|
|
73
|
+
}, 3600000);
|
|
72
74
|
} else {
|
|
73
75
|
messages.push(msg);
|
|
74
76
|
setTimeout(() => messages.splice(messages.indexOf(msg), 1), 3600000);
|
|
@@ -106,4 +108,4 @@ function getPrivateChat(params, privateChats) {
|
|
|
106
108
|
function fetchMessages(messages) {
|
|
107
109
|
if (messages.length > 0) return messages;
|
|
108
110
|
throw new Error('No messages stored.');
|
|
109
|
-
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a random color in multiple formats
|
|
3
|
+
*
|
|
4
|
+
* @returns {Object} Color in various formats (hex, rgb, hsl, hsv, hwb, cmyk)
|
|
5
|
+
*/
|
|
6
|
+
export default function color() {
|
|
7
|
+
const r = Math.floor(Math.random() * 256),
|
|
8
|
+
g = Math.floor(Math.random() * 256),
|
|
9
|
+
b = Math.floor(Math.random() * 256);
|
|
10
|
+
|
|
11
|
+
const hsl = (() => {
|
|
12
|
+
const r1 = r / 255,
|
|
13
|
+
g1 = g / 255,
|
|
14
|
+
b1 = b / 255,
|
|
15
|
+
max = Math.max(r1, g1, b1),
|
|
16
|
+
min = Math.min(r1, g1, b1),
|
|
17
|
+
l = (max + min) / 2;
|
|
18
|
+
if (max === min) return [0, 0, l * 100];
|
|
19
|
+
const d = max - min,
|
|
20
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
21
|
+
let h = { [r1]: (g1 - b1) / d + (g1 < b1 ? 6 : 0), [g1]: (b1 - r1) / d + 2, [b1]: (r1 - g1) / d + 4 }[max];
|
|
22
|
+
return [(h * 60) % 360, s * 100, l * 100];
|
|
23
|
+
})();
|
|
24
|
+
|
|
25
|
+
const hsv = (() => {
|
|
26
|
+
const max = Math.max(r, g, b),
|
|
27
|
+
min = Math.min(r, g, b),
|
|
28
|
+
v = max / 255,
|
|
29
|
+
s = max ? (max - min) / max : 0;
|
|
30
|
+
let h =
|
|
31
|
+
max === min
|
|
32
|
+
? 0
|
|
33
|
+
: { [r]: (g - b) / (max - min), [g]: 2 + (b - r) / (max - min), [b]: 4 + (r - g) / (max - min) }[max];
|
|
34
|
+
return [(h * 60) % 360, s * 100, v * 100];
|
|
35
|
+
})();
|
|
36
|
+
|
|
37
|
+
const hwb = (() => {
|
|
38
|
+
const [h] = hsv,
|
|
39
|
+
whiteness = Math.min(r, g, b) / 255,
|
|
40
|
+
blackness = 1 - Math.max(r, g, b) / 255;
|
|
41
|
+
return [h, whiteness * 100, blackness * 100];
|
|
42
|
+
})();
|
|
43
|
+
|
|
44
|
+
const cmyk = (() => {
|
|
45
|
+
const k = 1 - Math.max(r, g, b) / 255,
|
|
46
|
+
c = (1 - r / 255 - k) / (1 - k) || 0,
|
|
47
|
+
m = (1 - g / 255 - k) / (1 - k) || 0,
|
|
48
|
+
y = (1 - b / 255 - k) / (1 - k) || 0;
|
|
49
|
+
return [c, m, y, k].map((x) => x * 100);
|
|
50
|
+
})();
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
hex: `#${[r, g, b].map((x) => x.toString(16).padStart(2, '0')).join('')}`,
|
|
54
|
+
rgb: `rgb(${r}, ${g}, ${b})`,
|
|
55
|
+
hsl: `hsl(${hsl[0].toFixed(1)}, ${hsl[1].toFixed(1)}%, ${hsl[2].toFixed(1)}%)`,
|
|
56
|
+
hsv: `hsv(${hsv[0].toFixed(1)}, ${hsv[1].toFixed(1)}%, ${hsv[2].toFixed(1)}%)`,
|
|
57
|
+
hwb: `hwb(${hwb[0].toFixed(1)}, ${hwb[1].toFixed(1)}%, ${hwb[2].toFixed(1)}%)`,
|
|
58
|
+
cmyk: `cmyk(${cmyk.map((x) => x.toFixed(1)).join('%, ')}%)`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -11,15 +11,15 @@ export default function convert(value, from, to) {
|
|
|
11
11
|
const conversions = {
|
|
12
12
|
celsius: {
|
|
13
13
|
fahrenheit: (val) => (val * 9) / 5 + 32,
|
|
14
|
-
kelvin: (val) => val + 273.15
|
|
14
|
+
kelvin: (val) => val + 273.15,
|
|
15
15
|
},
|
|
16
16
|
fahrenheit: {
|
|
17
17
|
celsius: (val) => ((val - 32) * 5) / 9,
|
|
18
|
-
kelvin: (val) => ((val - 32) * 5) / 9 + 273.15
|
|
18
|
+
kelvin: (val) => ((val - 32) * 5) / 9 + 273.15,
|
|
19
19
|
},
|
|
20
20
|
kelvin: {
|
|
21
21
|
celsius: (val) => val - 273.15,
|
|
22
|
-
fahrenheit: (val) => ((val - 273.15) * 9) / 5 + 32
|
|
22
|
+
fahrenheit: (val) => ((val - 273.15) * 9) / 5 + 32,
|
|
23
23
|
},
|
|
24
24
|
};
|
|
25
25
|
|
|
@@ -34,6 +34,6 @@ export default function convert(value, from, to) {
|
|
|
34
34
|
from,
|
|
35
35
|
to,
|
|
36
36
|
value: parseFloat(value),
|
|
37
|
-
result: convert(parseFloat(value))
|
|
37
|
+
result: convert(parseFloat(value)),
|
|
38
38
|
};
|
|
39
|
-
}
|
|
39
|
+
}
|