@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.
- package/README.md +84 -47
- package/package.json +2 -1
- package/.github/FUNDING.yml +0 -1
- package/.prettierrc +0 -7
- package/eslint.config.js +0 -24
- package/robots.txt +0 -74
- package/src/app.ts +0 -53
- package/src/config/env.ts +0 -87
- package/src/config/plans.ts +0 -61
- package/src/config/versions.ts +0 -121
- package/src/constants.ts +0 -61
- package/src/favicon.ico +0 -0
- package/src/middleware/cors.ts +0 -23
- package/src/middleware/error.ts +0 -16
- package/src/middleware/json.ts +0 -9
- package/src/middleware/logger.ts +0 -32
- package/src/middleware/ratelimit.ts +0 -89
- package/src/middleware/version.ts +0 -40
- package/src/modules/v3/algorithms.js +0 -218
- package/src/modules/v3/captcha.js +0 -60
- package/src/modules/v3/chat.js +0 -111
- package/src/modules/v3/color.js +0 -60
- package/src/modules/v3/convert.js +0 -39
- package/src/modules/v3/domain.js +0 -86
- package/src/modules/v3/hash.js +0 -16
- package/src/modules/v3/hyperplanning.js +0 -56
- package/src/modules/v3/levenshtein.js +0 -46
- package/src/modules/v3/personal.js +0 -139
- package/src/modules/v3/qrcode.js +0 -20
- package/src/modules/v3/tic_tac_toe.js +0 -242
- package/src/modules/v3/time.js +0 -98
- package/src/modules/v3/token.js +0 -54
- package/src/modules/v3/username.js +0 -94
- package/src/modules/v3/utils.js +0 -77
- package/src/modules/v3.js +0 -15
- package/src/modules/v4/address.ts +0 -412
- package/src/modules/v4/agent.ts +0 -104
- package/src/modules/v4/algorithms.ts +0 -276
- package/src/modules/v4/captcha.ts +0 -114
- package/src/modules/v4/chat.ts +0 -130
- package/src/modules/v4/color.ts +0 -75
- package/src/modules/v4/convert.ts +0 -127
- package/src/modules/v4/cron.ts +0 -221
- package/src/modules/v4/dice.ts +0 -46
- package/src/modules/v4/domain.ts +0 -86
- package/src/modules/v4/encode.ts +0 -241
- package/src/modules/v4/geo.ts +0 -64
- package/src/modules/v4/hash.ts +0 -32
- package/src/modules/v4/hyperplanning.ts +0 -93
- package/src/modules/v4/ip.ts +0 -134
- package/src/modules/v4/levenshtein.ts +0 -46
- package/src/modules/v4/palette.ts +0 -71
- package/src/modules/v4/password.ts +0 -379
- package/src/modules/v4/personal.ts +0 -140
- package/src/modules/v4/placeholder.ts +0 -197
- package/src/modules/v4/qrcode.ts +0 -121
- package/src/modules/v4/regex.ts +0 -67
- package/src/modules/v4/statistics.ts +0 -62
- package/src/modules/v4/text.ts +0 -330
- package/src/modules/v4/tic_tac_toe.ts +0 -264
- package/src/modules/v4/time.ts +0 -149
- package/src/modules/v4/token.ts +0 -53
- package/src/modules/v4/username.ts +0 -94
- package/src/modules/v4/validate.ts +0 -76
- package/src/modules/v4.ts +0 -29
- package/src/routes/delete.ts +0 -72
- package/src/routes/get.ts +0 -856
- package/src/routes/index.ts +0 -59
- package/src/routes/patch.ts +0 -45
- package/src/routes/post.ts +0 -209
- package/src/storage/index.ts +0 -16
- package/src/types/express.d.ts +0 -17
- package/src/types/storage.ts +0 -48
- package/src/utils/colors.ts +0 -91
- package/src/utils/helpers.ts +0 -90
- package/src/utils/response.ts +0 -28
- package/tests/integration/api.test.ts +0 -772
- package/tests/tsconfig.json +0 -3
- package/tests/unit/address.test.ts +0 -84
- package/tests/unit/agent.test.ts +0 -113
- package/tests/unit/algorithms.test.ts +0 -121
- package/tests/unit/captcha.test.ts +0 -62
- package/tests/unit/chat.test.ts +0 -54
- package/tests/unit/color.test.ts +0 -54
- package/tests/unit/convert.test.ts +0 -68
- package/tests/unit/cron.test.ts +0 -110
- package/tests/unit/dice.test.ts +0 -58
- package/tests/unit/domain.test.ts +0 -48
- package/tests/unit/encode.test.ts +0 -109
- package/tests/unit/geo.test.ts +0 -46
- package/tests/unit/hash.test.ts +0 -45
- package/tests/unit/hyperplanning.test.ts +0 -97
- package/tests/unit/ip.test.ts +0 -140
- package/tests/unit/levenshtein.test.ts +0 -35
- package/tests/unit/palette.test.ts +0 -54
- package/tests/unit/password.test.ts +0 -86
- package/tests/unit/personal.test.ts +0 -66
- package/tests/unit/placeholder.test.ts +0 -58
- package/tests/unit/qrcode.test.ts +0 -83
- package/tests/unit/regex.test.ts +0 -95
- package/tests/unit/statistics.test.ts +0 -58
- package/tests/unit/text.test.ts +0 -108
- package/tests/unit/tic_tac_toe.test.ts +0 -56
- package/tests/unit/time.test.ts +0 -115
- package/tests/unit/token.test.ts +0 -62
- package/tests/unit/username.test.ts +0 -35
- package/tests/unit/validate.test.ts +0 -72
- package/tsconfig.json +0 -17
- package/tsconfig.test.json +0 -9
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
import { MAX_FACTORIAL, MAX_GCD_VALUE, MAX_PRIME_LIST, MAX_STRING_LENGTH, ROMAN_VALUES } from '../../constants.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checks whether two strings are anagrams of each other.
|
|
5
|
-
*
|
|
6
|
-
* @param value - First string
|
|
7
|
-
* @param value2 - Second string
|
|
8
|
-
* @returns True if the strings are anagrams, false otherwise
|
|
9
|
-
* @throws Error if either value is missing, not a string, or out of length bounds
|
|
10
|
-
*/
|
|
11
|
-
export function anagram(value: string, value2: string): boolean {
|
|
12
|
-
if (!value) throw new Error('First value is required');
|
|
13
|
-
if (!value2) throw new Error('Second value is required');
|
|
14
|
-
|
|
15
|
-
if (typeof value !== 'string') throw new Error('First value must be a string');
|
|
16
|
-
if (typeof value2 !== 'string') throw new Error('Second value must be a string');
|
|
17
|
-
|
|
18
|
-
if (value.length < 2) throw new Error('First value must be at least two characters long');
|
|
19
|
-
if (value.length > MAX_STRING_LENGTH) throw new Error('First value must be less than 1000 characters long');
|
|
20
|
-
|
|
21
|
-
if (value2.length < 2) throw new Error('Second value must be at least two characters long');
|
|
22
|
-
if (value2.length > MAX_STRING_LENGTH) throw new Error('Second value must be less than 1000 characters long');
|
|
23
|
-
|
|
24
|
-
return value.split('').sort().join('') === value2.split('').sort().join('');
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Sorts a comma-separated list of numbers using the bubble sort algorithm.
|
|
29
|
-
*
|
|
30
|
-
* @param value - Comma-separated string of numbers (e.g. "3,1,2")
|
|
31
|
-
* @returns Array of numbers sorted in ascending order
|
|
32
|
-
* @throws Error if value is missing, non-numeric, or contains fewer than two elements
|
|
33
|
-
*/
|
|
34
|
-
export function bubblesort(value: string): number[] {
|
|
35
|
-
if (!value) throw new Error('A value is required');
|
|
36
|
-
|
|
37
|
-
if (typeof value !== 'string') throw new Error('Value must be a string');
|
|
38
|
-
|
|
39
|
-
const arr = value.split(',').map(Number);
|
|
40
|
-
|
|
41
|
-
if (arr.some(isNaN)) throw new Error('Array must contain only numbers');
|
|
42
|
-
if (arr.length < 2) throw new Error('Array must contain at least two numbers');
|
|
43
|
-
if (arr.length > MAX_STRING_LENGTH) throw new Error('Array must contain less than 1000 numbers');
|
|
44
|
-
|
|
45
|
-
for (let i = 0; i < arr.length - 1; i++) {
|
|
46
|
-
for (let j = 0; j < arr.length - i - 1; j++) {
|
|
47
|
-
if (arr[j]! > arr[j + 1]!) [arr[j], arr[j + 1]] = [arr[j + 1]!, arr[j]!];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return arr;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Computes the factorial of a non-negative integer.
|
|
56
|
-
*
|
|
57
|
-
* @param value - The integer to compute the factorial of
|
|
58
|
-
* @returns The factorial of the given number
|
|
59
|
-
* @throws Error if value is not a number, is negative, or exceeds the maximum allowed
|
|
60
|
-
*/
|
|
61
|
-
export function factorial(value: string | number): number {
|
|
62
|
-
const num = Number(value);
|
|
63
|
-
if (isNaN(num)) throw new Error('Value must be a number');
|
|
64
|
-
if (num < 0) throw new Error('Number must be positive');
|
|
65
|
-
if (num > MAX_FACTORIAL) throw new Error('Number must be between 0 and 170');
|
|
66
|
-
|
|
67
|
-
return num <= 1 ? 1 : num * factorial(num - 1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Returns the first n numbers of the Fibonacci sequence.
|
|
72
|
-
*
|
|
73
|
-
* @param value - How many Fibonacci numbers to generate
|
|
74
|
-
* @returns Array of Fibonacci numbers starting from 0
|
|
75
|
-
* @throws Error if value is not a number, is negative, or exceeds 1000
|
|
76
|
-
*/
|
|
77
|
-
export function fibonacci(value: string | number): number[] {
|
|
78
|
-
const num = Number(value);
|
|
79
|
-
if (isNaN(num)) throw new Error('Value must be a number');
|
|
80
|
-
if (num < 0) throw new Error('Number must be positive');
|
|
81
|
-
if (num > 1000) throw new Error('Number must be between 0 and 1000');
|
|
82
|
-
|
|
83
|
-
const fib = [0, 1];
|
|
84
|
-
|
|
85
|
-
for (let i = 2; i < num; i++) {
|
|
86
|
-
fib.push(fib[i - 1]! + fib[i - 2]!);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return fib.slice(0, num);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Computes the greatest common divisor of two positive integers using the Euclidean algorithm.
|
|
94
|
-
*
|
|
95
|
-
* @param value - First positive integer
|
|
96
|
-
* @param value2 - Second positive integer
|
|
97
|
-
* @returns The greatest common divisor
|
|
98
|
-
* @throws Error if either value is not a positive number or exceeds the maximum allowed
|
|
99
|
-
*/
|
|
100
|
-
export function gcd(value: string | number, value2: string | number): number {
|
|
101
|
-
let a = Number(value);
|
|
102
|
-
let b = Number(value2);
|
|
103
|
-
if (isNaN(a)) throw new Error('First value must be a number');
|
|
104
|
-
if (isNaN(b)) throw new Error('Second value must be a number');
|
|
105
|
-
|
|
106
|
-
if (a <= 0) throw new Error('First number must be strictly positive');
|
|
107
|
-
if (b <= 0) throw new Error('Second number must be strictly positive');
|
|
108
|
-
|
|
109
|
-
if (a > MAX_GCD_VALUE) throw new Error('First number must be between 0 and 100000');
|
|
110
|
-
if (b > MAX_GCD_VALUE) throw new Error('Second number must be between 0 and 100000');
|
|
111
|
-
|
|
112
|
-
a = Math.abs(a);
|
|
113
|
-
b = Math.abs(b);
|
|
114
|
-
|
|
115
|
-
while (b) [a, b] = [b, a % b];
|
|
116
|
-
|
|
117
|
-
return a;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Determines whether a given number is prime.
|
|
122
|
-
*
|
|
123
|
-
* @param value - The number to test
|
|
124
|
-
* @returns True if the number is prime, false otherwise
|
|
125
|
-
* @throws Error if value is not a positive number or exceeds the maximum allowed
|
|
126
|
-
*/
|
|
127
|
-
export function isprime(value: string | number): boolean {
|
|
128
|
-
const num = Number(value);
|
|
129
|
-
if (isNaN(num)) throw new Error('Value must be a number');
|
|
130
|
-
if (num < 1) throw new Error('Number must be positive');
|
|
131
|
-
if (num > MAX_GCD_VALUE) throw new Error('Number must be between 1 and 100000');
|
|
132
|
-
|
|
133
|
-
for (let i = 2; i <= Math.sqrt(num); i++) {
|
|
134
|
-
if (num % i === 0) return false;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Checks whether a string reads the same forwards and backwards.
|
|
142
|
-
*
|
|
143
|
-
* @param value - The string to test
|
|
144
|
-
* @returns True if the string is a palindrome, false otherwise
|
|
145
|
-
* @throws Error if value is missing, not a string, or out of length bounds
|
|
146
|
-
*/
|
|
147
|
-
export function palindrome(value: string): boolean {
|
|
148
|
-
if (!value) throw new Error('A value is required');
|
|
149
|
-
|
|
150
|
-
if (typeof value !== 'string') throw new Error('Value must be a string');
|
|
151
|
-
|
|
152
|
-
if (value.length < 2) throw new Error('Value must be at least two characters long');
|
|
153
|
-
if (value.length > MAX_STRING_LENGTH) throw new Error('Value must be less than 1000 characters long');
|
|
154
|
-
|
|
155
|
-
return value === value.split('').reverse().join('');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Returns the prime factorization of a given integer.
|
|
160
|
-
*
|
|
161
|
-
* @param value - An integer greater than 1
|
|
162
|
-
* @returns Array of prime factors in ascending order
|
|
163
|
-
* @throws Error if value is not a number, less than 2, or exceeds the maximum allowed
|
|
164
|
-
*/
|
|
165
|
-
export function primefactors(value: string | number): number[] {
|
|
166
|
-
let num = Number(value);
|
|
167
|
-
if (isNaN(num)) throw new Error('Value must be a number');
|
|
168
|
-
if (num < 2) throw new Error('Number must be positive and greater than 1');
|
|
169
|
-
if (num > MAX_GCD_VALUE) throw new Error('Number must be between 2 and 100000');
|
|
170
|
-
|
|
171
|
-
const factors: number[] = [];
|
|
172
|
-
|
|
173
|
-
for (let i = 2; i <= num; i++) {
|
|
174
|
-
while (num % i === 0) {
|
|
175
|
-
factors.push(i);
|
|
176
|
-
num /= i;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return factors;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Returns all prime numbers up to and including the given limit.
|
|
185
|
-
*
|
|
186
|
-
* @param value - The upper bound (inclusive)
|
|
187
|
-
* @returns Array of prime numbers up to the given limit
|
|
188
|
-
* @throws Error if value is not a number, less than 2, or exceeds the maximum allowed
|
|
189
|
-
*/
|
|
190
|
-
export function primelist(value: string | number): number[] {
|
|
191
|
-
const num = Number(value);
|
|
192
|
-
if (isNaN(num)) throw new Error('Value must be a number');
|
|
193
|
-
if (num < 2) throw new Error('Number must be positive and greater than 1');
|
|
194
|
-
if (num > MAX_PRIME_LIST) throw new Error('Number must be between 2 and 10000');
|
|
195
|
-
|
|
196
|
-
const primes: number[] = [];
|
|
197
|
-
|
|
198
|
-
for (let i = 2; i <= num; i++) {
|
|
199
|
-
let isPrime = true;
|
|
200
|
-
|
|
201
|
-
for (let j = 2; j <= Math.sqrt(i); j++) {
|
|
202
|
-
if (i % j === 0) {
|
|
203
|
-
isPrime = false;
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (isPrime) primes.push(i);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return primes;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Reverses a string.
|
|
216
|
-
*
|
|
217
|
-
* @param value - The string to reverse
|
|
218
|
-
* @returns The reversed string
|
|
219
|
-
* @throws Error if value is missing or not a string
|
|
220
|
-
*/
|
|
221
|
-
export function reverse(value: string): string {
|
|
222
|
-
if (!value) throw new Error('A value is required');
|
|
223
|
-
|
|
224
|
-
if (typeof value !== 'string') throw new Error('Value must be a string');
|
|
225
|
-
|
|
226
|
-
return value.split('').reverse().join('');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Converts an integer to its Roman numeral representation, or a Roman numeral to an integer.
|
|
231
|
-
*
|
|
232
|
-
* @param value - An integer (1–3999) or a Roman numeral string
|
|
233
|
-
* @returns The converted value — a Roman numeral string if input was numeric, or an integer if input was a Roman numeral
|
|
234
|
-
* @throws Error if value is missing, out of range, or not a valid Roman numeral
|
|
235
|
-
*/
|
|
236
|
-
export function roman(value: string): number | string {
|
|
237
|
-
if (!value) throw new Error('A value is required');
|
|
238
|
-
|
|
239
|
-
const num = Number(value);
|
|
240
|
-
if (!isNaN(num)) {
|
|
241
|
-
if (!Number.isInteger(num)) throw new Error('Value must be an integer');
|
|
242
|
-
if (num < 1 || num > 3999) throw new Error('Number must be between 1 and 3999');
|
|
243
|
-
|
|
244
|
-
let result = '';
|
|
245
|
-
let n = num;
|
|
246
|
-
for (const [val, sym] of ROMAN_VALUES) {
|
|
247
|
-
while (n >= val) {
|
|
248
|
-
result += sym;
|
|
249
|
-
n -= val;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return result;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (typeof value !== 'string') throw new Error('Value must be a number or a Roman numeral');
|
|
256
|
-
const upper = value.toUpperCase();
|
|
257
|
-
if (!/^[MDCLXVI]+$/.test(upper)) throw new Error('Invalid Roman numeral');
|
|
258
|
-
|
|
259
|
-
let result = 0;
|
|
260
|
-
let i = 0;
|
|
261
|
-
while (i < upper.length) {
|
|
262
|
-
const two = upper.slice(i, i + 2);
|
|
263
|
-
const match = ROMAN_VALUES.find(([, sym]) => sym === two);
|
|
264
|
-
if (match) {
|
|
265
|
-
result += match[0];
|
|
266
|
-
i += 2;
|
|
267
|
-
} else {
|
|
268
|
-
const one = ROMAN_VALUES.find(([, sym]) => sym === upper[i]);
|
|
269
|
-
if (!one) throw new Error('Invalid Roman numeral');
|
|
270
|
-
result += one[0];
|
|
271
|
-
i += 1;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
return result;
|
|
276
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { createCanvas } from 'canvas';
|
|
2
|
-
|
|
3
|
-
import { normalizeColor } from '../../utils/colors.js';
|
|
4
|
-
|
|
5
|
-
export interface CaptchaOptions {
|
|
6
|
-
text?: string;
|
|
7
|
-
length?: number;
|
|
8
|
-
width?: number;
|
|
9
|
-
height?: number;
|
|
10
|
-
noise?: 'low' | 'medium' | 'high';
|
|
11
|
-
bg?: string;
|
|
12
|
-
color?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface CaptchaResult {
|
|
16
|
-
contentType: string;
|
|
17
|
-
body: Buffer;
|
|
18
|
-
text: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const NOISE_LEVELS = new Set(['low', 'medium', 'high']);
|
|
22
|
-
const NOISE_CONFIG = { low: { lines: 8, dots: 80 }, medium: { lines: 25, dots: 300 }, high: { lines: 50, dots: 600 } };
|
|
23
|
-
const CHARSET = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
|
|
24
|
-
|
|
25
|
-
function generateText(length: number): string {
|
|
26
|
-
let result = '';
|
|
27
|
-
for (let i = 0; i < length; i++) {
|
|
28
|
-
result += CHARSET[Math.floor(Math.random() * CHARSET.length)];
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function clamp(value: number | undefined, name: string, def: number, min: number, max: number): number {
|
|
34
|
-
if (value === undefined) return def;
|
|
35
|
-
if (isNaN(value)) throw new Error(`${name} must be a number`);
|
|
36
|
-
if (value < min || value > max) throw new Error(`${name} must be between ${min} and ${max}`);
|
|
37
|
-
return Math.floor(value);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Generates a CAPTCHA image with random or custom text and configurable noise.
|
|
42
|
-
*
|
|
43
|
-
* @param options - Captcha configuration options
|
|
44
|
-
* @returns Object containing the PNG buffer, content type, and the challenge text
|
|
45
|
-
* @throws Error if any option is out of the accepted range
|
|
46
|
-
*/
|
|
47
|
-
export default function captcha(options: CaptchaOptions): CaptchaResult {
|
|
48
|
-
const text = options.text || generateText(clamp(options.length, 'length', 6, 1, 20));
|
|
49
|
-
const height = clamp(options.height, 'height', 120, 50, 400);
|
|
50
|
-
const width = clamp(options.width, 'width', text.length * 60, 100, 800);
|
|
51
|
-
|
|
52
|
-
const noise = options.noise ?? 'medium';
|
|
53
|
-
if (!NOISE_LEVELS.has(noise)) throw new Error('noise must be one of: low, medium, high');
|
|
54
|
-
const { lines, dots } = NOISE_CONFIG[noise];
|
|
55
|
-
|
|
56
|
-
const bg = normalizeColor(options.bg, '#ffffff');
|
|
57
|
-
const baseColor = options.color ? normalizeColor(options.color, '#000000') : undefined;
|
|
58
|
-
|
|
59
|
-
const canvas = createCanvas(width, height);
|
|
60
|
-
const ctx = canvas.getContext('2d');
|
|
61
|
-
|
|
62
|
-
ctx.fillStyle = bg;
|
|
63
|
-
ctx.fillRect(0, 0, width, height);
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < lines; i++) {
|
|
66
|
-
ctx.strokeStyle = `rgba(${Math.floor(Math.random() * 180)}, ${Math.floor(Math.random() * 180)}, ${Math.floor(Math.random() * 180)}, ${0.3 + Math.random() * 0.4})`;
|
|
67
|
-
ctx.beginPath();
|
|
68
|
-
const x0 = Math.random() * width,
|
|
69
|
-
y0 = Math.random() * height;
|
|
70
|
-
ctx.moveTo(x0, y0);
|
|
71
|
-
ctx.bezierCurveTo(
|
|
72
|
-
Math.random() * width,
|
|
73
|
-
Math.random() * height,
|
|
74
|
-
Math.random() * width,
|
|
75
|
-
Math.random() * height,
|
|
76
|
-
Math.random() * width,
|
|
77
|
-
Math.random() * height,
|
|
78
|
-
);
|
|
79
|
-
ctx.lineWidth = 1 + Math.random() * 2;
|
|
80
|
-
ctx.stroke();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const baseFontSize = Math.floor(height * 0.5);
|
|
84
|
-
const charWidth = width / (text.length + 0.5);
|
|
85
|
-
let x = charWidth * 0.25;
|
|
86
|
-
|
|
87
|
-
for (let i = 0; i < text.length; i++) {
|
|
88
|
-
const size = Math.floor(baseFontSize * (0.8 + Math.random() * 0.4));
|
|
89
|
-
const y = height / 2 + (Math.random() - 0.5) * height * 0.3;
|
|
90
|
-
|
|
91
|
-
ctx.font = `${size}px Comic Sans Ms`;
|
|
92
|
-
if (baseColor) {
|
|
93
|
-
ctx.fillStyle = baseColor;
|
|
94
|
-
} else {
|
|
95
|
-
ctx.fillStyle = `rgb(${Math.floor(Math.random() * 180)}, ${Math.floor(Math.random() * 180)}, ${Math.floor(Math.random() * 180)})`;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
ctx.save();
|
|
99
|
-
ctx.translate(x + charWidth / 2, y);
|
|
100
|
-
ctx.rotate((Math.random() - 0.5) * 1.2);
|
|
101
|
-
ctx.fillText(text[i]!, -charWidth / 2, 0);
|
|
102
|
-
ctx.restore();
|
|
103
|
-
|
|
104
|
-
x += charWidth;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
for (let i = 0; i < dots; i++) {
|
|
108
|
-
ctx.fillStyle = `rgba(${Math.floor(Math.random() * 180)}, ${Math.floor(Math.random() * 180)}, ${Math.floor(Math.random() * 180)}, ${0.4 + Math.random() * 0.5})`;
|
|
109
|
-
const dotSize = 1 + Math.random() * 2;
|
|
110
|
-
ctx.fillRect(Math.floor(Math.random() * width), Math.floor(Math.random() * height), dotSize, dotSize);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return { contentType: 'image/png', body: canvas.toBuffer('image/png'), text };
|
|
114
|
-
}
|
package/src/modules/v4/chat.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { SESSION_TTL } from '../../constants.js';
|
|
2
|
-
import type { ChatMessage, ChatStorage } from '../../types/storage.js';
|
|
3
|
-
import { checkRateLimit } from '../../utils/helpers.js';
|
|
4
|
-
|
|
5
|
-
interface ChatParams {
|
|
6
|
-
username: string;
|
|
7
|
-
message?: string;
|
|
8
|
-
timestamp?: string;
|
|
9
|
-
session?: string;
|
|
10
|
-
token?: string;
|
|
11
|
-
storage: ChatStorage;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Handles real-time chat actions including sending, fetching, and clearing messages.
|
|
16
|
-
*
|
|
17
|
-
* @param action - The action to perform: "message", "private", "fetch", or "clear"
|
|
18
|
-
* @param params - Chat parameters including username, message, session, and shared storage
|
|
19
|
-
* @returns The result of the action — a message list, a sent confirmation, or a status message
|
|
20
|
-
* @throws Error if a required parameter is missing or the action is invalid
|
|
21
|
-
*/
|
|
22
|
-
export default function chat(action: string, params: ChatParams): ChatMessage[] | ChatMessage | { message: string } {
|
|
23
|
-
const storage = params.storage;
|
|
24
|
-
|
|
25
|
-
storage.messages ??= [];
|
|
26
|
-
storage.privateChats ??= {};
|
|
27
|
-
storage.sessions ??= {};
|
|
28
|
-
storage.rateLimits ??= {};
|
|
29
|
-
|
|
30
|
-
const { messages, privateChats, sessions, rateLimits } = storage;
|
|
31
|
-
|
|
32
|
-
if (!params.username) throw new Error('Please provide a username');
|
|
33
|
-
|
|
34
|
-
const u = params.username.toLowerCase();
|
|
35
|
-
const now = Date.now();
|
|
36
|
-
|
|
37
|
-
checkRateLimit(rateLimits, u, now);
|
|
38
|
-
|
|
39
|
-
if (action === 'message') {
|
|
40
|
-
return sendMessage(params, messages, privateChats, sessions, u, now);
|
|
41
|
-
} else if (action === 'private') {
|
|
42
|
-
return getPrivateChat(params, privateChats);
|
|
43
|
-
} else if (action === 'fetch') {
|
|
44
|
-
return fetchMessages(messages);
|
|
45
|
-
} else if (action === 'clear') {
|
|
46
|
-
return clearPrivateChat(params, privateChats, sessions, u);
|
|
47
|
-
} else {
|
|
48
|
-
throw new Error('Invalid action. Use "message", "private", "fetch", or "clear"');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function sendMessage(
|
|
53
|
-
params: ChatParams,
|
|
54
|
-
messages: ChatMessage[],
|
|
55
|
-
privateChats: Record<string, ChatMessage[]>,
|
|
56
|
-
sessions: Record<string, { user: string; last: number }>,
|
|
57
|
-
u: string,
|
|
58
|
-
now: number,
|
|
59
|
-
): { message: string } {
|
|
60
|
-
const { message, session, token } = params;
|
|
61
|
-
|
|
62
|
-
if (!message) throw new Error('Please provide a message');
|
|
63
|
-
if (!session) throw new Error('Please provide a valid session ID');
|
|
64
|
-
|
|
65
|
-
if (sessions[u] && sessions[u].user !== session) {
|
|
66
|
-
throw new Error('Session ID mismatch');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const msg: ChatMessage = {
|
|
70
|
-
username: params.username,
|
|
71
|
-
message,
|
|
72
|
-
timestamp: params.timestamp || new Date().toISOString(),
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
if (token) {
|
|
76
|
-
privateChats[token] = privateChats[token] || [];
|
|
77
|
-
privateChats[token]!.push(msg);
|
|
78
|
-
setTimeout(() => {
|
|
79
|
-
delete privateChats[token];
|
|
80
|
-
}, SESSION_TTL);
|
|
81
|
-
} else {
|
|
82
|
-
messages.push(msg);
|
|
83
|
-
setTimeout(() => messages.splice(messages.indexOf(msg), 1), SESSION_TTL);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
sessions[u] = sessions[u] || { user: session, last: now };
|
|
87
|
-
sessions[u]!.last = now;
|
|
88
|
-
|
|
89
|
-
setTimeout(() => {
|
|
90
|
-
if (sessions[u] && now - sessions[u].last >= SESSION_TTL) delete sessions[u];
|
|
91
|
-
}, SESSION_TTL);
|
|
92
|
-
|
|
93
|
-
return { message: 'Message sent successfully' };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function getPrivateChat(params: ChatParams, privateChats: Record<string, ChatMessage[]>): ChatMessage[] {
|
|
97
|
-
const { token } = params;
|
|
98
|
-
|
|
99
|
-
if (!token) throw new Error('Please provide a valid token');
|
|
100
|
-
|
|
101
|
-
if (privateChats[token]) return privateChats[token];
|
|
102
|
-
|
|
103
|
-
throw new Error('Invalid or expired token.');
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function fetchMessages(messages: ChatMessage[]): ChatMessage[] {
|
|
107
|
-
if (messages.length > 0) return messages;
|
|
108
|
-
throw new Error('No messages stored.');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function clearPrivateChat(
|
|
112
|
-
params: ChatParams,
|
|
113
|
-
privateChats: Record<string, ChatMessage[]>,
|
|
114
|
-
sessions: Record<string, { user: string; last: number }>,
|
|
115
|
-
u: string,
|
|
116
|
-
): { message: string } {
|
|
117
|
-
const { token, session } = params;
|
|
118
|
-
|
|
119
|
-
if (!token) throw new Error('Please provide a valid token');
|
|
120
|
-
if (!session) throw new Error('Please provide a valid session ID');
|
|
121
|
-
if (sessions[u] && sessions[u].user !== session) {
|
|
122
|
-
throw new Error('Session ID mismatch');
|
|
123
|
-
}
|
|
124
|
-
if (!privateChats[token]) throw new Error('Invalid or expired token.');
|
|
125
|
-
|
|
126
|
-
delete privateChats[token];
|
|
127
|
-
delete sessions[u];
|
|
128
|
-
|
|
129
|
-
return { message: 'Private chat cleared successfully' };
|
|
130
|
-
}
|
package/src/modules/v4/color.ts
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { hexToRgb, rgbToHex, rgbToHsl } from '../../utils/colors.js';
|
|
2
|
-
|
|
3
|
-
export interface ColorResult {
|
|
4
|
-
hex: string;
|
|
5
|
-
rgb: string;
|
|
6
|
-
hsl: string;
|
|
7
|
-
hsv: string;
|
|
8
|
-
hwb: string;
|
|
9
|
-
cmyk: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function rgbToHsv(r: number, g: number, b: number): [number, number, number] {
|
|
13
|
-
const max = Math.max(r, g, b),
|
|
14
|
-
min = Math.min(r, g, b),
|
|
15
|
-
v = max / 255,
|
|
16
|
-
s = max ? (max - min) / max : 0;
|
|
17
|
-
|
|
18
|
-
if (max === min) return [0, s * 100, v * 100];
|
|
19
|
-
|
|
20
|
-
let h = 0;
|
|
21
|
-
const d = max - min;
|
|
22
|
-
if (max === r) h = (g - b) / d + (g < b ? 6 : 0);
|
|
23
|
-
else if (max === g) h = (b - r) / d + 2;
|
|
24
|
-
else h = (r - g) / d + 4;
|
|
25
|
-
|
|
26
|
-
return [(h * 60) % 360, s * 100, v * 100];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function rgbToHwb(r: number, g: number, b: number): [number, number, number] {
|
|
30
|
-
const [h] = rgbToHsv(r, g, b);
|
|
31
|
-
const whiteness = Math.min(r, g, b) / 255;
|
|
32
|
-
const blackness = 1 - Math.max(r, g, b) / 255;
|
|
33
|
-
return [h, whiteness * 100, blackness * 100];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function rgbToCmyk(r: number, g: number, b: number): [number, number, number, number] {
|
|
37
|
-
const k = 1 - Math.max(r, g, b) / 255;
|
|
38
|
-
const c = (1 - r / 255 - k) / (1 - k) || 0;
|
|
39
|
-
const m = (1 - g / 255 - k) / (1 - k) || 0;
|
|
40
|
-
const y = (1 - b / 255 - k) / (1 - k) || 0;
|
|
41
|
-
return [c * 100, m * 100, y * 100, k * 100];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Converts a hex color to all major color space representations, or generates a random color.
|
|
46
|
-
*
|
|
47
|
-
* @param hex - Optional hex color string (e.g. "#ff5733"); generates a random color if omitted
|
|
48
|
-
* @returns Color in hex, RGB, HSL, HSV, HWB, and CMYK formats
|
|
49
|
-
* @throws Error if the hex string is invalid
|
|
50
|
-
*/
|
|
51
|
-
export default function color(hex?: string): ColorResult {
|
|
52
|
-
let r: number, g: number, b: number;
|
|
53
|
-
|
|
54
|
-
if (hex) {
|
|
55
|
-
[r, g, b] = hexToRgb(hex);
|
|
56
|
-
} else {
|
|
57
|
-
r = Math.floor(Math.random() * 256);
|
|
58
|
-
g = Math.floor(Math.random() * 256);
|
|
59
|
-
b = Math.floor(Math.random() * 256);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const [h, s, l] = rgbToHsl(r, g, b);
|
|
63
|
-
const hsv = rgbToHsv(r, g, b);
|
|
64
|
-
const hwb = rgbToHwb(r, g, b);
|
|
65
|
-
const cmyk = rgbToCmyk(r, g, b);
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
hex: rgbToHex(r, g, b),
|
|
69
|
-
rgb: `rgb(${r}, ${g}, ${b})`,
|
|
70
|
-
hsl: `hsl(${h.toFixed(1)}, ${(s * 100).toFixed(1)}%, ${(l * 100).toFixed(1)}%)`,
|
|
71
|
-
hsv: `hsv(${hsv[0].toFixed(1)}, ${hsv[1].toFixed(1)}%, ${hsv[2].toFixed(1)}%)`,
|
|
72
|
-
hwb: `hwb(${hwb[0].toFixed(1)}, ${hwb[1].toFixed(1)}%, ${hwb[2].toFixed(1)}%)`,
|
|
73
|
-
cmyk: `cmyk(${cmyk.map((x) => x.toFixed(1)).join('%, ')}%)`,
|
|
74
|
-
};
|
|
75
|
-
}
|