@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
@@ -1,241 +0,0 @@
1
- import { MAX_STRING_LENGTH } from '../../constants.js';
2
-
3
- const MORSE_TABLE: Record<string, string> = {
4
- A: '.-',
5
- B: '-...',
6
- C: '-.-.',
7
- D: '-..',
8
- E: '.',
9
- F: '..-.',
10
- G: '--.',
11
- H: '....',
12
- I: '..',
13
- J: '.---',
14
- K: '-.-',
15
- L: '.-..',
16
- M: '--',
17
- N: '-.',
18
- O: '---',
19
- P: '.--.',
20
- Q: '--.-',
21
- R: '.-.',
22
- S: '...',
23
- T: '-',
24
- U: '..-',
25
- V: '...-',
26
- W: '.--',
27
- X: '-..-',
28
- Y: '-.--',
29
- Z: '--..',
30
- '0': '-----',
31
- '1': '.----',
32
- '2': '..---',
33
- '3': '...--',
34
- '4': '....-',
35
- '5': '.....',
36
- '6': '-....',
37
- '7': '--...',
38
- '8': '---..',
39
- '9': '----.',
40
- '.': '.-.-.-',
41
- ',': '--..--',
42
- '?': '..--..',
43
- "'": '.----.',
44
- '!': '-.-.--',
45
- '/': '-..-.',
46
- '(': '-.--.',
47
- ')': '-.--.-',
48
- '&': '.-...',
49
- ':': '---...',
50
- ';': '-.-.-.',
51
- '=': '-...-',
52
- '+': '.-.-.',
53
- '-': '-....-',
54
- _: '..--.-',
55
- '"': '.-..-.',
56
- $: '...-..-',
57
- '@': '.--.-.',
58
- };
59
-
60
- const MORSE_REVERSE: Record<string, string> = Object.fromEntries(Object.entries(MORSE_TABLE).map(([k, v]) => [v, k]));
61
-
62
- function checkText(value: string): void {
63
- if (!value) throw new Error('A value is required');
64
- if (typeof value !== 'string') throw new Error('Value must be a string');
65
- if (value.length > MAX_STRING_LENGTH)
66
- throw new Error(`Value must be less than ${MAX_STRING_LENGTH} characters long`);
67
- }
68
-
69
- /**
70
- * Encodes a UTF-8 string to Base64.
71
- *
72
- * @param value - The string to encode
73
- * @returns Base64-encoded string
74
- * @throws Error if value is missing, not a string, or too long
75
- */
76
- export function base64encode(value: string): string {
77
- checkText(value);
78
- return Buffer.from(value, 'utf-8').toString('base64');
79
- }
80
-
81
- /**
82
- * Decodes a Base64 string to UTF-8.
83
- *
84
- * @param value - The Base64 string to decode
85
- * @returns Decoded UTF-8 string
86
- * @throws Error if value is missing, not valid Base64, or too long
87
- */
88
- export function base64decode(value: string): string {
89
- checkText(value);
90
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(value)) throw new Error('Invalid Base64 string');
91
- return Buffer.from(value, 'base64').toString('utf-8');
92
- }
93
-
94
- /**
95
- * Percent-encodes a string for safe use in a URL.
96
- *
97
- * @param value - The string to encode
98
- * @returns URL-encoded string
99
- * @throws Error if value is missing, not a string, or too long
100
- */
101
- export function urlencode(value: string): string {
102
- checkText(value);
103
- return encodeURIComponent(value);
104
- }
105
-
106
- /**
107
- * Decodes a percent-encoded URL string.
108
- *
109
- * @param value - The URL-encoded string to decode
110
- * @returns Decoded string
111
- * @throws Error if value is missing, not a valid URL-encoded string, or too long
112
- */
113
- export function urldecode(value: string): string {
114
- checkText(value);
115
- try {
116
- return decodeURIComponent(value);
117
- } catch {
118
- throw new Error('Invalid URL-encoded string');
119
- }
120
- }
121
-
122
- /**
123
- * Converts a plain text string to Morse code.
124
- *
125
- * @param value - The text to encode in Morse code
126
- * @returns Morse code string where letters are separated by spaces and words by " / "
127
- * @throws Error if value contains unsupported characters or is too long
128
- */
129
- export function morse(value: string): string {
130
- checkText(value);
131
- return value
132
- .toUpperCase()
133
- .split(' ')
134
- .map((word) =>
135
- word
136
- .split('')
137
- .map((c) => {
138
- if (c === '\n' || c === '\t') return '';
139
- const code = MORSE_TABLE[c];
140
- if (!code) throw new Error(`Unsupported character in Morse code: '${c}'`);
141
- return code;
142
- })
143
- .filter(Boolean)
144
- .join(' '),
145
- )
146
- .join(' / ');
147
- }
148
-
149
- /**
150
- * Converts a Morse code string back to plain text.
151
- *
152
- * @param value - Morse code string (letters separated by spaces, words by " / ")
153
- * @returns Decoded plain text string
154
- * @throws Error if the Morse code contains an unrecognized sequence or is too long
155
- */
156
- export function unmorse(value: string): string {
157
- checkText(value);
158
- return value
159
- .split(' / ')
160
- .map((word) =>
161
- word
162
- .split(' ')
163
- .map((code) => {
164
- if (!code) return '';
165
- const char = MORSE_REVERSE[code];
166
- if (!char) throw new Error(`Unsupported Morse sequence: '${code}'`);
167
- return char;
168
- })
169
- .join(''),
170
- )
171
- .join(' ');
172
- }
173
-
174
- /**
175
- * Applies the ROT13 substitution cipher to a string.
176
- *
177
- * @param value - The string to encode
178
- * @returns ROT13-encoded string (applying it twice restores the original)
179
- * @throws Error if value is missing, not a string, or too long
180
- */
181
- export function rot13(value: string): string {
182
- checkText(value);
183
- return value.replace(/[a-zA-Z]/g, (c) => {
184
- const base = c <= 'Z' ? 65 : 97;
185
- return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base);
186
- });
187
- }
188
-
189
- /**
190
- * Applies the Caesar cipher by shifting alphabetic characters by a given amount.
191
- *
192
- * @param value - The string to encode
193
- * @param shift - Number of positions to shift (can be negative for left shift)
194
- * @returns Caesar-shifted string
195
- * @throws Error if value is missing or shift is not a number
196
- */
197
- export function caesar(value: string, shift: string): string {
198
- checkText(value);
199
- const n = Number(shift);
200
- if (isNaN(n)) throw new Error('Shift must be a number');
201
- const s = ((n % 26) + 26) % 26;
202
- return value.replace(/[a-zA-Z]/g, (c) => {
203
- const base = c <= 'Z' ? 65 : 97;
204
- return String.fromCharCode(((c.charCodeAt(0) - base + s) % 26) + base);
205
- });
206
- }
207
-
208
- /**
209
- * Converts a string to its binary representation, one byte per character.
210
- *
211
- * @param value - The string to convert
212
- * @returns Space-separated 8-bit binary groups (one per character)
213
- * @throws Error if value is missing, not a string, or too long
214
- */
215
- export function binary(value: string): string {
216
- checkText(value);
217
- return value
218
- .split('')
219
- .map((c) => c.charCodeAt(0).toString(2).padStart(8, '0'))
220
- .join(' ');
221
- }
222
-
223
- /**
224
- * Converts a binary string back to plain text.
225
- *
226
- * @param value - Space-separated 8-bit binary groups
227
- * @returns Decoded string
228
- * @throws Error if value contains non-binary characters or groups are not 8 bits wide
229
- */
230
- export function unbinary(value: string): string {
231
- checkText(value);
232
- if (!/^[01\s]+$/.test(value)) throw new Error('Invalid binary string');
233
- return value
234
- .trim()
235
- .split(/\s+/)
236
- .map((b) => {
237
- if (b.length !== 8) throw new Error('Each binary group must contain 8 bits');
238
- return String.fromCharCode(parseInt(b, 2));
239
- })
240
- .join('');
241
- }
@@ -1,64 +0,0 @@
1
- export interface GeoResult {
2
- distance: { km: number; miles: number; nauticalMiles: number };
3
- bearing: { degrees: number; cardinal: string };
4
- from: { lat: number; lon: number };
5
- to: { lat: number; lon: number };
6
- }
7
-
8
- import { EARTH_RADIUS_KM } from '../../constants.js';
9
-
10
- const CARDINALS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
11
-
12
- function toRad(deg: number): number {
13
- return (deg * Math.PI) / 180;
14
- }
15
-
16
- function toDeg(rad: number): number {
17
- return (rad * 180) / Math.PI;
18
- }
19
-
20
- function parseCoord(value: string, name: string, min: number, max: number): number {
21
- const n = Number(value);
22
- if (isNaN(n)) throw new Error(`${name} must be a number`);
23
- if (n < min || n > max) throw new Error(`${name} must be between ${min} and ${max}`);
24
- return n;
25
- }
26
-
27
- /**
28
- * Calculates the great-circle distance and bearing between two geographic coordinates.
29
- *
30
- * @param lat1 - Latitude of the first point (-90 to 90)
31
- * @param lon1 - Longitude of the first point (-180 to 180)
32
- * @param lat2 - Latitude of the second point (-90 to 90)
33
- * @param lon2 - Longitude of the second point (-180 to 180)
34
- * @returns Distance in km/miles/nautical miles, compass bearing, and both coordinates
35
- * @throws Error if any coordinate is out of range or not a number
36
- */
37
- export default function geo(lat1: string, lon1: string, lat2: string, lon2: string): GeoResult {
38
- const a = parseCoord(lat1, 'lat1', -90, 90);
39
- const b = parseCoord(lon1, 'lon1', -180, 180);
40
- const c = parseCoord(lat2, 'lat2', -90, 90);
41
- const d = parseCoord(lon2, 'lon2', -180, 180);
42
-
43
- const dLat = toRad(c - a);
44
- const dLon = toRad(d - b);
45
- const sa = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(a)) * Math.cos(toRad(c)) * Math.sin(dLon / 2) ** 2;
46
- const distance = 2 * EARTH_RADIUS_KM * Math.asin(Math.sqrt(sa));
47
-
48
- const y = Math.sin(toRad(d - b)) * Math.cos(toRad(c));
49
- const x =
50
- Math.cos(toRad(a)) * Math.sin(toRad(c)) - Math.sin(toRad(a)) * Math.cos(toRad(c)) * Math.cos(toRad(d - b));
51
- const bearing = (toDeg(Math.atan2(y, x)) + 360) % 360;
52
- const cardinal = CARDINALS[Math.round(bearing / 22.5) % 16]!;
53
-
54
- return {
55
- distance: {
56
- km: +distance.toFixed(3),
57
- miles: +(distance * 0.621371).toFixed(3),
58
- nauticalMiles: +(distance * 0.539957).toFixed(3),
59
- },
60
- bearing: { degrees: +bearing.toFixed(2), cardinal },
61
- from: { lat: a, lon: b },
62
- to: { lat: c, lon: d },
63
- };
64
- }
@@ -1,32 +0,0 @@
1
- import { createHash, getHashes } from 'crypto';
2
-
3
- export interface HashResult {
4
- method: string;
5
- hash: string;
6
- encoding: string;
7
- }
8
-
9
- const ENCODINGS = new Set(['hex', 'base64']);
10
-
11
- /**
12
- * Hashes a text string using the specified algorithm and encoding.
13
- *
14
- * @param text - The text to hash
15
- * @param method - Hashing algorithm (e.g. "sha256", "md5")
16
- * @param encoding - Output encoding: "hex" (default) or "base64"
17
- * @returns Object containing the method, hash result, and encoding used
18
- * @throws Error if text is missing, the method is unsupported, or the encoding is invalid
19
- */
20
- export default function hash(text: string, method: string, encoding: string = 'hex'): HashResult {
21
- if (!text) throw new Error('Text is required');
22
-
23
- const methods = getHashes();
24
- if (!methods.includes(method)) throw new Error(`Unsupported method. Use one of: ${methods.join(', ')}`);
25
-
26
- if (!ENCODINGS.has(encoding)) throw new Error('Encoding must be one of: hex, base64');
27
-
28
- const result = createHash(method)
29
- .update(text)
30
- .digest(encoding as 'hex' | 'base64');
31
- return { method, hash: result, encoding };
32
- }
@@ -1,93 +0,0 @@
1
- import ical from 'ical.js';
2
-
3
- import { formatDate } from '../../utils/helpers.js';
4
-
5
- interface CalendarEvent {
6
- summary: string[] | string;
7
- subject?: string;
8
- teacher?: string;
9
- classes?: string[];
10
- type?: string;
11
- start: string;
12
- end: string;
13
- }
14
-
15
- function blocked(hostname: string): boolean {
16
- const list = ['localhost', '127.0.0.1', '0.0.0.0', '[::1]', 'metadata.google.internal'];
17
- if (list.includes(hostname)) return true;
18
-
19
- const parts = hostname.split('.').map(Number);
20
- if (parts.length === 4 && parts.every((n) => !isNaN(n))) {
21
- if (parts[0] === 10) return true;
22
- if (parts[0] === 172 && parts[1]! >= 16 && parts[1]! <= 31) return true;
23
- if (parts[0] === 192 && parts[1] === 168) return true;
24
- if (parts[0] === 169 && parts[1] === 254) return true;
25
- if (parts[0] === 0) return true;
26
- }
27
-
28
- return false;
29
- }
30
-
31
- /**
32
- * Fetches and parses an ICS calendar from a Hyperplanning URL, filtering out past events.
33
- *
34
- * @param url - HTTPS URL to the ICS calendar
35
- * @param detail - Level of detail: "full" (subject/teacher/classes), "list" (summary/times), or default (summary only)
36
- * @returns Array of calendar events sorted by start time, excluding past events
37
- * @throws Error if the URL is invalid, uses HTTP, points to a private host, or does not return a valid ICS file
38
- */
39
- export default async function hyperplanning(url: string, detail?: string): Promise<CalendarEvent[]> {
40
- let parsed: URL;
41
- try {
42
- parsed = new URL(url);
43
- } catch {
44
- throw new Error('Invalid URL.');
45
- }
46
-
47
- if (parsed.protocol !== 'https:') throw new Error('Only HTTPS URLs are allowed.');
48
- if (blocked(parsed.hostname)) throw new Error('Access to private/internal hosts is not allowed.');
49
-
50
- const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
51
-
52
- if (!response.ok || !(response.headers.get('content-type') || '').includes('text/calendar')) {
53
- throw new Error('Invalid ICS file format.');
54
- }
55
-
56
- const events: CalendarEvent[] = new ical.Component(ical.parse(await response.text()))
57
- .getAllSubcomponents('vevent')
58
- .map((e: ical.Component) => {
59
- const evt = new ical.Event(e);
60
- const summary = (evt.summary || '').split(' ').filter((part: string) => part !== '-');
61
- const start = formatDate(evt.startDate.toJSDate());
62
- const end = formatDate(evt.endDate.toJSDate());
63
-
64
- if (detail === 'full') {
65
- const desc = (evt.description || '').split('\n').map((l: string) => l.trim());
66
- const extract = (p: string) => (desc.find((l: string) => l.startsWith(p)) || '').replace(p, '').trim();
67
-
68
- return {
69
- summary,
70
- subject: extract('Matière :'),
71
- teacher: extract('Enseignant :'),
72
- classes: extract('Promotions :')
73
- .split(', ')
74
- .map((c: string) => c.trim()),
75
- type: extract('Salle :') || undefined,
76
- start,
77
- end,
78
- };
79
- }
80
-
81
- if (detail === 'list') return { summary, start, end };
82
-
83
- return {
84
- summary: evt.summary || '',
85
- start,
86
- end,
87
- };
88
- })
89
- .sort((a: CalendarEvent, b: CalendarEvent) => new Date(a.start).getTime() - new Date(b.start).getTime())
90
- .filter((e: CalendarEvent) => new Date(e.end) >= new Date());
91
-
92
- return events;
93
- }
@@ -1,134 +0,0 @@
1
- import { MAX_STRING_LENGTH } from '../../constants.js';
2
-
3
- export interface IpResult {
4
- ip: string;
5
- version: 'IPv4' | 'IPv6';
6
- type: 'public' | 'private' | 'loopback' | 'link-local' | 'multicast' | 'broadcast';
7
- class?: 'A' | 'B' | 'C' | 'D' | 'E';
8
- range?: string;
9
- binary: string;
10
- decimal?: number;
11
- reverse: string;
12
- }
13
-
14
- /**
15
- * Parses and analyzes an IPv4 address.
16
- *
17
- * @param raw - The IPv4 address string (e.g. "192.168.1.1")
18
- * @returns Detailed information about the address
19
- * @throws Error if the address is malformed
20
- */
21
- function parseIPv4(raw: string): IpResult {
22
- const parts = raw.split('.');
23
- if (parts.length !== 4) throw new Error('Invalid IPv4 address');
24
- const octets = parts.map(Number);
25
- if (octets.some((o) => isNaN(o) || !Number.isInteger(o) || o < 0 || o > 255))
26
- throw new Error('Invalid IPv4 address');
27
-
28
- const [a, b, c, d] = octets as [number, number, number, number];
29
-
30
- let cls: 'A' | 'B' | 'C' | 'D' | 'E';
31
- if (a < 128) cls = 'A';
32
- else if (a < 192) cls = 'B';
33
- else if (a < 224) cls = 'C';
34
- else if (a < 240) cls = 'D';
35
- else cls = 'E';
36
-
37
- let type: IpResult['type'] = 'public';
38
- let range: string | undefined;
39
-
40
- if (a === 127) {
41
- type = 'loopback';
42
- range = '127.0.0.0/8';
43
- } else if (a === 10) {
44
- type = 'private';
45
- range = '10.0.0.0/8';
46
- } else if (a === 172 && b >= 16 && b <= 31) {
47
- type = 'private';
48
- range = '172.16.0.0/12';
49
- } else if (a === 192 && b === 168) {
50
- type = 'private';
51
- range = '192.168.0.0/16';
52
- } else if (a === 169 && b === 254) {
53
- type = 'link-local';
54
- range = '169.254.0.0/16';
55
- } else if (a >= 224 && a <= 239) {
56
- type = 'multicast';
57
- range = '224.0.0.0/4';
58
- } else if (raw === '255.255.255.255') {
59
- type = 'broadcast';
60
- }
61
-
62
- const binary = octets.map((o) => o.toString(2).padStart(8, '0')).join('.');
63
- const decimal = ((a << 24) | (b << 16) | (c << 8) | d) >>> 0;
64
- const reverse = [...octets].reverse().join('.') + '.in-addr.arpa';
65
-
66
- const result: IpResult = { ip: raw, version: 'IPv4', type, class: cls, binary, decimal, reverse };
67
- if (range) result.range = range;
68
- return result;
69
- }
70
-
71
- /**
72
- * Parses and analyzes an IPv6 address, expanding :: shorthand.
73
- *
74
- * @param raw - The IPv6 address string (e.g. "::1" or "fe80::1")
75
- * @returns Detailed information about the address
76
- * @throws Error if the address is malformed
77
- */
78
- function parseIPv6(raw: string): IpResult {
79
- let expanded = raw;
80
- if (raw.includes('::')) {
81
- const halves = raw.split('::');
82
- const left = halves[0] ? halves[0].split(':') : [];
83
- const right = halves[1] ? halves[1].split(':') : [];
84
- const fill = Array(8 - left.length - right.length).fill('0');
85
- expanded = [...left, ...fill, ...right].join(':');
86
- }
87
-
88
- const groups = expanded.split(':');
89
- if (groups.length !== 8) throw new Error('Invalid IPv6 address');
90
- const values = groups.map((g) => parseInt(g || '0', 16));
91
- if (values.some((v) => isNaN(v) || v < 0 || v > 0xffff)) throw new Error('Invalid IPv6 address');
92
-
93
- let type: IpResult['type'] = 'public';
94
- let range: string | undefined;
95
-
96
- if (raw === '::1' || values.every((v, i) => (i < 7 ? v === 0 : v === 1))) {
97
- type = 'loopback';
98
- range = '::1/128';
99
- } else if ((values[0]! & 0xfe00) === 0xfc00) {
100
- type = 'private';
101
- range = 'fc00::/7';
102
- } else if ((values[0]! & 0xffc0) === 0xfe80) {
103
- type = 'link-local';
104
- range = 'fe80::/10';
105
- } else if ((values[0]! & 0xff00) === 0xff00) {
106
- type = 'multicast';
107
- range = 'ff00::/8';
108
- }
109
-
110
- const binary = values.map((v) => v.toString(2).padStart(16, '0')).join(':');
111
- const hex = values.map((v) => v.toString(16).padStart(4, '0')).join('');
112
- const reverse = hex.split('').reverse().join('.') + '.ip6.arpa';
113
-
114
- const result: IpResult = { ip: expanded, version: 'IPv6', type, binary, reverse };
115
- if (range) result.range = range;
116
- return result;
117
- }
118
-
119
- /**
120
- * Analyzes an IPv4 or IPv6 address and returns its type, class, binary
121
- * representation, decimal value, and reverse DNS notation.
122
- *
123
- * @param address - The IP address to analyze
124
- * @returns Detailed breakdown of the address
125
- * @throws Error if the address is missing or invalid
126
- */
127
- export default function ip(address: string): IpResult {
128
- if (!address || typeof address !== 'string') throw new Error('An IP address is required');
129
- if (address.length > MAX_STRING_LENGTH) throw new Error('Address is too long');
130
-
131
- if (address.includes(':')) return parseIPv6(address);
132
- if (address.includes('.')) return parseIPv4(address);
133
- throw new Error('Invalid IP address format');
134
- }
@@ -1,46 +0,0 @@
1
- import { MAX_LEVENSHTEIN_LENGTH } from '../../constants.js';
2
-
3
- /**
4
- * Computes the Levenshtein edit distance between two strings.
5
- *
6
- * @param str1 - First string
7
- * @param str2 - Second string
8
- * @returns Object containing both strings and the minimum number of single-character edits to transform one into the other
9
- * @throws Error if either string is missing, not a string, or exceeds the maximum length
10
- */
11
- export default function levenshtein(str1: string, str2: string): { str1: string; str2: string; distance: number } {
12
- if (!str1 || typeof str1 !== 'string') {
13
- throw new Error('Please provide a valid first string');
14
- }
15
-
16
- if (!str2 || typeof str2 !== 'string') {
17
- throw new Error('Please provide a valid second string');
18
- }
19
-
20
- if (str1.length > MAX_LEVENSHTEIN_LENGTH) {
21
- throw new Error('First string exceeds 1000 characters');
22
- }
23
-
24
- if (str2.length > MAX_LEVENSHTEIN_LENGTH) {
25
- throw new Error('Second string exceeds 1000 characters');
26
- }
27
-
28
- const m: number[][] = Array.from({ length: str1.length + 1 }, (_, i) => [i]);
29
- for (let j = 0; j <= str2.length; j++) m[0]![j] = j;
30
-
31
- for (let i = 1; i <= str1.length; i++) {
32
- for (let j = 1; j <= str2.length; j++) {
33
- m[i]![j] = Math.min(
34
- m[i - 1]![j]! + 1,
35
- m[i]![j - 1]! + 1,
36
- m[i - 1]![j - 1]! + (str1[i - 1] !== str2[j - 1] ? 1 : 0),
37
- );
38
- }
39
- }
40
-
41
- return {
42
- str1,
43
- str2,
44
- distance: m[str1.length]![str2.length]!,
45
- };
46
- }
@@ -1,71 +0,0 @@
1
- import { hexToRgb, hslToRgb, rgbToHex, rgbToHsl } from '../../utils/colors.js';
2
-
3
- export interface PaletteColor {
4
- hex: string;
5
- rgb: string;
6
- hsl: string;
7
- }
8
-
9
- export interface PaletteResult {
10
- base: PaletteColor;
11
- type: string;
12
- colors: PaletteColor[];
13
- }
14
-
15
- function format(r: number, g: number, b: number): PaletteColor {
16
- const [h, s, l] = rgbToHsl(r, g, b);
17
- return {
18
- hex: rgbToHex(r, g, b),
19
- rgb: `rgb(${r}, ${g}, ${b})`,
20
- hsl: `hsl(${h.toFixed(1)}, ${(s * 100).toFixed(1)}%, ${(l * 100).toFixed(1)}%)`,
21
- };
22
- }
23
-
24
- function fromHueShifts(base: [number, number, number], shifts: number[]): PaletteColor[] {
25
- const [h, s, l] = base;
26
- return shifts.map((shift) => {
27
- const newH = (h + shift + 360) % 360;
28
- const [r, g, b] = hslToRgb(newH, s, l);
29
- return format(r, g, b);
30
- });
31
- }
32
-
33
- /**
34
- * Generates a color palette from a base hex color using a specified harmony type.
35
- *
36
- * @param color - Base hex color (e.g. "#ff5733")
37
- * @param type - Palette type: "complementary", "triadic", "analogous", "tetradic", or "split-complementary"
38
- * @returns Object containing the base color and the derived palette colors in hex, RGB, and HSL
39
- * @throws Error if color or type is missing, or the type is not one of the accepted values
40
- */
41
- export default function palette(color: string, type: string): PaletteResult {
42
- if (!color) throw new Error('A base color is required');
43
- if (!type) throw new Error('A palette type is required');
44
-
45
- const [r, g, b] = hexToRgb(color);
46
- const base = format(r, g, b);
47
- const hsl = rgbToHsl(r, g, b);
48
-
49
- let colors: PaletteColor[];
50
- switch (type) {
51
- case 'complementary':
52
- colors = fromHueShifts(hsl, [0, 180]);
53
- break;
54
- case 'triadic':
55
- colors = fromHueShifts(hsl, [0, 120, 240]);
56
- break;
57
- case 'analogous':
58
- colors = fromHueShifts(hsl, [-60, -30, 0, 30, 60]);
59
- break;
60
- case 'tetradic':
61
- colors = fromHueShifts(hsl, [0, 90, 180, 270]);
62
- break;
63
- case 'split-complementary':
64
- colors = fromHueShifts(hsl, [0, 150, 210]);
65
- break;
66
- default:
67
- throw new Error('Type must be one of: complementary, triadic, analogous, tetradic, split-complementary');
68
- }
69
-
70
- return { base, type, colors };
71
- }