@20syldev/api 2.8.0 → 3.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 (3) hide show
  1. package/README.md +32 -5
  2. package/app.js +236 -73
  3. package/package.json +13 -12
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <a href="https://api.sylvain.pro"><img src="https://api.sylvain.pro/favicon.ico" alt="Logo" width="25%" height="auto"/></a>
3
3
 
4
4
  # API Personnelle
5
- [![Version](https://custom-icon-badges.demolab.com/badge/Version%20:-v2.8.0-6479ee?logo=api.sylvain.pro&labelColor=23272A)](https://github.com/20syldev/api/releases/latest)
5
+ [![Version](https://custom-icon-badges.demolab.com/badge/Version%20:-v3.0.0-6479ee?logo=api.sylvain.pro&labelColor=23272A)](https://github.com/20syldev/api/releases/latest)
6
6
  </div>
7
7
 
8
8
  ---
@@ -10,14 +10,41 @@
10
10
  ## À propos de l'API
11
11
  Voici mon API personnelle, disponible sur le domaine [api.sylvain.pro](https://api.sylvain.pro).
12
12
  L'API est développée avec Node.js et hébergée **24h/7j**. Elle est **simple d'utilisation** et a une **documentation** disponible sur [docs.sylvain.pro](https://docs.sylvain.pro) !
13
- > *Une limite de **1000** requêtes maximum chaque **10 secondes** est fixée.*
13
+ > *Une limite de **1000** requêtes maximum chaque **10 secondes** est fixée. Elle est baissé pour certains endpoints nécessitant plus de ressources.*
14
14
 
15
- ## Tester l'API localement
15
+ ## Installer le paquet de l'API sur votre machine
16
+ ```console
17
+ $ sudo apt install nodejs npm
18
+ $ npm install @20syldev/api
19
+ ```
20
+
21
+ Pour utiliser le **paquet** dans votre projet, **créez** un fichier **JavaScript**. Par exemple, `index.js` :
22
+ ```js
23
+ require('@20syldev/api');
24
+ ```
25
+
26
+ Puis, **démarrez** un serveur [Node.js](https://nodejs.org) pour utiliser l'**API** :
27
+ ```console
28
+ $ node index.js
29
+ API is running on
30
+ - http://127.0.0.1:3000
31
+ - http://localhost:3000
32
+ ```
33
+ > *Remplacez `index.js` par le nom de votre fichier JavaScript.*
34
+
35
+ ## Tester l'API sur votre machine
36
+ **Téléchargez** la [dernière mise à jour](https://github.com/20syldev/api/releases/latest) de l'API, puis **extrayez** le contenu du fichier `.zip` ou `.tar.gz` dans un de vos **répertoires**.
37
+ Ensuite, **déplacez-vous** dans le dossier du projet, via un terminal **Linux**, **Windows** ou **macOS** :
38
+ ```console
39
+ $ cd /chemin/vers/le/projet
40
+ ```
41
+
42
+ Enfin, **exécutez** le script de build, qui installera les **dépendances** et lancera le **serveur** de l'[API](https://api.sylvain.pro) :
16
43
  ```console
17
44
  $ npm run build
18
45
  ```
19
46
  ```console
20
- > @20syldev/api@2.8.0 build
47
+ > @20syldev/api@3.0.0 build
21
48
  > npm install && node app.js
22
49
 
23
50
  [...]
@@ -28,4 +55,4 @@ API is running on
28
55
  - http://localhost:3000
29
56
  ```
30
57
 
31
- *Visitez la [documentation](https://docs.sylvain.pro) dédiée, vous y retrouverez des exemples de requêtes et des codes simples pour tester l'[API](https://api.sylvain.pro) !*
58
+ *Visitez la [documentation](https://docs.sylvain.pro) dédiée, vous y retrouverez des exemples de requêtes et des codes simples pour tester l'[API](https://api.sylvain.pro) !*
package/app.js CHANGED
@@ -1,45 +1,97 @@
1
- require('dotenv').config();
2
-
3
- // Built-in module
4
- const crypto = require('crypto');
5
-
6
- // Imported module
7
- const { createCanvas } = require('canvas');
8
- const cors = require('cors');
9
- const express = require('express');
10
- const fetch = require('node-fetch');
11
- const math = require('mathjs');
12
- const path = require('path');
13
- const qrcode = require('qrcode');
14
- const random = require('random');
15
- const uuid = require('uuid');
1
+ import cors from 'cors';
2
+ import dotenv from 'dotenv';
3
+ import express from 'express';
4
+ import fetch from 'node-fetch';
5
+ import { createCanvas } from 'canvas';
6
+ import { randomBytes, getHashes, createHash } from 'crypto';
7
+ import { urlencoded, json } from 'express';
8
+ import { factorial } from 'mathjs';
9
+ import { dirname, join } from 'path';
10
+ import { toDataURL } from 'qrcode';
11
+ import { fileURLToPath } from 'url';
12
+ import { v4 } from 'uuid';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
16
  const app = express();
17
17
 
18
18
  // Define allowed versions & endpoints for each version
19
- const versions = ['v1', 'v2'];
19
+ const versions = ['v1', 'v2', 'v3'];
20
20
  const endpoints = {
21
21
  v1: ['algorithms', 'captcha', 'color', 'convert', 'domain', 'infos', 'personal', 'qrcode', 'token', 'username', 'website'],
22
- v2: ['algorithms', 'captcha', 'chat', 'color', 'convert', 'domain', 'hash', 'infos', 'personal', 'qrcode', 'tic-tac-toe', 'token', 'username', 'website']
22
+ v2: ['algorithms', 'captcha', 'chat', 'color', 'convert', 'domain', 'hash', 'infos', 'personal', 'qrcode', 'tic-tac-toe', 'token', 'username', 'website'],
23
+ v3: ['algorithms', 'captcha', 'chat', 'color', 'convert', 'date', 'domain', 'hash', 'infos', 'levenshtein', 'personal', 'qrcode', 'tic-tac-toe', 'time', 'token', 'username', 'website']
23
24
  };
24
25
 
26
+ // Arrowed functions (math & random)
27
+ const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
28
+ const genID = () => {
29
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
30
+ return Array.from(randomBytes(5)).map(b => chars[b % chars.length]).join('');
31
+ };
32
+ const genIP = () => `${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`;
33
+ const genToken = (chars, length) => Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
34
+ const random = (arr) => arr[Math.floor(Math.random() * arr.length)];
35
+
25
36
  // Store data
26
37
  const logs = [], chat = [], privateChats = {}, sessions = {}, rateLimits = {}, games = {};
27
38
 
28
39
  // Define global variables
29
- let lastFetch = 0, requests = 0, resetTime = Date.now() + 10000;
40
+ let contributions, lastFetch = 0, requests = 0, resetTime = Date.now() + 10000;
41
+
42
+ // ----------- ----------- MAIN FUNCTIONS ----------- ----------- //
43
+
44
+ /**
45
+ * Check the game result of a Tic-Tac-Toe game.
46
+ *
47
+ * @param {Array} moves - The moves of the game.
48
+ * @returns {Object} - The result of the game.
49
+ */
50
+ function checkGame(moves) {
51
+ let board = Array(3).fill().map(() => Array(3).fill(null));
52
+ let playerSymbols = {};
53
+ let playersOrder = [];
54
+
55
+ moves.forEach(({ username, move }) => {
56
+ if (!playerSymbols[username]) {
57
+ playersOrder.push(username);
58
+ playerSymbols[username] = playersOrder.length === 1 ? 'X' : 'O';
59
+ }
60
+ let [row, col] = move.split('-').map(Number);
61
+ board[row - 1][col - 1] = playerSymbols[username];
62
+ });
63
+
64
+ const checkWinner = (symbol) => {
65
+ for (let i = 0; i < 3; i++) {
66
+ if (board[i][0] === symbol && board[i][1] === symbol && board[i][2] === symbol) return true;
67
+ if (board[0][i] === symbol && board[1][i] === symbol && board[2][i] === symbol) return true;
68
+ }
69
+ if (board[0][0] === symbol && board[1][1] === symbol && board[2][2] === symbol) return true;
70
+ if (board[0][2] === symbol && board[1][1] === symbol && board[2][0] === symbol) return true;
71
+ return false;
72
+ };
73
+
74
+ let winner = Object.keys(playerSymbols).find(player => checkWinner(playerSymbols[player]));
75
+ let isTie = !winner && moves.length === 9;
76
+ let loser = winner && playersOrder.length === 2 ? playersOrder.find(player => player !== winner) : null;
77
+
78
+ return { winner, loser, tie: isTie };
79
+ }
30
80
 
31
81
  // ----------- ----------- MIDDLEWARES SETUP ----------- ----------- //
32
82
 
83
+ dotenv.config();
84
+
33
85
  // CORS & Express setup
34
86
  app.use(cors({ methods: ['GET', 'POST'] }));
35
- app.use(express.urlencoded({ extended: true }));
36
- app.use(express.json());
87
+ app.use(urlencoded({ extended: true }));
88
+ app.use(json());
37
89
 
38
90
  // Set favicon for API
39
- app.use('/favicon.ico', express.static(path.join(__dirname, 'src', 'favicon.ico')));
91
+ app.use('/favicon.ico', express.static(join(__dirname, 'src', 'favicon.ico')));
40
92
 
41
93
  // Display robots.txt
42
- app.use('/robots.txt', express.static(path.join(__dirname, 'robots.txt')));
94
+ app.use('/robots.txt', express.static(join(__dirname, 'robots.txt')));
43
95
 
44
96
  // Return formatted JSON
45
97
  app.use((req, res, next) => {
@@ -104,7 +156,7 @@ app.use('/:version', (req, res, next) => {
104
156
  return res.status(404).jsonResponse({
105
157
  message: 'Not Found',
106
158
  error: `Invalid API version (${version}).`,
107
- documentation: 'https://docs.sylvain.pro',
159
+ documentation: `https://docs.sylvain.pro/${versions.at(-1)}`,
108
160
  status: '404'
109
161
  });
110
162
  }
@@ -119,7 +171,7 @@ app.use('/:version/:endpoint', (req, res, next) => {
119
171
  return res.status(404).jsonResponse({
120
172
  message: 'Not Found',
121
173
  error: `Endpoint '${endpoint}' does not exist in ${version}.`,
122
- documentation: 'https://docs.sylvain.pro',
174
+ documentation: `https://docs.sylvain.pro/${versions.at(-1)}`,
123
175
  status: '404'
124
176
  });
125
177
  }
@@ -137,7 +189,8 @@ app.get('/', (req, res) => {
137
189
  logs: 'https://api.sylvain.pro/logs',
138
190
  versions: {
139
191
  v1: 'https://api.sylvain.pro/v1',
140
- v2: 'https://api.sylvain.pro/v2'
192
+ v2: 'https://api.sylvain.pro/v2',
193
+ v3: 'https://api.sylvain.pro/v3'
141
194
  }
142
195
  });
143
196
  });
@@ -198,6 +251,41 @@ app.get('/v2', (req, res) => {
198
251
  });
199
252
  });
200
253
 
254
+ // Display v3 endpoints
255
+ app.get('/v3', (req, res) => {
256
+ res.jsonResponse({
257
+ version: 'v3',
258
+ endpoints: {
259
+ get: {
260
+ algorithm: '/v3/algorithms?method={algorithm}&value={value}(&value2={value2})',
261
+ captcha: '/v3/captcha?text={text}',
262
+ chat: '/v3/chat',
263
+ color: '/v3/color',
264
+ convert: '/v3/convert?value={value}&from={unit}&to={unit}',
265
+ domain: '/v3/domain',
266
+ infos: '/v3/infos',
267
+ levenshtein: '/v3/levenshtein?str1={string}&str2={string}',
268
+ personal: '/v3/personal',
269
+ qrcode: '/v3/qrcode?url={URL}',
270
+ time: '/v3/time(?type={type}&start={timestamp}&end={timestamp}&format={format}&timezone={timezone})',
271
+ username: '/v3/username'
272
+ },
273
+ post: {
274
+ chat: {
275
+ chat: '/v3/chat',
276
+ private: '/v3/chat/private'
277
+ },
278
+ hash: '/v3/hash',
279
+ tic_tac_toe: {
280
+ tic_tac_toe: '/v3/tic-tac-toe',
281
+ fetch: '/v3/tic-tac-toe/fetch'
282
+ },
283
+ token: '/v3/token'
284
+ }
285
+ }
286
+ });
287
+ });
288
+
201
289
  // Display logs
202
290
  app.get('/logs', (req, res) => res.jsonResponse(logs));
203
291
 
@@ -206,11 +294,12 @@ app.get('/logs', (req, res) => res.jsonResponse(logs));
206
294
  // Algorithms
207
295
  app.get('/:version/algorithms', (req, res) => {
208
296
  const { method, value, value2 } = req.query;
297
+ const { version } = req.params;
209
298
 
210
299
  if (!['anagram', 'bubblesort', 'factorial', 'fibonacci', 'gcd', 'isprime', 'palindrome', 'primefactors', 'primelist', 'reverse'].includes(method)) {
211
300
  return res.jsonResponse({
212
301
  error: 'Please provide a valid algorithm (?method={algorithm})',
213
- documentation: 'https://docs.sylvain.pro/v1/algorithms'
302
+ documentation: `https://docs.sylvain.pro/${version}/algorithms`
214
303
  });
215
304
  }
216
305
  if (!value) return res.jsonResponse({ error: 'Please provide a valid value (&value={value})' });
@@ -233,7 +322,7 @@ app.get('/:version/algorithms', (req, res) => {
233
322
 
234
323
  if (method === 'factorial') {
235
324
  if (isNaN(value) || value < 0 || value > 170) return res.jsonResponse({ error: 'Please provide a valid number between 0 and 170.' });
236
- return res.jsonResponse({ answer: math.factorial(value) });
325
+ return res.jsonResponse({ answer: factorial(value) });
237
326
  }
238
327
 
239
328
  if (method === 'fibonacci') {
@@ -243,7 +332,6 @@ app.get('/:version/algorithms', (req, res) => {
243
332
  }
244
333
 
245
334
  if (method === 'gcd') {
246
- const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
247
335
  if (!value2) return res.jsonResponse({ error: 'Please provide a second value (&value2={value})' });
248
336
  if (isNaN(value) || isNaN(value2)) return res.jsonResponse({ error: 'Invalid numbers.' });
249
337
  return res.jsonResponse({ answer: gcd(value, value2) });
@@ -354,7 +442,7 @@ app.get('/:version/chat/private', (req, res) => {
354
442
 
355
443
  // Generate color
356
444
  app.get('/:version/color', (req, res) => {
357
- const r = random.int(0, 255), g = random.int(0, 255), b = random.int(0, 255);
445
+ const r = Math.floor(Math.random() * 256), g = Math.floor(Math.random() * 256), b = Math.floor(Math.random() * 256);
358
446
  const hsl = (() => {
359
447
  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;
360
448
  if (max === min) return [0, 0, l * 100];
@@ -407,17 +495,15 @@ app.get('/:version/convert', (req, res) => {
407
495
 
408
496
  // Generate domain informations
409
497
  app.get('/:version/domain', (req, res) => {
410
- const random = (arr) => arr[Math.floor(Math.random() * arr.length)];
411
498
  const subdomains = ['fr.', 'en.', 'docs.', 'api.', 'projects.', 'app.', 'web.', 'info.', 'dev.', 'shop.', 'blog.', 'support.', 'mail.', 'forum.'];
412
499
  const domains = ['example', 'site', 'test', 'demo', 'page', 'store', 'portfolio', 'platform', 'hub', 'network', 'service', 'cloud', 'solutions', 'company'];
413
500
  const tlds = ['.com', '.fr', '.eu', '.dev', '.net', '.org', '.io', '.tech', '.biz', '.info', '.co', '.app', '.store', '.online', '.shop', '.tv'];
414
501
 
415
502
  const domain = `${random(domains)}${random(tlds)}`;
416
503
  const fulldomain = `${random(subdomains)}${domain}`;
417
-
418
- const getRandomIp = () => `${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}.${Math.floor(Math.random() * 256)}`;
419
- const ips = Array.from({ length: Math.floor(Math.random() * 3) + 1 }, getRandomIp);
420
- const dns = Array.from({ length: Math.floor(Math.random() * 5) + 1 }, getRandomIp);
504
+
505
+ const ips = Array.from({ length: Math.floor(Math.random() * 3) + 1 }, genIP);
506
+ const dns = Array.from({ length: Math.floor(Math.random() * 5) + 1 }, genIP);
421
507
 
422
508
  res.jsonResponse({
423
509
  domain,
@@ -449,7 +535,7 @@ app.get('/:version/hash', (req, res) => {
449
535
  // Display API informations
450
536
  app.get('/:version/infos', (req, res) => {
451
537
  res.jsonResponse({
452
- endpoints: endpoints.length,
538
+ endpoints: endpoints[versions.at(-1)].length,
453
539
  last_version: versions.at(-1),
454
540
  documentation: 'https://docs.sylvain.pro',
455
541
  github: 'https://github.com/20syldev/api',
@@ -457,10 +543,28 @@ app.get('/:version/infos', (req, res) => {
457
543
  });
458
544
  });
459
545
 
546
+ // Calculate Levenshtein distance
547
+ app.get('/:version/levenshtein', (req, res) => {
548
+ const { str1, str2 } = req.query;
549
+
550
+ if (!str1) return res.jsonResponse({ error: 'Please provide a first string (?str1={string})' });
551
+ if (!str2) return res.jsonResponse({ error: 'Please provide a second string (&str2={string})' });
552
+
553
+ const lev = (a, b) => {
554
+ const m = Array.from({ length: a.length + 1 }, (_, i) => [i]);
555
+ for (let j = 0; j <= b.length; j++) m[0][j] = j;
556
+ for (let i = 1; i <= a.length; i++)
557
+ for (let j = 1; j <= b.length; j++)
558
+ 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]));
559
+
560
+ return m[a.length][b.length];
561
+ };
562
+
563
+ res.jsonResponse({ str1, str2, distance: lev(str1, str2) });
564
+ });
565
+
460
566
  // Generate personal data
461
567
  app.get('/:version/personal', (req, res) => {
462
- const random = (arr) => arr[Math.floor(Math.random() * arr.length)];
463
-
464
568
  const people = [
465
569
  { name: 'John Doe', social: 'john_doe', email: 'john@example.com', country: 'US' },
466
570
  { name: 'Jane Martin', social: 'jane_martin', email: 'jane@example.com', country: 'FR' },
@@ -579,7 +683,7 @@ app.get('/:version/qrcode', async (req, res) => {
579
683
 
580
684
  if (!url) return res.jsonResponse({ error: 'Please provide a valid url (?url={URL})' });
581
685
 
582
- try { res.jsonResponse({ qr: await qrcode.toDataURL(url) }); }
686
+ try { res.jsonResponse({ qr: await toDataURL(url) }); }
583
687
  catch { res.jsonResponse({ error: 'Error generating QR code.' }); }
584
688
  });
585
689
 
@@ -593,6 +697,59 @@ app.get('/:version/tic-tac-toe/fetch', (req, res) => {
593
697
  res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
594
698
  });
595
699
 
700
+ // Display or generate time informations
701
+ app.get('/:version/time', (req, res) => {
702
+ const { type = 'live', start, end, format, timezone } = req.query;
703
+
704
+ const validFormats = ['iso', 'utc', 'timestamp', 'locale', 'date', 'time', 'year', 'month', 'day', 'hour', 'minute', 'second', 'ms', 'dayOfWeek', 'dayOfYear', 'weekNumber', 'timezone', 'timezoneOffset'];
705
+ const validTimezones = ['UTC', 'America/New_York', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'];
706
+
707
+ if (type !== 'live' && type !== 'random') return res.jsonResponse({ error: 'Please provide a valid type (?type={type})' });
708
+ if (start && !Date.parse(start)) return res.jsonResponse({ error: 'Please provide a valid start date (?start={YYYY-MM-DD})' });
709
+ if (end && !Date.parse(end)) return res.jsonResponse({ error: 'Please provide a valid end date (?end={YYYY-MM-DD})' });
710
+ if (format && !validFormats.includes(format)) return res.jsonResponse({ error: 'Please provide a valid format (?format={format})' });
711
+ if (timezone && !validTimezones.includes(timezone)) return res.jsonResponse({ error: 'Please provide a valid timezone (?timezone={timezone})' });
712
+
713
+ const getTimeFormats = (date, timezoneOption) => {
714
+ return {
715
+ iso: date.toISOString(),
716
+ utc: date.toUTCString(),
717
+ timestamp: date.getTime(),
718
+ locale: date.toLocaleString('en-US', { timeZone: timezoneOption, timeZoneName: 'long' }),
719
+ date: date.toLocaleDateString('en-US', { timeZone: timezoneOption }),
720
+ time: date.toLocaleTimeString('en-US', { timeZone: timezoneOption }),
721
+ year: date.getFullYear(),
722
+ month: date.getMonth() + 1,
723
+ day: date.getDate(),
724
+ hour: date.getHours(),
725
+ minute: date.getMinutes(),
726
+ second: date.getSeconds(),
727
+ ms: date.getMilliseconds(),
728
+ dayOfWeek: date.getDay(),
729
+ dayOfYear: Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000),
730
+ weekNumber: Math.ceil((((date - new Date(date.getFullYear(), 0, 0)) / 86400000) + 1) / 7),
731
+ timezone: timezoneOption,
732
+ timezoneOffset: date.getTimezoneOffset()
733
+ };
734
+ };
735
+
736
+ if (type === 'random') {
737
+ const startDate = new Date(start || '1900-01-01').getTime();
738
+ const endDate = new Date(end || '2100-12-31').getTime();
739
+ const randomDate = new Date(start ? startDate : startDate + Math.random() * (endDate - startDate));
740
+ const timezoneOption = timezone || validTimezones[Math.floor(Math.random() * 5)];
741
+ const formats = getTimeFormats(randomDate, timezoneOption);
742
+
743
+ return res.jsonResponse(format && formats[format] ? { date: formats[format] } : formats);
744
+ }
745
+
746
+ const now = new Date();
747
+ const timezoneOption = timezone || 'UTC';
748
+ const formats = getTimeFormats(now, timezoneOption);
749
+
750
+ res.jsonResponse(format && formats[format] ? { date: formats[format] } : formats);
751
+ });
752
+
596
753
  // GET token error
597
754
  app.get('/:version/token', (req, res) => {
598
755
  res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
@@ -604,7 +761,6 @@ app.get('/:version/username', (req, res) => {
604
761
  const ani = ['Cat', 'Dog', 'Tiger', 'Elephant', 'Monkey', 'Penguin', 'Dolphin', 'Lion', 'Bear', 'Fox', 'Owl', 'Giraffe', 'Zebra', 'Koala', 'Rabbit', 'Squirrel', 'Panda', 'Horse', 'Wolf', 'Eagle'];
605
762
  const job = ['Writer', 'Artist', 'Musician', 'Explorer', 'Scientist', 'Engineer', 'Athlete', 'Chef', 'Doctor', 'Teacher', 'Lawyer', 'Entrepreneur', 'Actor', 'Dancer', 'Photographer', 'Architect', 'Pilot', 'Designer', 'Journalist', 'Veterinarian'];
606
763
 
607
- const random = (arr) => arr[Math.floor(Math.random() * arr.length)];
608
764
  const nombre = Math.floor(Math.random() * 100);
609
765
  const choix = {
610
766
  adj_num: () => random(adj) + nombre,
@@ -702,8 +858,9 @@ app.get('/:version/website', async (req, res) => {
702
858
  wrkit: process.env.WRKIT,
703
859
  zpki: process.env.ZPKI
704
860
  },
705
- updated_projects: process.env.RECENT.split(' '),
706
- new_projects: process.env.NEW.split(' '),
861
+ updated_projects: process.env.RECENT !== undefined ? process.env.RECENT.split(' ') : [],
862
+ new_projects: process.env.NEW !== undefined ? process.env.NEW.split(' ') : [],
863
+ sub_domains: process.env.DOMAINS !== undefined ? process.env.DOMAINS.split(' ') : [],
707
864
  stats: {
708
865
  os: process.env.STATS1,
709
866
  front: process.env.STATS2,
@@ -805,15 +962,21 @@ app.post('/:version/tic-tac-toe', (req, res) => {
805
962
  games[game] = games[game] || [];
806
963
 
807
964
  const players = [...new Set(games[game].map(play => play.username))];
808
- if (players.length >= 2 && !players.includes(username)) {
809
- return res.jsonResponse({ error: 'Game is full, you can only watch.' });
810
- }
811
-
965
+ if (players.length >= 2 && !players.includes(username)) return res.jsonResponse({ error: 'Game is full, you can only watch.' });
812
966
  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.' });
813
967
  if (games[game].some(play => play.move === move)) return res.jsonResponse({ error: 'Move already made. Please choose a different move.' });
814
968
 
815
969
  games[game].push(play);
816
- setTimeout(() => { delete games[game]; }, 3600000);
970
+
971
+ const result = checkGame(games[game]);
972
+ if (result.winner || result.tie) {
973
+ setTimeout(() => delete games[game], 600000);
974
+ return res.jsonResponse({
975
+ message: `Move sent successfully. ${result.winner ? result.winner + " wins. " + result.loser + " loses." : "It's a tie."}`,
976
+ ...result
977
+ });
978
+ }
979
+ if (!result.winner && !result.tie) setTimeout(() => delete games[game], 3600000);
817
980
 
818
981
  sessions[u] = sessions[u] || { user: session, last: now };
819
982
  sessions[u].last = now;
@@ -829,12 +992,7 @@ app.post('/:version/tic-tac-toe/fetch', (req, res) => {
829
992
 
830
993
  if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
831
994
 
832
- const generateId = () => {
833
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
834
- return Array.from(crypto.randomBytes(5)).map(b => chars[b % chars.length]).join('');
835
- };
836
-
837
- const ID = game || generateId();
995
+ const ID = game || genID();
838
996
  const u = username.toLowerCase(), now = Date.now();
839
997
 
840
998
  rateLimits[u] = (rateLimits[u] || []).filter(ts => now - ts < 10000);
@@ -846,49 +1004,54 @@ app.post('/:version/tic-tac-toe/fetch', (req, res) => {
846
1004
 
847
1005
  if (!games[ID]) games[ID] = [];
848
1006
 
849
- const data = games[ID], last = data.length ? data[data.length - 1].username : null;
850
- const players = [...new Set(data.map(p => p.username))], turn = players.find(p => p !== last);
1007
+ const data = games[ID];
1008
+ const last = data.length ? data[data.length - 1].username : null;
1009
+ const players = [...new Set(data.map(p => p.username))];
1010
+ const turn = players.find(p => p !== last);
1011
+ const result = data.length ? checkGame(data) : {};
851
1012
 
852
- res.jsonResponse({ game: data, turn, ID });
1013
+ res.jsonResponse({ game: data, turn, ID, ...result });
853
1014
  });
854
1015
 
855
1016
  // Generate hash
856
1017
  app.post('/:version/hash', (req, res) => {
857
1018
  const { text, method } = req.body;
1019
+ const { version } = req.params;
858
1020
 
859
1021
  if (!text) return res.jsonResponse({ error: 'Please provide a text (?text={text})' });
860
1022
  if (!method) return res.jsonResponse({
861
1023
  error: 'Please provide a valid hash algorithm (?method={algorithm})',
862
- documentation: 'https://docs.sylvain.pro/v1/hash'
1024
+ documentation: `https://docs.sylvain.pro/${version}/hash`
863
1025
  });
864
1026
 
865
- const methods = crypto.getHashes();
1027
+ const methods = getHashes();
866
1028
  if (!methods.includes(method)) return res.jsonResponse({ error: `Unsupported method. Use one of: ${methods.join(', ')}` });
867
1029
 
868
- const hash = crypto.createHash(method).update(text).digest('hex');
1030
+ const hash = createHash(method).update(text).digest('hex');
869
1031
  res.jsonResponse({ method, hash });
870
1032
  });
871
1033
 
872
1034
  // Generate Token
873
1035
  app.post('/:version/token', (req, res) => {
874
- const length = parseInt(req.body.len || 24, 10);
875
- const type = req.body.type || 'alpha';
1036
+ let { len, type } = req.body;
1037
+
1038
+ len = parseInt(len || 24, 10);
1039
+ type = type ? type.toLowerCase() : 'alpha';
876
1040
 
877
- if (isNaN(length) || length < 0) return res.jsonResponse({ error: 'Invalid number.' });
878
- if (length > 4096) return res.jsonResponse({ error: 'Length cannot exceed 4096.' });
879
- if (length < 12) return res.jsonResponse({ error: 'Length cannot be less than 12.' });
1041
+ if (isNaN(len) || len < 0) return res.jsonResponse({ error: 'Invalid number.' });
1042
+ if (len > 4096) return res.jsonResponse({ error: 'Length cannot exceed 4096.' });
1043
+ if (len < 12) return res.jsonResponse({ error: 'Length cannot be less than 12.' });
880
1044
 
881
- const generateToken = (chars) => Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
882
1045
  const token = {
883
- alpha: generateToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'),
884
- alphanum: generateToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'),
885
- base64: crypto.randomBytes(length).toString('base64').slice(0, length),
886
- hex: crypto.randomBytes(length).toString('hex').slice(0, length),
887
- num: generateToken('0123456789'),
888
- punct: generateToken('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'),
889
- urlsafe: generateToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_'),
890
- uuid: uuid.v4().replace(/-/g, '').slice(0, length)
891
- }[type] || generateToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
1046
+ alpha: genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', len),
1047
+ alphanum: genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', len),
1048
+ base64: randomBytes(len).toString('base64').slice(0, len),
1049
+ hex: randomBytes(len).toString('hex').slice(0, len),
1050
+ num: genToken('0123456789', len),
1051
+ punct: genToken('!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~', len),
1052
+ urlsafe: genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_', len),
1053
+ uuid: v4().replace(/-/g, '').slice(0, len)
1054
+ }[type] || genToken('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', len);
892
1055
 
893
1056
  res.jsonResponse({ token });
894
1057
  });
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
- "version": "2.8.0",
2
+ "version": "3.0.0",
3
3
  "name": "@20syldev/api",
4
- "description": "Personnal API",
4
+ "description": "Node.js API with multiple features. Check the documentation at https://docs.sylvain.pro",
5
5
  "main": "app.js",
6
+ "type": "module",
6
7
  "scripts": {
7
8
  "start": "node app.js",
8
9
  "build": "npm install && node app.js",
@@ -11,16 +12,16 @@
11
12
  "upgrade:build": "npm upgrade && npm install && node app.js"
12
13
  },
13
14
  "dependencies": {
14
- "canvas": "^3.1.0",
15
- "cors": "2.8.5",
16
- "dotenv": "^16.4.7",
17
- "express": "^4.21.2",
18
- "mathjs": "^14.2.0",
19
- "node-fetch": "^2.7.0",
20
- "prettier": "^3.4.2",
21
- "qrcode": "^1.5.4",
22
- "random": "^5.1.1",
23
- "uuid": "^11.0.5"
15
+ "canvas": "latest",
16
+ "cors": "latest",
17
+ "dotenv": "latest",
18
+ "express": "latest",
19
+ "mathjs": "latest",
20
+ "node-fetch": "latest",
21
+ "prettier": "latest",
22
+ "qrcode": "latest",
23
+ "random": "latest",
24
+ "uuid": "latest"
24
25
  },
25
26
  "repository": {
26
27
  "type": "git",