@20syldev/api 3.3.9 → 3.4.1

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/app.js CHANGED
@@ -1,85 +1,109 @@
1
+ // Import modules
2
+ import * as apiv3 from './modules/v3.js';
3
+
4
+ // Import dependencies
1
5
  import cors from 'cors';
2
6
  import dotenv from 'dotenv';
3
7
  import express from 'express';
4
8
  import fetch from 'node-fetch';
5
- import ical from 'ical.js';
6
- import { createCanvas } from 'canvas';
7
- import { randomBytes, getHashes, createHash } from 'crypto';
8
9
  import { urlencoded, json } from 'express';
9
- import { factorial } from 'mathjs';
10
10
  import { dirname, join } from 'path';
11
- import { toDataURL } from 'qrcode';
12
11
  import { fileURLToPath } from 'url';
13
- import { v4 } from 'uuid';
14
12
 
15
13
  const __filename = fileURLToPath(import.meta.url);
16
14
  const __dirname = dirname(__filename);
17
15
  const app = express();
18
16
 
19
17
  // Define allowed versions & endpoints for each version
20
- const versions = ['v1', 'v2', 'v3'];
21
- const endpoints = {
22
- v1: ['algorithms', 'captcha', 'color', 'convert', 'domain', 'infos', 'personal', 'qrcode', 'token', 'username', 'website'],
23
- v2: ['algorithms', 'captcha', 'chat', 'color', 'convert', 'domain', 'hash', 'infos', 'personal', 'qrcode', 'tic-tac-toe', 'token', 'username', 'website'],
24
- v3: ['algorithms', 'captcha', 'chat', 'color', 'convert', 'domain', 'hash', 'hyperplanning', 'infos', 'levenshtein', 'personal', 'qrcode', 'tic-tac-toe', 'time', 'token', 'username', 'website']
18
+ const v1 = {
19
+ get: [
20
+ { name: 'algorithms', path: '/algorithms?method={algorithm}&value={value}(&value2={value2})' },
21
+ { name: 'captcha', path: '/captcha?text={text}' },
22
+ { name: 'color', path: '/color' },
23
+ { name: 'convert', path: '/convert?value={value}&from={unit}&to={unit}' },
24
+ { name: 'domain', path: '/domain' },
25
+ { name: 'infos', path: '/infos' },
26
+ { name: 'personal', path: '/personal' },
27
+ { name: 'qrcode', path: '/qrcode?url={URL}' },
28
+ { name: 'username', path: '/username' },
29
+ { name: 'website', path: '/website' }
30
+ ],
31
+ post: [
32
+ { name: 'token', path: '/token' }
33
+ ]
34
+ };
35
+ const v2 = {
36
+ get: [
37
+ ...v1.get,
38
+ { name: 'chat', path: '/chat' }
39
+ ],
40
+ post: [
41
+ ...v1.post,
42
+ {
43
+ name: 'chat',
44
+ children: {
45
+ chat: '/chat',
46
+ private: '/chat/private'
47
+ }
48
+ },
49
+ { name: 'hash', path: '/hash' },
50
+ {
51
+ name: 'tic_tac_toe',
52
+ children: {
53
+ tic_tac_toe: '/tic-tac-toe',
54
+ fetch: '/tic-tac-toe/fetch'
55
+ }
56
+ },
57
+ { name: 'token', path: '/token' }
58
+ ]
59
+ };
60
+ const v3 = {
61
+ get: [
62
+ ...v2.get,
63
+ { name: 'levenshtein', path: '/levenshtein?str1={string}&str2={string}' },
64
+ { name: 'time', path: '/time(?type={type}&start={timestamp}&end={timestamp}&format={format}&timezone={timezone})' },
65
+ ],
66
+ post: [
67
+ ...v2.post,
68
+ { name: 'hyperplanning', path: '/hyperplanning' }
69
+ ]
70
+ };
71
+ const versions = {
72
+ v1: {
73
+ endpoints: v1,
74
+ modules: apiv3
75
+ },
76
+ v2: {
77
+ endpoints: v2,
78
+ modules: apiv3
79
+ },
80
+ v3: {
81
+ endpoints: v3,
82
+ modules: apiv3
83
+ }
25
84
  };
26
85
 
27
- // Arrowed functions (formatting, math & random)
28
- const formatDate = d => new Date(d.getTime() - d.getTimezoneOffset() * 60000).toISOString().replace('Z', '');
29
- const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
30
- const genID = () => {
31
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
32
- return Array.from(randomBytes(5)).map(b => chars[b % chars.length]).join('');
86
+ // API storage
87
+ const logs = [], ipLimits = {};
88
+
89
+ // Chat storage
90
+ const chatStorage = {
91
+ messages: [],
92
+ privateChats: {},
93
+ sessions: {},
94
+ rateLimits: {}
33
95
  };
34
- const genIP = () => `${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`;
35
- const genToken = (chars, length) => Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
36
- const random = (arr) => arr[Math.floor(Math.random() * arr.length)];
37
96
 
38
- // Store data
39
- const logs = [], chat = [], privateChats = {}, sessions = {}, rateLimits = {}, ipLimits = {}, games = {};
97
+ // Tic-Tac-Toe storage
98
+ const ticTacToeStorage = {
99
+ games: {},
100
+ sessions: {},
101
+ rateLimits: {}
102
+ };
40
103
 
41
104
  // Define global variables
42
105
  let contributions, lastFetch = 0, requests = 0, requestLimit, resetTime = Date.now() + 3600000;
43
106
 
44
- // ----------- ----------- MAIN FUNCTIONS ----------- ----------- //
45
-
46
- /**
47
- * Check the game result of a Tic-Tac-Toe game.
48
- *
49
- * @param {Array} moves - The moves of the game.
50
- * @returns {Object} - The result of the game.
51
- */
52
- function checkGame(moves) {
53
- let board = Array(3).fill().map(() => Array(3).fill(null));
54
- let playerSymbols = {};
55
- let playersOrder = [];
56
-
57
- moves.forEach(({ username, move }) => {
58
- if (!playerSymbols[username]) {
59
- playersOrder.push(username);
60
- playerSymbols[username] = playersOrder.length === 1 ? 'X' : 'O';
61
- }
62
- let [row, col] = move.split('-').map(Number);
63
- board[row - 1][col - 1] = playerSymbols[username];
64
- });
65
-
66
- const checkWinner = (symbol) => {
67
- for (let i = 0; i < 3; i++) {
68
- if (board[i][0] === symbol && board[i][1] === symbol && board[i][2] === symbol) return true;
69
- if (board[0][i] === symbol && board[1][i] === symbol && board[2][i] === symbol) return true;
70
- }
71
- if (board[0][0] === symbol && board[1][1] === symbol && board[2][2] === symbol) return true;
72
- if (board[0][2] === symbol && board[1][1] === symbol && board[2][0] === symbol) return true;
73
- return false;
74
- };
75
-
76
- let winner = Object.keys(playerSymbols).find(player => checkWinner(playerSymbols[player]));
77
- let isTie = !winner && moves.length === 9;
78
- let loser = winner && playersOrder.length === 2 ? playersOrder.find(player => player !== winner) : null;
79
-
80
- return { winner, loser, tie: isTie };
81
- }
82
-
83
107
  // ----------- ----------- MIDDLEWARES SETUP ----------- ----------- //
84
108
 
85
109
  dotenv.config();
@@ -196,21 +220,38 @@ app.use((err, req, res, next) => {
196
220
  // Check if version exists
197
221
  app.use('/:version', (req, res, next) => {
198
222
  const { version } = req.params;
199
- const latest = versions[versions.length - 1];
223
+ const latest = Object.keys(versions).pop();
200
224
  const endpoint = req.originalUrl.split('/').slice(2).join('/');
201
225
 
226
+ req.version = version;
227
+ req.latest = latest;
228
+
202
229
  if (['latest', 'fr', 'en'].includes(version)) {
203
230
  return res.redirect(endpoint ? `/${latest}/${endpoint}` : `/${latest}`);
204
231
  }
205
232
 
206
- if (!versions.includes(version) && version !== 'logs') {
233
+ if (version === 'logs') return res.jsonResponse(logs);
234
+
235
+ if (!versions[version] && version !== 'logs') {
207
236
  return res.status(404).jsonResponse({
208
237
  message: 'Not Found',
209
238
  error: `Invalid API version (${version}).`,
210
- documentation: `https://docs.sylvain.pro/${versions.at(-1)}`,
239
+ documentation: `https://docs.sylvain.pro/${latest}`,
240
+ status: '404'
241
+ });
242
+ }
243
+
244
+ req.module = versions[version].modules;
245
+
246
+ if (!req.module) {
247
+ return res.status(404).jsonResponse({
248
+ message: 'Not Found',
249
+ error: `Module not found for version ${version}.`,
250
+ documentation: `https://docs.sylvain.pro/${latest}`,
211
251
  status: '404'
212
252
  });
213
253
  }
254
+
214
255
  next();
215
256
  });
216
257
 
@@ -218,11 +259,14 @@ app.use('/:version', (req, res, next) => {
218
259
  app.use('/:version/:endpoint', (req, res, next) => {
219
260
  const { version, endpoint } = req.params;
220
261
 
221
- if (!versions.includes(version) || !endpoints[version].includes(endpoint)) {
262
+ req.version = version;
263
+ req.endpoint = endpoint;
264
+
265
+ if (!req.endpoint) {
222
266
  return res.status(404).jsonResponse({
223
267
  message: 'Not Found',
224
- error: `Endpoint '${endpoint}' does not exist in ${version}.`,
225
- documentation: `https://docs.sylvain.pro/${versions.at(-1)}`,
268
+ error: `Endpoint '${endpoint}' does not exist in ${req.version}.`,
269
+ documentation: `https://docs.sylvain.pro/${versions[version.length - 1]}`,
226
270
  status: '404'
227
271
  });
228
272
  }
@@ -233,117 +277,60 @@ app.use('/:version/:endpoint', (req, res, next) => {
233
277
 
234
278
  // Main route
235
279
  app.get('/', (req, res) => {
236
- res.setHeader('Content-Type', 'application/json');
280
+ const base = `${req.protocol}://${req.get('host')}`;
281
+ const links = Object.keys(versions).reduce((link, version) => {
282
+ link[version] = `${base}/${version}`;
283
+ return link;
284
+ }, {});
285
+
237
286
  res.jsonResponse({
238
287
  documentation: 'https://docs.sylvain.pro',
239
- latest: 'https://api.sylvain.pro/latest',
240
- logs: 'https://api.sylvain.pro/logs',
241
- versions: {
242
- v1: 'https://api.sylvain.pro/v1',
243
- v2: 'https://api.sylvain.pro/v2',
244
- v3: 'https://api.sylvain.pro/v3'
245
- }
288
+ latest: `${base}/latest`,
289
+ logs: `${base}/logs`,
290
+ versions: links
246
291
  });
247
292
  });
248
293
 
249
- // Display v1 endpoints
250
- app.get('/v1', (req, res) => {
251
- res.jsonResponse({
252
- version: 'v1',
253
- documentation: 'https://docs.sylvain.pro/v1',
254
- endpoints: {
255
- get: {
256
- algorithm: '/v1/algorithms?method={algorithm}&value={value}(&value2={value2})',
257
- captcha: '/v1/captcha?text={text}',
258
- color: '/v1/color',
259
- convert: '/v1/convert?value={value}&from={unit}&to={unit}',
260
- domain: '/v1/domain',
261
- infos: '/v1/infos',
262
- personal: '/v1/personal',
263
- qrcode: '/v1/qrcode?url={URL}',
264
- username: '/v1/username'
265
- },
266
- post: {
267
- token: '/v1/token'
268
- }
269
- }
270
- });
271
- });
294
+ // Display version information
295
+ app.get('/:version', (req, res) => {
296
+ const { version } = req.params;
272
297
 
273
- // Display v2 endpoints
274
- app.get('/v2', (req, res) => {
275
- res.jsonResponse({
276
- version: 'v2',
277
- documentation: 'https://docs.sylvain.pro/v2',
278
- endpoints: {
279
- get: {
280
- algorithm: '/v2/algorithms?method={algorithm}&value={value}(&value2={value2})',
281
- captcha: '/v2/captcha?text={text}',
282
- chat: '/v2/chat',
283
- color: '/v2/color',
284
- convert: '/v2/convert?value={value}&from={unit}&to={unit}',
285
- domain: '/v2/domain',
286
- infos: '/v2/infos',
287
- personal: '/v2/personal',
288
- qrcode: '/v2/qrcode?url={URL}',
289
- username: '/v2/username'
290
- },
291
- post: {
292
- chat: {
293
- chat: '/v2/chat',
294
- private: '/v2/chat/private'
295
- },
296
- hash: '/v2/hash',
297
- tic_tac_toe: {
298
- tic_tac_toe: '/v2/tic-tac-toe',
299
- fetch: '/v2/tic-tac-toe/fetch'
300
- },
301
- token: '/v2/token'
302
- }
303
- }
304
- });
305
- });
298
+ if (!versions[version]) {
299
+ return res.status(404).jsonResponse({
300
+ message: 'Not Found',
301
+ error: `Invalid API version (${version}).`,
302
+ documentation: `https://docs.sylvain.pro/${Object.keys(versions).pop()}`,
303
+ status: '404'
304
+ });
305
+ }
306
+
307
+ const endpoints = Object.keys(versions[version].endpoints).reduce((acc, method) => {
308
+ acc[method] = versions[version].endpoints[method]
309
+ .filter(({ name }) => name !== 'website')
310
+ .sort((a, b) => a.name.localeCompare(b.name))
311
+ .reduce((group, endpoint) => {
312
+ if (endpoint.children) {
313
+ group[endpoint.name] = Object.keys(endpoint.children)
314
+ .sort((a, b) => a.localeCompare(b))
315
+ .reduce((childGroup, childName) => {
316
+ childGroup[childName] = `/${version}${endpoint.children[childName]}`;
317
+ return childGroup;
318
+ }, {});
319
+ } else {
320
+ group[endpoint.name] = `/${version}${endpoint.path}`;
321
+ }
322
+ return group;
323
+ }, {});
324
+ return acc;
325
+ }, {});
306
326
 
307
- // Display v3 endpoints
308
- app.get('/v3', (req, res) => {
309
327
  res.jsonResponse({
310
- version: 'v3',
311
- documentation: 'https://docs.sylvain.pro/v3',
312
- endpoints: {
313
- get: {
314
- algorithm: '/v3/algorithms?method={algorithm}&value={value}(&value2={value2})',
315
- captcha: '/v3/captcha?text={text}',
316
- chat: '/v3/chat',
317
- color: '/v3/color',
318
- convert: '/v3/convert?value={value}&from={unit}&to={unit}',
319
- domain: '/v3/domain',
320
- infos: '/v3/infos',
321
- levenshtein: '/v3/levenshtein?str1={string}&str2={string}',
322
- personal: '/v3/personal',
323
- qrcode: '/v3/qrcode?url={URL}',
324
- time: '/v3/time(?type={type}&start={timestamp}&end={timestamp}&format={format}&timezone={timezone})',
325
- username: '/v3/username'
326
- },
327
- post: {
328
- chat: {
329
- chat: '/v3/chat',
330
- private: '/v3/chat/private'
331
- },
332
- hash: '/v3/hash',
333
- hyperplanning: '/v3/hyperplanning',
334
- tic_tac_toe: {
335
- tic_tac_toe: '/v3/tic-tac-toe',
336
- fetch: '/v3/tic-tac-toe/fetch'
337
- },
338
- token: '/v3/token'
339
- }
340
- }
328
+ version,
329
+ documentation: `https://docs.sylvain.pro/${version}`,
330
+ endpoints
341
331
  });
342
332
  });
343
333
 
344
- // Display logs
345
- app.get('/logs', (req, res) => res.jsonResponse(logs));
346
-
347
334
  // ----------- ----------- GET ENDPOINTS ----------- ----------- //
348
335
 
349
336
  // Algorithms
@@ -351,144 +338,52 @@ app.get('/:version/algorithms', (req, res) => {
351
338
  const { method, value, value2 } = req.query;
352
339
  const { version } = req.params;
353
340
 
354
- if (!['anagram', 'bubblesort', 'factorial', 'fibonacci', 'gcd', 'isprime', 'palindrome', 'primefactors', 'primelist', 'reverse'].includes(method)) {
341
+ if (!req.module.algorithms || !req.module.algorithms[method]) {
355
342
  return res.jsonResponse({
356
343
  error: 'Please provide a valid algorithm (?method={algorithm})',
357
344
  documentation: `https://docs.sylvain.pro/${version}/en/algorithms`
358
345
  });
359
346
  }
360
- if (!value) return res.jsonResponse({ error: 'Please provide a valid value (&value={value})' });
361
-
362
- if (method === 'anagram') {
363
- if (!value2) return res.jsonResponse({ error: 'Please provide a second value (&value2={value})' });
364
- return res.jsonResponse({ answer: value.split('').sort().join('') === value2.split('').sort().join('') });
365
- }
366
-
367
- if (method === 'bubblesort') {
368
- const arr = value.split(',').map(Number);
369
- const n = arr.length;
370
- for (let i = 0; i < n-1; i++) {
371
- for (let j = 0; j < n-i-1; j++) {
372
- if (arr[j] > arr[j + 1]) [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
373
- }
374
- }
375
- return res.jsonResponse({ answer: arr });
376
- }
377
-
378
- if (method === 'factorial') {
379
- if (isNaN(value) || value < 0 || value > 170) return res.jsonResponse({ error: 'Please provide a valid number between 0 and 170.' });
380
- return res.jsonResponse({ answer: factorial(value) });
381
- }
382
-
383
- if (method === 'fibonacci') {
384
- let fib = [0, 1];
385
- if (isNaN(value) || value < 0 || value > 1000) return res.jsonResponse({ error: 'Please provide a valid number between 0 and 1000.' });
386
- for (let i = 2; i < parseInt(value); i++) fib.push(fib[i - 1] + fib[i - 2]);
387
- return res.jsonResponse({ answer: fib.slice(0, parseInt(value)) });
388
- }
389
-
390
- if (method === 'gcd') {
391
- if (!value2) return res.jsonResponse({ error: 'Please provide a second value (&value2={value})' });
392
- if (isNaN(value) || isNaN(value2)) return res.jsonResponse({ error: 'Invalid numbers.' });
393
- return res.jsonResponse({ answer: gcd(value, value2) });
394
- }
395
-
396
- if (method === 'isprime') {
397
- let isPrime = true;
398
- if (isNaN(value) || value < 1) return res.jsonResponse({ error: 'Please provide a valid number greater than or equal to 1.' });
399
- for (let i = 2; i <= Math.sqrt(value); i++) {
400
- if (value % i === 0) {
401
- isPrime = false;
402
- break;
403
- }
404
- }
405
- return res.jsonResponse({ answer: isPrime });
406
- }
407
-
408
- if (method === 'palindrome') return res.jsonResponse({ answer: value === value.split('').reverse().join('') });
409
-
410
- if (method === 'primefactors') {
411
- let num = value;
412
- let factors = [];
413
- if (isNaN(num) || num < 2 || num > 100000) return res.jsonResponse({ error: 'Please provide a valid number between 2 and 100 000.' });
414
- for (let i = 2; i <= num; i++) {
415
- while (num % i === 0) {
416
- factors.push(i);
417
- num /= i;
418
- }
419
- }
420
- return res.jsonResponse({ answer: factors });
421
- }
422
347
 
423
- if (method === 'primelist') {
424
- const primes = [];
425
- if (isNaN(value) || value < 2 || value > 10000) return res.jsonResponse({ error: 'Please provide a valid number between 2 and 10 000.' });
426
- for (let i = 2; i <= value; i++) {
427
- let isPrime = true;
428
- for (let j = 2; j <= Math.sqrt(i); j++) {
429
- if (i % j === 0) {
430
- isPrime = false;
431
- break;
432
- }
433
- }
434
- if (isPrime) primes.push(i);
435
- }
436
- return res.jsonResponse({ answer: primes });
348
+ try {
349
+ const answer = req.module.algorithms[method](value, value2);
350
+ res.jsonResponse({ answer });
351
+ } catch (err) {
352
+ res.jsonResponse({
353
+ error: err.message,
354
+ documentation: `https://docs.sylvain.pro/${version}/en/algorithms`
355
+ });
437
356
  }
438
-
439
- if (method === 'reverse') return res.jsonResponse({ answer: value.split('').reverse().join('') });
440
357
  });
441
358
 
442
359
  // Generate captcha
443
360
  app.get('/:version/captcha', (req, res) => {
444
- const captcha = req.query.text;
445
-
446
- if (!captcha) return res.jsonResponse({ error: 'Please provide a valid argument (?text={text})' });
447
-
448
- const size = 60, font = '60px Comic Sans Ms', width = captcha.length * size, height = 120;
449
- const canvas = createCanvas(width, height), ctx = canvas.getContext('2d');
450
-
451
- ctx.fillStyle = 'white';
452
- ctx.fillRect(0, 0, width, height);
453
-
454
- for (let i = 0; i < 20; i++) {
455
- ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
456
- ctx.beginPath();
457
- ctx.moveTo(Math.random() * width, Math.random() * height);
458
- ctx.lineTo(Math.random() * width, Math.random() * height);
459
- ctx.lineWidth = Math.random() * 2;
460
- ctx.stroke();
461
- }
462
-
463
- let x = (canvas.width + 20 - width) / 2;
464
- for (let i = 0; i < captcha.length; i++) {
465
- const offsetX = Math.cos(i * 0.3) * 10, y = height / 2.5 + Math.floor(Math.random() * (height / 2));
466
-
467
- ctx.font = font;
468
- ctx.fillStyle = `rgb(${Math.floor(Math.random() * 192)}, ${Math.floor(Math.random() * 192)}, ${Math.floor(Math.random() * 192)})`;
361
+ const text = req.query.text;
469
362
 
470
- ctx.save();
471
- ctx.translate(x + size / 2, y);
472
- ctx.rotate((Math.random() - 0.5) * 0.5);
473
- ctx.fillText(captcha[i], -size / 2 + offsetX, 0);
474
- ctx.restore();
363
+ if (!text) return res.jsonResponse({ error: 'Please provide a valid argument (?text={text})' });
475
364
 
476
- x += size;
477
- }
478
-
479
- for (let i = 0; i < 200; i++) {
480
- ctx.fillStyle = 'black';
481
- ctx.fillRect(Math.floor(Math.random() * width), Math.floor(Math.random() * height), 1.2, 1.2);
365
+ try {
366
+ const result = req.module.captcha(text);
367
+ res.type('png').send(result);
368
+ } catch (err) {
369
+ res.jsonResponse({
370
+ error: err.message,
371
+ documentation: `https://docs.sylvain.pro/${req.version}/en/captcha`
372
+ });
482
373
  }
483
-
484
- res.set('Content-Type', 'image/png');
485
- res.send(canvas.toBuffer('image/png'));
486
374
  });
487
375
 
488
376
  // Display stored data
489
377
  app.get('/:version/chat', (req, res) => {
490
- if (chat.length > 0) res.jsonResponse(chat);
491
- else res.jsonResponse({ error: 'No messages stored.' });
378
+ try {
379
+ const messages = req.module.chat('fetch', {
380
+ username: 'system',
381
+ storage: chatStorage
382
+ });
383
+ res.jsonResponse(messages);
384
+ } catch (err) {
385
+ res.jsonResponse({ error: err.message });
386
+ }
492
387
  });
493
388
 
494
389
  // GET private chat error
@@ -498,35 +393,15 @@ app.get('/:version/chat/private', (req, res) => {
498
393
 
499
394
  // Generate color
500
395
  app.get('/:version/color', (req, res) => {
501
- const r = Math.floor(Math.random() * 256), g = Math.floor(Math.random() * 256), b = Math.floor(Math.random() * 256);
502
- const hsl = (() => {
503
- const r1 = r / 255, g1 = g / 255, b1 = b / 255, max = Math.max(r1, g1, b1), min = Math.min(r1, g1, b1), l = (max + min) / 2;
504
- if (max === min) return [0, 0, l * 100];
505
- const d = max - min, s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
506
- let h = { [r1]: (g1 - b1) / d + (g1 < b1 ? 6 : 0), [g1]: (b1 - r1) / d + 2, [b1]: (r1 - g1) / d + 4 }[max];
507
- return [h * 60 % 360, s * 100, l * 100];
508
- })();
509
- const hsv = (() => {
510
- const max = Math.max(r, g, b), min = Math.min(r, g, b), v = max / 255, s = max ? (max - min) / max : 0;
511
- let h = max === min ? 0 : { [r]: (g - b) / (max - min), [g]: 2 + (b - r) / (max - min), [b]: 4 + (r - g) / (max - min) }[max];
512
- return [h * 60 % 360, s * 100, v * 100];
513
- })();
514
- const hwb = (() => {
515
- const [h] = hsv, whiteness = Math.min(r, g, b) / 255, blackness = 1 - Math.max(r, g, b) / 255;
516
- return [h, whiteness * 100, blackness * 100];
517
- })();
518
- const cmyk = (() => {
519
- const k = 1 - Math.max(r, g, b) / 255, c = (1 - r / 255 - k) / (1 - k) || 0, m = (1 - g / 255 - k) / (1 - k) || 0, y = (1 - b / 255 - k) / (1 - k) || 0;
520
- return [c, m, y, k].map(x => x * 100);
521
- })();
522
- res.jsonResponse({
523
- hex: `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`,
524
- rgb: `rgb(${r}, ${g}, ${b})`,
525
- hsl: `hsl(${hsl[0].toFixed(1)}, ${hsl[1].toFixed(1)}%, ${hsl[2].toFixed(1)}%)`,
526
- hsv: `hsv(${hsv[0].toFixed(1)}, ${hsv[1].toFixed(1)}%, ${hsv[2].toFixed(1)}%)`,
527
- hwb: `hwb(${hwb[0].toFixed(1)}, ${hwb[1].toFixed(1)}%, ${hwb[2].toFixed(1)}%)`,
528
- cmyk: `cmyk(${cmyk.map(x => x.toFixed(1)).join('%, ')}%)`
529
- });
396
+ try {
397
+ const result = req.module.color();
398
+ res.jsonResponse(result);
399
+ } catch (err) {
400
+ res.jsonResponse({
401
+ error: err.message,
402
+ documentation: `https://docs.sylvain.pro/${req.version}/en/color`
403
+ });
404
+ }
530
405
  });
531
406
 
532
407
  // Convert units
@@ -537,50 +412,28 @@ app.get('/:version/convert', (req, res) => {
537
412
  if (!from) return res.jsonResponse({ error: 'Please provide a valid source unit (&from={unit})' });
538
413
  if (!to) return res.jsonResponse({ error: 'Please provide a valid target unit (&to={unit})' });
539
414
 
540
- const conversions = {
541
- celsius: { fahrenheit: (val) => (val * 9) / 5 + 32, kelvin: (val) => val + 273.15 },
542
- fahrenheit: { celsius: (val) => ((val - 32) * 5) / 9, kelvin: (val) => ((val - 32) * 5) / 9 + 273.15 },
543
- kelvin: { celsius: (val) => val - 273.15, fahrenheit: (val) => ((val - 273.15) * 9) / 5 + 32 },
544
- };
545
-
546
- const convert = conversions[from.toLowerCase()]?.[to.toLowerCase()];
547
- if (!convert) return res.jsonResponse({ error: 'Invalid conversion units.' });
548
-
549
- res.jsonResponse({ from, to, value: parseFloat(value), result: convert(parseFloat(value)) });
415
+ try {
416
+ const result = req.module.convert(value, from, to);
417
+ res.jsonResponse(result);
418
+ } catch (err) {
419
+ res.jsonResponse({
420
+ error: err.message,
421
+ documentation: `https://docs.sylvain.pro/${req.version}/en/convert`
422
+ });
423
+ }
550
424
  });
551
425
 
552
426
  // Generate domain informations
553
427
  app.get('/:version/domain', (req, res) => {
554
- const subdomains = ['fr.', 'en.', 'docs.', 'api.', 'projects.', 'app.', 'web.', 'info.', 'dev.', 'shop.', 'blog.', 'support.', 'mail.', 'forum.'];
555
- const domains = ['example', 'site', 'test', 'demo', 'page', 'store', 'portfolio', 'platform', 'hub', 'network', 'service', 'cloud', 'solutions', 'company'];
556
- const tlds = ['.com', '.fr', '.eu', '.dev', '.net', '.org', '.io', '.tech', '.biz', '.info', '.co', '.app', '.store', '.online', '.shop', '.tv'];
557
-
558
- const domain = `${random(domains)}${random(tlds)}`;
559
- const fulldomain = `${random(subdomains)}${domain}`;
560
-
561
- const ips = Array.from({ length: Math.floor(Math.random() * 3) + 1 }, genIP);
562
- const dns = Array.from({ length: Math.floor(Math.random() * 5) + 1 }, genIP);
563
-
564
- res.jsonResponse({
565
- domain,
566
- full_domain: fulldomain,
567
- ip_address: ips,
568
- ssl_certified: Math.random() > 0.5,
569
- hosting_provider: random(['AWS', 'Bluehost', 'DigitalOcean', 'GitHub', 'HostGator', 'Render', 'SiteGround']),
570
- dns_servers: dns,
571
- dns_provider: random(['AWS Route 53', 'Cloudflare', 'GoDaddy', 'Google DNS', 'Namecheap']),
572
- traffic: `${Math.floor(Math.random() * 10000)} visits/day`,
573
- seo_score: Math.floor(Math.random() * 100),
574
- page_rank: Math.floor(Math.random() * 10),
575
- country: random(['Australia', 'Canada', 'France', 'Germany', 'India', 'Japan', 'UK', 'USA']),
576
- website_type: random(['Blog', 'Community', 'Corporate', 'Educational', 'E-commerce', 'Personal', 'Portfolio']),
577
- random_name: domain.split('.')[0],
578
- random_subdomain: fulldomain.split('.')[0],
579
- random_tld: domain.split('.').pop(),
580
- backlinks_count: Math.floor(Math.random() * 1000),
581
- creation_date: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toISOString(),
582
- expiration_date: new Date(Date.now() + Math.floor(Math.random() * 10000000000)).toISOString(),
583
- });
428
+ try {
429
+ const result = req.module.domain();
430
+ res.jsonResponse(result);
431
+ } catch (err) {
432
+ res.jsonResponse({
433
+ error: err.message,
434
+ documentation: `https://docs.sylvain.pro/${req.version}/en/domain`
435
+ });
436
+ }
584
437
  });
585
438
 
586
439
  // GET planning error
@@ -595,9 +448,15 @@ app.get('/:version/hash', (req, res) => {
595
448
 
596
449
  // Display API informations
597
450
  app.get('/:version/infos', (req, res) => {
451
+ const endpoints = Object.values(versions[req.version].endpoints).flat();
452
+
453
+ const paths = endpoints.flatMap(e => e.children
454
+ ? Object.values(e.children)
455
+ : (e.path ? [e.path] : []));
456
+
598
457
  res.jsonResponse({
599
- endpoints: endpoints[versions.at(-1)].length,
600
- last_version: versions.at(-1),
458
+ endpoints: new Set(paths).size,
459
+ last_version: Object.keys(versions).pop(),
601
460
  documentation: 'https://docs.sylvain.pro',
602
461
  github: 'https://github.com/20syldev/api',
603
462
  creation: 'November 25th 2024',
@@ -611,134 +470,28 @@ app.get('/:version/levenshtein', (req, res) => {
611
470
  if (!str1 || typeof str1 !== 'string') return res.jsonResponse({ error: 'Please provide a first string (?str1={string})' });
612
471
  if (!str2 || typeof str2 !== 'string') return res.jsonResponse({ error: 'Please provide a second string (&str2={string})' });
613
472
 
614
- if (str1.length > 1000) return res.jsonResponse({ error: 'First string exceeds 1000 characters.' });
615
- if (str2.length > 1000) return res.jsonResponse({ error: 'Second string exceeds 1000 characters.' });
616
-
617
- const lev = (a, b) => {
618
- const m = Array.from({ length: a.length + 1 }, (_, i) => [i]);
619
- for (let j = 0; j <= b.length; j++) m[0][j] = j;
620
- for (let i = 1; i <= a.length; i++)
621
- for (let j = 1; j <= b.length; j++)
622
- m[i][j] = Math.min(m[i - 1][j] + 1, m[i][j - 1] + 1, m[i - 1][j - 1] + (a[i - 1] !== b[j - 1]));
623
-
624
- return m[a.length][b.length];
625
- };
626
-
627
- res.jsonResponse({ str1, str2, distance: lev(str1, str2) });
473
+ try {
474
+ const result = req.module.levenshtein(str1, str2);
475
+ res.jsonResponse(result);
476
+ } catch (err) {
477
+ res.jsonResponse({
478
+ error: err.message,
479
+ documentation: `https://docs.sylvain.pro/${req.version}/en/levenshtein`
480
+ });
481
+ }
628
482
  });
629
483
 
630
484
  // Generate personal data
631
485
  app.get('/:version/personal', (req, res) => {
632
- const people = [
633
- { name: 'John Doe', social: 'john_doe', email: 'john@example.com', country: 'US' },
634
- { name: 'Jane Martin', social: 'jane_martin', email: 'jane@example.com', country: 'FR' },
635
- { name: 'Michael Johnson', social: 'mike_johnson', email: 'michael@example.com', country: 'UK' },
636
- { name: 'Emily Davis', social: 'emily_davis', email: 'emily@example.com', country: 'ES' },
637
- { name: 'Alexis Barbos', social: 'alexis_barbos', email: 'alexis@example.com', country: 'DE' },
638
- { name: 'Sarah Williams', social: 'sarah_williams', email: 'sarah@example.com', country: 'IT' },
639
- { name: 'Daniel Brown', social: 'daniel_brown', email: 'daniel@example.com', country: 'JP' },
640
- { name: 'Sophia Wilson', social: 'sophia_wilson', email: 'sophia@example.com', country: 'BR' },
641
- { name: 'James Taylor', social: 'james_taylor', email: 'james@example.com', country: 'CA' },
642
- { name: 'Olivia Thomas', social: 'olivia_thomas', email: 'olivia@example.com', country: 'AU' }
643
- ];
644
-
645
- const countries = {
646
- US: { tel: '123-456-7890', code: '1', lang: 'English' },
647
- FR: { tel: '06 78 90 12 34', code: '33', lang: 'French' },
648
- UK: { tel: '7911 123456', code: '44', lang: 'English' },
649
- ES: { tel: '678 901 234', code: '34', lang: 'Spanish' },
650
- DE: { tel: '163 555 1584', code: '49', lang: 'German' },
651
- IT: { tel: '345 678 9012', code: '39', lang: 'Italian' },
652
- JP: { tel: '080-1234-5678', code: '81', lang: 'Japanese' },
653
- BR: { tel: '(11) 98765-4321', code: '55', lang: 'Portuguese' },
654
- CA: { tel: '416-123-4567', code: '1', lang: 'English' },
655
- AU: { tel: '0412 345 678', code: '61', lang: 'English' }
656
- };
657
-
658
- const jobs = ['Writer', 'Artist', 'Musician', 'Explorer', 'Scientist', 'Engineer', 'Athlete', 'Doctor'];
659
- const hobbies = ['Reading', 'Traveling', 'Gaming', 'Cooking', 'Fitness', 'Music', 'Photography', 'Writing'];
660
- const cities = ['New York', 'Paris', 'London', 'Madrid', 'Berlin', 'Rome', 'Tokyo', 'Los Angeles', 'Sydney', 'São Paulo', 'Toronto'];
661
- const streets = ['Main St', '2nd Ave', 'Broadway', 'Park Lane', 'Elm St', 'Sunset Blvd', 'Maple St', 'Highland Rd'];
662
-
663
- const card = Array.from({ length: 4 }, () => Math.floor(Math.random() * 9000) + 1000).join(' ');
664
- const cvc = Math.floor(Math.random() * 900) + 100;
665
- const expiration = `${String(Math.floor(Math.random() * 12) + 1).padStart(2, '0')}/${(new Date().getFullYear() + Math.floor(Math.random() * 3)).toString().slice(-2)}`;
666
-
667
- const person = random(people);
668
- const social = person.social;
669
- const country = person.country;
670
- const phone = countries[country].tel;
671
- const lang = countries[country].lang;
672
-
673
- const age = Math.floor(Math.random() * 50) + 18;
674
- const birthday = new Date(Date.now() - Math.floor((Math.random() * 50 + 18) * 365.25 * 24 * 60 * 60 * 1000)).toISOString();
675
-
676
- let emergencyContacts = [], yearIncome = Math.floor(Math.random() * 100000), subscriptions = [], pets = [], vehicles = [];
677
- let civilStatus = 'Single';
678
- let children = 0;
679
-
680
- if (age >= 21 && Math.random() > 0.7) civilStatus = 'Married';
681
-
682
- if (civilStatus === 'Married' && age >= 25) children = Math.floor(Math.random() * 4);
683
-
684
- while (emergencyContacts.length < Math.floor(Math.random() * 3) + 1) {
685
- let emergencyContact = random(people);
686
- while (emergencyContact.email === person.email || emergencyContacts.some(e => e.email === emergencyContact.email)) {
687
- emergencyContact = random(people);
688
- }
689
- emergencyContacts.push({
690
- name: emergencyContact.name,
691
- relationship: random(['Spouse', 'Parent', 'Sibling', 'Friend']),
692
- phone: `+${countries[country].code} ${countries[country].tel}`
486
+ try {
487
+ const result = req.module.personal();
488
+ res.jsonResponse(result);
489
+ } catch (err) {
490
+ res.jsonResponse({
491
+ error: err.message,
492
+ documentation: `https://docs.sylvain.pro/${req.version}/en/personal`
693
493
  });
694
494
  }
695
-
696
- while (subscriptions.length < Math.floor(Math.random() * 3) + 1) {
697
- let subscription = random(['Netflix', 'Spotify', 'Amazon Prime', 'Disney+', 'Hulu']);
698
- if (!subscriptions.includes(subscription)) subscriptions.push(subscription);
699
- }
700
-
701
- while (pets.length < Math.floor(Math.random() * 3) + 1) {
702
- let pet = random(['Dog', 'Cat', 'Fish', 'Bird', 'None']);
703
- if (!pets.includes(pet)) pets.push(pet);
704
- }
705
-
706
- while (vehicles.length < Math.floor(Math.random() * 3) + 1) {
707
- let vehicle = random(['Car', 'Bike', 'Motorcycle', 'Bus', 'None']);
708
- if (!vehicles.includes(vehicle)) vehicles.push(vehicle);
709
- }
710
-
711
- res.jsonResponse({
712
- name: person.name,
713
- email: person.email,
714
- localisation: country,
715
- phone: `+${countries[country].code} ${phone}`,
716
- job: random(jobs),
717
- hobbies: random(hobbies),
718
- language: lang,
719
- card,
720
- cvc,
721
- expiration,
722
- address: `${Math.floor(Math.random() * 9999)} ${random(streets)}, ${random(cities)}`,
723
- birthday,
724
- civil_status: civilStatus,
725
- children,
726
- vehicle: vehicles,
727
- social_profiles: {
728
- twitter: `@${social}`,
729
- facebook: `facebook.com/${social}`,
730
- linkedin: `linkedin.com/in/${social}`,
731
- instagram: `instagram.com/${social}`
732
- },
733
- year_income: `${yearIncome} USD/year`,
734
- month_income: `${(yearIncome / 12).toFixed(2)} USD/month`,
735
- education: random(['High School', 'Bachelor\'s', 'Master\'s', 'PhD']),
736
- work_experience: `${Math.floor(Math.random() * 20)} years`,
737
- health_status: random(['Healthy', 'Minor Issues', 'Chronic Conditions']),
738
- emergency_contacts: emergencyContacts,
739
- subscriptions,
740
- pets,
741
- });
742
495
  });
743
496
 
744
497
  // Generate QR Code
@@ -747,8 +500,15 @@ app.get('/:version/qrcode', async (req, res) => {
747
500
 
748
501
  if (!url) return res.jsonResponse({ error: 'Please provide a valid url (?url={URL})' });
749
502
 
750
- try { res.jsonResponse({ qr: await toDataURL(url) }); }
751
- catch { res.jsonResponse({ error: 'Error generating QR code.' }); }
503
+ try {
504
+ const result = await req.module.qrcode(url);
505
+ res.jsonResponse(result);
506
+ } catch (err) {
507
+ res.jsonResponse({
508
+ error: err.message,
509
+ documentation: `https://docs.sylvain.pro/${req.version}/en/qrcode`
510
+ });
511
+ }
752
512
  });
753
513
 
754
514
  // GET tic-tac-toe game error
@@ -774,44 +534,15 @@ app.get('/:version/time', (req, res) => {
774
534
  if (format && !validFormats.includes(format)) return res.jsonResponse({ error: 'Please provide a valid format (?format={format})' });
775
535
  if (timezone && !validTimezones.includes(timezone)) return res.jsonResponse({ error: 'Please provide a valid timezone (?timezone={timezone})' });
776
536
 
777
- const getTimeFormats = (date, timezoneOption) => {
778
- return {
779
- iso: date.toISOString(),
780
- utc: date.toUTCString(),
781
- timestamp: date.getTime(),
782
- locale: date.toLocaleString('en-US', { timeZone: timezoneOption, timeZoneName: 'long' }),
783
- date: date.toLocaleDateString('en-US', { timeZone: timezoneOption }),
784
- time: date.toLocaleTimeString('en-US', { timeZone: timezoneOption }),
785
- year: date.getFullYear(),
786
- month: date.getMonth() + 1,
787
- day: date.getDate(),
788
- hour: date.getHours(),
789
- minute: date.getMinutes(),
790
- second: date.getSeconds(),
791
- ms: date.getMilliseconds(),
792
- dayOfWeek: date.getDay(),
793
- dayOfYear: Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000),
794
- weekNumber: Math.ceil((((date - new Date(date.getFullYear(), 0, 0)) / 86400000) + 1) / 7),
795
- timezone: timezoneOption,
796
- timezoneOffset: date.getTimezoneOffset()
797
- };
798
- };
799
-
800
- if (type === 'random') {
801
- const startDate = new Date(start || '1900-01-01').getTime();
802
- const endDate = new Date(end || '2100-12-31').getTime();
803
- const randomDate = new Date(start ? startDate : startDate + Math.random() * (endDate - startDate));
804
- const timezoneOption = timezone || validTimezones[Math.floor(Math.random() * 5)];
805
- const formats = getTimeFormats(randomDate, timezoneOption);
806
-
807
- return res.jsonResponse(format && formats[format] ? { date: formats[format] } : formats);
537
+ try {
538
+ const time = req.module.time(type, start, end, format, timezone);
539
+ res.jsonResponse(time);
540
+ } catch (err) {
541
+ res.jsonResponse({
542
+ error: err.message,
543
+ documentation: `https://docs.sylvain.pro/${req.version}/en/time`
544
+ });
808
545
  }
809
-
810
- const now = new Date();
811
- const timezoneOption = timezone || 'UTC';
812
- const formats = getTimeFormats(now, timezoneOption);
813
-
814
- res.jsonResponse(format && formats[format] ? { date: formats[format] } : formats);
815
546
  });
816
547
 
817
548
  // GET token error
@@ -821,24 +552,15 @@ app.get('/:version/token', (req, res) => {
821
552
 
822
553
  // Generate username
823
554
  app.get('/:version/username', (req, res) => {
824
- const adj = ['Happy', 'Silly', 'Clever', 'Creative', 'Brave', 'Gentle', 'Kind', 'Funny', 'Wise', 'Charming', 'Sincere', 'Resourceful', 'Patient', 'Energetic', 'Adventurous', 'Ambitious', 'Courageous', 'Courteous', 'Determined'];
825
- const ani = ['Cat', 'Dog', 'Tiger', 'Elephant', 'Monkey', 'Penguin', 'Dolphin', 'Lion', 'Bear', 'Fox', 'Owl', 'Giraffe', 'Zebra', 'Koala', 'Rabbit', 'Squirrel', 'Panda', 'Horse', 'Wolf', 'Eagle'];
826
- const job = ['Writer', 'Artist', 'Musician', 'Explorer', 'Scientist', 'Engineer', 'Athlete', 'Chef', 'Doctor', 'Teacher', 'Lawyer', 'Entrepreneur', 'Actor', 'Dancer', 'Photographer', 'Architect', 'Pilot', 'Designer', 'Journalist', 'Veterinarian'];
827
-
828
- const nombre = Math.floor(Math.random() * 100);
829
- const choix = {
830
- adj_num: () => random(adj) + nombre,
831
- ani_num: () => random(ani) + nombre,
832
- pro_num: () => random(job) + nombre,
833
- adj_ani: () => random(adj) + random(ani),
834
- adj_ani_num: () => random(adj) + random(ani) + nombre,
835
- adj_pro: () => random(adj) + random(job),
836
- pro_ani: () => random(job) + random(ani),
837
- pro_ani_num: () => random(job) + random(ani) + nombre
838
- };
839
-
840
- const username = choix[random(Object.keys(choix))]();
841
- res.jsonResponse({ adjective: adj, animal: ani, job, number: nombre, username });
555
+ try {
556
+ const result = req.module.username();
557
+ res.jsonResponse(result);
558
+ } catch (err) {
559
+ res.jsonResponse({
560
+ error: err.message,
561
+ documentation: `https://docs.sylvain.pro/${req.version}/en/username`
562
+ });
563
+ }
842
564
  });
843
565
 
844
566
  // Display informations for owner's website
@@ -910,6 +632,7 @@ app.get('/:version/website', async (req, res) => {
910
632
  flowers: process.env.FLOWERS,
911
633
  gemsync: process.env.GEMSYNC,
912
634
  gitsite: process.env.GITSITE,
635
+ lebonchar: process.env.LEBONCHAR,
913
636
  logs: process.env.LOGS,
914
637
  logvault: process.env.LOGVAULT,
915
638
  lyah: process.env.LYAH,
@@ -955,33 +678,19 @@ app.post('/:version/chat', (req, res) => {
955
678
  if (!message) return res.jsonResponse({ error: 'Please provide a message (&message={message})' });
956
679
  if (!session) return res.jsonResponse({ error: 'Please provide a valid session ID (&session={ID})' });
957
680
 
958
- const u = username.toLowerCase(), now = Date.now();
959
- const msg = { username, message, timestamp: timestamp || new Date().toISOString() };
960
-
961
- rateLimits[u] = (rateLimits[u] || []).filter(ts => now - ts < 10000);
962
- if (rateLimits[u].length > 50) {
963
- const remainingTime = Math.ceil((rateLimits[u][0] + 10000 - now) / 1000);
964
- return res.jsonResponse({ error: `Rate limit exceeded. Try again in ${remainingTime} seconds.` });
965
- }
966
- rateLimits[u].push(now);
967
-
968
- if (sessions[u] && sessions[u].user !== session) return res.jsonResponse({ error: 'Session ID mismatch' });
969
-
970
- if (token) {
971
- privateChats[token] = privateChats[token] || [];
972
- privateChats[token].push(msg);
973
- setTimeout(() => { delete privateChats[token]; }, 3600000);
974
- } else {
975
- chat.push(msg);
976
- setTimeout(() => chat.splice(chat.indexOf(msg), 1), 3600000);
681
+ try {
682
+ const result = req.module.chat('message', {
683
+ username,
684
+ message,
685
+ timestamp,
686
+ session,
687
+ token,
688
+ storage: chatStorage
689
+ });
690
+ res.jsonResponse(result);
691
+ } catch (err) {
692
+ res.jsonResponse({ error: err.message });
977
693
  }
978
-
979
- sessions[u] = sessions[u] || { user: session, last: now };
980
- sessions[u].last = now;
981
-
982
- setTimeout(() => { if (now - sessions[u].last >= 3600000) delete sessions[u]; }, 3600000);
983
-
984
- res.jsonResponse({ message: 'Message sent successfully' });
985
694
  });
986
695
 
987
696
  // Display a private chat with a token
@@ -991,36 +700,37 @@ app.post('/:version/chat/private', (req, res) => {
991
700
  if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
992
701
  if (!token) return res.jsonResponse({ error: 'Please provide a valid token (&token={key}).' });
993
702
 
994
- const u = username.toLowerCase(), now = Date.now();
995
-
996
- rateLimits[u] = (rateLimits[u] || []).filter(ts => now - ts < 10000);
997
- if (rateLimits[u].length > 50) {
998
- const remainingTime = Math.ceil((rateLimits[u][0] + 10000 - now) / 1000);
999
- return res.jsonResponse({ error: `Rate limit exceeded. Try again in ${remainingTime} seconds.` });
703
+ try {
704
+ const messages = req.module.chat('private', {
705
+ username,
706
+ token,
707
+ storage: chatStorage
708
+ });
709
+ res.jsonResponse(messages);
710
+ } catch (err) {
711
+ res.jsonResponse({ error: err.message });
1000
712
  }
1001
- rateLimits[u].push(now);
1002
-
1003
- if (privateChats[token]) return res.jsonResponse(privateChats[token]);
1004
-
1005
- return res.jsonResponse({ error: 'Invalid or expired token.' });
1006
713
  });
1007
714
 
1008
715
  // Generate hash
1009
716
  app.post('/:version/hash', (req, res) => {
1010
717
  const { text, method } = req.body;
1011
- const { version } = req.params;
1012
718
 
1013
719
  if (!text) return res.jsonResponse({ error: 'Please provide a text (?text={text})' });
1014
720
  if (!method) return res.jsonResponse({
1015
721
  error: 'Please provide a valid hash algorithm (&method={algorithm})',
1016
- documentation: `https://docs.sylvain.pro/${version}/en/hash`
722
+ documentation: `https://docs.sylvain.pro/${req.version}/en/hash`
1017
723
  });
1018
724
 
1019
- const methods = getHashes();
1020
- if (!methods.includes(method)) return res.jsonResponse({ error: `Unsupported method. Use one of: ${methods.join(', ')}` });
1021
-
1022
- const hash = createHash(method).update(text).digest('hex');
1023
- res.jsonResponse({ method, hash });
725
+ try {
726
+ const result = req.module.hash(text, method);
727
+ res.jsonResponse(result);
728
+ } catch (err) {
729
+ res.jsonResponse({
730
+ error: err.message,
731
+ documentation: `https://docs.sylvain.pro/${req.version}/en/hash`
732
+ })
733
+ }
1024
734
  });
1025
735
 
1026
736
  // Display a planning from an ICS file
@@ -1030,40 +740,14 @@ app.post('/:version/hyperplanning', async (req, res) => {
1030
740
  if (!url) return res.jsonResponse({ error: 'Please provide a valid ICS file URL (?url={URL})' });
1031
741
 
1032
742
  try {
1033
- const response = await fetch(url);
1034
- if (!response.ok || !(response.headers.get('content-type') || '').includes('text/calendar')) return res.jsonResponse({ error: 'Invalid ICS file format.' });
1035
-
1036
- const events = new ical.Component(ical.parse(await response.text()))
1037
- .getAllSubcomponents('vevent')
1038
- .map(e => {
1039
- const evt = new ical.Event(e);
1040
- const summary = evt.summary.split(' ').filter(part => part !== '-');
1041
- const start = formatDate(evt.startDate.toJSDate());
1042
- const end = formatDate(evt.endDate.toJSDate());
1043
-
1044
- if (detail === 'full') {
1045
- const desc = evt.description.split('\n').map(l => l.trim());
1046
- const extract = (p) => (desc.find(l => l.startsWith(p)) || '').replace(p, '').trim();
1047
-
1048
- return {
1049
- summary,
1050
- subject: extract('Matière :'),
1051
- teacher: extract('Enseignant :'),
1052
- classes: extract('Promotions :').split(', ').map(c => c.trim()),
1053
- type: extract('Salle :') || undefined,
1054
- start,
1055
- end
1056
- };
1057
- }
1058
- if (detail === 'list') return { summary, start, end };
1059
-
1060
- return { summary: evt.summary, start, end };
1061
- })
1062
- .sort((a, b) => new Date(a.start) - new Date(b.start))
1063
- .filter(e => new Date(e.end) >= new Date());
1064
-
1065
- res.jsonResponse(events);
1066
- } catch { res.jsonResponse({ error: 'Failed to parse ICS file.' }); }
743
+ const hyperplanning = req.module.hyperplanning(url, detail);
744
+ res.jsonResponse(hyperplanning);
745
+ } catch (err){
746
+ res.jsonResponse({
747
+ error: 'Failed to parse ICS file.',
748
+ documentation: `https://docs.sylvain.pro/${req.version}/en/hyperplanning`
749
+ });
750
+ }
1067
751
  });
1068
752
 
1069
753
  // Store tic tac toe games
@@ -1075,46 +759,18 @@ app.post('/:version/tic-tac-toe', (req, res) => {
1075
759
  if (!session) return res.jsonResponse({ error: 'Please provide a valid session ID (&session={ID})' });
1076
760
  if (!game) return res.jsonResponse({ error: 'Please provide a valid game ID (&game={ID})' });
1077
761
 
1078
- const u = username.toLowerCase(), now = Date.now();
1079
- const play = { username, move, session };
1080
- const validMoves = ['1-1', '1-2', '1-3', '2-1', '2-2', '2-3', '3-1', '3-2', '3-3'];
1081
-
1082
- if (!validMoves.includes(move)) return res.jsonResponse({ error: 'Invalid move. Please provide a valid move (e.g., 1-1, 2-2, 3-3).' });
1083
-
1084
- rateLimits[u] = (rateLimits[u] || []).filter(ts => now - ts < 10000);
1085
- if (rateLimits[u].length > 50) {
1086
- const remainingTime = Math.ceil((rateLimits[u][0] + 10000 - now) / 1000);
1087
- return res.jsonResponse({ error: `Rate limit exceeded. Try again in ${remainingTime} seconds.` });
1088
- }
1089
- rateLimits[u].push(now);
1090
-
1091
- if (sessions[u] && sessions[u].user !== session) return res.jsonResponse({ error: 'Session ID mismatch' });
1092
-
1093
- games[game] = games[game] || [];
1094
-
1095
- const players = [...new Set(games[game].map(play => play.username))];
1096
- if (players.length >= 2 && !players.includes(username)) return res.jsonResponse({ error: 'Game is full, you can only watch.' });
1097
- if (games[game].length > 0 && games[game][games[game].length - 1].username === username) return res.jsonResponse({ error: 'Please wait for the other player to make a move.' });
1098
- if (games[game].some(play => play.move === move)) return res.jsonResponse({ error: 'Move already made. Please choose a different move.' });
1099
-
1100
- games[game].push(play);
1101
-
1102
- const result = checkGame(games[game]);
1103
- if (result.winner || result.tie) {
1104
- setTimeout(() => delete games[game], 600000);
1105
- return res.jsonResponse({
1106
- message: `Move sent successfully. ${result.winner ? result.winner + ' wins. ' + result.loser + ' loses.' : 'It\'s a tie.'}`,
1107
- ...result
762
+ try {
763
+ const result = req.module.tic_tac_toe('play', {
764
+ username,
765
+ move,
766
+ session,
767
+ game,
768
+ storage: ticTacToeStorage
1108
769
  });
770
+ res.jsonResponse(result);
771
+ } catch (err) {
772
+ res.jsonResponse({ error: err.message });
1109
773
  }
1110
- if (!result.winner && !result.tie) setTimeout(() => delete games[game], 3600000);
1111
-
1112
- sessions[u] = sessions[u] || { user: session, last: now };
1113
- sessions[u].last = now;
1114
-
1115
- setTimeout(() => { if (now - sessions[u].last >= 3600000) delete sessions[u]; }, 3600000);
1116
-
1117
- res.jsonResponse({ message: 'Move sent successfully' });
1118
774
  });
1119
775
 
1120
776
  // Display a tic tac toe game with a token
@@ -1123,25 +779,16 @@ app.post('/:version/tic-tac-toe/fetch', (req, res) => {
1123
779
 
1124
780
  if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
1125
781
 
1126
- const ID = game || genID();
1127
- const u = username.toLowerCase(), now = Date.now();
1128
-
1129
- rateLimits[u] = (rateLimits[u] || []).filter(ts => now - ts < 10000);
1130
- if (rateLimits[u].length > 50) {
1131
- const remainingTime = Math.ceil((rateLimits[u][0] + 10000 - now) / 1000);
1132
- return res.jsonResponse({ error: `Rate limit exceeded. Try again in ${remainingTime} seconds.` });
782
+ try {
783
+ const result = req.module.tic_tac_toe('fetch', {
784
+ username,
785
+ game,
786
+ storage: ticTacToeStorage
787
+ });
788
+ res.jsonResponse(result);
789
+ } catch (err) {
790
+ res.jsonResponse({ error: err.message });
1133
791
  }
1134
- rateLimits[u].push(now);
1135
-
1136
- if (!games[ID]) games[ID] = [];
1137
-
1138
- const data = games[ID];
1139
- const last = data.length ? data[data.length - 1].username : null;
1140
- const players = [...new Set(data.map(p => p.username))];
1141
- const turn = players.find(p => p !== last);
1142
- const result = data.length ? checkGame(data) : {};
1143
-
1144
- res.jsonResponse({ game: data, turn, ID, ...result });
1145
792
  });
1146
793
 
1147
794
  // Generate Token
@@ -1155,18 +802,15 @@ app.post('/:version/token', (req, res) => {
1155
802
  if (len > 4096) return res.jsonResponse({ error: 'Length cannot exceed 4096.' });
1156
803
  if (len < 12) return res.jsonResponse({ error: 'Length cannot be less than 12.' });
1157
804
 
1158
- const token = {
1159
- alpha: genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', len),
1160
- alphanum: genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', len),
1161
- base64: randomBytes(len).toString('base64').slice(0, len),
1162
- hex: randomBytes(len).toString('hex').slice(0, len),
1163
- num: genToken('0123456789', len),
1164
- punct: genToken('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~', len),
1165
- urlsafe: genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_', len),
1166
- uuid: v4().replace(/-/g, '').slice(0, len)
1167
- }[type] || genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', len);
1168
-
1169
- res.jsonResponse({ token });
805
+ try {
806
+ const token = req.module.token(len, type);
807
+ res.jsonResponse({ token });
808
+ } catch (err) {
809
+ return res.jsonResponse({
810
+ error: err.message,
811
+ documentation: `https://docs.sylvain.pro/${req.version}/en/token`
812
+ });
813
+ }
1170
814
  });
1171
815
 
1172
816
  // ----------- ----------- SERVER SETUP ----------- ----------- //