@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
package/src/modules/v4/encode.ts
DELETED
|
@@ -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
|
-
}
|
package/src/modules/v4/geo.ts
DELETED
|
@@ -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
|
-
}
|
package/src/modules/v4/hash.ts
DELETED
|
@@ -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
|
-
}
|
package/src/modules/v4/ip.ts
DELETED
|
@@ -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
|
-
}
|