@20syldev/api 4.8.0 → 4.9.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/README.md +84 -47
- package/package.json +2 -1
- package/.github/FUNDING.yml +0 -1
- package/.prettierrc +0 -7
- package/eslint.config.js +0 -24
- package/robots.txt +0 -74
- package/src/app.ts +0 -53
- package/src/config/env.ts +0 -87
- package/src/config/plans.ts +0 -61
- package/src/config/versions.ts +0 -121
- package/src/constants.ts +0 -61
- package/src/favicon.ico +0 -0
- package/src/middleware/cors.ts +0 -23
- package/src/middleware/error.ts +0 -16
- package/src/middleware/json.ts +0 -9
- package/src/middleware/logger.ts +0 -32
- package/src/middleware/ratelimit.ts +0 -89
- package/src/middleware/version.ts +0 -40
- package/src/modules/v3/algorithms.js +0 -218
- package/src/modules/v3/captcha.js +0 -60
- package/src/modules/v3/chat.js +0 -111
- package/src/modules/v3/color.js +0 -60
- package/src/modules/v3/convert.js +0 -39
- package/src/modules/v3/domain.js +0 -86
- package/src/modules/v3/hash.js +0 -16
- package/src/modules/v3/hyperplanning.js +0 -56
- package/src/modules/v3/levenshtein.js +0 -46
- package/src/modules/v3/personal.js +0 -139
- package/src/modules/v3/qrcode.js +0 -20
- package/src/modules/v3/tic_tac_toe.js +0 -242
- package/src/modules/v3/time.js +0 -98
- package/src/modules/v3/token.js +0 -54
- package/src/modules/v3/username.js +0 -94
- package/src/modules/v3/utils.js +0 -77
- package/src/modules/v3.js +0 -15
- package/src/modules/v4/address.ts +0 -412
- package/src/modules/v4/agent.ts +0 -104
- package/src/modules/v4/algorithms.ts +0 -276
- package/src/modules/v4/captcha.ts +0 -114
- package/src/modules/v4/chat.ts +0 -130
- package/src/modules/v4/color.ts +0 -75
- package/src/modules/v4/convert.ts +0 -127
- package/src/modules/v4/cron.ts +0 -221
- package/src/modules/v4/dice.ts +0 -46
- package/src/modules/v4/domain.ts +0 -86
- package/src/modules/v4/encode.ts +0 -241
- package/src/modules/v4/geo.ts +0 -64
- package/src/modules/v4/hash.ts +0 -32
- package/src/modules/v4/hyperplanning.ts +0 -93
- package/src/modules/v4/ip.ts +0 -134
- package/src/modules/v4/levenshtein.ts +0 -46
- package/src/modules/v4/palette.ts +0 -71
- package/src/modules/v4/password.ts +0 -379
- package/src/modules/v4/personal.ts +0 -140
- package/src/modules/v4/placeholder.ts +0 -197
- package/src/modules/v4/qrcode.ts +0 -121
- package/src/modules/v4/regex.ts +0 -67
- package/src/modules/v4/statistics.ts +0 -62
- package/src/modules/v4/text.ts +0 -330
- package/src/modules/v4/tic_tac_toe.ts +0 -264
- package/src/modules/v4/time.ts +0 -149
- package/src/modules/v4/token.ts +0 -53
- package/src/modules/v4/username.ts +0 -94
- package/src/modules/v4/validate.ts +0 -76
- package/src/modules/v4.ts +0 -29
- package/src/routes/delete.ts +0 -72
- package/src/routes/get.ts +0 -856
- package/src/routes/index.ts +0 -59
- package/src/routes/patch.ts +0 -45
- package/src/routes/post.ts +0 -209
- package/src/storage/index.ts +0 -16
- package/src/types/express.d.ts +0 -17
- package/src/types/storage.ts +0 -48
- package/src/utils/colors.ts +0 -91
- package/src/utils/helpers.ts +0 -90
- package/src/utils/response.ts +0 -28
- package/tests/integration/api.test.ts +0 -772
- package/tests/tsconfig.json +0 -3
- package/tests/unit/address.test.ts +0 -84
- package/tests/unit/agent.test.ts +0 -113
- package/tests/unit/algorithms.test.ts +0 -121
- package/tests/unit/captcha.test.ts +0 -62
- package/tests/unit/chat.test.ts +0 -54
- package/tests/unit/color.test.ts +0 -54
- package/tests/unit/convert.test.ts +0 -68
- package/tests/unit/cron.test.ts +0 -110
- package/tests/unit/dice.test.ts +0 -58
- package/tests/unit/domain.test.ts +0 -48
- package/tests/unit/encode.test.ts +0 -109
- package/tests/unit/geo.test.ts +0 -46
- package/tests/unit/hash.test.ts +0 -45
- package/tests/unit/hyperplanning.test.ts +0 -97
- package/tests/unit/ip.test.ts +0 -140
- package/tests/unit/levenshtein.test.ts +0 -35
- package/tests/unit/palette.test.ts +0 -54
- package/tests/unit/password.test.ts +0 -86
- package/tests/unit/personal.test.ts +0 -66
- package/tests/unit/placeholder.test.ts +0 -58
- package/tests/unit/qrcode.test.ts +0 -83
- package/tests/unit/regex.test.ts +0 -95
- package/tests/unit/statistics.test.ts +0 -58
- package/tests/unit/text.test.ts +0 -108
- package/tests/unit/tic_tac_toe.test.ts +0 -56
- package/tests/unit/time.test.ts +0 -115
- package/tests/unit/token.test.ts +0 -62
- package/tests/unit/username.test.ts +0 -35
- package/tests/unit/validate.test.ts +0 -72
- package/tsconfig.json +0 -17
- package/tsconfig.test.json +0 -9
package/src/routes/get.ts
DELETED
|
@@ -1,856 +0,0 @@
|
|
|
1
|
-
import { type Request, type Response, Router } from 'express';
|
|
2
|
-
|
|
3
|
-
import { env } from '../config/env.js';
|
|
4
|
-
import { versions } from '../config/versions.js';
|
|
5
|
-
import { DOCS_URL, GITHUB_CACHE_TTL } from '../constants.js';
|
|
6
|
-
import type { AddressResult } from '../modules/v4/address.js';
|
|
7
|
-
import type { UserAgentResult } from '../modules/v4/agent.js';
|
|
8
|
-
import type { CaptchaOptions, CaptchaResult } from '../modules/v4/captcha.js';
|
|
9
|
-
import type { ColorResult } from '../modules/v4/color.js';
|
|
10
|
-
import type { IpResult } from '../modules/v4/ip.js';
|
|
11
|
-
import type { PasswordResult } from '../modules/v4/password.js';
|
|
12
|
-
import type { QRCodeOptions, QRCodeResult } from '../modules/v4/qrcode.js';
|
|
13
|
-
import { chatStorage, ipLimits } from '../storage/index.js';
|
|
14
|
-
import { since } from '../utils/helpers.js';
|
|
15
|
-
import { error } from '../utils/response.js';
|
|
16
|
-
|
|
17
|
-
const router = Router();
|
|
18
|
-
|
|
19
|
-
let activity: Record<string, unknown>[] = [];
|
|
20
|
-
let lastFetch = 0;
|
|
21
|
-
|
|
22
|
-
// Display version information
|
|
23
|
-
router.get('/:version', (req: Request, res: Response) => {
|
|
24
|
-
const version = req.params.version as string;
|
|
25
|
-
const versionConfig = versions[version]!;
|
|
26
|
-
|
|
27
|
-
const endpoints = Object.keys(versionConfig.endpoints).reduce<Record<string, unknown>>((acc, method) => {
|
|
28
|
-
const endpointList = versionConfig.endpoints[method as keyof typeof versionConfig.endpoints];
|
|
29
|
-
if (!endpointList) return acc;
|
|
30
|
-
acc[method] = endpointList
|
|
31
|
-
.filter(({ name }: { name: string }) => name !== 'website')
|
|
32
|
-
.sort((a: { name: string }, b: { name: string }) => a.name.localeCompare(b.name))
|
|
33
|
-
.reduce<Record<string, unknown>>(
|
|
34
|
-
(
|
|
35
|
-
group: Record<string, unknown>,
|
|
36
|
-
endpoint: { name: string; path?: string; children?: Record<string, string> },
|
|
37
|
-
) => {
|
|
38
|
-
if (endpoint.children) {
|
|
39
|
-
group[endpoint.name] = Object.keys(endpoint.children)
|
|
40
|
-
.sort((a: string, b: string) => a.localeCompare(b))
|
|
41
|
-
.reduce<Record<string, string>>((childGroup, childName) => {
|
|
42
|
-
childGroup[childName] = `/${version}${endpoint.children![childName]}`;
|
|
43
|
-
return childGroup;
|
|
44
|
-
}, {});
|
|
45
|
-
} else {
|
|
46
|
-
group[endpoint.name] = `/${version}${endpoint.path}`;
|
|
47
|
-
}
|
|
48
|
-
return group;
|
|
49
|
-
},
|
|
50
|
-
{},
|
|
51
|
-
);
|
|
52
|
-
return acc;
|
|
53
|
-
}, {});
|
|
54
|
-
|
|
55
|
-
res.jsonResponse({
|
|
56
|
-
version,
|
|
57
|
-
documentation: `${DOCS_URL}/${version}`,
|
|
58
|
-
endpoints,
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Algorithms
|
|
63
|
-
router.get('/:version/algorithms', (req: Request, res: Response) => {
|
|
64
|
-
const { method, value, value2 } = req.query;
|
|
65
|
-
const { version } = req.params;
|
|
66
|
-
|
|
67
|
-
const algorithms = req.module.algorithms as Record<string, (v: string, v2?: string) => unknown>;
|
|
68
|
-
if (!algorithms || !method || !Object.hasOwn(algorithms, method as string)) {
|
|
69
|
-
error(res, 400, 'Please provide a valid algorithm (?method={algorithm})', `${version}/algorithms`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
const answer = algorithms[method as string]!(value as string, value2 as string);
|
|
75
|
-
res.jsonResponse({ answer });
|
|
76
|
-
} catch (err) {
|
|
77
|
-
error(res, 400, (err as Error).message, `${req.version}/algorithms`);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// Generate captcha
|
|
82
|
-
router.get('/:version/captcha', (req: Request, res: Response) => {
|
|
83
|
-
try {
|
|
84
|
-
if (since(req.version, 4)) {
|
|
85
|
-
const captchaFn = req.module.captcha as (o: CaptchaOptions) => CaptchaResult;
|
|
86
|
-
const result = captchaFn({
|
|
87
|
-
text: req.query.text as string | undefined,
|
|
88
|
-
length: req.query.length ? Number(req.query.length) : undefined,
|
|
89
|
-
width: req.query.width ? Number(req.query.width) : undefined,
|
|
90
|
-
height: req.query.height ? Number(req.query.height) : undefined,
|
|
91
|
-
noise: req.query.noise as CaptchaOptions['noise'],
|
|
92
|
-
bg: req.query.bg as string | undefined,
|
|
93
|
-
color: req.query.color as string | undefined,
|
|
94
|
-
});
|
|
95
|
-
res.set('X-Captcha-Text', result.text);
|
|
96
|
-
res.type('png').send(result.body);
|
|
97
|
-
} else {
|
|
98
|
-
const text = req.query.text as string;
|
|
99
|
-
if (!text) {
|
|
100
|
-
error(res, 400, 'Please provide a valid argument (?text={text})', `${req.version}/captcha`);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
const result = (req.module.captcha as (t: string) => Buffer)(text);
|
|
104
|
-
res.type('png').send(result);
|
|
105
|
-
}
|
|
106
|
-
} catch (err) {
|
|
107
|
-
error(res, 400, (err as Error).message, `${req.version}/captcha`);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// Display stored data
|
|
112
|
-
router.get('/:version/chat', (req: Request, res: Response) => {
|
|
113
|
-
try {
|
|
114
|
-
const messages = req.module.chat('fetch', {
|
|
115
|
-
username: 'system',
|
|
116
|
-
storage: chatStorage,
|
|
117
|
-
});
|
|
118
|
-
res.jsonResponse(messages);
|
|
119
|
-
} catch (err) {
|
|
120
|
-
error(res, 400, (err as Error).message);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
// GET private chat error
|
|
125
|
-
router.get('/:version/chat/private', (_req: Request, res: Response) => {
|
|
126
|
-
error(res, 405, 'This endpoint only supports POST requests.');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Generate color
|
|
130
|
-
router.get('/:version/color', (req: Request, res: Response) => {
|
|
131
|
-
try {
|
|
132
|
-
if (since(req.version, 4)) {
|
|
133
|
-
const colorFn = req.module.color as (hex?: string) => ColorResult;
|
|
134
|
-
const hex = req.query.hex as string | undefined;
|
|
135
|
-
const result = colorFn(hex || undefined);
|
|
136
|
-
res.jsonResponse(result);
|
|
137
|
-
} else {
|
|
138
|
-
const result = (req.module.color as () => Record<string, string>)();
|
|
139
|
-
res.jsonResponse(result);
|
|
140
|
-
}
|
|
141
|
-
} catch (err) {
|
|
142
|
-
error(res, 400, (err as Error).message, `${req.version}/color`);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Convert units
|
|
147
|
-
router.get('/:version/convert', (req: Request, res: Response) => {
|
|
148
|
-
const { value, from, to } = req.query;
|
|
149
|
-
|
|
150
|
-
if (!value || isNaN(Number(value))) {
|
|
151
|
-
error(res, 400, 'Please provide a valid value (?value={value})', `${req.version}/convert`);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (!from) {
|
|
155
|
-
error(res, 400, 'Please provide a valid source unit (&from={unit})', `${req.version}/convert`);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (!to) {
|
|
159
|
-
error(res, 400, 'Please provide a valid target unit (&to={unit})', `${req.version}/convert`);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
if (since(req.version, 4)) {
|
|
165
|
-
const convertFn = req.module.convert as (v: number, f: string, t: string) => Record<string, unknown>;
|
|
166
|
-
const result = convertFn(Number(value), from as string, to as string);
|
|
167
|
-
res.jsonResponse(result);
|
|
168
|
-
} else {
|
|
169
|
-
const result = (req.module.convert as (v: string, f: string, t: string) => Record<string, unknown>)(
|
|
170
|
-
value as string,
|
|
171
|
-
from as string,
|
|
172
|
-
to as string,
|
|
173
|
-
);
|
|
174
|
-
res.jsonResponse(result);
|
|
175
|
-
}
|
|
176
|
-
} catch (err) {
|
|
177
|
-
error(res, 400, (err as Error).message, `${req.version}/convert`);
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// Generate a fictional postal address
|
|
182
|
-
router.get('/:version/address', (req: Request, res: Response) => {
|
|
183
|
-
const { country, count } = req.query;
|
|
184
|
-
const { version } = req.params;
|
|
185
|
-
|
|
186
|
-
const addressFn = (req.module as { address?: (c?: string, n?: number) => AddressResult }).address;
|
|
187
|
-
if (!addressFn) {
|
|
188
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/address`);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const parsedCount = count !== undefined ? parseInt(count as string, 10) : 1;
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
const result = addressFn(country as string | undefined, parsedCount);
|
|
196
|
-
res.jsonResponse(result);
|
|
197
|
-
} catch (err) {
|
|
198
|
-
error(res, 400, (err as Error).message, `${req.version}/address`);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Echo request headers
|
|
203
|
-
router.get('/:version/headers', (req: Request, res: Response) => {
|
|
204
|
-
const redacted = new Set(['authorization', 'cookie', 'set-cookie', 'proxy-authorization']);
|
|
205
|
-
let headers: Record<string, unknown> = {};
|
|
206
|
-
|
|
207
|
-
for (const [k, v] of Object.entries(req.headers)) {
|
|
208
|
-
headers[k] = redacted.has(k) ? '[redacted]' : v;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
const filter = req.query.filter as string | undefined;
|
|
212
|
-
if (filter) {
|
|
213
|
-
const keys = new Set(filter.split(',').map((k) => k.trim().toLowerCase()));
|
|
214
|
-
headers = Object.fromEntries(Object.entries(headers).filter(([k]) => keys.has(k)));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
res.jsonResponse({
|
|
218
|
-
count: Object.keys(headers).length,
|
|
219
|
-
headers,
|
|
220
|
-
ip: req.ip,
|
|
221
|
-
method: req.method,
|
|
222
|
-
url: req.originalUrl,
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// Analyze an IP address
|
|
227
|
-
router.get('/:version/ip', (req: Request, res: Response) => {
|
|
228
|
-
const address = (req.query.address as string | undefined) ?? req.ip ?? '';
|
|
229
|
-
try {
|
|
230
|
-
const ipFn = (req.module as Record<string, unknown>).ip as (a: string) => IpResult;
|
|
231
|
-
const result = ipFn(address);
|
|
232
|
-
res.jsonResponse(result);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
error(res, 400, (err as Error).message, `${req.version}/ip`);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// Parse a User-Agent string
|
|
239
|
-
router.get('/:version/agent', (req: Request, res: Response) => {
|
|
240
|
-
const ua = (req.query.ua as string | undefined) ?? (req.headers['user-agent'] as string) ?? '';
|
|
241
|
-
try {
|
|
242
|
-
const agentFn = (req.module as Record<string, unknown>).agent as (ua: string) => UserAgentResult;
|
|
243
|
-
const result = agentFn(ua);
|
|
244
|
-
res.jsonResponse(result);
|
|
245
|
-
} catch (err) {
|
|
246
|
-
error(res, 400, (err as Error).message, `${req.version}/agent`);
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// Generate domain informations
|
|
251
|
-
router.get('/:version/domain', (req: Request, res: Response) => {
|
|
252
|
-
try {
|
|
253
|
-
const result = req.module.domain();
|
|
254
|
-
res.jsonResponse(result);
|
|
255
|
-
} catch (err) {
|
|
256
|
-
error(res, 400, (err as Error).message, `${req.version}/domain`);
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
// Parse a cron expression and compute next execution dates
|
|
261
|
-
router.get('/:version/cron', (req: Request, res: Response) => {
|
|
262
|
-
const { expr, count, from, timezone } = req.query;
|
|
263
|
-
const { version } = req.params;
|
|
264
|
-
|
|
265
|
-
const cronFn = (req.module as { cron?: (e: string, n?: number, f?: string, tz?: string) => unknown }).cron;
|
|
266
|
-
if (!cronFn) {
|
|
267
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/cron`);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
if (!expr) {
|
|
271
|
-
error(res, 400, 'Please provide a cron expression (?expr=* * * * *)', `${version}/cron`);
|
|
272
|
-
return;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
const result = cronFn(
|
|
277
|
-
expr as string,
|
|
278
|
-
count !== undefined ? parseInt(count as string, 10) : 5,
|
|
279
|
-
from as string | undefined,
|
|
280
|
-
(timezone as string) ?? 'UTC',
|
|
281
|
-
);
|
|
282
|
-
res.jsonResponse(result);
|
|
283
|
-
} catch (err) {
|
|
284
|
-
error(res, 400, (err as Error).message, `${req.version}/cron`);
|
|
285
|
-
}
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// RPG Dice roller
|
|
289
|
-
router.get('/:version/dice', (req: Request, res: Response) => {
|
|
290
|
-
const { roll } = req.query;
|
|
291
|
-
const { version } = req.params;
|
|
292
|
-
|
|
293
|
-
const dice = (req.module as { dice?: (r: string) => unknown }).dice;
|
|
294
|
-
if (!dice) {
|
|
295
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/dice`);
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (!roll) {
|
|
299
|
-
error(res, 400, 'Please provide a roll notation (?roll=2d6+3)', `${version}/dice`);
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
const result = dice(roll as string);
|
|
305
|
-
res.jsonResponse(result);
|
|
306
|
-
} catch (err) {
|
|
307
|
-
error(res, 400, (err as Error).message, `${req.version}/dice`);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
// Encode / decode text
|
|
312
|
-
router.get('/:version/encode', (req: Request, res: Response) => {
|
|
313
|
-
const { method, text, shift } = req.query;
|
|
314
|
-
const { version } = req.params;
|
|
315
|
-
|
|
316
|
-
const encode = (req.module as { encode?: Record<string, (v: string, v2?: string) => string> }).encode;
|
|
317
|
-
if (!encode) {
|
|
318
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/encode`);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
if (!method || !Object.hasOwn(encode, method as string)) {
|
|
322
|
-
error(res, 400, 'Please provide a valid method (?method={method})', `${version}/encode`);
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
const result = encode[method as string]!(text as string, shift as string);
|
|
328
|
-
res.jsonResponse({ method, result });
|
|
329
|
-
} catch (err) {
|
|
330
|
-
error(res, 400, (err as Error).message, `${req.version}/encode`);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
// Geographic distance and bearing between two coordinates
|
|
335
|
-
router.get('/:version/geo', (req: Request, res: Response) => {
|
|
336
|
-
const { lat1, lon1, lat2, lon2 } = req.query;
|
|
337
|
-
const { version } = req.params;
|
|
338
|
-
|
|
339
|
-
const geo = (req.module as { geo?: (a: string, b: string, c: string, d: string) => unknown }).geo;
|
|
340
|
-
if (!geo) {
|
|
341
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/geo`);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
if (lat1 === undefined || lon1 === undefined || lat2 === undefined || lon2 === undefined) {
|
|
345
|
-
error(res, 400, 'Please provide lat1, lon1, lat2 and lon2', `${version}/geo`);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
try {
|
|
350
|
-
const result = geo(lat1 as string, lon1 as string, lat2 as string, lon2 as string);
|
|
351
|
-
res.jsonResponse(result);
|
|
352
|
-
} catch (err) {
|
|
353
|
-
error(res, 400, (err as Error).message, `${req.version}/geo`);
|
|
354
|
-
}
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// GET planning error
|
|
358
|
-
router.get('/:version/hyperplanning', (_req: Request, res: Response) => {
|
|
359
|
-
error(res, 405, 'This endpoint only supports POST requests.');
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// GET hash error
|
|
363
|
-
router.get('/:version/hash', (_req: Request, res: Response) => {
|
|
364
|
-
error(res, 405, 'This endpoint only supports POST requests.');
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
// Display API informations
|
|
368
|
-
router.get('/:version/infos', (req: Request, res: Response) => {
|
|
369
|
-
const endpoints = Object.values(versions[req.version]!.endpoints).flat();
|
|
370
|
-
|
|
371
|
-
const paths = endpoints.flatMap((e) => (e.children ? Object.values(e.children) : e.path ? [e.path] : []));
|
|
372
|
-
|
|
373
|
-
res.jsonResponse({
|
|
374
|
-
endpoints: new Set(paths).size,
|
|
375
|
-
last_version: Object.keys(versions).pop(),
|
|
376
|
-
documentation: DOCS_URL,
|
|
377
|
-
github: 'https://github.com/20syldev/api',
|
|
378
|
-
creation: 'November 25th 2024',
|
|
379
|
-
});
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
// Calculate Levenshtein distance
|
|
383
|
-
router.get('/:version/levenshtein', (req: Request, res: Response) => {
|
|
384
|
-
const { str1, str2 } = req.query;
|
|
385
|
-
|
|
386
|
-
if (!str1 || typeof str1 !== 'string') {
|
|
387
|
-
error(res, 400, 'Please provide a first string (?str1={string})', `${req.version}/levenshtein`);
|
|
388
|
-
return;
|
|
389
|
-
}
|
|
390
|
-
if (!str2 || typeof str2 !== 'string') {
|
|
391
|
-
error(res, 400, 'Please provide a second string (&str2={string})', `${req.version}/levenshtein`);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
try {
|
|
396
|
-
const result = req.module.levenshtein(str1, str2);
|
|
397
|
-
res.jsonResponse(result);
|
|
398
|
-
} catch (err) {
|
|
399
|
-
error(res, 400, (err as Error).message, `${req.version}/levenshtein`);
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// Generate a color palette from a base color
|
|
404
|
-
router.get('/:version/palette', (req: Request, res: Response) => {
|
|
405
|
-
const { color, type } = req.query;
|
|
406
|
-
const { version } = req.params;
|
|
407
|
-
|
|
408
|
-
const palette = (req.module as { palette?: (c: string, t: string) => unknown }).palette;
|
|
409
|
-
if (!palette) {
|
|
410
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/palette`);
|
|
411
|
-
return;
|
|
412
|
-
}
|
|
413
|
-
if (!color) {
|
|
414
|
-
error(res, 400, 'Please provide a base color (?color=#ff6600)', `${version}/palette`);
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
if (!type) {
|
|
418
|
-
error(res, 400, 'Please provide a palette type (&type=complementary)', `${version}/palette`);
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
const result = palette(color as string, type as string);
|
|
424
|
-
res.jsonResponse(result);
|
|
425
|
-
} catch (err) {
|
|
426
|
-
error(res, 400, (err as Error).message, `${req.version}/palette`);
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
// Generate a password or passphrase
|
|
431
|
-
router.get('/:version/password', (req: Request, res: Response) => {
|
|
432
|
-
const { type, length, uppercase, lowercase, digits, symbols, exclude, count, separator } = req.query;
|
|
433
|
-
const { version } = req.params;
|
|
434
|
-
|
|
435
|
-
const passwordFn = (
|
|
436
|
-
req.module as { password?: (t: string, l: number, o: Record<string, unknown>) => PasswordResult }
|
|
437
|
-
).password;
|
|
438
|
-
if (!passwordFn) {
|
|
439
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/password`);
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const parseBool = (v: unknown, def: boolean): boolean => (v === undefined ? def : v !== 'false');
|
|
444
|
-
|
|
445
|
-
try {
|
|
446
|
-
const result = passwordFn(
|
|
447
|
-
(type as string) ?? 'random',
|
|
448
|
-
length !== undefined ? parseInt(length as string, 10) : 16,
|
|
449
|
-
{
|
|
450
|
-
uppercase: parseBool(uppercase, true),
|
|
451
|
-
lowercase: parseBool(lowercase, true),
|
|
452
|
-
digits: parseBool(digits, true),
|
|
453
|
-
symbols: parseBool(symbols, false),
|
|
454
|
-
exclude: (exclude as string) ?? '',
|
|
455
|
-
count: count !== undefined ? parseInt(count as string, 10) : 1,
|
|
456
|
-
separator: (separator as string) ?? '-',
|
|
457
|
-
},
|
|
458
|
-
);
|
|
459
|
-
res.jsonResponse(result);
|
|
460
|
-
} catch (err) {
|
|
461
|
-
error(res, 400, (err as Error).message, `${req.version}/password`);
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
|
|
465
|
-
// Generate personal data
|
|
466
|
-
router.get('/:version/personal', (req: Request, res: Response) => {
|
|
467
|
-
try {
|
|
468
|
-
const result = req.module.personal();
|
|
469
|
-
res.jsonResponse(result);
|
|
470
|
-
} catch (err) {
|
|
471
|
-
error(res, 400, (err as Error).message, `${req.version}/personal`);
|
|
472
|
-
}
|
|
473
|
-
});
|
|
474
|
-
|
|
475
|
-
// Generate a placeholder image or skeleton
|
|
476
|
-
router.get('/:version/placeholder', (req: Request, res: Response) => {
|
|
477
|
-
const { type = 'image' } = req.query;
|
|
478
|
-
const { version } = req.params;
|
|
479
|
-
|
|
480
|
-
const placeholder = (
|
|
481
|
-
req.module as {
|
|
482
|
-
placeholder?: (
|
|
483
|
-
t: string,
|
|
484
|
-
q: Record<string, string | undefined>,
|
|
485
|
-
) => { type: string; contentType: string; body: Buffer | string };
|
|
486
|
-
}
|
|
487
|
-
).placeholder;
|
|
488
|
-
if (!placeholder) {
|
|
489
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/placeholder`);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
try {
|
|
494
|
-
const result = placeholder(type as string, req.query as Record<string, string | undefined>);
|
|
495
|
-
res.type(result.contentType).send(result.body);
|
|
496
|
-
} catch (err) {
|
|
497
|
-
error(res, 400, (err as Error).message, `${req.version}/placeholder`);
|
|
498
|
-
}
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
// Generate QR Code
|
|
502
|
-
router.get('/:version/qrcode', async (req: Request, res: Response) => {
|
|
503
|
-
const { url } = req.query;
|
|
504
|
-
|
|
505
|
-
if (!url) {
|
|
506
|
-
error(res, 400, 'Please provide a valid url (?url={URL})', `${req.version}/qrcode`);
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
try {
|
|
511
|
-
if (since(req.version, 4)) {
|
|
512
|
-
const qrcodeFn = req.module.qrcode as (o: QRCodeOptions) => Promise<QRCodeResult>;
|
|
513
|
-
const result = await qrcodeFn({
|
|
514
|
-
url: url as string,
|
|
515
|
-
size: req.query.size ? Number(req.query.size) : undefined,
|
|
516
|
-
margin: req.query.margin ? Number(req.query.margin) : undefined,
|
|
517
|
-
correction: req.query.correction as QRCodeOptions['correction'],
|
|
518
|
-
dark: req.query.dark as string | undefined,
|
|
519
|
-
light: req.query.light as string | undefined,
|
|
520
|
-
icon: req.query.icon as string | undefined,
|
|
521
|
-
iconSize: req.query.iconSize ? Number(req.query.iconSize) : undefined,
|
|
522
|
-
iconPadding: req.query.iconPadding ? Number(req.query.iconPadding) : undefined,
|
|
523
|
-
iconRadius: req.query.iconRadius ? Number(req.query.iconRadius) : undefined,
|
|
524
|
-
format: req.query.format as QRCodeOptions['format'],
|
|
525
|
-
});
|
|
526
|
-
if (result.contentType === 'application/json') {
|
|
527
|
-
res.jsonResponse(result.body);
|
|
528
|
-
} else {
|
|
529
|
-
res.type(result.contentType).send(result.body);
|
|
530
|
-
}
|
|
531
|
-
} else {
|
|
532
|
-
const result = await (req.module.qrcode as (u: string) => Promise<string>)(url as string);
|
|
533
|
-
res.jsonResponse(result);
|
|
534
|
-
}
|
|
535
|
-
} catch (err) {
|
|
536
|
-
error(res, 400, (err as Error).message, `${req.version}/qrcode`);
|
|
537
|
-
}
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
// Test a regex pattern against a text
|
|
541
|
-
router.get('/:version/regex', (req: Request, res: Response) => {
|
|
542
|
-
const { pattern, text, flags } = req.query;
|
|
543
|
-
const { version } = req.params;
|
|
544
|
-
|
|
545
|
-
const regexFn = (req.module as { regex?: (p: string, t: string, f?: string) => unknown }).regex;
|
|
546
|
-
if (!regexFn) {
|
|
547
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/regex`);
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
if (!pattern) {
|
|
551
|
-
error(res, 400, 'Please provide a pattern (?pattern={regex})', `${version}/regex`);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
if (!text) {
|
|
555
|
-
error(res, 400, 'Please provide a text (&text={string})', `${version}/regex`);
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
try {
|
|
560
|
-
const result = regexFn(pattern as string, text as string, flags as string | undefined);
|
|
561
|
-
res.jsonResponse(result);
|
|
562
|
-
} catch (err) {
|
|
563
|
-
error(res, 400, (err as Error).message, `${req.version}/regex`);
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
|
|
567
|
-
// Statistics on a list of numbers
|
|
568
|
-
router.get('/:version/statistics', (req: Request, res: Response) => {
|
|
569
|
-
const { values } = req.query;
|
|
570
|
-
const { version } = req.params;
|
|
571
|
-
|
|
572
|
-
const statistics = (req.module as { statistics?: (v: string) => unknown }).statistics;
|
|
573
|
-
if (!statistics) {
|
|
574
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/statistics`);
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
if (!values) {
|
|
578
|
-
error(res, 400, 'Please provide a list of values (?values=1,2,3)', `${version}/statistics`);
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
try {
|
|
583
|
-
const result = statistics(values as string);
|
|
584
|
-
res.jsonResponse(result);
|
|
585
|
-
} catch (err) {
|
|
586
|
-
error(res, 400, (err as Error).message, `${req.version}/statistics`);
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
// Text utilities (slug, stats, lorem, number)
|
|
591
|
-
router.get('/:version/text', (req: Request, res: Response) => {
|
|
592
|
-
const { method, value, type, count, lang, text } = req.query;
|
|
593
|
-
const { version } = req.params;
|
|
594
|
-
|
|
595
|
-
const textMod = (req.module as { text?: Record<string, (...args: string[]) => unknown> }).text;
|
|
596
|
-
if (!textMod) {
|
|
597
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/text`);
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
if (!method || !Object.hasOwn(textMod, method as string)) {
|
|
601
|
-
error(res, 400, 'Please provide a valid method (?method={slug|stats|lorem|number})', `${version}/text`);
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
try {
|
|
606
|
-
let result: unknown;
|
|
607
|
-
switch (method) {
|
|
608
|
-
case 'slug':
|
|
609
|
-
case 'stats':
|
|
610
|
-
result = textMod[method as string]!((value ?? text) as string);
|
|
611
|
-
break;
|
|
612
|
-
case 'lorem':
|
|
613
|
-
result = textMod.lorem!((type as string) || 'words', (count as string) || '5');
|
|
614
|
-
break;
|
|
615
|
-
case 'number':
|
|
616
|
-
result = textMod.number!(value as string, (lang as string) || 'en');
|
|
617
|
-
break;
|
|
618
|
-
default:
|
|
619
|
-
throw new Error('Unknown method');
|
|
620
|
-
}
|
|
621
|
-
res.jsonResponse({ method, result });
|
|
622
|
-
} catch (err) {
|
|
623
|
-
error(res, 400, (err as Error).message, `${req.version}/text`);
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
// Validate data (luhn, iban, email)
|
|
628
|
-
router.get('/:version/validate', (req: Request, res: Response) => {
|
|
629
|
-
const { type, value } = req.query;
|
|
630
|
-
const { version } = req.params;
|
|
631
|
-
|
|
632
|
-
const validate = (req.module as { validate?: Record<string, (v: string) => unknown> }).validate;
|
|
633
|
-
if (!validate) {
|
|
634
|
-
error(res, 404, `Endpoint not available in ${version}.`, `${version}/validate`);
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
if (!type || !Object.hasOwn(validate, type as string)) {
|
|
638
|
-
error(res, 400, 'Please provide a valid type (?type={luhn|iban|email})', `${version}/validate`);
|
|
639
|
-
return;
|
|
640
|
-
}
|
|
641
|
-
if (!value) {
|
|
642
|
-
error(res, 400, 'Please provide a value (&value={value})', `${version}/validate`);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
try {
|
|
647
|
-
const result = validate[type as string]!(value as string);
|
|
648
|
-
res.jsonResponse(result);
|
|
649
|
-
} catch (err) {
|
|
650
|
-
error(res, 400, (err as Error).message, `${req.version}/validate`);
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
|
|
654
|
-
// GET tic-tac-toe errors
|
|
655
|
-
router.get('/:version/tic-tac-toe', (_req: Request, res: Response) => {
|
|
656
|
-
error(res, 405, 'This endpoint only supports POST requests.');
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
router.get('/:version/tic-tac-toe/fetch', (_req: Request, res: Response) => {
|
|
660
|
-
error(res, 405, 'This endpoint only supports POST requests.');
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
router.get('/:version/tic-tac-toe/list', (_req: Request, res: Response) => {
|
|
664
|
-
error(res, 405, 'This endpoint only supports POST requests.');
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
// Display or generate time informations, or compute a countdown
|
|
668
|
-
router.get('/:version/time', (req: Request, res: Response) => {
|
|
669
|
-
const { type = 'live', start, end, format, timezone, target } = req.query;
|
|
670
|
-
|
|
671
|
-
try {
|
|
672
|
-
const timeFn = req.module.time as (
|
|
673
|
-
t: string,
|
|
674
|
-
s?: string,
|
|
675
|
-
e?: string,
|
|
676
|
-
f?: string,
|
|
677
|
-
tz?: string,
|
|
678
|
-
target?: string,
|
|
679
|
-
) => Record<string, unknown>;
|
|
680
|
-
const time = timeFn(
|
|
681
|
-
type as string,
|
|
682
|
-
start as string,
|
|
683
|
-
end as string,
|
|
684
|
-
format as string,
|
|
685
|
-
timezone as string,
|
|
686
|
-
target as string,
|
|
687
|
-
);
|
|
688
|
-
res.jsonResponse(time);
|
|
689
|
-
} catch (err) {
|
|
690
|
-
error(res, 400, (err as Error).message, `${req.version}/time`);
|
|
691
|
-
}
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
// GET token error
|
|
695
|
-
router.get('/:version/token', (_req: Request, res: Response) => {
|
|
696
|
-
error(res, 405, 'This endpoint only supports POST requests.');
|
|
697
|
-
});
|
|
698
|
-
|
|
699
|
-
// Generate username
|
|
700
|
-
router.get('/:version/username', (req: Request, res: Response) => {
|
|
701
|
-
try {
|
|
702
|
-
const result = req.module.username();
|
|
703
|
-
res.jsonResponse(result);
|
|
704
|
-
} catch (err) {
|
|
705
|
-
error(res, 400, (err as Error).message, `${req.version}/username`);
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
// Display informations for owner's website
|
|
710
|
-
router.get('/:version/website', async (req: Request, res: Response) => {
|
|
711
|
-
const currentTime = Date.now();
|
|
712
|
-
|
|
713
|
-
if (currentTime - lastFetch >= GITHUB_CACHE_TTL) {
|
|
714
|
-
try {
|
|
715
|
-
const username = '20syldev';
|
|
716
|
-
const token = env.GITHUB_TOKEN;
|
|
717
|
-
const lastYear = new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString().split('T')[0];
|
|
718
|
-
|
|
719
|
-
const query = `
|
|
720
|
-
{
|
|
721
|
-
user(login: "${username}") {
|
|
722
|
-
contributionsCollection(from: "${lastYear}T00:00:00Z") {
|
|
723
|
-
contributionCalendar {
|
|
724
|
-
totalContributions
|
|
725
|
-
weeks {
|
|
726
|
-
firstDay
|
|
727
|
-
contributionDays {
|
|
728
|
-
date
|
|
729
|
-
contributionCount
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}`;
|
|
736
|
-
|
|
737
|
-
const apiResponse = await fetch('https://api.github.com/graphql', {
|
|
738
|
-
method: 'POST',
|
|
739
|
-
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
740
|
-
body: JSON.stringify({ query }),
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
if (!apiResponse.ok) throw new Error('Error fetching data.');
|
|
744
|
-
|
|
745
|
-
const data = (await apiResponse.json()) as {
|
|
746
|
-
data?: {
|
|
747
|
-
user?: {
|
|
748
|
-
contributionsCollection?: {
|
|
749
|
-
contributionCalendar?: {
|
|
750
|
-
weeks?: {
|
|
751
|
-
firstDay: string;
|
|
752
|
-
contributionDays: { date: string; contributionCount: number }[];
|
|
753
|
-
}[];
|
|
754
|
-
};
|
|
755
|
-
};
|
|
756
|
-
};
|
|
757
|
-
};
|
|
758
|
-
};
|
|
759
|
-
const user = data?.data?.user;
|
|
760
|
-
|
|
761
|
-
const weeks = user?.contributionsCollection?.contributionCalendar?.weeks || [];
|
|
762
|
-
activity = weeks.map((w) => ({
|
|
763
|
-
week: w.firstDay,
|
|
764
|
-
total: w.contributionDays.reduce((sum, d) => sum + d.contributionCount, 0),
|
|
765
|
-
days: w.contributionDays.map((d) => ({ date: d.date, count: d.contributionCount })),
|
|
766
|
-
}));
|
|
767
|
-
|
|
768
|
-
lastFetch = currentTime;
|
|
769
|
-
} catch {
|
|
770
|
-
activity = [];
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
const response: Record<string, unknown> = {
|
|
775
|
-
versions: {
|
|
776
|
-
2048: env.G_2048,
|
|
777
|
-
api: env.API,
|
|
778
|
-
cdn: env.CDN,
|
|
779
|
-
coop_api: env.COOP_API,
|
|
780
|
-
coop_status: env.COOP_STATUS,
|
|
781
|
-
chat: env.CHAT,
|
|
782
|
-
digit: env.DIGIT,
|
|
783
|
-
doc_coopbot: env.DOC_COOPBOT,
|
|
784
|
-
docs: env.DOCS,
|
|
785
|
-
donut: env.DONUT,
|
|
786
|
-
drawio_plugin: env.DRAWIO_PLUGIN,
|
|
787
|
-
flowers: env.FLOWERS,
|
|
788
|
-
gemsync: env.GEMSYNC,
|
|
789
|
-
gft: env.GFT,
|
|
790
|
-
gitsite: env.GITSITE,
|
|
791
|
-
lebonchar: env.LEBONCHAR,
|
|
792
|
-
logger: env.LOGGER,
|
|
793
|
-
logs: env.LOGS,
|
|
794
|
-
logvault: env.LOGVAULT,
|
|
795
|
-
lyah: env.LYAH,
|
|
796
|
-
minify: env.MINIFY,
|
|
797
|
-
mn: env.MN,
|
|
798
|
-
monitoring: env.MONITORING,
|
|
799
|
-
morpion: env.MORPION,
|
|
800
|
-
nitrogen: env.NITROGEN,
|
|
801
|
-
old_database: env.OLD_DATABASE,
|
|
802
|
-
password: env.PASSWORD,
|
|
803
|
-
php: env.PHP,
|
|
804
|
-
planning: env.PLANNING,
|
|
805
|
-
ping: env.PING,
|
|
806
|
-
portfolio: env.PORTFOLIO,
|
|
807
|
-
python_api: env.PYTHON_API,
|
|
808
|
-
readme: env.README,
|
|
809
|
-
timestamp: env.TIMESTAMP,
|
|
810
|
-
terminal: env.TERMINAL,
|
|
811
|
-
valentine: env.VALENTINE,
|
|
812
|
-
wrkit: env.WRKIT,
|
|
813
|
-
zpki: env.ZPKI,
|
|
814
|
-
},
|
|
815
|
-
patched_projects: env.PATCH,
|
|
816
|
-
updated_projects: env.RECENT,
|
|
817
|
-
new_projects: env.NEW,
|
|
818
|
-
sub_domains: env.DOMAINS,
|
|
819
|
-
stats: {
|
|
820
|
-
1: env.STATS1,
|
|
821
|
-
2: env.STATS2,
|
|
822
|
-
3: env.STATS3,
|
|
823
|
-
4: env.STATS4,
|
|
824
|
-
5: Object.keys(ipLimits).length,
|
|
825
|
-
activity,
|
|
826
|
-
},
|
|
827
|
-
tag: env.TAG,
|
|
828
|
-
active: env.ACTIVE,
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
const key = req.query.key as string | undefined;
|
|
832
|
-
if (key) {
|
|
833
|
-
if (['__proto__', 'constructor', 'prototype'].some((p) => key.includes(p))) {
|
|
834
|
-
error(res, 400, 'Invalid key.');
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
const keys = key.split('.');
|
|
839
|
-
let result: unknown = response;
|
|
840
|
-
|
|
841
|
-
for (const k of keys) {
|
|
842
|
-
if (result == null || typeof result !== 'object' || !(k in result)) {
|
|
843
|
-
error(res, 404, `Key '${key}' not found.`);
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
|
-
result = (result as Record<string, unknown>)[k];
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
res.jsonResponse({ [key]: result });
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
res.jsonResponse(response);
|
|
854
|
-
});
|
|
855
|
-
|
|
856
|
-
export default router;
|