@20syldev/api 3.5.0 → 4.1.0

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