@20syldev/api 4.8.0 → 5.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 (126) hide show
  1. package/README.md +93 -55
  2. package/dist/config/versions.js +16 -0
  3. package/dist/config/versions.js.map +1 -1
  4. package/dist/constants.js +5 -0
  5. package/dist/constants.js.map +1 -1
  6. package/dist/modules/v5/chart.js +379 -0
  7. package/dist/modules/v5/chart.js.map +1 -0
  8. package/dist/modules/v5/evaluate.js +281 -0
  9. package/dist/modules/v5/evaluate.js.map +1 -0
  10. package/dist/modules/v5/matrix.js +238 -0
  11. package/dist/modules/v5/matrix.js.map +1 -0
  12. package/dist/modules/v5.js +5 -0
  13. package/dist/modules/v5.js.map +1 -0
  14. package/dist/routes/get.js +191 -162
  15. package/dist/routes/get.js.map +1 -1
  16. package/dist/routes/post.js +69 -0
  17. package/dist/routes/post.js.map +1 -1
  18. package/package.json +4 -2
  19. package/recap.md +98 -0
  20. package/.github/FUNDING.yml +0 -1
  21. package/.prettierrc +0 -7
  22. package/eslint.config.js +0 -24
  23. package/robots.txt +0 -74
  24. package/src/app.ts +0 -53
  25. package/src/config/env.ts +0 -87
  26. package/src/config/plans.ts +0 -61
  27. package/src/config/versions.ts +0 -121
  28. package/src/constants.ts +0 -61
  29. package/src/favicon.ico +0 -0
  30. package/src/middleware/cors.ts +0 -23
  31. package/src/middleware/error.ts +0 -16
  32. package/src/middleware/json.ts +0 -9
  33. package/src/middleware/logger.ts +0 -32
  34. package/src/middleware/ratelimit.ts +0 -89
  35. package/src/middleware/version.ts +0 -40
  36. package/src/modules/v3/algorithms.js +0 -218
  37. package/src/modules/v3/captcha.js +0 -60
  38. package/src/modules/v3/chat.js +0 -111
  39. package/src/modules/v3/color.js +0 -60
  40. package/src/modules/v3/convert.js +0 -39
  41. package/src/modules/v3/domain.js +0 -86
  42. package/src/modules/v3/hash.js +0 -16
  43. package/src/modules/v3/hyperplanning.js +0 -56
  44. package/src/modules/v3/levenshtein.js +0 -46
  45. package/src/modules/v3/personal.js +0 -139
  46. package/src/modules/v3/qrcode.js +0 -20
  47. package/src/modules/v3/tic_tac_toe.js +0 -242
  48. package/src/modules/v3/time.js +0 -98
  49. package/src/modules/v3/token.js +0 -54
  50. package/src/modules/v3/username.js +0 -94
  51. package/src/modules/v3/utils.js +0 -77
  52. package/src/modules/v3.js +0 -15
  53. package/src/modules/v4/address.ts +0 -412
  54. package/src/modules/v4/agent.ts +0 -104
  55. package/src/modules/v4/algorithms.ts +0 -276
  56. package/src/modules/v4/captcha.ts +0 -114
  57. package/src/modules/v4/chat.ts +0 -130
  58. package/src/modules/v4/color.ts +0 -75
  59. package/src/modules/v4/convert.ts +0 -127
  60. package/src/modules/v4/cron.ts +0 -221
  61. package/src/modules/v4/dice.ts +0 -46
  62. package/src/modules/v4/domain.ts +0 -86
  63. package/src/modules/v4/encode.ts +0 -241
  64. package/src/modules/v4/geo.ts +0 -64
  65. package/src/modules/v4/hash.ts +0 -32
  66. package/src/modules/v4/hyperplanning.ts +0 -93
  67. package/src/modules/v4/ip.ts +0 -134
  68. package/src/modules/v4/levenshtein.ts +0 -46
  69. package/src/modules/v4/palette.ts +0 -71
  70. package/src/modules/v4/password.ts +0 -379
  71. package/src/modules/v4/personal.ts +0 -140
  72. package/src/modules/v4/placeholder.ts +0 -197
  73. package/src/modules/v4/qrcode.ts +0 -121
  74. package/src/modules/v4/regex.ts +0 -67
  75. package/src/modules/v4/statistics.ts +0 -62
  76. package/src/modules/v4/text.ts +0 -330
  77. package/src/modules/v4/tic_tac_toe.ts +0 -264
  78. package/src/modules/v4/time.ts +0 -149
  79. package/src/modules/v4/token.ts +0 -53
  80. package/src/modules/v4/username.ts +0 -94
  81. package/src/modules/v4/validate.ts +0 -76
  82. package/src/modules/v4.ts +0 -29
  83. package/src/routes/delete.ts +0 -72
  84. package/src/routes/get.ts +0 -856
  85. package/src/routes/index.ts +0 -59
  86. package/src/routes/patch.ts +0 -45
  87. package/src/routes/post.ts +0 -209
  88. package/src/storage/index.ts +0 -16
  89. package/src/types/express.d.ts +0 -17
  90. package/src/types/storage.ts +0 -48
  91. package/src/utils/colors.ts +0 -91
  92. package/src/utils/helpers.ts +0 -90
  93. package/src/utils/response.ts +0 -28
  94. package/tests/integration/api.test.ts +0 -772
  95. package/tests/tsconfig.json +0 -3
  96. package/tests/unit/address.test.ts +0 -84
  97. package/tests/unit/agent.test.ts +0 -113
  98. package/tests/unit/algorithms.test.ts +0 -121
  99. package/tests/unit/captcha.test.ts +0 -62
  100. package/tests/unit/chat.test.ts +0 -54
  101. package/tests/unit/color.test.ts +0 -54
  102. package/tests/unit/convert.test.ts +0 -68
  103. package/tests/unit/cron.test.ts +0 -110
  104. package/tests/unit/dice.test.ts +0 -58
  105. package/tests/unit/domain.test.ts +0 -48
  106. package/tests/unit/encode.test.ts +0 -109
  107. package/tests/unit/geo.test.ts +0 -46
  108. package/tests/unit/hash.test.ts +0 -45
  109. package/tests/unit/hyperplanning.test.ts +0 -97
  110. package/tests/unit/ip.test.ts +0 -140
  111. package/tests/unit/levenshtein.test.ts +0 -35
  112. package/tests/unit/palette.test.ts +0 -54
  113. package/tests/unit/password.test.ts +0 -86
  114. package/tests/unit/personal.test.ts +0 -66
  115. package/tests/unit/placeholder.test.ts +0 -58
  116. package/tests/unit/qrcode.test.ts +0 -83
  117. package/tests/unit/regex.test.ts +0 -95
  118. package/tests/unit/statistics.test.ts +0 -58
  119. package/tests/unit/text.test.ts +0 -108
  120. package/tests/unit/tic_tac_toe.test.ts +0 -56
  121. package/tests/unit/time.test.ts +0 -115
  122. package/tests/unit/token.test.ts +0 -62
  123. package/tests/unit/username.test.ts +0 -35
  124. package/tests/unit/validate.test.ts +0 -72
  125. package/tsconfig.json +0 -17
  126. 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
- }