@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.
- package/README.md +32 -5
- package/app.js +236 -73
- 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
|
-
[](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
|
-
##
|
|
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@
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
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(
|
|
36
|
-
app.use(
|
|
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(
|
|
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(
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
|
419
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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]
|
|
850
|
-
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) : {};
|
|
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:
|
|
1024
|
+
documentation: `https://docs.sylvain.pro/${version}/hash`
|
|
863
1025
|
});
|
|
864
1026
|
|
|
865
|
-
const methods =
|
|
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 =
|
|
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
|
-
|
|
875
|
-
|
|
1036
|
+
let { len, type } = req.body;
|
|
1037
|
+
|
|
1038
|
+
len = parseInt(len || 24, 10);
|
|
1039
|
+
type = type ? type.toLowerCase() : 'alpha';
|
|
876
1040
|
|
|
877
|
-
if (isNaN(
|
|
878
|
-
if (
|
|
879
|
-
if (
|
|
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:
|
|
884
|
-
alphanum:
|
|
885
|
-
base64:
|
|
886
|
-
hex:
|
|
887
|
-
num:
|
|
888
|
-
punct:
|
|
889
|
-
urlsafe:
|
|
890
|
-
uuid:
|
|
891
|
-
}[type] ||
|
|
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
|
+
"version": "3.0.0",
|
|
3
3
|
"name": "@20syldev/api",
|
|
4
|
-
"description": "
|
|
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": "
|
|
15
|
-
"cors": "
|
|
16
|
-
"dotenv": "
|
|
17
|
-
"express": "
|
|
18
|
-
"mathjs": "
|
|
19
|
-
"node-fetch": "
|
|
20
|
-
"prettier": "
|
|
21
|
-
"qrcode": "
|
|
22
|
-
"random": "
|
|
23
|
-
"uuid": "
|
|
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",
|