@20syldev/api 3.0.0 → 3.2.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 +2 -2
  2. package/app.js +65 -6
  3. package/package.json +4 -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:-v3.0.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.2.0-6479ee?logo=api.sylvain.pro&labelColor=23272A)](https://github.com/20syldev/api/releases/latest)
6
6
  </div>
7
7
 
8
8
  ---
@@ -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@3.0.0 build
47
+ > @20syldev/api@3.2.0 build
48
48
  > npm install && node app.js
49
49
 
50
50
  [...]
package/app.js CHANGED
@@ -2,6 +2,7 @@ import cors from 'cors';
2
2
  import dotenv from 'dotenv';
3
3
  import express from 'express';
4
4
  import fetch from 'node-fetch';
5
+ import ical from 'ical.js';
5
6
  import { createCanvas } from 'canvas';
6
7
  import { randomBytes, getHashes, createHash } from 'crypto';
7
8
  import { urlencoded, json } from 'express';
@@ -20,10 +21,11 @@ const versions = ['v1', 'v2', 'v3'];
20
21
  const endpoints = {
21
22
  v1: ['algorithms', 'captcha', 'color', 'convert', 'domain', 'infos', 'personal', 'qrcode', 'token', 'username', 'website'],
22
23
  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']
24
+ v3: ['algorithms', 'captcha', 'chat', 'color', 'convert', 'date', 'domain', 'hash', 'hyperplanning', 'infos', 'levenshtein', 'personal', 'qrcode', 'tic-tac-toe', 'time', 'token', 'username', 'website']
24
25
  };
25
26
 
26
- // Arrowed functions (math & random)
27
+ // Arrowed functions (formatting, math & random)
28
+ const formatDate = d => new Date(d.getTime() - d.getTimezoneOffset() * 60000).toISOString().replace('Z', '');
27
29
  const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
28
30
  const genID = () => {
29
31
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
@@ -134,7 +136,7 @@ app.use((req, res, next) => {
134
136
  // Internal Server Error
135
137
  app.use((err, req, res, next) => {
136
138
  console.error(err.stack);
137
- res.status(500).json({
139
+ res.status(500).jsonResponse({
138
140
  message: 'Internal Server Error',
139
141
  error: err.message,
140
142
  documentation: 'https://docs.sylvain.pro',
@@ -199,6 +201,7 @@ app.get('/', (req, res) => {
199
201
  app.get('/v1', (req, res) => {
200
202
  res.jsonResponse({
201
203
  version: 'v1',
204
+ documentation: 'https://docs.sylvain.pro/v1',
202
205
  endpoints: {
203
206
  get: {
204
207
  algorithm: '/v1/algorithms?method={algorithm}&value={value}(&value2={value2})',
@@ -222,6 +225,7 @@ app.get('/v1', (req, res) => {
222
225
  app.get('/v2', (req, res) => {
223
226
  res.jsonResponse({
224
227
  version: 'v2',
228
+ documentation: 'https://docs.sylvain.pro/v2',
225
229
  endpoints: {
226
230
  get: {
227
231
  algorithm: '/v2/algorithms?method={algorithm}&value={value}(&value2={value2})',
@@ -255,6 +259,7 @@ app.get('/v2', (req, res) => {
255
259
  app.get('/v3', (req, res) => {
256
260
  res.jsonResponse({
257
261
  version: 'v3',
262
+ documentation: 'https://docs.sylvain.pro/v3',
258
263
  endpoints: {
259
264
  get: {
260
265
  algorithm: '/v3/algorithms?method={algorithm}&value={value}(&value2={value2})',
@@ -276,6 +281,7 @@ app.get('/v3', (req, res) => {
276
281
  private: '/v3/chat/private'
277
282
  },
278
283
  hash: '/v3/hash',
284
+ hyperplanning: '/v3/hyperplanning',
279
285
  tic_tac_toe: {
280
286
  tic_tac_toe: '/v3/tic-tac-toe',
281
287
  fetch: '/v3/tic-tac-toe/fetch'
@@ -527,6 +533,11 @@ app.get('/:version/domain', (req, res) => {
527
533
  });
528
534
  });
529
535
 
536
+ // GET planning error
537
+ app.get('/:version/hyperplanning', (req, res) => {
538
+ res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
539
+ });
540
+
530
541
  // GET hash error
531
542
  app.get('/:version/hash', (req, res) => {
532
543
  res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
@@ -547,8 +558,11 @@ app.get('/:version/infos', (req, res) => {
547
558
  app.get('/:version/levenshtein', (req, res) => {
548
559
  const { str1, str2 } = req.query;
549
560
 
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})' });
561
+ if (!str1 || typeof str1 !== 'string') return res.jsonResponse({ error: 'Please provide a first string (?str1={string})' });
562
+ if (!str2 || typeof str2 !== 'string') return res.jsonResponse({ error: 'Please provide a second string (&str2={string})' });
563
+
564
+ if (str1.length > 1000) return res.jsonResponse({ error: 'First string exceeds 1000 characters.' });
565
+ if (str2.length > 1000) return res.jsonResponse({ error: 'Second string exceeds 1000 characters.' });
552
566
 
553
567
  const lev = (a, b) => {
554
568
  const m = Array.from({ length: a.length + 1 }, (_, i) => [i]);
@@ -834,6 +848,7 @@ app.get('/:version/website', async (req, res) => {
834
848
  res.jsonResponse({
835
849
  versions: {
836
850
  api: process.env.API,
851
+ cdn: process.env.CDN,
837
852
  coop_api: process.env.COOP_API,
838
853
  coop_status: process.env.COOP_STATUS,
839
854
  chat: process.env.CHAT,
@@ -846,6 +861,7 @@ app.get('/:version/website', async (req, res) => {
846
861
  gemsync: process.env.GEMSYNC,
847
862
  gitsite: process.env.GITSITE,
848
863
  logs: process.env.LOGS,
864
+ minify: process.env.MINIFY,
849
865
  morpion: process.env.MORPION,
850
866
  nitrogen: process.env.NITROGEN,
851
867
  old_database: process.env.OLD_DATABASE,
@@ -935,6 +951,49 @@ app.post('/:version/chat/private', (req, res) => {
935
951
  return res.jsonResponse({ error: 'Invalid or expired token.' });
936
952
  });
937
953
 
954
+ // Display a planning from an ICS file
955
+ app.post('/:version/hyperplanning', async (req, res) => {
956
+ const { url, detail } = req.body;
957
+
958
+ if (!url) res.jsonResponse({ error: 'Please provide a valid ICS file URL.' });
959
+
960
+ try {
961
+ const response = await fetch(url);
962
+ if (!response.ok || !(response.headers.get('content-type') || '').includes('text/calendar')) return res.jsonResponse({ error: 'Invalid ICS file format.' });
963
+
964
+ const events = new ical.Component(ical.parse(await response.text()))
965
+ .getAllSubcomponents('vevent')
966
+ .map(e => {
967
+ const evt = new ical.Event(e);
968
+ const summary = evt.summary.split(' ').filter(part => part !== '-');
969
+ const start = formatDate(evt.startDate.toJSDate());
970
+ const end = formatDate(evt.endDate.toJSDate());
971
+
972
+ if (detail === 'full') {
973
+ const desc = evt.description.split('\n').map(l => l.trim());
974
+ const extract = (p) => (desc.find(l => l.startsWith(p)) || '').replace(p, '').trim();
975
+
976
+ return {
977
+ summary,
978
+ subject: extract('Matière :'),
979
+ teacher: extract('Enseignant :'),
980
+ classes: extract('Promotions :').split(', ').map(c => c.trim()),
981
+ type: extract('Salle :') || undefined,
982
+ start,
983
+ end
984
+ };
985
+ }
986
+ if (detail === 'list') return { summary, start, end };
987
+
988
+ return { summary: evt.summary, start, end };
989
+ })
990
+ .sort((a, b) => new Date(a.start) - new Date(b.start))
991
+ .filter(e => new Date(e.start) >= new Date());
992
+
993
+ res.jsonResponse(events);
994
+ } catch { res.jsonResponse({ error: 'Failed to parse ICS file.' }); }
995
+ });
996
+
938
997
  // Store tic tac toe games
939
998
  app.post('/:version/tic-tac-toe', (req, res) => {
940
999
  const { username, move, session, game } = req.body;
@@ -972,7 +1031,7 @@ app.post('/:version/tic-tac-toe', (req, res) => {
972
1031
  if (result.winner || result.tie) {
973
1032
  setTimeout(() => delete games[game], 600000);
974
1033
  return res.jsonResponse({
975
- message: `Move sent successfully. ${result.winner ? result.winner + " wins. " + result.loser + " loses." : "It's a tie."}`,
1034
+ message: `Move sent successfully. ${result.winner ? result.winner + ' wins. ' + result.loser + ' loses.' : 'It\'s a tie.'}`,
976
1035
  ...result
977
1036
  });
978
1037
  }
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
- "version": "3.0.0",
2
+ "version": "3.2.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",
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "start": "node app.js",
9
+ "dev": "nodemon app.js",
9
10
  "build": "npm install && node app.js",
11
+ "lint": "prettier --write .",
10
12
  "upgrade:minor": "npm upgrade",
11
13
  "upgrade:major": "npx npm-check-updates -u && npm install",
12
14
  "upgrade:build": "npm upgrade && npm install && node app.js"
@@ -16,6 +18,7 @@
16
18
  "cors": "latest",
17
19
  "dotenv": "latest",
18
20
  "express": "latest",
21
+ "ical.js": "latest",
19
22
  "mathjs": "latest",
20
23
  "node-fetch": "latest",
21
24
  "prettier": "latest",