@20syldev/api 2.9.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 +4 -4
  2. package/app.js +177 -17
  3. package/package.json +1 -1
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.9.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,7 +10,7 @@
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
15
  ## Installer le paquet de l'API sur votre machine
16
16
  ```console
@@ -23,7 +23,7 @@ Pour utiliser le **paquet** dans votre projet, **créez** un fichier **JavaScrip
23
23
  require('@20syldev/api');
24
24
  ```
25
25
 
26
- Puis, **démarrez** un serveur [Node.js](http://nodejs.org) pour utiliser l'**API** :
26
+ Puis, **démarrez** un serveur [Node.js](https://nodejs.org) pour utiliser l'**API** :
27
27
  ```console
28
28
  $ node index.js
29
29
  API is running on
@@ -44,7 +44,7 @@ Enfin, **exécutez** le script de build, qui installera les **dépendances** et
44
44
  $ npm run build
45
45
  ```
46
46
  ```console
47
- > @20syldev/api@2.9.0 build
47
+ > @20syldev/api@3.0.0 build
48
48
  > npm install && node app.js
49
49
 
50
50
  [...]
package/app.js CHANGED
@@ -1,10 +1,11 @@
1
- import { createCanvas } from 'canvas';
2
1
  import cors from 'cors';
3
2
  import dotenv from 'dotenv';
3
+ import express from 'express';
4
+ import fetch from 'node-fetch';
5
+ import { createCanvas } from 'canvas';
4
6
  import { randomBytes, getHashes, createHash } from 'crypto';
5
- import express, { urlencoded, json } from 'express';
7
+ import { urlencoded, json } from 'express';
6
8
  import { factorial } from 'mathjs';
7
- import fetch from 'node-fetch';
8
9
  import { dirname, join } from 'path';
9
10
  import { toDataURL } from 'qrcode';
10
11
  import { fileURLToPath } from 'url';
@@ -15,10 +16,11 @@ const __dirname = dirname(__filename);
15
16
  const app = express();
16
17
 
17
18
  // Define allowed versions & endpoints for each version
18
- const versions = ['v1', 'v2'];
19
+ const versions = ['v1', 'v2', 'v3'];
19
20
  const endpoints = {
20
21
  v1: ['algorithms', 'captcha', 'color', 'convert', 'domain', 'infos', 'personal', 'qrcode', 'token', 'username', 'website'],
21
- 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']
22
24
  };
23
25
 
24
26
  // Arrowed functions (math & random)
@@ -35,7 +37,46 @@ const random = (arr) => arr[Math.floor(Math.random() * arr.length)];
35
37
  const logs = [], chat = [], privateChats = {}, sessions = {}, rateLimits = {}, games = {};
36
38
 
37
39
  // Define global variables
38
- 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
+ }
39
80
 
40
81
  // ----------- ----------- MIDDLEWARES SETUP ----------- ----------- //
41
82
 
@@ -148,7 +189,8 @@ app.get('/', (req, res) => {
148
189
  logs: 'https://api.sylvain.pro/logs',
149
190
  versions: {
150
191
  v1: 'https://api.sylvain.pro/v1',
151
- v2: 'https://api.sylvain.pro/v2'
192
+ v2: 'https://api.sylvain.pro/v2',
193
+ v3: 'https://api.sylvain.pro/v3'
152
194
  }
153
195
  });
154
196
  });
@@ -209,6 +251,41 @@ app.get('/v2', (req, res) => {
209
251
  });
210
252
  });
211
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
+
212
289
  // Display logs
213
290
  app.get('/logs', (req, res) => res.jsonResponse(logs));
214
291
 
@@ -466,6 +543,26 @@ app.get('/:version/infos', (req, res) => {
466
543
  });
467
544
  });
468
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
+
469
566
  // Generate personal data
470
567
  app.get('/:version/personal', (req, res) => {
471
568
  const people = [
@@ -600,6 +697,59 @@ app.get('/:version/tic-tac-toe/fetch', (req, res) => {
600
697
  res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
601
698
  });
602
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
+
603
753
  // GET token error
604
754
  app.get('/:version/token', (req, res) => {
605
755
  res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
@@ -708,8 +858,9 @@ app.get('/:version/website', async (req, res) => {
708
858
  wrkit: process.env.WRKIT,
709
859
  zpki: process.env.ZPKI
710
860
  },
711
- updated_projects: process.env.RECENT.split(' '),
712
- 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(' ') : [],
713
864
  stats: {
714
865
  os: process.env.STATS1,
715
866
  front: process.env.STATS2,
@@ -811,15 +962,21 @@ app.post('/:version/tic-tac-toe', (req, res) => {
811
962
  games[game] = games[game] || [];
812
963
 
813
964
  const players = [...new Set(games[game].map(play => play.username))];
814
- if (players.length >= 2 && !players.includes(username)) {
815
- return res.jsonResponse({ error: 'Game is full, you can only watch.' });
816
- }
817
-
965
+ if (players.length >= 2 && !players.includes(username)) return res.jsonResponse({ error: 'Game is full, you can only watch.' });
818
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.' });
819
967
  if (games[game].some(play => play.move === move)) return res.jsonResponse({ error: 'Move already made. Please choose a different move.' });
820
968
 
821
969
  games[game].push(play);
822
- 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);
823
980
 
824
981
  sessions[u] = sessions[u] || { user: session, last: now };
825
982
  sessions[u].last = now;
@@ -847,10 +1004,13 @@ app.post('/:version/tic-tac-toe/fetch', (req, res) => {
847
1004
 
848
1005
  if (!games[ID]) games[ID] = [];
849
1006
 
850
- const data = games[ID], last = data.length ? data[data.length - 1].username : null;
851
- 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) : {};
852
1012
 
853
- res.jsonResponse({ game: data, turn, ID });
1013
+ res.jsonResponse({ game: data, turn, ID, ...result });
854
1014
  });
855
1015
 
856
1016
  // Generate hash
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.9.0",
2
+ "version": "3.0.0",
3
3
  "name": "@20syldev/api",
4
4
  "description": "Node.js API with multiple features. Check the documentation at https://docs.sylvain.pro",
5
5
  "main": "app.js",