@20syldev/api 3.4.4 → 3.4.6

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 CHANGED
@@ -1,16 +1,16 @@
1
1
  <div align="center">
2
- <a href="https://api.sylvain.pro"><img src="https://api.sylvain.pro/favicon.ico" alt="Logo" width="25%" height="auto"/></a>
2
+ <a href="https://api.sylvain.sh"><img src="https://api.sylvain.sh/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:-v3.4.4-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.4.6-6479ee?logo=api.sylvain.sh&labelColor=23272A)](https://github.com/20syldev/api/releases/latest)
6
6
  </div>
7
7
 
8
8
  ---
9
9
 
10
10
  ## À propos de l'API
11
- Voici mon API personnelle, disponible sur le domaine [api.sylvain.pro](https://api.sylvain.pro).
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 **2000** requêtes **par heure** est fixée. Elle change pour certains endpoints nécessitant plus de ressources. Si vous souhaitez une **augmentation** de cette limite, n'hésitez pas à visiter la [documentation](https://docs.sylvain.pro/pricing).*
11
+ Voici mon API personnelle, disponible sur le domaine [api.sylvain.sh](https://api.sylvain.sh).
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.sh](https://docs.sylvain.sh) !
13
+ > *Une limite de **2000** requêtes **par heure** est fixée. Elle change pour certains endpoints nécessitant plus de ressources. Si vous souhaitez une **augmentation** de cette limite, n'hésitez pas à visiter la [documentation](https://docs.sylvain.sh/pricing).*
14
14
 
15
15
  ## Installer le paquet de l'API sur votre machine
16
16
  ```console
@@ -67,12 +67,12 @@ Ensuite, **déplacez-vous** dans le dossier du projet, via un terminal **Linux**
67
67
  $ cd /chemin/vers/le/projet
68
68
  ```
69
69
 
70
- Enfin, **exécutez** le script de build, qui installera les **dépendances** et lancera le **serveur** de l'[API](https://api.sylvain.pro) :
70
+ Enfin, **exécutez** le script de build, qui installera les **dépendances** et lancera le **serveur** de l'[API](https://api.sylvain.sh) :
71
71
  ```console
72
72
  $ npm run build
73
73
  ```
74
74
  ```console
75
- > @20syldev/api@3.4.4 build
75
+ > @20syldev/api@3.4.6 build
76
76
  > npm install && node app.js
77
77
 
78
78
  [...]
@@ -107,4 +107,4 @@ import {
107
107
  } from '@20syldev/api/v3';
108
108
  ```
109
109
 
110
- *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) !*
110
+ *Visitez la [documentation](https://docs.sylvain.sh) dédiée, vous y retrouverez des exemples de requêtes et des codes simples pour tester l'[API](https://api.sylvain.sh) !*
package/app.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Import modules
2
2
  import * as apiv3 from './modules/v3.js';
3
+ import { envList } from './modules/v3/utils.js';
3
4
 
4
5
  // Import dependencies
5
6
  import cors from 'cors';
@@ -51,7 +52,8 @@ const v2 = {
51
52
  name: 'tic_tac_toe',
52
53
  children: {
53
54
  tic_tac_toe: '/tic-tac-toe',
54
- fetch: '/tic-tac-toe/fetch'
55
+ fetch: '/tic-tac-toe/fetch',
56
+ list: '/tic-tac-toe/list'
55
57
  }
56
58
  },
57
59
  { name: 'token', path: '/token' }
@@ -101,6 +103,9 @@ const ticTacToeStorage = {
101
103
  rateLimits: {}
102
104
  };
103
105
 
106
+ // Define documentation URL
107
+ const documentation = 'https://docs.sylvain.sh';
108
+
104
109
  // Define global variables
105
110
  let contributions, lastFetch = 0, requests = 0, requestLimit, resetTime = Date.now() + 3600000;
106
111
 
@@ -212,7 +217,7 @@ app.use((err, req, res, next) => {
212
217
  res.status(500).jsonResponse({
213
218
  message: 'Internal Server Error',
214
219
  error: err.message,
215
- documentation: 'https://docs.sylvain.pro',
220
+ documentation,
216
221
  status: '500'
217
222
  });
218
223
  });
@@ -236,7 +241,7 @@ app.use('/:version', (req, res, next) => {
236
241
  return res.status(404).jsonResponse({
237
242
  message: 'Not Found',
238
243
  error: `Invalid API version (${version}).`,
239
- documentation: `https://docs.sylvain.pro/${latest}`,
244
+ documentation: `${documentation}/${latest}`,
240
245
  status: '404'
241
246
  });
242
247
  }
@@ -247,7 +252,7 @@ app.use('/:version', (req, res, next) => {
247
252
  return res.status(404).jsonResponse({
248
253
  message: 'Not Found',
249
254
  error: `Module not found for version ${version}.`,
250
- documentation: `https://docs.sylvain.pro/${latest}`,
255
+ documentation: `${documentation}/${latest}`,
251
256
  status: '404'
252
257
  });
253
258
  }
@@ -266,7 +271,7 @@ app.use('/:version/:endpoint', (req, res, next) => {
266
271
  return res.status(404).jsonResponse({
267
272
  message: 'Not Found',
268
273
  error: `Endpoint '${endpoint}' does not exist in ${req.version}.`,
269
- documentation: `https://docs.sylvain.pro/${versions[version.length - 1]}`,
274
+ documentation: `${documentation}/${versions[version.length - 1]}`,
270
275
  status: '404'
271
276
  });
272
277
  }
@@ -284,7 +289,7 @@ app.get('/', (req, res) => {
284
289
  }, {});
285
290
 
286
291
  res.jsonResponse({
287
- documentation: 'https://docs.sylvain.pro',
292
+ documentation,
288
293
  latest: `${base}/latest`,
289
294
  logs: `${base}/logs`,
290
295
  versions: links
@@ -299,7 +304,7 @@ app.get('/:version', (req, res) => {
299
304
  return res.status(404).jsonResponse({
300
305
  message: 'Not Found',
301
306
  error: `Invalid API version (${version}).`,
302
- documentation: `https://docs.sylvain.pro/${Object.keys(versions).pop()}`,
307
+ documentation: `${documentation}/${Object.keys(versions).pop()}`,
303
308
  status: '404'
304
309
  });
305
310
  }
@@ -326,7 +331,7 @@ app.get('/:version', (req, res) => {
326
331
 
327
332
  res.jsonResponse({
328
333
  version,
329
- documentation: `https://docs.sylvain.pro/${version}`,
334
+ documentation: `${documentation}/${version}`,
330
335
  endpoints
331
336
  });
332
337
  });
@@ -341,7 +346,7 @@ app.get('/:version/algorithms', (req, res) => {
341
346
  if (!req.module.algorithms || !req.module.algorithms[method]) {
342
347
  return res.jsonResponse({
343
348
  error: 'Please provide a valid algorithm (?method={algorithm})',
344
- documentation: `https://docs.sylvain.pro/${version}/en/algorithms`
349
+ documentation: `${documentation}/${version}/en/algorithms`
345
350
  });
346
351
  }
347
352
 
@@ -351,7 +356,7 @@ app.get('/:version/algorithms', (req, res) => {
351
356
  } catch (err) {
352
357
  res.jsonResponse({
353
358
  error: err.message,
354
- documentation: `https://docs.sylvain.pro/${version}/en/algorithms`
359
+ documentation: `${documentation}/${version}/en/algorithms`
355
360
  });
356
361
  }
357
362
  });
@@ -368,7 +373,7 @@ app.get('/:version/captcha', (req, res) => {
368
373
  } catch (err) {
369
374
  res.jsonResponse({
370
375
  error: err.message,
371
- documentation: `https://docs.sylvain.pro/${req.version}/en/captcha`
376
+ documentation: `${documentation}/${req.version}/en/captcha`
372
377
  });
373
378
  }
374
379
  });
@@ -399,7 +404,7 @@ app.get('/:version/color', (req, res) => {
399
404
  } catch (err) {
400
405
  res.jsonResponse({
401
406
  error: err.message,
402
- documentation: `https://docs.sylvain.pro/${req.version}/en/color`
407
+ documentation: `${documentation}/${req.version}/en/color`
403
408
  });
404
409
  }
405
410
  });
@@ -418,7 +423,7 @@ app.get('/:version/convert', (req, res) => {
418
423
  } catch (err) {
419
424
  res.jsonResponse({
420
425
  error: err.message,
421
- documentation: `https://docs.sylvain.pro/${req.version}/en/convert`
426
+ documentation: `${documentation}/${req.version}/en/convert`
422
427
  });
423
428
  }
424
429
  });
@@ -431,7 +436,7 @@ app.get('/:version/domain', (req, res) => {
431
436
  } catch (err) {
432
437
  res.jsonResponse({
433
438
  error: err.message,
434
- documentation: `https://docs.sylvain.pro/${req.version}/en/domain`
439
+ documentation: `${documentation}/${req.version}/en/domain`
435
440
  });
436
441
  }
437
442
  });
@@ -457,7 +462,7 @@ app.get('/:version/infos', (req, res) => {
457
462
  res.jsonResponse({
458
463
  endpoints: new Set(paths).size,
459
464
  last_version: Object.keys(versions).pop(),
460
- documentation: 'https://docs.sylvain.pro',
465
+ documentation,
461
466
  github: 'https://github.com/20syldev/api',
462
467
  creation: 'November 25th 2024',
463
468
  });
@@ -476,7 +481,7 @@ app.get('/:version/levenshtein', (req, res) => {
476
481
  } catch (err) {
477
482
  res.jsonResponse({
478
483
  error: err.message,
479
- documentation: `https://docs.sylvain.pro/${req.version}/en/levenshtein`
484
+ documentation: `${documentation}/${req.version}/en/levenshtein`
480
485
  });
481
486
  }
482
487
  });
@@ -489,7 +494,7 @@ app.get('/:version/personal', (req, res) => {
489
494
  } catch (err) {
490
495
  res.jsonResponse({
491
496
  error: err.message,
492
- documentation: `https://docs.sylvain.pro/${req.version}/en/personal`
497
+ documentation: `${documentation}/${req.version}/en/personal`
493
498
  });
494
499
  }
495
500
  });
@@ -506,7 +511,7 @@ app.get('/:version/qrcode', async (req, res) => {
506
511
  } catch (err) {
507
512
  res.jsonResponse({
508
513
  error: err.message,
509
- documentation: `https://docs.sylvain.pro/${req.version}/en/qrcode`
514
+ documentation: `${documentation}/${req.version}/en/qrcode`
510
515
  });
511
516
  }
512
517
  });
@@ -521,6 +526,11 @@ app.get('/:version/tic-tac-toe/fetch', (req, res) => {
521
526
  res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
522
527
  });
523
528
 
529
+ // GET tic-tac-toe list error
530
+ app.get('/:version/tic-tac-toe/list', (req, res) => {
531
+ res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
532
+ });
533
+
524
534
  // Display or generate time informations
525
535
  app.get('/:version/time', (req, res) => {
526
536
  const { type = 'live', start, end, format, timezone } = req.query;
@@ -540,7 +550,7 @@ app.get('/:version/time', (req, res) => {
540
550
  } catch (err) {
541
551
  res.jsonResponse({
542
552
  error: err.message,
543
- documentation: `https://docs.sylvain.pro/${req.version}/en/time`
553
+ documentation: `${documentation}/${req.version}/en/time`
544
554
  });
545
555
  }
546
556
  });
@@ -558,7 +568,7 @@ app.get('/:version/username', (req, res) => {
558
568
  } catch (err) {
559
569
  res.jsonResponse({
560
570
  error: err.message,
561
- documentation: `https://docs.sylvain.pro/${req.version}/en/username`
571
+ documentation: `${documentation}/${req.version}/en/username`
562
572
  });
563
573
  }
564
574
  });
@@ -632,31 +642,35 @@ app.get('/:version/website', async (req, res) => {
632
642
  flowers: process.env.FLOWERS,
633
643
  g_2048: process.env.G_2048,
634
644
  gemsync: process.env.GEMSYNC,
645
+ gft: process.env.GFT,
635
646
  gitsite: process.env.GITSITE,
636
647
  lebonchar: process.env.LEBONCHAR,
637
648
  logs: process.env.LOGS,
638
649
  logvault: process.env.LOGVAULT,
639
650
  lyah: process.env.LYAH,
640
651
  minify: process.env.MINIFY,
652
+ mn: process.env.MN,
641
653
  monitoring: process.env.MONITORING,
642
654
  morpion: process.env.MORPION,
643
655
  nitrogen: process.env.NITROGEN,
644
656
  old_database: process.env.OLD_DATABASE,
645
657
  password: process.env.PASSWORD,
646
658
  php: process.env.PHP,
659
+ planning: process.env.PLANNING,
647
660
  ping: process.env.PING,
648
661
  portfolio: process.env.PORTFOLIO,
649
662
  python_api: process.env.PYTHON_API,
650
663
  readme: process.env.README,
651
664
  timestamp: process.env.TIMESTAMP,
652
665
  terminal: process.env.TERMINAL,
666
+ valentine: process.env.VALENTINE,
653
667
  wrkit: process.env.WRKIT,
654
668
  zpki: process.env.ZPKI
655
669
  },
656
- patched_projects: process.env.PATCH?.split(' ') || [],
657
- updated_projects: process.env.RECENT?.split(' ') || [],
658
- new_projects: process.env.NEW?.split(' ') || [],
659
- sub_domains: process.env.DOMAINS?.split(' ') || [],
670
+ patched_projects: envList('PATCH'),
671
+ updated_projects: envList('RECENT'),
672
+ new_projects: envList('NEW'),
673
+ sub_domains: envList('DOMAINS'),
660
674
  stats: {
661
675
  1: process.env.STATS1,
662
676
  2: process.env.STATS2,
@@ -667,8 +681,8 @@ app.get('/:version/website', async (req, res) => {
667
681
  this_month: contributions.month.toString(),
668
682
  last_year: contributions.year.toString(),
669
683
  },
670
- notif_tag: process.env.TAG,
671
- active: process.env.ACTIVE
684
+ tag: process.env.TAG,
685
+ active: process.env.ACTIVE === 'true'
672
686
  });
673
687
  });
674
688
 
@@ -676,7 +690,7 @@ app.get('/:version/website', async (req, res) => {
676
690
 
677
691
  // Store chat messages
678
692
  app.post('/:version/chat', (req, res) => {
679
- const { username, message, timestamp, session, token } = req.body;
693
+ const { username, message, timestamp, session, token } = req.body || {};
680
694
 
681
695
  if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
682
696
  if (!message) return res.jsonResponse({ error: 'Please provide a message (&message={message})' });
@@ -699,7 +713,7 @@ app.post('/:version/chat', (req, res) => {
699
713
 
700
714
  // Display a private chat with a token
701
715
  app.post('/:version/chat/private', (req, res) => {
702
- const { username, token } = req.body;
716
+ const { username, token } = req.body || {};
703
717
 
704
718
  if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
705
719
  if (!token) return res.jsonResponse({ error: 'Please provide a valid token (&token={key}).' });
@@ -718,12 +732,12 @@ app.post('/:version/chat/private', (req, res) => {
718
732
 
719
733
  // Generate hash
720
734
  app.post('/:version/hash', (req, res) => {
721
- const { text, method } = req.body;
735
+ const { text, method } = req.body || {};
722
736
 
723
737
  if (!text) return res.jsonResponse({ error: 'Please provide a text (?text={text})' });
724
738
  if (!method) return res.jsonResponse({
725
739
  error: 'Please provide a valid hash algorithm (&method={algorithm})',
726
- documentation: `https://docs.sylvain.pro/${req.version}/en/hash`
740
+ documentation: `${documentation}/${req.version}/en/hash`
727
741
  });
728
742
 
729
743
  try {
@@ -732,31 +746,31 @@ app.post('/:version/hash', (req, res) => {
732
746
  } catch (err) {
733
747
  res.jsonResponse({
734
748
  error: err.message,
735
- documentation: `https://docs.sylvain.pro/${req.version}/en/hash`
749
+ documentation: `${documentation}/${req.version}/en/hash`
736
750
  })
737
751
  }
738
752
  });
739
753
 
740
754
  // Display a planning from an ICS file
741
755
  app.post('/:version/hyperplanning', async (req, res) => {
742
- const { url, detail } = req.body;
756
+ const { url, detail } = req.body || {};
743
757
 
744
758
  if (!url) return res.jsonResponse({ error: 'Please provide a valid ICS file URL (?url={URL})' });
745
759
 
746
760
  try {
747
- const hyperplanning = req.module.hyperplanning(url, detail);
761
+ const hyperplanning = await req.module.hyperplanning(url, detail);
748
762
  res.jsonResponse(hyperplanning);
749
763
  } catch (err){
750
764
  res.jsonResponse({
751
- error: 'Failed to parse ICS file.',
752
- documentation: `https://docs.sylvain.pro/${req.version}/en/hyperplanning`
765
+ error: err.message,
766
+ documentation: `${documentation}/${req.version}/en/hyperplanning`
753
767
  });
754
768
  }
755
769
  });
756
770
 
757
771
  // Store tic tac toe games
758
772
  app.post('/:version/tic-tac-toe', (req, res) => {
759
- const { username, move, session, game } = req.body;
773
+ const { username, move, session, game } = req.body || {};
760
774
 
761
775
  if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
762
776
  if (!move) return res.jsonResponse({ error: 'Please provide a valid move (&move={move})' });
@@ -779,7 +793,8 @@ app.post('/:version/tic-tac-toe', (req, res) => {
779
793
 
780
794
  // Display a tic tac toe game with a token
781
795
  app.post('/:version/tic-tac-toe/fetch', (req, res) => {
782
- const { username, game } = req.body;
796
+ const { username, game } = req.body || {};
797
+ const privateGame = (req.body || {}).private;
783
798
 
784
799
  if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
785
800
 
@@ -787,6 +802,19 @@ app.post('/:version/tic-tac-toe/fetch', (req, res) => {
787
802
  const result = req.module.tic_tac_toe('fetch', {
788
803
  username,
789
804
  game,
805
+ private: privateGame,
806
+ storage: ticTacToeStorage
807
+ });
808
+ res.jsonResponse(result);
809
+ } catch (err) {
810
+ res.jsonResponse({ error: err.message });
811
+ }
812
+ });
813
+
814
+ // List public tic tac toe games
815
+ app.post('/:version/tic-tac-toe/list', (req, res) => {
816
+ try {
817
+ const result = req.module.tic_tac_toe('list', {
790
818
  storage: ticTacToeStorage
791
819
  });
792
820
  res.jsonResponse(result);
@@ -797,7 +825,7 @@ app.post('/:version/tic-tac-toe/fetch', (req, res) => {
797
825
 
798
826
  // Generate Token
799
827
  app.post('/:version/token', (req, res) => {
800
- let { len, type } = req.body;
828
+ let { len, type } = req.body || {};
801
829
 
802
830
  len = parseInt(len || 24, 10);
803
831
  type = type ? type.toLowerCase() : 'alpha';
@@ -812,7 +840,7 @@ app.post('/:version/token', (req, res) => {
812
840
  } catch (err) {
813
841
  return res.jsonResponse({
814
842
  error: err.message,
815
- documentation: `https://docs.sylvain.pro/${req.version}/en/token`
843
+ documentation: `${documentation}/${req.version}/en/token`
816
844
  });
817
845
  }
818
846
  });
@@ -21,12 +21,12 @@ export default async function hyperplanning(url, detail) {
21
21
  .getAllSubcomponents('vevent')
22
22
  .map(e => {
23
23
  const evt = new ical.Event(e);
24
- const summary = evt.summary.split(' ').filter(part => part !== '-');
24
+ const summary = (evt.summary || '').split(' ').filter(part => part !== '-');
25
25
  const start = formatDate(evt.startDate.toJSDate());
26
26
  const end = formatDate(evt.endDate.toJSDate());
27
27
 
28
28
  if (detail === 'full') {
29
- const desc = evt.description.split('\n').map(l => l.trim());
29
+ const desc = (evt.description || '').split('\n').map(l => l.trim());
30
30
  const extract = (p) => (desc.find(l => l.startsWith(p)) || '').replace(p, '').trim();
31
31
 
32
32
  return {
@@ -42,7 +42,11 @@ export default async function hyperplanning(url, detail) {
42
42
 
43
43
  if (detail === 'list') return { summary, start, end };
44
44
 
45
- return { summary: evt.summary, start, end };
45
+ return {
46
+ summary: evt.summary || '',
47
+ start,
48
+ end
49
+ };
46
50
  })
47
51
  .sort((a, b) => new Date(a.start) - new Date(b.start))
48
52
  .filter(e => new Date(e.end) >= new Date());
@@ -3,7 +3,7 @@ import { randomBytes } from 'crypto';
3
3
  /**
4
4
  * Manages a Tic-Tac-Toe game session
5
5
  *
6
- * @param {string} action - The action to perform ('play' or 'fetch')
6
+ * @param {string} action - The action to perform ('play', 'fetch', or 'list')
7
7
  * @param {Object} params - The parameters for the action
8
8
  * @returns {Object} - The result of the action
9
9
  * @throws {Error} - If parameters are invalid
@@ -20,6 +20,9 @@ export default function tic_tac_toe(action, params = {}) {
20
20
  // Reference the storage properties
21
21
  const { games, sessions, rateLimits } = storage;
22
22
 
23
+ // List public games
24
+ if (action === 'list') return listGames(games);
25
+
23
26
  // Input validation for common parameters
24
27
  if (!params.username) throw new Error('Please provide a username');
25
28
 
@@ -40,7 +43,7 @@ export default function tic_tac_toe(action, params = {}) {
40
43
  } else if (action === 'fetch') {
41
44
  return fetchGame(params, games, u);
42
45
  } else {
43
- throw new Error('Invalid action. Use "play" or "fetch"');
46
+ throw new Error('Invalid action. Use "play", "fetch", or "list"');
44
47
  }
45
48
  }
46
49
 
@@ -63,7 +66,12 @@ function playMove(params, games, sessions, u, now) {
63
66
  }
64
67
 
65
68
  // Initialize game if needed
66
- games[game] = games[game] || { moves: [], players: [] };
69
+ games[game] = games[game] || {
70
+ moves: [],
71
+ players: [],
72
+ private: params.private || false,
73
+ creation: Date.now()
74
+ };
67
75
  const moves = games[game].moves;
68
76
 
69
77
  // Check if game is full
@@ -112,11 +120,12 @@ function playMove(params, games, sessions, u, now) {
112
120
  */
113
121
  function fetchGame(params, games, u) {
114
122
  const id = params.game || generateGameId();
115
-
116
123
  if (!games[id]) {
117
124
  games[id] = {
118
125
  moves: [],
119
- players: []
126
+ players: [],
127
+ private: params.private || false,
128
+ creation: Date.now()
120
129
  };
121
130
  }
122
131
  if (!games[id].players.includes(u)) {
@@ -125,6 +134,7 @@ function fetchGame(params, games, u) {
125
134
 
126
135
  const moves = games[id].moves;
127
136
  const players = games[id].players;
137
+ const privateGame = games[id].private;
128
138
  const lastPlayer = moves.length ? moves[moves.length - 1].username : null;
129
139
  const turn = players.find(p => p !== lastPlayer) || players[0];
130
140
  const status = players.length >= 2 ? 'ready' : 'waiting';
@@ -136,10 +146,53 @@ function fetchGame(params, games, u) {
136
146
  players,
137
147
  turn,
138
148
  status,
149
+ private: privateGame,
139
150
  ...result
140
151
  };
141
152
  }
142
153
 
154
+ /**
155
+ * List all public games in progress
156
+ */
157
+ function listGames(games) {
158
+ const publicGames = [];
159
+ const now = Date.now();
160
+
161
+ for (const [gameId, game] of Object.entries(games)) {
162
+ // Only include public games
163
+ if (!game.private) {
164
+ const result = game.moves.length ? checkGame(game.moves) : {};
165
+ const isFinished = result.winner || result.tie;
166
+
167
+ // Only include active games (not finished)
168
+ if (!isFinished) {
169
+ const lastPlayer = game.moves.length ? game.moves[game.moves.length - 1].username : null;
170
+ const turn = game.players.find(p => p !== lastPlayer) || game.players[0];
171
+ const status = game.players.length >= 2 ? 'ready' : 'waiting';
172
+
173
+ publicGames.push({
174
+ id: gameId,
175
+ players: game.players,
176
+ playersCount: game.players.length,
177
+ moves: game.moves.length,
178
+ turn,
179
+ status,
180
+ creation: game.creation || now
181
+ });
182
+ }
183
+ }
184
+ }
185
+
186
+ // Sort by creation time (newest first)
187
+ publicGames.sort((a, b) => b.creation - a.creation);
188
+
189
+ return {
190
+ message: 'Public games available',
191
+ count: publicGames.length,
192
+ games: publicGames
193
+ };
194
+ }
195
+
143
196
  /**
144
197
  * Generate a random game ID
145
198
  */
@@ -42,6 +42,17 @@ export function formatDate(date) {
42
42
  return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().replace('Z', '');
43
43
  }
44
44
 
45
+ /**
46
+ * Parse a space-separated env variable into an array, or return null.
47
+ *
48
+ * @param {string} key - The environment variable name.
49
+ * @returns {string[]|null} - The parsed array or null.
50
+ */
51
+ export function envList(key) {
52
+ const v = process.env[key];
53
+ return v && v !== 'undefined' ? v.split(' ') : null;
54
+ }
55
+
45
56
  /**
46
57
  * Manage rate limiting for users
47
58
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "3.4.4",
2
+ "version": "3.4.6",
3
3
  "name": "@20syldev/api",
4
- "description": "Node.js API with multiple features. Check the documentation at https://docs.sylvain.pro",
4
+ "description": "Node.js API with multiple features. Check the documentation at https://docs.sylvain.sh",
5
5
  "main": "app.js",
6
6
  "type": "module",
7
7
  "scripts": {
@@ -49,5 +49,5 @@
49
49
  "bugs": {
50
50
  "url": "https://github.com/20syldev/api/issues"
51
51
  },
52
- "homepage": "https://api.sylvain.pro"
52
+ "homepage": "https://api.sylvain.sh"
53
53
  }