@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
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { Router, type Request, type Response } from 'express';
|
|
2
|
+
import { versions } from '../config/versions.js';
|
|
3
|
+
import { env } from '../config/env.js';
|
|
4
|
+
import { ipLimits } from '../storage/index.js';
|
|
5
|
+
import { chatStorage } from '../storage/index.js';
|
|
6
|
+
import { DOCS_URL, GITHUB_CACHE_TTL } from '../constants.js';
|
|
7
|
+
import { error } from '../utils/response.js';
|
|
8
|
+
|
|
9
|
+
const router = Router();
|
|
10
|
+
|
|
11
|
+
let activity: Record<string, unknown>[] = [];
|
|
12
|
+
let lastFetch = 0;
|
|
13
|
+
|
|
14
|
+
// Display version information
|
|
15
|
+
router.get('/:version', (req: Request, res: Response) => {
|
|
16
|
+
const version = req.params.version as string;
|
|
17
|
+
const versionConfig = versions[version]!;
|
|
18
|
+
|
|
19
|
+
const endpoints = Object.keys(versionConfig.endpoints).reduce<Record<string, unknown>>((acc, method) => {
|
|
20
|
+
const endpointList = versionConfig.endpoints[method as keyof typeof versionConfig.endpoints];
|
|
21
|
+
if (!endpointList) return acc;
|
|
22
|
+
acc[method] = endpointList
|
|
23
|
+
.filter(({ name }: { name: string }) => name !== 'website')
|
|
24
|
+
.sort((a: { name: string }, b: { name: string }) => a.name.localeCompare(b.name))
|
|
25
|
+
.reduce<Record<string, unknown>>(
|
|
26
|
+
(
|
|
27
|
+
group: Record<string, unknown>,
|
|
28
|
+
endpoint: { name: string; path?: string; children?: Record<string, string> },
|
|
29
|
+
) => {
|
|
30
|
+
if (endpoint.children) {
|
|
31
|
+
group[endpoint.name] = Object.keys(endpoint.children)
|
|
32
|
+
.sort((a: string, b: string) => a.localeCompare(b))
|
|
33
|
+
.reduce<Record<string, string>>((childGroup, childName) => {
|
|
34
|
+
childGroup[childName] = `/${version}${endpoint.children![childName]}`;
|
|
35
|
+
return childGroup;
|
|
36
|
+
}, {});
|
|
37
|
+
} else {
|
|
38
|
+
group[endpoint.name] = `/${version}${endpoint.path}`;
|
|
39
|
+
}
|
|
40
|
+
return group;
|
|
41
|
+
},
|
|
42
|
+
{},
|
|
43
|
+
);
|
|
44
|
+
return acc;
|
|
45
|
+
}, {});
|
|
46
|
+
|
|
47
|
+
res.jsonResponse({
|
|
48
|
+
version,
|
|
49
|
+
documentation: `${DOCS_URL}/${version}`,
|
|
50
|
+
endpoints,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Algorithms
|
|
55
|
+
router.get('/:version/algorithms', (req: Request, res: Response) => {
|
|
56
|
+
const { method, value, value2 } = req.query;
|
|
57
|
+
const { version } = req.params;
|
|
58
|
+
|
|
59
|
+
const algorithms = req.module.algorithms as Record<string, (v: string, v2?: string) => unknown>;
|
|
60
|
+
if (!algorithms || !algorithms[method as string]) {
|
|
61
|
+
error(res, 400, 'Please provide a valid algorithm (?method={algorithm})', `${version}/algorithms`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const answer = algorithms[method as string]!(value as string, value2 as string);
|
|
67
|
+
res.jsonResponse({ answer });
|
|
68
|
+
} catch (err) {
|
|
69
|
+
error(res, 400, (err as Error).message, `${req.version}/algorithms`);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Generate captcha
|
|
74
|
+
router.get('/:version/captcha', (req: Request, res: Response) => {
|
|
75
|
+
const text = req.query.text as string;
|
|
76
|
+
|
|
77
|
+
if (!text) {
|
|
78
|
+
error(res, 400, 'Please provide a valid argument (?text={text})', `${req.version}/captcha`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const result = req.module.captcha(text);
|
|
84
|
+
res.type('png').send(result);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
error(res, 400, (err as Error).message, `${req.version}/captcha`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Display stored data
|
|
91
|
+
router.get('/:version/chat', (req: Request, res: Response) => {
|
|
92
|
+
try {
|
|
93
|
+
const messages = req.module.chat('fetch', {
|
|
94
|
+
username: 'system',
|
|
95
|
+
storage: chatStorage,
|
|
96
|
+
});
|
|
97
|
+
res.jsonResponse(messages);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
error(res, 400, (err as Error).message);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// GET private chat error
|
|
104
|
+
router.get('/:version/chat/private', (_req: Request, res: Response) => {
|
|
105
|
+
error(res, 405, 'This endpoint only supports POST requests.');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Generate color
|
|
109
|
+
router.get('/:version/color', (req: Request, res: Response) => {
|
|
110
|
+
try {
|
|
111
|
+
const result = req.module.color();
|
|
112
|
+
res.jsonResponse(result);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
error(res, 400, (err as Error).message, `${req.version}/color`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Convert units
|
|
119
|
+
router.get('/:version/convert', (req: Request, res: Response) => {
|
|
120
|
+
const { value, from, to } = req.query;
|
|
121
|
+
|
|
122
|
+
if (!value || isNaN(Number(value))) {
|
|
123
|
+
error(res, 400, 'Please provide a valid value (?value={value})', `${req.version}/convert`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!from) {
|
|
127
|
+
error(res, 400, 'Please provide a valid source unit (&from={unit})', `${req.version}/convert`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (!to) {
|
|
131
|
+
error(res, 400, 'Please provide a valid target unit (&to={unit})', `${req.version}/convert`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const result = req.module.convert(value as string, from as string, to as string);
|
|
137
|
+
res.jsonResponse(result);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
error(res, 400, (err as Error).message, `${req.version}/convert`);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Generate domain informations
|
|
144
|
+
router.get('/:version/domain', (req: Request, res: Response) => {
|
|
145
|
+
try {
|
|
146
|
+
const result = req.module.domain();
|
|
147
|
+
res.jsonResponse(result);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
error(res, 400, (err as Error).message, `${req.version}/domain`);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// RPG Dice roller
|
|
154
|
+
router.get('/:version/dice', (req: Request, res: Response) => {
|
|
155
|
+
const { roll } = req.query;
|
|
156
|
+
const { version } = req.params;
|
|
157
|
+
|
|
158
|
+
const dice = (req.module as { dice?: (r: string) => unknown }).dice;
|
|
159
|
+
if (!dice) {
|
|
160
|
+
error(res, 404, `Endpoint not available in ${version}.`, `${version}/dice`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (!roll) {
|
|
164
|
+
error(res, 400, 'Please provide a roll notation (?roll=2d6+3)', `${version}/dice`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const result = dice(roll as string);
|
|
170
|
+
res.jsonResponse(result);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
error(res, 400, (err as Error).message, `${req.version}/dice`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// GET planning error
|
|
177
|
+
router.get('/:version/hyperplanning', (_req: Request, res: Response) => {
|
|
178
|
+
error(res, 405, 'This endpoint only supports POST requests.');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// GET hash error
|
|
182
|
+
router.get('/:version/hash', (_req: Request, res: Response) => {
|
|
183
|
+
error(res, 405, 'This endpoint only supports POST requests.');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Display API informations
|
|
187
|
+
router.get('/:version/infos', (req: Request, res: Response) => {
|
|
188
|
+
const endpoints = Object.values(versions[req.version]!.endpoints).flat();
|
|
189
|
+
|
|
190
|
+
const paths = endpoints.flatMap((e) => (e.children ? Object.values(e.children) : e.path ? [e.path] : []));
|
|
191
|
+
|
|
192
|
+
res.jsonResponse({
|
|
193
|
+
endpoints: new Set(paths).size,
|
|
194
|
+
last_version: Object.keys(versions).pop(),
|
|
195
|
+
documentation: DOCS_URL,
|
|
196
|
+
github: 'https://github.com/20syldev/api',
|
|
197
|
+
creation: 'November 25th 2024',
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Calculate Levenshtein distance
|
|
202
|
+
router.get('/:version/levenshtein', (req: Request, res: Response) => {
|
|
203
|
+
const { str1, str2 } = req.query;
|
|
204
|
+
|
|
205
|
+
if (!str1 || typeof str1 !== 'string') {
|
|
206
|
+
error(res, 400, 'Please provide a first string (?str1={string})', `${req.version}/levenshtein`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!str2 || typeof str2 !== 'string') {
|
|
210
|
+
error(res, 400, 'Please provide a second string (&str2={string})', `${req.version}/levenshtein`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = req.module.levenshtein(str1, str2);
|
|
216
|
+
res.jsonResponse(result);
|
|
217
|
+
} catch (err) {
|
|
218
|
+
error(res, 400, (err as Error).message, `${req.version}/levenshtein`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Generate personal data
|
|
223
|
+
router.get('/:version/personal', (req: Request, res: Response) => {
|
|
224
|
+
try {
|
|
225
|
+
const result = req.module.personal();
|
|
226
|
+
res.jsonResponse(result);
|
|
227
|
+
} catch (err) {
|
|
228
|
+
error(res, 400, (err as Error).message, `${req.version}/personal`);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Generate QR Code
|
|
233
|
+
router.get('/:version/qrcode', async (req: Request, res: Response) => {
|
|
234
|
+
const { url } = req.query;
|
|
235
|
+
|
|
236
|
+
if (!url) {
|
|
237
|
+
error(res, 400, 'Please provide a valid url (?url={URL})', `${req.version}/qrcode`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const result = await req.module.qrcode(url as string);
|
|
243
|
+
res.jsonResponse(result);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
error(res, 400, (err as Error).message, `${req.version}/qrcode`);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Statistics on a list of numbers
|
|
250
|
+
router.get('/:version/statistics', (req: Request, res: Response) => {
|
|
251
|
+
const { values } = req.query;
|
|
252
|
+
const { version } = req.params;
|
|
253
|
+
|
|
254
|
+
const statistics = (req.module as { statistics?: (v: string) => unknown }).statistics;
|
|
255
|
+
if (!statistics) {
|
|
256
|
+
error(res, 404, `Endpoint not available in ${version}.`, `${version}/statistics`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (!values) {
|
|
260
|
+
error(res, 400, 'Please provide a list of values (?values=1,2,3)', `${version}/statistics`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
const result = statistics(values as string);
|
|
266
|
+
res.jsonResponse(result);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
error(res, 400, (err as Error).message, `${req.version}/statistics`);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// GET tic-tac-toe errors
|
|
273
|
+
router.get('/:version/tic-tac-toe', (_req: Request, res: Response) => {
|
|
274
|
+
error(res, 405, 'This endpoint only supports POST requests.');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
router.get('/:version/tic-tac-toe/fetch', (_req: Request, res: Response) => {
|
|
278
|
+
error(res, 405, 'This endpoint only supports POST requests.');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
router.get('/:version/tic-tac-toe/list', (_req: Request, res: Response) => {
|
|
282
|
+
error(res, 405, 'This endpoint only supports POST requests.');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Display or generate time informations
|
|
286
|
+
router.get('/:version/time', (req: Request, res: Response) => {
|
|
287
|
+
const { type = 'live', start, end, format, timezone } = req.query;
|
|
288
|
+
|
|
289
|
+
const validFormats = [
|
|
290
|
+
'iso',
|
|
291
|
+
'utc',
|
|
292
|
+
'timestamp',
|
|
293
|
+
'locale',
|
|
294
|
+
'date',
|
|
295
|
+
'time',
|
|
296
|
+
'year',
|
|
297
|
+
'month',
|
|
298
|
+
'day',
|
|
299
|
+
'hour',
|
|
300
|
+
'minute',
|
|
301
|
+
'second',
|
|
302
|
+
'ms',
|
|
303
|
+
'dayOfWeek',
|
|
304
|
+
'dayOfYear',
|
|
305
|
+
'weekNumber',
|
|
306
|
+
'timezone',
|
|
307
|
+
'timezoneOffset',
|
|
308
|
+
];
|
|
309
|
+
const validTimezones = ['UTC', 'America/New_York', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'];
|
|
310
|
+
|
|
311
|
+
if (type !== 'live' && type !== 'random') {
|
|
312
|
+
error(res, 400, 'Please provide a valid type (?type={type})', `${req.version}/time`);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (start && !Date.parse(start as string)) {
|
|
316
|
+
error(res, 400, 'Please provide a valid start date (?start={YYYY-MM-DD})', `${req.version}/time`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (end && !Date.parse(end as string)) {
|
|
320
|
+
error(res, 400, 'Please provide a valid end date (?end={YYYY-MM-DD})', `${req.version}/time`);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (format && !validFormats.includes(format as string)) {
|
|
324
|
+
error(res, 400, 'Please provide a valid format (?format={format})', `${req.version}/time`);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
if (timezone && !validTimezones.includes(timezone as string)) {
|
|
328
|
+
error(res, 400, 'Please provide a valid timezone (?timezone={timezone})', `${req.version}/time`);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const time = req.module.time(
|
|
334
|
+
type as string,
|
|
335
|
+
start as string,
|
|
336
|
+
end as string,
|
|
337
|
+
format as string,
|
|
338
|
+
timezone as string,
|
|
339
|
+
);
|
|
340
|
+
res.jsonResponse(time);
|
|
341
|
+
} catch (err) {
|
|
342
|
+
error(res, 400, (err as Error).message, `${req.version}/time`);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// GET token error
|
|
347
|
+
router.get('/:version/token', (_req: Request, res: Response) => {
|
|
348
|
+
error(res, 405, 'This endpoint only supports POST requests.');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// Generate username
|
|
352
|
+
router.get('/:version/username', (req: Request, res: Response) => {
|
|
353
|
+
try {
|
|
354
|
+
const result = req.module.username();
|
|
355
|
+
res.jsonResponse(result);
|
|
356
|
+
} catch (err) {
|
|
357
|
+
error(res, 400, (err as Error).message, `${req.version}/username`);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Display informations for owner's website
|
|
362
|
+
router.get('/:version/website', async (req: Request, res: Response) => {
|
|
363
|
+
const currentTime = Date.now();
|
|
364
|
+
|
|
365
|
+
if (currentTime - lastFetch >= GITHUB_CACHE_TTL) {
|
|
366
|
+
try {
|
|
367
|
+
const username = '20syldev';
|
|
368
|
+
const token = env.GITHUB_TOKEN;
|
|
369
|
+
const lastYear = new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString().split('T')[0];
|
|
370
|
+
|
|
371
|
+
const query = `
|
|
372
|
+
{
|
|
373
|
+
user(login: "${username}") {
|
|
374
|
+
contributionsCollection(from: "${lastYear}T00:00:00Z") {
|
|
375
|
+
contributionCalendar {
|
|
376
|
+
totalContributions
|
|
377
|
+
weeks {
|
|
378
|
+
firstDay
|
|
379
|
+
contributionDays {
|
|
380
|
+
date
|
|
381
|
+
contributionCount
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}`;
|
|
388
|
+
|
|
389
|
+
const apiResponse = await fetch('https://api.github.com/graphql', {
|
|
390
|
+
method: 'POST',
|
|
391
|
+
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
392
|
+
body: JSON.stringify({ query }),
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if (!apiResponse.ok) throw new Error('Error fetching data.');
|
|
396
|
+
|
|
397
|
+
const data = (await apiResponse.json()) as {
|
|
398
|
+
data?: {
|
|
399
|
+
user?: {
|
|
400
|
+
contributionsCollection?: {
|
|
401
|
+
contributionCalendar?: {
|
|
402
|
+
weeks?: {
|
|
403
|
+
firstDay: string;
|
|
404
|
+
contributionDays: { date: string; contributionCount: number }[];
|
|
405
|
+
}[];
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
};
|
|
409
|
+
};
|
|
410
|
+
};
|
|
411
|
+
const user = data?.data?.user;
|
|
412
|
+
|
|
413
|
+
const weeks = user?.contributionsCollection?.contributionCalendar?.weeks || [];
|
|
414
|
+
activity = weeks.map((w) => ({
|
|
415
|
+
week: w.firstDay,
|
|
416
|
+
total: w.contributionDays.reduce((sum, d) => sum + d.contributionCount, 0),
|
|
417
|
+
days: w.contributionDays.map((d) => ({ date: d.date, count: d.contributionCount })),
|
|
418
|
+
}));
|
|
419
|
+
|
|
420
|
+
lastFetch = currentTime;
|
|
421
|
+
} catch {
|
|
422
|
+
activity = [];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const response: Record<string, unknown> = {
|
|
427
|
+
versions: {
|
|
428
|
+
2048: env.G_2048,
|
|
429
|
+
api: env.API,
|
|
430
|
+
cdn: env.CDN,
|
|
431
|
+
coop_api: env.COOP_API,
|
|
432
|
+
coop_status: env.COOP_STATUS,
|
|
433
|
+
chat: env.CHAT,
|
|
434
|
+
digit: env.DIGIT,
|
|
435
|
+
doc_coopbot: env.DOC_COOPBOT,
|
|
436
|
+
docs: env.DOCS,
|
|
437
|
+
donut: env.DONUT,
|
|
438
|
+
drawio_plugin: env.DRAWIO_PLUGIN,
|
|
439
|
+
flowers: env.FLOWERS,
|
|
440
|
+
gemsync: env.GEMSYNC,
|
|
441
|
+
gft: env.GFT,
|
|
442
|
+
gitsite: env.GITSITE,
|
|
443
|
+
lebonchar: env.LEBONCHAR,
|
|
444
|
+
logger: env.LOGGER,
|
|
445
|
+
logs: env.LOGS,
|
|
446
|
+
logvault: env.LOGVAULT,
|
|
447
|
+
lyah: env.LYAH,
|
|
448
|
+
minify: env.MINIFY,
|
|
449
|
+
mn: env.MN,
|
|
450
|
+
monitoring: env.MONITORING,
|
|
451
|
+
morpion: env.MORPION,
|
|
452
|
+
nitrogen: env.NITROGEN,
|
|
453
|
+
old_database: env.OLD_DATABASE,
|
|
454
|
+
password: env.PASSWORD,
|
|
455
|
+
php: env.PHP,
|
|
456
|
+
planning: env.PLANNING,
|
|
457
|
+
ping: env.PING,
|
|
458
|
+
portfolio: env.PORTFOLIO,
|
|
459
|
+
python_api: env.PYTHON_API,
|
|
460
|
+
readme: env.README,
|
|
461
|
+
timestamp: env.TIMESTAMP,
|
|
462
|
+
terminal: env.TERMINAL,
|
|
463
|
+
valentine: env.VALENTINE,
|
|
464
|
+
wrkit: env.WRKIT,
|
|
465
|
+
zpki: env.ZPKI,
|
|
466
|
+
},
|
|
467
|
+
patched_projects: env.PATCH,
|
|
468
|
+
updated_projects: env.RECENT,
|
|
469
|
+
new_projects: env.NEW,
|
|
470
|
+
sub_domains: env.DOMAINS,
|
|
471
|
+
stats: {
|
|
472
|
+
1: env.STATS1,
|
|
473
|
+
2: env.STATS2,
|
|
474
|
+
3: env.STATS3,
|
|
475
|
+
4: env.STATS4,
|
|
476
|
+
5: Object.keys(ipLimits).length,
|
|
477
|
+
activity,
|
|
478
|
+
},
|
|
479
|
+
tag: env.TAG,
|
|
480
|
+
active: env.ACTIVE,
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const key = req.query.key as string | undefined;
|
|
484
|
+
if (key) {
|
|
485
|
+
if (['__proto__', 'constructor', 'prototype'].some((p) => key.includes(p))) {
|
|
486
|
+
error(res, 400, 'Invalid key.');
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const keys = key.split('.');
|
|
491
|
+
let result: unknown = response;
|
|
492
|
+
|
|
493
|
+
for (const k of keys) {
|
|
494
|
+
if (result == null || typeof result !== 'object' || !(k in result)) {
|
|
495
|
+
error(res, 404, `Key '${key}' not found.`);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
result = (result as Record<string, unknown>)[k];
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
res.jsonResponse({ [key]: result });
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
res.jsonResponse(response);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
export default router;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Router, type Request, type Response } from 'express';
|
|
2
|
+
import { versions } from '../config/versions.js';
|
|
3
|
+
import { ipLimits } from '../storage/index.js';
|
|
4
|
+
import { logger } from '../middleware/logger.js';
|
|
5
|
+
import { APP_VERSION, DOCS_URL, START_TIME } from '../constants.js';
|
|
6
|
+
|
|
7
|
+
const router = Router();
|
|
8
|
+
|
|
9
|
+
router.get('/', (req: Request, res: Response) => {
|
|
10
|
+
const base = `${req.protocol}://${req.get('host')}`;
|
|
11
|
+
const links = Object.keys(versions).reduce<Record<string, string>>((link, version) => {
|
|
12
|
+
link[version] = `${base}/${version}`;
|
|
13
|
+
return link;
|
|
14
|
+
}, {});
|
|
15
|
+
|
|
16
|
+
res.jsonResponse({
|
|
17
|
+
documentation: DOCS_URL,
|
|
18
|
+
latest: `${base}/latest`,
|
|
19
|
+
health: `${base}/health`,
|
|
20
|
+
logs: `${base}/logs`,
|
|
21
|
+
versions: links,
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
router.get('/health', (_req: Request, res: Response) => {
|
|
26
|
+
const mem = process.memoryUsage();
|
|
27
|
+
|
|
28
|
+
res.jsonResponse({
|
|
29
|
+
status: 'ok',
|
|
30
|
+
uptime: Math.floor((Date.now() - START_TIME) / 1000),
|
|
31
|
+
version: APP_VERSION,
|
|
32
|
+
node: process.version,
|
|
33
|
+
memory: {
|
|
34
|
+
rss: `${(mem.rss / 1024 / 1024).toFixed(1)} MB`,
|
|
35
|
+
heap_used: `${(mem.heapUsed / 1024 / 1024).toFixed(1)} MB`,
|
|
36
|
+
heap_total: `${(mem.heapTotal / 1024 / 1024).toFixed(1)} MB`,
|
|
37
|
+
},
|
|
38
|
+
connections: Object.keys(ipLimits).length,
|
|
39
|
+
logs: logger.entries().length,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
router.get('/logs', (_req: Request, res: Response) => {
|
|
44
|
+
res.jsonResponse(logger.entries());
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
router.get('/latest', (_req: Request, res: Response) => {
|
|
48
|
+
const latest = Object.keys(versions).pop()!;
|
|
49
|
+
res.redirect(`/${latest}`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
router.get('/latest/{*rest}', (req: Request, res: Response) => {
|
|
53
|
+
const latest = Object.keys(versions).pop()!;
|
|
54
|
+
const rest = (req.params as Record<string, string>).rest;
|
|
55
|
+
res.redirect(`/${latest}/${rest}`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export default router;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Router, type Request, type Response } from 'express';
|
|
2
|
+
import { ticTacToeStorage } from '../storage/index.js';
|
|
3
|
+
import { error } from '../utils/response.js';
|
|
4
|
+
import { supportsRest } from '../utils/version.js';
|
|
5
|
+
|
|
6
|
+
const router = Router();
|
|
7
|
+
|
|
8
|
+
// Play a tic-tac-toe move (REST: PATCH /tic-tac-toe/:game)
|
|
9
|
+
router.patch('/:version/tic-tac-toe/:game', (req: Request, res: Response) => {
|
|
10
|
+
if (!supportsRest(req)) {
|
|
11
|
+
error(res, 405, 'PATCH is only supported in v4+.', `${req.version}/tic-tac-toe`);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const game = req.params.game as string;
|
|
16
|
+
const { username, move, session } = (req.body as Record<string, string>) || {};
|
|
17
|
+
|
|
18
|
+
if (!username) {
|
|
19
|
+
error(res, 400, 'Please provide a username (?username={username})');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!move) {
|
|
23
|
+
error(res, 400, 'Please provide a valid move (&move={move})');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!session) {
|
|
27
|
+
error(res, 400, 'Please provide a valid session ID (&session={ID})');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const result = req.module.tic_tac_toe('play', {
|
|
33
|
+
username,
|
|
34
|
+
move,
|
|
35
|
+
session,
|
|
36
|
+
game,
|
|
37
|
+
storage: ticTacToeStorage,
|
|
38
|
+
});
|
|
39
|
+
res.jsonResponse(result);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
error(res, 400, (err as Error).message);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export default router;
|