@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.
- package/README.md +4 -4
- package/app.js +177 -17
- 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
|
-
[](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](
|
|
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@
|
|
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
|
|
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
|
-
|
|
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]
|
|
851
|
-
const
|
|
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