@20syldev/api 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +84 -47
  2. package/package.json +2 -1
  3. package/.github/FUNDING.yml +0 -1
  4. package/.prettierrc +0 -7
  5. package/eslint.config.js +0 -24
  6. package/robots.txt +0 -74
  7. package/src/app.ts +0 -53
  8. package/src/config/env.ts +0 -87
  9. package/src/config/plans.ts +0 -61
  10. package/src/config/versions.ts +0 -121
  11. package/src/constants.ts +0 -61
  12. package/src/favicon.ico +0 -0
  13. package/src/middleware/cors.ts +0 -23
  14. package/src/middleware/error.ts +0 -16
  15. package/src/middleware/json.ts +0 -9
  16. package/src/middleware/logger.ts +0 -32
  17. package/src/middleware/ratelimit.ts +0 -89
  18. package/src/middleware/version.ts +0 -40
  19. package/src/modules/v3/algorithms.js +0 -218
  20. package/src/modules/v3/captcha.js +0 -60
  21. package/src/modules/v3/chat.js +0 -111
  22. package/src/modules/v3/color.js +0 -60
  23. package/src/modules/v3/convert.js +0 -39
  24. package/src/modules/v3/domain.js +0 -86
  25. package/src/modules/v3/hash.js +0 -16
  26. package/src/modules/v3/hyperplanning.js +0 -56
  27. package/src/modules/v3/levenshtein.js +0 -46
  28. package/src/modules/v3/personal.js +0 -139
  29. package/src/modules/v3/qrcode.js +0 -20
  30. package/src/modules/v3/tic_tac_toe.js +0 -242
  31. package/src/modules/v3/time.js +0 -98
  32. package/src/modules/v3/token.js +0 -54
  33. package/src/modules/v3/username.js +0 -94
  34. package/src/modules/v3/utils.js +0 -77
  35. package/src/modules/v3.js +0 -15
  36. package/src/modules/v4/address.ts +0 -412
  37. package/src/modules/v4/agent.ts +0 -104
  38. package/src/modules/v4/algorithms.ts +0 -276
  39. package/src/modules/v4/captcha.ts +0 -114
  40. package/src/modules/v4/chat.ts +0 -130
  41. package/src/modules/v4/color.ts +0 -75
  42. package/src/modules/v4/convert.ts +0 -127
  43. package/src/modules/v4/cron.ts +0 -221
  44. package/src/modules/v4/dice.ts +0 -46
  45. package/src/modules/v4/domain.ts +0 -86
  46. package/src/modules/v4/encode.ts +0 -241
  47. package/src/modules/v4/geo.ts +0 -64
  48. package/src/modules/v4/hash.ts +0 -32
  49. package/src/modules/v4/hyperplanning.ts +0 -93
  50. package/src/modules/v4/ip.ts +0 -134
  51. package/src/modules/v4/levenshtein.ts +0 -46
  52. package/src/modules/v4/palette.ts +0 -71
  53. package/src/modules/v4/password.ts +0 -379
  54. package/src/modules/v4/personal.ts +0 -140
  55. package/src/modules/v4/placeholder.ts +0 -197
  56. package/src/modules/v4/qrcode.ts +0 -121
  57. package/src/modules/v4/regex.ts +0 -67
  58. package/src/modules/v4/statistics.ts +0 -62
  59. package/src/modules/v4/text.ts +0 -330
  60. package/src/modules/v4/tic_tac_toe.ts +0 -264
  61. package/src/modules/v4/time.ts +0 -149
  62. package/src/modules/v4/token.ts +0 -53
  63. package/src/modules/v4/username.ts +0 -94
  64. package/src/modules/v4/validate.ts +0 -76
  65. package/src/modules/v4.ts +0 -29
  66. package/src/routes/delete.ts +0 -72
  67. package/src/routes/get.ts +0 -856
  68. package/src/routes/index.ts +0 -59
  69. package/src/routes/patch.ts +0 -45
  70. package/src/routes/post.ts +0 -209
  71. package/src/storage/index.ts +0 -16
  72. package/src/types/express.d.ts +0 -17
  73. package/src/types/storage.ts +0 -48
  74. package/src/utils/colors.ts +0 -91
  75. package/src/utils/helpers.ts +0 -90
  76. package/src/utils/response.ts +0 -28
  77. package/tests/integration/api.test.ts +0 -772
  78. package/tests/tsconfig.json +0 -3
  79. package/tests/unit/address.test.ts +0 -84
  80. package/tests/unit/agent.test.ts +0 -113
  81. package/tests/unit/algorithms.test.ts +0 -121
  82. package/tests/unit/captcha.test.ts +0 -62
  83. package/tests/unit/chat.test.ts +0 -54
  84. package/tests/unit/color.test.ts +0 -54
  85. package/tests/unit/convert.test.ts +0 -68
  86. package/tests/unit/cron.test.ts +0 -110
  87. package/tests/unit/dice.test.ts +0 -58
  88. package/tests/unit/domain.test.ts +0 -48
  89. package/tests/unit/encode.test.ts +0 -109
  90. package/tests/unit/geo.test.ts +0 -46
  91. package/tests/unit/hash.test.ts +0 -45
  92. package/tests/unit/hyperplanning.test.ts +0 -97
  93. package/tests/unit/ip.test.ts +0 -140
  94. package/tests/unit/levenshtein.test.ts +0 -35
  95. package/tests/unit/palette.test.ts +0 -54
  96. package/tests/unit/password.test.ts +0 -86
  97. package/tests/unit/personal.test.ts +0 -66
  98. package/tests/unit/placeholder.test.ts +0 -58
  99. package/tests/unit/qrcode.test.ts +0 -83
  100. package/tests/unit/regex.test.ts +0 -95
  101. package/tests/unit/statistics.test.ts +0 -58
  102. package/tests/unit/text.test.ts +0 -108
  103. package/tests/unit/tic_tac_toe.test.ts +0 -56
  104. package/tests/unit/time.test.ts +0 -115
  105. package/tests/unit/token.test.ts +0 -62
  106. package/tests/unit/username.test.ts +0 -35
  107. package/tests/unit/validate.test.ts +0 -72
  108. package/tsconfig.json +0 -17
  109. package/tsconfig.test.json +0 -9
package/src/constants.ts DELETED
@@ -1,61 +0,0 @@
1
- import pkg from '../package.json' with { type: 'json' };
2
-
3
- // App
4
- export const APP_VERSION = pkg.version;
5
- export const DEFAULT_PORT = 3000;
6
- export const DOCS_URL = 'https://docs.sylvain.sh';
7
- export const START_TIME = Date.now();
8
-
9
- // Server
10
- export const GAME_CLEANUP_TTL = 600_000;
11
- export const GITHUB_CACHE_TTL = 600_000;
12
- export const MAX_LOG_ENTRIES = 1000;
13
- export const RATE_LIMIT_WINDOW = 10_000;
14
- export const RATE_LIMIT_MAX = 50;
15
- export const SESSION_TTL = 3_600_000;
16
-
17
- export const STATUS_MESSAGES: Record<number, string> = {
18
- 400: 'Bad Request',
19
- 401: 'Unauthorized',
20
- 404: 'Not Found',
21
- 405: 'Method Not Allowed',
22
- 413: 'Payload Too Large',
23
- 429: 'Too Many Requests',
24
- 500: 'Internal Server Error',
25
- };
26
-
27
- // Modules
28
- export const EARTH_RADIUS_KM = 6371;
29
- export const MAX_ADDRESS_COUNT = 10;
30
- export const MAX_COUNTDOWN_YEARS = 100;
31
- export const MAX_CRON_ITERATIONS = 365 * 24 * 60;
32
- export const MAX_CRON_RESULTS = 20;
33
- export const MAX_FACTORIAL = 170;
34
- export const MAX_GCD_VALUE = 100_000;
35
- export const MAX_LEVENSHTEIN_LENGTH = 1000;
36
- export const MIN_PASSWORD_LENGTH = 8;
37
- export const MAX_PASSWORD_LENGTH = 128;
38
- export const MAX_PASSWORD_COUNT = 20;
39
- export const MAX_PATTERN_LENGTH = 200;
40
- export const MAX_PRIME_LIST = 10_000;
41
- export const MAX_QRCODE_LOGO_BYTES = 2 * 1024 * 1024;
42
- export const MAX_REGEX_MATCHES = 100;
43
- export const MAX_STRING_LENGTH = 1000;
44
- export const MIN_TOKEN_LENGTH = 12;
45
- export const MAX_TOKEN_LENGTH = 4096;
46
-
47
- export const ROMAN_VALUES: [number, string][] = [
48
- [1000, 'M'],
49
- [900, 'CM'],
50
- [500, 'D'],
51
- [400, 'CD'],
52
- [100, 'C'],
53
- [90, 'XC'],
54
- [50, 'L'],
55
- [40, 'XL'],
56
- [10, 'X'],
57
- [9, 'IX'],
58
- [5, 'V'],
59
- [4, 'IV'],
60
- [1, 'I'],
61
- ];
package/src/favicon.ico DELETED
Binary file
@@ -1,23 +0,0 @@
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, limit: '10kb' }));
13
- app.use(express.json({ limit: '10kb' }));
14
-
15
- app.use((_req, res, next) => {
16
- res.setHeader('X-Content-Type-Options', 'nosniff');
17
- res.setHeader('X-Frame-Options', 'DENY');
18
- next();
19
- });
20
-
21
- app.use('/favicon.ico', express.static(join(__dirname, '..', '..', 'src', 'favicon.ico')));
22
- app.use('/robots.txt', express.static(join(__dirname, '..', '..', 'robots.txt')));
23
- }
@@ -1,16 +0,0 @@
1
- import type { NextFunction, Request, Response } from 'express';
2
-
3
- import { error } from '../utils/response.js';
4
-
5
- export function errorHandler(
6
- err: Error & { status?: number },
7
- _req: Request,
8
- res: Response,
9
- _next: NextFunction,
10
- ): void {
11
- console.error(err.stack);
12
-
13
- const status = err.status && err.status >= 400 && err.status < 600 ? err.status : 500;
14
- const sensitive = status === 500 && /\/[a-z]|\\[a-z]|ECONNREFUSED|ENOENT|EPERM|at\s+\w/i.test(err.message);
15
- error(res, status, sensitive ? 'An unexpected error occurred.' : err.message);
16
- }
@@ -1,9 +0,0 @@
1
- import type { NextFunction, Request, Response } 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
- }
@@ -1,32 +0,0 @@
1
- import { createLogger } from '@20syldev/logger.ts';
2
- import type { NextFunction, Request, Response } from 'express';
3
-
4
- import { MAX_LOG_ENTRIES } from '../constants.js';
5
-
6
- export const logger = createLogger({
7
- maxEntries: MAX_LOG_ENTRIES,
8
- console: true,
9
- theme: 'colored',
10
- });
11
-
12
- export function loggerMiddleware(req: Request, res: Response, next: NextFunction): void {
13
- if (req.method === 'HEAD') {
14
- next();
15
- return;
16
- }
17
- if (req.originalUrl === '/logs') {
18
- next();
19
- return;
20
- }
21
-
22
- const startTime = Date.now();
23
- const platform = (req.headers['sec-ch-ua-platform'] as string)?.replace(/"/g, '');
24
-
25
- res.on('finish', () => {
26
- const status = res.statusCode === 304 ? 200 : res.statusCode;
27
- const duration = `${Date.now() - startTime}ms`;
28
-
29
- logger.log({ method: req.method, url: req.originalUrl, status, duration, platform });
30
- });
31
- next();
32
- }
@@ -1,89 +0,0 @@
1
- import type { NextFunction, Request, Response } from 'express';
2
-
3
- import { getPlan, globalLimit } from '../config/plans.js';
4
- import { RATE_LIMIT_WINDOW, SESSION_TTL } from '../constants.js';
5
- import { ipLimits } from '../storage/index.js';
6
- import { error } from '../utils/response.js';
7
-
8
- let requests = 0;
9
- let resetTime = Date.now() + SESSION_TTL;
10
-
11
- const burstTracker: Record<string, number[]> = {};
12
-
13
- // Cleanup stale IPs every hour
14
- setInterval(() => {
15
- const currentHour = String(Math.floor(Date.now() / 3600000) % 24);
16
- for (const ip of Object.keys(ipLimits)) {
17
- const hours = Object.keys(ipLimits[ip]!);
18
- if (hours.length === 0 || (hours.length === 1 && hours[0] !== currentHour)) {
19
- delete ipLimits[ip];
20
- }
21
- }
22
-
23
- const now = Date.now();
24
- for (const ip of Object.keys(burstTracker)) {
25
- burstTracker[ip] = burstTracker[ip]!.filter((t) => now - t < RATE_LIMIT_WINDOW);
26
- if (burstTracker[ip]!.length === 0) delete burstTracker[ip];
27
- }
28
- }, SESSION_TTL);
29
-
30
- export function rateLimitMiddleware(req: Request, res: Response, next: NextFunction): void {
31
- const ip = req.ip || req.socket.remoteAddress || '';
32
- const token = req.headers.authorization?.split(' ')[1] || '';
33
-
34
- const now = Date.now();
35
- const minute = String(Math.floor(now / 60000) % 60);
36
- const hour = String(Math.floor(now / 3600000) % 24);
37
-
38
- const match = getPlan(token);
39
- if (!match) {
40
- error(res, 401, 'Invalid token.');
41
- return;
42
- }
43
-
44
- const { plan } = match;
45
-
46
- if (now > resetTime) {
47
- requests = 0;
48
- resetTime = now + SESSION_TTL;
49
- }
50
-
51
- if (++requests > Math.max(globalLimit, plan.hourly)) {
52
- error(res, 429, 'Global rate limit exceeded.');
53
- return;
54
- }
55
-
56
- if (['__proto__', 'constructor', 'prototype'].includes(ip)) {
57
- error(res, 400, 'Invalid IP address.');
58
- return;
59
- }
60
-
61
- // Burst protection per tier
62
- if (process.env.NODE_ENV !== 'test') {
63
- if (!burstTracker[ip]) burstTracker[ip] = [];
64
- burstTracker[ip] = burstTracker[ip]!.filter((t) => now - t < RATE_LIMIT_WINDOW);
65
- burstTracker[ip]!.push(now);
66
-
67
- if (burstTracker[ip]!.length > plan.burst) {
68
- error(res, 429, 'Too many requests, please slow down.');
69
- return;
70
- }
71
- }
72
-
73
- // Hourly rate limit per IP
74
- if (!ipLimits[ip]) ipLimits[ip] = {};
75
- if (!ipLimits[ip]![hour]) ipLimits[ip]![hour] = {};
76
- ipLimits[ip]![hour]![minute] = (ipLimits[ip]![hour]![minute] ?? 0) + 1;
77
-
78
- const hourTotal = Object.values(ipLimits[ip]![hour]!).reduce((sum, count) => sum + count, 0);
79
- if (hourTotal > plan.hourly) {
80
- error(res, 429, `You have exceeded the limit of ${plan.hourly} requests per hour.`);
81
- return;
82
- }
83
-
84
- Object.keys(ipLimits[ip]!).forEach((h) => {
85
- if (h != hour) delete ipLimits[ip]![h];
86
- });
87
-
88
- next();
89
- }
@@ -1,40 +0,0 @@
1
- import type { NextFunction, Request, Response } from 'express';
2
-
3
- import { versions } from '../config/versions.js';
4
- import { error } from '../utils/response.js';
5
-
6
- export function versionCheckMiddleware(req: Request, res: Response, next: NextFunction): void {
7
- const version = req.params.version as string;
8
- const latest = Object.keys(versions).pop()!;
9
-
10
- req.version = version;
11
- req.latest = latest;
12
-
13
- if (!versions[version]) {
14
- error(res, 404, `Invalid API version (${version}).`, latest);
15
- return;
16
- }
17
-
18
- req.module = versions[version]!.modules;
19
-
20
- if (!req.module) {
21
- error(res, 404, `Module not found for version ${version}.`, latest);
22
- return;
23
- }
24
-
25
- next();
26
- }
27
-
28
- export function endpointCheckMiddleware(req: Request, res: Response, next: NextFunction): void {
29
- const version = req.params.version as string;
30
- const endpoint = req.params.endpoint as string;
31
-
32
- req.version = version;
33
- req.endpoint = endpoint;
34
-
35
- if (!endpoint) {
36
- error(res, 404, `Endpoint '${endpoint}' does not exist in ${req.version}.`);
37
- return;
38
- }
39
- next();
40
- }
@@ -1,218 +0,0 @@
1
- /**
2
- * Check if two strings are anagrams of each other
3
- *
4
- * @param {string} value - First string to compare
5
- * @param {string} value2 - Second string to compare
6
- * @returns {boolean} - True if strings are anagrams, false otherwise
7
- * @throws {Error} - If inputs are invalid
8
- */
9
- export function anagram(value, value2) {
10
- if (!value) throw new Error('First value is required');
11
- if (!value2) throw new Error('Second value is required');
12
-
13
- if (typeof value !== 'string') throw new Error('First value must be a string');
14
- if (typeof value2 !== 'string') throw new Error('Second value must be a string');
15
-
16
- if (value.length < 2) throw new Error('First value must be at least two characters long');
17
- if (value.length > 1000) throw new Error('First value must be less than 1000 characters long');
18
-
19
- if (value2.length < 2) throw new Error('Second value must be at least two characters long');
20
- if (value2.length > 1000) throw new Error('Second value must be less than 1000 characters long');
21
-
22
- return value.split('').sort().join('') === value2.split('').sort().join('');
23
- }
24
-
25
- /**
26
- * Sort an array using bubble sort algorithm
27
- *
28
- * @param {string} value - Comma-separated list of numbers
29
- * @returns {number[]} - Sorted array of numbers
30
- * @throws {Error} - If input is invalid
31
- */
32
- export function bubblesort(value) {
33
- if (!value) throw new Error('A value is required');
34
-
35
- if (typeof value !== 'string') throw new Error('Value must be a string');
36
-
37
- const arr = value.split(',').map(Number);
38
-
39
- if (arr.some(isNaN)) throw new Error('Array must contain only numbers');
40
- if (arr.length < 2) throw new Error('Array must contain at least two numbers');
41
- if (arr.length > 1000) throw new Error('Array must contain less than 1000 numbers');
42
-
43
- for (let i = 0; i < arr.length - 1; i++) {
44
- for (let j = 0; j < arr.length - i - 1; j++) {
45
- if (arr[j] > arr[j + 1]) [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
46
- }
47
- }
48
-
49
- return arr;
50
- }
51
-
52
- /**
53
- * Calculate the factorial of a number
54
- *
55
- * @param {number} value - Number to calculate factorial for
56
- * @returns {number} - Factorial result
57
- * @throws {Error} - If input is invalid or out of range
58
- */
59
- export function factorial(value) {
60
- if (isNaN(value)) throw new Error('Value must be a number');
61
- if (value < 0) throw new Error('Number must be positive');
62
- if (value > 170) throw new Error('Number must be between 0 and 170');
63
-
64
- return value <= 1 ? 1 : value * factorial(value - 1);
65
- }
66
-
67
- /**
68
- * Generate a Fibonacci sequence up to the specified length
69
- *
70
- * @param {number} value - Length of Fibonacci sequence to generate
71
- * @returns {number[]} - Array of Fibonacci numbers
72
- * @throws {Error} - If input is invalid or out of range
73
- */
74
- export function fibonacci(value) {
75
- if (isNaN(value)) throw new Error('Value must be a number');
76
- if (value < 0) throw new Error('Number must be positive');
77
- if (value > 1000) throw new Error('Number must be between 0 and 1000');
78
-
79
- let fib = [0, 1];
80
-
81
- for (let i = 2; i < parseInt(value); i++) {
82
- fib.push(fib[i - 1] + fib[i - 2]);
83
- }
84
-
85
- return fib.slice(0, parseInt(value));
86
- }
87
-
88
- /**
89
- * Calculate the greatest common divisor (GCD) of two numbers
90
- *
91
- * @param {number} value - First number
92
- * @param {number} value2 - Second number
93
- * @returns {number} - Greatest common divisor
94
- * @throws {Error} - If inputs are invalid or out of range
95
- */
96
- export function gcd(value, value2) {
97
- if (isNaN(value)) throw new Error('First value must be a number');
98
- if (isNaN(value2)) throw new Error('Second value must be a number');
99
-
100
- if (value <= 0) throw new Error('First number must be strictly positive');
101
- if (value2 <= 0) throw new Error('Second number must be strictly positive');
102
-
103
- if (value > 100000) throw new Error('First number must be between 0 and 100000');
104
- if (value2 > 100000) throw new Error('Second number must be between 0 and 100000');
105
-
106
- value = Math.abs(value);
107
- value2 = Math.abs(value2);
108
-
109
- while (value2) [value, value2] = [value2, value % value2];
110
-
111
- return value;
112
- }
113
-
114
- /**
115
- * Check if a number is prime
116
- *
117
- * @param {number} value - Number to check
118
- * @returns {boolean} - True if the number is prime, false otherwise
119
- * @throws {Error} - If input is invalid or out of range
120
- */
121
- export function isprime(value) {
122
- if (isNaN(value)) throw new Error('Value must be a number');
123
- if (value < 1) throw new Error('Number must be positive');
124
- if (value > 100000) throw new Error('Number must be between 1 and 100000');
125
-
126
- for (let i = 2; i <= Math.sqrt(value); i++) {
127
- if (value % i === 0) return false;
128
- }
129
-
130
- return true;
131
- }
132
-
133
- /**
134
- * Check if a string is a palindrome
135
- *
136
- * @param {string} value - String to check
137
- * @returns {boolean} - True if the string is a palindrome, false otherwise
138
- * @throws {Error} - If input is invalid
139
- */
140
- export function palindrome(value) {
141
- if (!value) throw new Error('A value is required');
142
-
143
- if (typeof value !== 'string') throw new Error('Value must be a string');
144
-
145
- if (value.length < 2) throw new Error('Value must be at least two characters long');
146
- if (value.length > 1000) throw new Error('Value must be less than 1000 characters long');
147
-
148
- return value === value.split('').reverse().join('');
149
- }
150
-
151
- /**
152
- * Find all prime factors of a number
153
- *
154
- * @param {number} value - Number to factorize
155
- * @returns {number[]} - Array of prime factors
156
- * @throws {Error} - If input is invalid or out of range
157
- */
158
- export function primefactors(value) {
159
- if (isNaN(value)) throw new Error('Value must be a number');
160
- if (value < 2) throw new Error('Number must be positive and greater than 1');
161
- if (value > 100000) throw new Error('Number must be between 2 and 100000');
162
-
163
- const factors = [];
164
-
165
- for (let i = 2; i <= value; i++) {
166
- while (value % i === 0) {
167
- factors.push(i);
168
- value /= i;
169
- }
170
- }
171
-
172
- return factors;
173
- }
174
-
175
- /**
176
- * Generate a list of all prime numbers up to the specified limit
177
- *
178
- * @param {number} value - Upper limit to check for primes
179
- * @returns {number[]} - Array of prime numbers
180
- * @throws {Error} - If input is invalid or out of range
181
- */
182
- export function primelist(value) {
183
- if (isNaN(value)) throw new Error('Value must be a number');
184
- if (value < 2) throw new Error('Number must be positive and greater than 1');
185
- if (value > 10000) throw new Error('Number must be between 2 and 10000');
186
-
187
- const primes = [];
188
-
189
- for (let i = 2; i <= value; i++) {
190
- let isPrime = true;
191
-
192
- for (let j = 2; j <= Math.sqrt(i); j++) {
193
- if (i % j === 0) {
194
- isPrime = false;
195
- break;
196
- }
197
- }
198
-
199
- if (isPrime) primes.push(i);
200
- }
201
-
202
- return primes;
203
- }
204
-
205
- /**
206
- * Reverse a string
207
- *
208
- * @param {string} value - String to reverse
209
- * @returns {string} - Reversed string
210
- * @throws {Error} - If input is invalid
211
- */
212
- export function reverse(value) {
213
- if (!value) throw new Error('A value is required');
214
-
215
- if (typeof value !== 'string') throw new Error('Value must be a string');
216
-
217
- return value.split('').reverse().join('');
218
- }
@@ -1,60 +0,0 @@
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,111 +0,0 @@
1
- import { checkRateLimit } from './utils.js';
2
-
3
- /**
4
- * Manages a chat system with public and private messages
5
- *
6
- * @param {string} action - The action to perform ('message' or 'private')
7
- * @param {Object} params - The parameters for the action
8
- * @returns {Object} - The result of the action
9
- * @throws {Error} - If parameters are invalid
10
- */
11
- export default function chat(action, params = {}) {
12
- // In-memory storage
13
- const storage = params.storage || {};
14
-
15
- // Initialize storage
16
- storage.messages ??= [];
17
- storage.privateChats ??= {};
18
- storage.sessions ??= {};
19
- storage.rateLimits ??= {};
20
-
21
- // Reference the storage properties
22
- const { messages, privateChats, sessions, rateLimits } = storage;
23
-
24
- // Input validation for common parameters
25
- if (!params.username) throw new Error('Please provide a username');
26
-
27
- const u = params.username.toLowerCase();
28
- const now = Date.now();
29
-
30
- // Rate limiting
31
- checkRateLimit(rateLimits, u, now);
32
-
33
- // Handle different actions
34
- if (action === 'message') {
35
- return sendMessage(params, messages, privateChats, sessions, u, now);
36
- } else if (action === 'private') {
37
- return getPrivateChat(params, privateChats);
38
- } else if (action === 'fetch') {
39
- return fetchMessages(messages);
40
- } else {
41
- throw new Error('Invalid action. Use "message", "private", or "fetch"');
42
- }
43
- }
44
-
45
- /**
46
- * Send a new message
47
- */
48
- function sendMessage(params, messages, privateChats, sessions, u, now) {
49
- const { message, session, token } = params;
50
-
51
- // Input validation for message action
52
- if (!message) throw new Error('Please provide a message');
53
- if (!session) throw new Error('Please provide a valid session ID');
54
-
55
- // Session validation
56
- if (sessions[u] && sessions[u].user !== session) {
57
- throw new Error('Session ID mismatch');
58
- }
59
-
60
- // Create message object
61
- const msg = {
62
- username: params.username,
63
- message,
64
- timestamp: params.timestamp || new Date().toISOString(),
65
- };
66
-
67
- // Store message based on whether it's private or public
68
- if (token) {
69
- privateChats[token] = privateChats[token] || [];
70
- privateChats[token].push(msg);
71
- setTimeout(() => {
72
- delete privateChats[token];
73
- }, 3600000);
74
- } else {
75
- messages.push(msg);
76
- setTimeout(() => messages.splice(messages.indexOf(msg), 1), 3600000);
77
- }
78
-
79
- // Update session
80
- sessions[u] = sessions[u] || { user: session, last: now };
81
- sessions[u].last = now;
82
-
83
- setTimeout(() => {
84
- if (now - sessions[u].last >= 3600000) delete sessions[u];
85
- }, 3600000);
86
-
87
- return { message: 'Message sent successfully' };
88
- }
89
-
90
- /**
91
- * Get private chat messages
92
- */
93
- function getPrivateChat(params, privateChats) {
94
- const { token } = params;
95
-
96
- // Input validation for private action
97
- if (!token) throw new Error('Please provide a valid token');
98
-
99
- // Return messages if token exists
100
- if (privateChats[token]) return privateChats[token];
101
-
102
- throw new Error('Invalid or expired token.');
103
- }
104
-
105
- /**
106
- * Fetch all public messages
107
- */
108
- function fetchMessages(messages) {
109
- if (messages.length > 0) return messages;
110
- throw new Error('No messages stored.');
111
- }