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