@20syldev/api 3.5.0 → 4.0.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 (180) hide show
  1. package/.prettierrc +7 -0
  2. package/README.md +32 -33
  3. package/dist/app.js +36 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/config/env.js +73 -0
  6. package/dist/config/env.js.map +1 -0
  7. package/dist/config/versions.js +72 -0
  8. package/dist/config/versions.js.map +1 -0
  9. package/dist/constants.js +42 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/middleware/cors.js +15 -0
  12. package/dist/middleware/cors.js.map +1 -0
  13. package/dist/middleware/error.js +6 -0
  14. package/dist/middleware/error.js.map +1 -0
  15. package/dist/middleware/json.js +8 -0
  16. package/dist/middleware/json.js.map +1 -0
  17. package/dist/middleware/logger.js +26 -0
  18. package/dist/middleware/logger.js.map +1 -0
  19. package/dist/middleware/ratelimit.js +58 -0
  20. package/dist/middleware/ratelimit.js.map +1 -0
  21. package/dist/middleware/version.js +30 -0
  22. package/dist/middleware/version.js.map +1 -0
  23. package/dist/modules/v3/algorithms.js +221 -0
  24. package/dist/modules/v3/algorithms.js.map +1 -0
  25. package/{modules → dist/modules}/v3/captcha.js +4 -13
  26. package/dist/modules/v3/captcha.js.map +1 -0
  27. package/dist/modules/v3/chat.js +103 -0
  28. package/dist/modules/v3/chat.js.map +1 -0
  29. package/{modules → dist/modules}/v3/color.js +12 -13
  30. package/dist/modules/v3/color.js.map +1 -0
  31. package/dist/modules/v3/convert.js +41 -0
  32. package/dist/modules/v3/convert.js.map +1 -0
  33. package/dist/modules/v3/domain.js +83 -0
  34. package/dist/modules/v3/domain.js.map +1 -0
  35. package/dist/modules/v3/hash.js +16 -0
  36. package/dist/modules/v3/hash.js.map +1 -0
  37. package/dist/modules/v3/hyperplanning.js +50 -0
  38. package/dist/modules/v3/hyperplanning.js.map +1 -0
  39. package/dist/modules/v3/levenshtein.js +38 -0
  40. package/dist/modules/v3/levenshtein.js.map +1 -0
  41. package/dist/modules/v3/personal.js +129 -0
  42. package/dist/modules/v3/personal.js.map +1 -0
  43. package/dist/modules/v3/qrcode.js +21 -0
  44. package/dist/modules/v3/qrcode.js.map +1 -0
  45. package/dist/modules/v3/tic_tac_toe.js +221 -0
  46. package/dist/modules/v3/tic_tac_toe.js.map +1 -0
  47. package/dist/modules/v3/time.js +88 -0
  48. package/dist/modules/v3/time.js.map +1 -0
  49. package/{modules → dist/modules}/v3/token.js +10 -11
  50. package/dist/modules/v3/token.js.map +1 -0
  51. package/dist/modules/v3/username.js +92 -0
  52. package/dist/modules/v3/username.js.map +1 -0
  53. package/dist/modules/v3/utils.js +70 -0
  54. package/dist/modules/v3/utils.js.map +1 -0
  55. package/dist/modules/v3.js +16 -0
  56. package/dist/modules/v3.js.map +1 -0
  57. package/dist/modules/v4/algorithms.js +200 -0
  58. package/dist/modules/v4/algorithms.js.map +1 -0
  59. package/dist/modules/v4/captcha.js +35 -0
  60. package/dist/modules/v4/captcha.js.map +1 -0
  61. package/dist/modules/v4/chat.js +92 -0
  62. package/dist/modules/v4/chat.js.map +1 -0
  63. package/dist/modules/v4/color.js +43 -0
  64. package/dist/modules/v4/color.js.map +1 -0
  65. package/dist/modules/v4/convert.js +33 -0
  66. package/dist/modules/v4/convert.js.map +1 -0
  67. package/dist/modules/v4/dice.js +30 -0
  68. package/dist/modules/v4/dice.js.map +1 -0
  69. package/dist/modules/v4/domain.js +78 -0
  70. package/dist/modules/v4/domain.js.map +1 -0
  71. package/dist/modules/v4/encode.js +165 -0
  72. package/dist/modules/v4/encode.js.map +1 -0
  73. package/dist/modules/v4/geo.js +41 -0
  74. package/dist/modules/v4/geo.js.map +1 -0
  75. package/dist/modules/v4/hash.js +9 -0
  76. package/dist/modules/v4/hash.js.map +1 -0
  77. package/dist/modules/v4/hyperplanning.js +42 -0
  78. package/dist/modules/v4/hyperplanning.js.map +1 -0
  79. package/dist/modules/v4/levenshtein.js +29 -0
  80. package/dist/modules/v4/levenshtein.js.map +1 -0
  81. package/dist/modules/v4/palette.js +89 -0
  82. package/dist/modules/v4/palette.js.map +1 -0
  83. package/dist/modules/v4/personal.js +125 -0
  84. package/dist/modules/v4/personal.js.map +1 -0
  85. package/dist/modules/v4/placeholder.js +96 -0
  86. package/dist/modules/v4/placeholder.js.map +1 -0
  87. package/dist/modules/v4/qrcode.js +12 -0
  88. package/dist/modules/v4/qrcode.js.map +1 -0
  89. package/dist/modules/v4/statistics.js +41 -0
  90. package/dist/modules/v4/statistics.js.map +1 -0
  91. package/dist/modules/v4/text.js +293 -0
  92. package/dist/modules/v4/text.js.map +1 -0
  93. package/dist/modules/v4/tic_tac_toe.js +206 -0
  94. package/dist/modules/v4/tic_tac_toe.js.map +1 -0
  95. package/dist/modules/v4/time.js +73 -0
  96. package/dist/modules/v4/time.js.map +1 -0
  97. package/dist/modules/v4/token.js +36 -0
  98. package/dist/modules/v4/token.js.map +1 -0
  99. package/dist/modules/v4/username.js +87 -0
  100. package/dist/modules/v4/username.js.map +1 -0
  101. package/dist/modules/v4/utils.js +26 -0
  102. package/dist/modules/v4/utils.js.map +1 -0
  103. package/dist/modules/v4/validate.js +54 -0
  104. package/dist/modules/v4/validate.js.map +1 -0
  105. package/dist/modules/v4.js +18 -0
  106. package/dist/modules/v4.js.map +1 -0
  107. package/dist/routes/delete.js +65 -0
  108. package/dist/routes/delete.js.map +1 -0
  109. package/dist/routes/get.js +441 -0
  110. package/dist/routes/get.js.map +1 -0
  111. package/dist/routes/index.js +50 -0
  112. package/dist/routes/index.js.map +1 -0
  113. package/dist/routes/patch.js +41 -0
  114. package/dist/routes/patch.js.map +1 -0
  115. package/dist/routes/post.js +185 -0
  116. package/dist/routes/post.js.map +1 -0
  117. package/dist/storage/index.js +13 -0
  118. package/dist/storage/index.js.map +1 -0
  119. package/dist/types/storage.js +2 -0
  120. package/dist/types/storage.js.map +1 -0
  121. package/dist/utils/response.js +10 -0
  122. package/dist/utils/response.js.map +1 -0
  123. package/dist/utils/version.js +3 -0
  124. package/dist/utils/version.js.map +1 -0
  125. package/eslint.config.js +20 -0
  126. package/package.json +30 -15
  127. package/src/app.ts +44 -0
  128. package/src/config/env.ts +81 -0
  129. package/src/config/versions.ts +80 -0
  130. package/src/constants.ts +28 -0
  131. package/src/middleware/cors.ts +17 -0
  132. package/src/middleware/error.ts +7 -0
  133. package/src/middleware/json.ts +9 -0
  134. package/src/middleware/logger.ts +31 -0
  135. package/src/middleware/ratelimit.ts +66 -0
  136. package/src/middleware/version.ts +39 -0
  137. package/{modules → src/modules}/v3/algorithms.js +1 -1
  138. package/src/modules/v3/captcha.js +60 -0
  139. package/{modules → src/modules}/v3/chat.js +6 -4
  140. package/src/modules/v3/color.js +60 -0
  141. package/{modules → src/modules}/v3/convert.js +5 -5
  142. package/{modules → src/modules}/v3/domain.js +51 -4
  143. package/{modules → src/modules}/v3/hash.js +1 -1
  144. package/{modules → src/modules}/v3/hyperplanning.js +11 -10
  145. package/{modules → src/modules}/v3/levenshtein.js +3 -3
  146. package/{modules → src/modules}/v3/personal.js +20 -8
  147. package/{modules → src/modules}/v3/qrcode.js +1 -1
  148. package/{modules → src/modules}/v3/tic_tac_toe.js +24 -18
  149. package/{modules → src/modules}/v3/time.js +22 -11
  150. package/src/modules/v3/token.js +54 -0
  151. package/src/modules/v3/username.js +94 -0
  152. package/{modules → src/modules}/v3/utils.js +2 -2
  153. package/{modules → src/modules}/v3.js +1 -1
  154. package/src/modules/v4/algorithms.ts +155 -0
  155. package/src/modules/v4/captcha.ts +49 -0
  156. package/src/modules/v4/chat.ts +99 -0
  157. package/src/modules/v4/color.ts +64 -0
  158. package/src/modules/v4/convert.ts +38 -0
  159. package/src/modules/v4/domain.ts +81 -0
  160. package/src/modules/v4/hash.ts +9 -0
  161. package/src/modules/v4/hyperplanning.ts +58 -0
  162. package/src/modules/v4/levenshtein.ts +38 -0
  163. package/src/modules/v4/personal.ts +147 -0
  164. package/src/modules/v4/qrcode.ts +11 -0
  165. package/src/modules/v4/tic_tac_toe.ts +223 -0
  166. package/src/modules/v4/time.ts +92 -0
  167. package/src/modules/v4/token.ts +43 -0
  168. package/src/modules/v4/username.ts +89 -0
  169. package/src/modules/v4/utils.ts +38 -0
  170. package/src/modules/v4.ts +15 -0
  171. package/src/routes/get.ts +461 -0
  172. package/src/routes/index.ts +58 -0
  173. package/src/routes/post.ts +200 -0
  174. package/src/storage/index.ts +16 -0
  175. package/src/types/express.d.ts +17 -0
  176. package/src/types/storage.ts +48 -0
  177. package/src/utils/response.ts +11 -0
  178. package/tsconfig.json +17 -0
  179. package/app.js +0 -865
  180. package/modules/v3/username.js +0 -32
@@ -0,0 +1,43 @@
1
+ import { randomBytes } from 'crypto';
2
+ import { v4 } from 'uuid';
3
+ import { MIN_TOKEN_LENGTH, MAX_TOKEN_LENGTH } from '../../constants.js';
4
+
5
+ export default function token(len: number, type: string = 'alphanum'): string {
6
+ if (isNaN(len) || len < MIN_TOKEN_LENGTH) {
7
+ throw new Error('Length must be a number greater than or equal to 12');
8
+ }
9
+
10
+ if (len > MAX_TOKEN_LENGTH) {
11
+ throw new Error('Length cannot exceed 4096');
12
+ }
13
+
14
+ const genToken = (chars: string, length: number): string => {
15
+ return Array.from({ length }, () => {
16
+ return chars[Math.floor(Math.random() * chars.length)];
17
+ }).join('');
18
+ };
19
+
20
+ const tokenTypes: Record<string, () => string> = {
21
+ alpha: () => genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', len),
22
+ alphanum: () => genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', len),
23
+ base64: () =>
24
+ randomBytes(Math.ceil(len * 0.75))
25
+ .toString('base64')
26
+ .slice(0, len),
27
+ hex: () =>
28
+ randomBytes(Math.ceil(len * 0.5))
29
+ .toString('hex')
30
+ .slice(0, len),
31
+ num: () => genToken('0123456789', len),
32
+ punct: () => genToken('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~', len),
33
+ urlsafe: () => genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_', len),
34
+ uuid: () => v4().replace(/-/g, '').slice(0, len),
35
+ };
36
+
37
+ const lowerType = type.toLowerCase();
38
+ if (!tokenTypes[lowerType]) {
39
+ throw new Error(`Invalid token type. Valid types: ${Object.keys(tokenTypes).join(', ')}`);
40
+ }
41
+
42
+ return tokenTypes[lowerType]!();
43
+ }
@@ -0,0 +1,89 @@
1
+ import { random, randomNumber } from './utils.js';
2
+
3
+ export default function username(): Record<string, unknown> {
4
+ const adj = [
5
+ 'Happy',
6
+ 'Silly',
7
+ 'Clever',
8
+ 'Creative',
9
+ 'Brave',
10
+ 'Gentle',
11
+ 'Kind',
12
+ 'Funny',
13
+ 'Wise',
14
+ 'Charming',
15
+ 'Sincere',
16
+ 'Resourceful',
17
+ 'Patient',
18
+ 'Energetic',
19
+ 'Adventurous',
20
+ 'Ambitious',
21
+ 'Courageous',
22
+ 'Courteous',
23
+ 'Determined',
24
+ ];
25
+ const ani = [
26
+ 'Cat',
27
+ 'Dog',
28
+ 'Tiger',
29
+ 'Elephant',
30
+ 'Monkey',
31
+ 'Penguin',
32
+ 'Dolphin',
33
+ 'Lion',
34
+ 'Bear',
35
+ 'Fox',
36
+ 'Owl',
37
+ 'Giraffe',
38
+ 'Zebra',
39
+ 'Koala',
40
+ 'Rabbit',
41
+ 'Squirrel',
42
+ 'Panda',
43
+ 'Horse',
44
+ 'Wolf',
45
+ 'Eagle',
46
+ ];
47
+ const job = [
48
+ 'Writer',
49
+ 'Artist',
50
+ 'Musician',
51
+ 'Explorer',
52
+ 'Scientist',
53
+ 'Engineer',
54
+ 'Athlete',
55
+ 'Chef',
56
+ 'Doctor',
57
+ 'Teacher',
58
+ 'Lawyer',
59
+ 'Entrepreneur',
60
+ 'Actor',
61
+ 'Dancer',
62
+ 'Photographer',
63
+ 'Architect',
64
+ 'Pilot',
65
+ 'Designer',
66
+ 'Journalist',
67
+ 'Veterinarian',
68
+ ];
69
+
70
+ const nombre = randomNumber(0, 99);
71
+ const choix: Record<string, () => string> = {
72
+ adj_num: () => random(adj) + nombre,
73
+ ani_num: () => random(ani) + nombre,
74
+ pro_num: () => random(job) + nombre,
75
+ adj_ani: () => random(adj) + random(ani),
76
+ adj_ani_num: () => random(adj) + random(ani) + nombre,
77
+ adj_pro: () => random(adj) + random(job),
78
+ pro_ani: () => random(job) + random(ani),
79
+ pro_ani_num: () => random(job) + random(ani) + nombre,
80
+ };
81
+
82
+ return {
83
+ username: choix[random(Object.keys(choix))]!(),
84
+ number: nombre,
85
+ adjective: random(adj),
86
+ animal: random(ani),
87
+ job: random(job),
88
+ };
89
+ }
@@ -0,0 +1,38 @@
1
+ export function random<T>(arr: T[]): T {
2
+ return arr[Math.floor(Math.random() * arr.length)]!;
3
+ }
4
+
5
+ export function randomNumber(min: number, max: number): number {
6
+ return Math.floor(Math.random() * (max - min + 1)) + min;
7
+ }
8
+
9
+ export function genIP(): string {
10
+ return `${randomNumber(0, 255)}.${randomNumber(0, 255)}.${randomNumber(0, 255)}.${randomNumber(0, 255)}`;
11
+ }
12
+
13
+ export function formatDate(date: Date): string {
14
+ return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().replace('Z', '');
15
+ }
16
+
17
+ export function envList(key: string): string[] | null {
18
+ const v = process.env[key];
19
+ return v && v !== 'undefined' ? v.split(' ') : null;
20
+ }
21
+
22
+ export function checkRateLimit(
23
+ rateLimits: Record<string, number[]>,
24
+ userId: string,
25
+ timestamp: number,
26
+ window = 10000,
27
+ limit = 50,
28
+ ): boolean {
29
+ rateLimits[userId] = (rateLimits[userId] ?? []).filter((ts) => timestamp - ts < window);
30
+
31
+ if (rateLimits[userId]!.length > limit) {
32
+ const remainingTime = Math.ceil((rateLimits[userId]![0]! + window - timestamp) / 1000);
33
+ throw new Error(`Rate limit exceeded. Try again in ${remainingTime} seconds.`);
34
+ }
35
+
36
+ rateLimits[userId]!.push(timestamp);
37
+ return false;
38
+ }
@@ -0,0 +1,15 @@
1
+ export * as algorithms from './v4/algorithms.js';
2
+ export { default as chat } from './v4/chat.js';
3
+ export { default as captcha } from './v4/captcha.js';
4
+ export { default as color } from './v4/color.js';
5
+ export { default as convert } from './v4/convert.js';
6
+ export { default as domain } from './v4/domain.js';
7
+ export { default as hash } from './v4/hash.js';
8
+ export { default as hyperplanning } from './v4/hyperplanning.js';
9
+ export { default as levenshtein } from './v4/levenshtein.js';
10
+ export { default as personal } from './v4/personal.js';
11
+ export { default as qrcode } from './v4/qrcode.js';
12
+ export { default as tic_tac_toe } from './v4/tic_tac_toe.js';
13
+ export { default as time } from './v4/time.js';
14
+ export { default as token } from './v4/token.js';
15
+ export { default as username } from './v4/username.js';
@@ -0,0 +1,461 @@
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
+ acc[method] = endpointList
22
+ .filter(({ name }: { name: string }) => name !== 'website')
23
+ .sort((a: { name: string }, b: { name: string }) => a.name.localeCompare(b.name))
24
+ .reduce<Record<string, unknown>>(
25
+ (
26
+ group: Record<string, unknown>,
27
+ endpoint: { name: string; path?: string; children?: Record<string, string> },
28
+ ) => {
29
+ if (endpoint.children) {
30
+ group[endpoint.name] = Object.keys(endpoint.children)
31
+ .sort((a: string, b: string) => a.localeCompare(b))
32
+ .reduce<Record<string, string>>((childGroup, childName) => {
33
+ childGroup[childName] = `/${version}${endpoint.children![childName]}`;
34
+ return childGroup;
35
+ }, {});
36
+ } else {
37
+ group[endpoint.name] = `/${version}${endpoint.path}`;
38
+ }
39
+ return group;
40
+ },
41
+ {},
42
+ );
43
+ return acc;
44
+ }, {});
45
+
46
+ res.jsonResponse({
47
+ version,
48
+ documentation: `${DOCS_URL}/${version}`,
49
+ endpoints,
50
+ });
51
+ });
52
+
53
+ // Algorithms
54
+ router.get('/:version/algorithms', (req: Request, res: Response) => {
55
+ const { method, value, value2 } = req.query;
56
+ const { version } = req.params;
57
+
58
+ const algorithms = req.module.algorithms as Record<string, (v: string, v2?: string) => unknown>;
59
+ if (!algorithms || !algorithms[method as string]) {
60
+ error(res, 400, 'Please provide a valid algorithm (?method={algorithm})', `${version}/algorithms`);
61
+ return;
62
+ }
63
+
64
+ try {
65
+ const answer = algorithms[method as string]!(value as string, value2 as string);
66
+ res.jsonResponse({ answer });
67
+ } catch (err) {
68
+ error(res, 400, (err as Error).message, `${req.version}/algorithms`);
69
+ }
70
+ });
71
+
72
+ // Generate captcha
73
+ router.get('/:version/captcha', (req: Request, res: Response) => {
74
+ const text = req.query.text as string;
75
+
76
+ if (!text) {
77
+ error(res, 400, 'Please provide a valid argument (?text={text})', `${req.version}/captcha`);
78
+ return;
79
+ }
80
+
81
+ try {
82
+ const result = req.module.captcha(text);
83
+ res.type('png').send(result);
84
+ } catch (err) {
85
+ error(res, 400, (err as Error).message, `${req.version}/captcha`);
86
+ }
87
+ });
88
+
89
+ // Display stored data
90
+ router.get('/:version/chat', (req: Request, res: Response) => {
91
+ try {
92
+ const messages = req.module.chat('fetch', {
93
+ username: 'system',
94
+ storage: chatStorage,
95
+ });
96
+ res.jsonResponse(messages);
97
+ } catch (err) {
98
+ error(res, 400, (err as Error).message);
99
+ }
100
+ });
101
+
102
+ // GET private chat error
103
+ router.get('/:version/chat/private', (_req: Request, res: Response) => {
104
+ error(res, 405, 'This endpoint only supports POST requests.');
105
+ });
106
+
107
+ // Generate color
108
+ router.get('/:version/color', (req: Request, res: Response) => {
109
+ try {
110
+ const result = req.module.color();
111
+ res.jsonResponse(result);
112
+ } catch (err) {
113
+ error(res, 400, (err as Error).message, `${req.version}/color`);
114
+ }
115
+ });
116
+
117
+ // Convert units
118
+ router.get('/:version/convert', (req: Request, res: Response) => {
119
+ const { value, from, to } = req.query;
120
+
121
+ if (!value || isNaN(Number(value))) {
122
+ error(res, 400, 'Please provide a valid value (?value={value})', `${req.version}/convert`);
123
+ return;
124
+ }
125
+ if (!from) {
126
+ error(res, 400, 'Please provide a valid source unit (&from={unit})', `${req.version}/convert`);
127
+ return;
128
+ }
129
+ if (!to) {
130
+ error(res, 400, 'Please provide a valid target unit (&to={unit})', `${req.version}/convert`);
131
+ return;
132
+ }
133
+
134
+ try {
135
+ const result = req.module.convert(value as string, from as string, to as string);
136
+ res.jsonResponse(result);
137
+ } catch (err) {
138
+ error(res, 400, (err as Error).message, `${req.version}/convert`);
139
+ }
140
+ });
141
+
142
+ // Generate domain informations
143
+ router.get('/:version/domain', (req: Request, res: Response) => {
144
+ try {
145
+ const result = req.module.domain();
146
+ res.jsonResponse(result);
147
+ } catch (err) {
148
+ error(res, 400, (err as Error).message, `${req.version}/domain`);
149
+ }
150
+ });
151
+
152
+ // GET planning error
153
+ router.get('/:version/hyperplanning', (_req: Request, res: Response) => {
154
+ error(res, 405, 'This endpoint only supports POST requests.');
155
+ });
156
+
157
+ // GET hash error
158
+ router.get('/:version/hash', (_req: Request, res: Response) => {
159
+ error(res, 405, 'This endpoint only supports POST requests.');
160
+ });
161
+
162
+ // Display API informations
163
+ router.get('/:version/infos', (req: Request, res: Response) => {
164
+ const endpoints = Object.values(versions[req.version]!.endpoints).flat();
165
+
166
+ const paths = endpoints.flatMap((e) => (e.children ? Object.values(e.children) : e.path ? [e.path] : []));
167
+
168
+ res.jsonResponse({
169
+ endpoints: new Set(paths).size,
170
+ last_version: Object.keys(versions).pop(),
171
+ documentation: DOCS_URL,
172
+ github: 'https://github.com/20syldev/api',
173
+ creation: 'November 25th 2024',
174
+ });
175
+ });
176
+
177
+ // Calculate Levenshtein distance
178
+ router.get('/:version/levenshtein', (req: Request, res: Response) => {
179
+ const { str1, str2 } = req.query;
180
+
181
+ if (!str1 || typeof str1 !== 'string') {
182
+ error(res, 400, 'Please provide a first string (?str1={string})', `${req.version}/levenshtein`);
183
+ return;
184
+ }
185
+ if (!str2 || typeof str2 !== 'string') {
186
+ error(res, 400, 'Please provide a second string (&str2={string})', `${req.version}/levenshtein`);
187
+ return;
188
+ }
189
+
190
+ try {
191
+ const result = req.module.levenshtein(str1, str2);
192
+ res.jsonResponse(result);
193
+ } catch (err) {
194
+ error(res, 400, (err as Error).message, `${req.version}/levenshtein`);
195
+ }
196
+ });
197
+
198
+ // Generate personal data
199
+ router.get('/:version/personal', (req: Request, res: Response) => {
200
+ try {
201
+ const result = req.module.personal();
202
+ res.jsonResponse(result);
203
+ } catch (err) {
204
+ error(res, 400, (err as Error).message, `${req.version}/personal`);
205
+ }
206
+ });
207
+
208
+ // Generate QR Code
209
+ router.get('/:version/qrcode', async (req: Request, res: Response) => {
210
+ const { url } = req.query;
211
+
212
+ if (!url) {
213
+ error(res, 400, 'Please provide a valid url (?url={URL})', `${req.version}/qrcode`);
214
+ return;
215
+ }
216
+
217
+ try {
218
+ const result = await req.module.qrcode(url as string);
219
+ res.jsonResponse(result);
220
+ } catch (err) {
221
+ error(res, 400, (err as Error).message, `${req.version}/qrcode`);
222
+ }
223
+ });
224
+
225
+ // GET tic-tac-toe errors
226
+ router.get('/:version/tic-tac-toe', (_req: Request, res: Response) => {
227
+ error(res, 405, 'This endpoint only supports POST requests.');
228
+ });
229
+
230
+ router.get('/:version/tic-tac-toe/fetch', (_req: Request, res: Response) => {
231
+ error(res, 405, 'This endpoint only supports POST requests.');
232
+ });
233
+
234
+ router.get('/:version/tic-tac-toe/list', (_req: Request, res: Response) => {
235
+ error(res, 405, 'This endpoint only supports POST requests.');
236
+ });
237
+
238
+ // Display or generate time informations
239
+ router.get('/:version/time', (req: Request, res: Response) => {
240
+ const { type = 'live', start, end, format, timezone } = req.query;
241
+
242
+ const validFormats = [
243
+ 'iso',
244
+ 'utc',
245
+ 'timestamp',
246
+ 'locale',
247
+ 'date',
248
+ 'time',
249
+ 'year',
250
+ 'month',
251
+ 'day',
252
+ 'hour',
253
+ 'minute',
254
+ 'second',
255
+ 'ms',
256
+ 'dayOfWeek',
257
+ 'dayOfYear',
258
+ 'weekNumber',
259
+ 'timezone',
260
+ 'timezoneOffset',
261
+ ];
262
+ const validTimezones = ['UTC', 'America/New_York', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'];
263
+
264
+ if (type !== 'live' && type !== 'random') {
265
+ error(res, 400, 'Please provide a valid type (?type={type})', `${req.version}/time`);
266
+ return;
267
+ }
268
+ if (start && !Date.parse(start as string)) {
269
+ error(res, 400, 'Please provide a valid start date (?start={YYYY-MM-DD})', `${req.version}/time`);
270
+ return;
271
+ }
272
+ if (end && !Date.parse(end as string)) {
273
+ error(res, 400, 'Please provide a valid end date (?end={YYYY-MM-DD})', `${req.version}/time`);
274
+ return;
275
+ }
276
+ if (format && !validFormats.includes(format as string)) {
277
+ error(res, 400, 'Please provide a valid format (?format={format})', `${req.version}/time`);
278
+ return;
279
+ }
280
+ if (timezone && !validTimezones.includes(timezone as string)) {
281
+ error(res, 400, 'Please provide a valid timezone (?timezone={timezone})', `${req.version}/time`);
282
+ return;
283
+ }
284
+
285
+ try {
286
+ const time = req.module.time(
287
+ type as string,
288
+ start as string,
289
+ end as string,
290
+ format as string,
291
+ timezone as string,
292
+ );
293
+ res.jsonResponse(time);
294
+ } catch (err) {
295
+ error(res, 400, (err as Error).message, `${req.version}/time`);
296
+ }
297
+ });
298
+
299
+ // GET token error
300
+ router.get('/:version/token', (_req: Request, res: Response) => {
301
+ error(res, 405, 'This endpoint only supports POST requests.');
302
+ });
303
+
304
+ // Generate username
305
+ router.get('/:version/username', (req: Request, res: Response) => {
306
+ try {
307
+ const result = req.module.username();
308
+ res.jsonResponse(result);
309
+ } catch (err) {
310
+ error(res, 400, (err as Error).message, `${req.version}/username`);
311
+ }
312
+ });
313
+
314
+ // Display informations for owner's website
315
+ router.get('/:version/website', async (req: Request, res: Response) => {
316
+ const currentTime = Date.now();
317
+
318
+ if (currentTime - lastFetch >= GITHUB_CACHE_TTL) {
319
+ try {
320
+ const username = '20syldev';
321
+ const token = env.GITHUB_TOKEN;
322
+ const lastYear = new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString().split('T')[0];
323
+
324
+ const query = `
325
+ {
326
+ user(login: "${username}") {
327
+ contributionsCollection(from: "${lastYear}T00:00:00Z") {
328
+ contributionCalendar {
329
+ totalContributions
330
+ weeks {
331
+ firstDay
332
+ contributionDays {
333
+ date
334
+ contributionCount
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }`;
341
+
342
+ const apiResponse = await fetch('https://api.github.com/graphql', {
343
+ method: 'POST',
344
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
345
+ body: JSON.stringify({ query }),
346
+ });
347
+
348
+ if (!apiResponse.ok) throw new Error('Error fetching data.');
349
+
350
+ const data = (await apiResponse.json()) as {
351
+ data?: {
352
+ user?: {
353
+ contributionsCollection?: {
354
+ contributionCalendar?: {
355
+ weeks?: {
356
+ firstDay: string;
357
+ contributionDays: { date: string; contributionCount: number }[];
358
+ }[];
359
+ };
360
+ };
361
+ };
362
+ };
363
+ };
364
+ const user = data?.data?.user;
365
+
366
+ const weeks = user?.contributionsCollection?.contributionCalendar?.weeks || [];
367
+ activity = weeks.map((w) => ({
368
+ week: w.firstDay,
369
+ total: w.contributionDays.reduce((sum, d) => sum + d.contributionCount, 0),
370
+ days: w.contributionDays.map((d) => ({ date: d.date, count: d.contributionCount })),
371
+ }));
372
+
373
+ lastFetch = currentTime;
374
+ } catch {
375
+ activity = [];
376
+ }
377
+ }
378
+
379
+ const response: Record<string, unknown> = {
380
+ versions: {
381
+ 2048: env.G_2048,
382
+ api: env.API,
383
+ cdn: env.CDN,
384
+ coop_api: env.COOP_API,
385
+ coop_status: env.COOP_STATUS,
386
+ chat: env.CHAT,
387
+ digit: env.DIGIT,
388
+ doc_coopbot: env.DOC_COOPBOT,
389
+ docs: env.DOCS,
390
+ donut: env.DONUT,
391
+ drawio_plugin: env.DRAWIO_PLUGIN,
392
+ flowers: env.FLOWERS,
393
+ gemsync: env.GEMSYNC,
394
+ gft: env.GFT,
395
+ gitsite: env.GITSITE,
396
+ lebonchar: env.LEBONCHAR,
397
+ logger: env.LOGGER,
398
+ logs: env.LOGS,
399
+ logvault: env.LOGVAULT,
400
+ lyah: env.LYAH,
401
+ minify: env.MINIFY,
402
+ mn: env.MN,
403
+ monitoring: env.MONITORING,
404
+ morpion: env.MORPION,
405
+ nitrogen: env.NITROGEN,
406
+ old_database: env.OLD_DATABASE,
407
+ password: env.PASSWORD,
408
+ php: env.PHP,
409
+ planning: env.PLANNING,
410
+ ping: env.PING,
411
+ portfolio: env.PORTFOLIO,
412
+ python_api: env.PYTHON_API,
413
+ readme: env.README,
414
+ timestamp: env.TIMESTAMP,
415
+ terminal: env.TERMINAL,
416
+ valentine: env.VALENTINE,
417
+ wrkit: env.WRKIT,
418
+ zpki: env.ZPKI,
419
+ },
420
+ patched_projects: env.PATCH,
421
+ updated_projects: env.RECENT,
422
+ new_projects: env.NEW,
423
+ sub_domains: env.DOMAINS,
424
+ stats: {
425
+ 1: env.STATS1,
426
+ 2: env.STATS2,
427
+ 3: env.STATS3,
428
+ 4: env.STATS4,
429
+ 5: Object.keys(ipLimits).length,
430
+ activity,
431
+ },
432
+ tag: env.TAG,
433
+ active: env.ACTIVE,
434
+ };
435
+
436
+ const key = req.query.key as string | undefined;
437
+ if (key) {
438
+ if (['__proto__', 'constructor', 'prototype'].some((p) => key.includes(p))) {
439
+ error(res, 400, 'Invalid key.');
440
+ return;
441
+ }
442
+
443
+ const keys = key.split('.');
444
+ let result: unknown = response;
445
+
446
+ for (const k of keys) {
447
+ if (result == null || typeof result !== 'object' || !(k in result)) {
448
+ error(res, 404, `Key '${key}' not found.`);
449
+ return;
450
+ }
451
+ result = (result as Record<string, unknown>)[k];
452
+ }
453
+
454
+ res.jsonResponse({ [key]: result });
455
+ return;
456
+ }
457
+
458
+ res.jsonResponse(response);
459
+ });
460
+
461
+ export default router;