@20syldev/api 3.4.5 → 3.4.7

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/app.js CHANGED
@@ -1,842 +1,850 @@
1
- // Import modules
2
- import * as apiv3 from './modules/v3.js';
3
-
4
- // Import dependencies
5
- import cors from 'cors';
6
- import dotenv from 'dotenv';
7
- import express from 'express';
8
- import fetch from 'node-fetch';
9
- import { urlencoded, json } from 'express';
10
- import { dirname, join } from 'path';
11
- import { fileURLToPath } from 'url';
12
-
13
- const __filename = fileURLToPath(import.meta.url);
14
- const __dirname = dirname(__filename);
15
- const app = express();
16
-
17
- // Define allowed versions & endpoints for each version
18
- const v1 = {
19
- get: [
20
- { name: 'algorithms', path: '/algorithms?method={algorithm}&value={value}(&value2={value2})' },
21
- { name: 'captcha', path: '/captcha?text={text}' },
22
- { name: 'color', path: '/color' },
23
- { name: 'convert', path: '/convert?value={value}&from={unit}&to={unit}' },
24
- { name: 'domain', path: '/domain' },
25
- { name: 'infos', path: '/infos' },
26
- { name: 'personal', path: '/personal' },
27
- { name: 'qrcode', path: '/qrcode?url={URL}' },
28
- { name: 'username', path: '/username' },
29
- { name: 'website', path: '/website' }
30
- ],
31
- post: [
32
- { name: 'token', path: '/token' }
33
- ]
34
- };
35
- const v2 = {
36
- get: [
37
- ...v1.get,
38
- { name: 'chat', path: '/chat' }
39
- ],
40
- post: [
41
- ...v1.post,
42
- {
43
- name: 'chat',
44
- children: {
45
- chat: '/chat',
46
- private: '/chat/private'
47
- }
48
- },
49
- { name: 'hash', path: '/hash' },
50
- {
51
- name: 'tic_tac_toe',
52
- children: {
53
- tic_tac_toe: '/tic-tac-toe',
54
- fetch: '/tic-tac-toe/fetch',
55
- list: '/tic-tac-toe/list'
56
- }
57
- },
58
- { name: 'token', path: '/token' }
59
- ]
60
- };
61
- const v3 = {
62
- get: [
63
- ...v2.get,
64
- { name: 'levenshtein', path: '/levenshtein?str1={string}&str2={string}' },
65
- { name: 'time', path: '/time(?type={type}&start={timestamp}&end={timestamp}&format={format}&timezone={timezone})' },
66
- ],
67
- post: [
68
- ...v2.post,
69
- { name: 'hyperplanning', path: '/hyperplanning' }
70
- ]
71
- };
72
- const versions = {
73
- v1: {
74
- endpoints: v1,
75
- modules: apiv3
76
- },
77
- v2: {
78
- endpoints: v2,
79
- modules: apiv3
80
- },
81
- v3: {
82
- endpoints: v3,
83
- modules: apiv3
84
- }
85
- };
86
-
87
- // API storage
88
- const logs = [], ipLimits = {};
89
-
90
- // Chat storage
91
- const chatStorage = {
92
- messages: [],
93
- privateChats: {},
94
- sessions: {},
95
- rateLimits: {}
96
- };
97
-
98
- // Tic-Tac-Toe storage
99
- const ticTacToeStorage = {
100
- games: {},
101
- sessions: {},
102
- rateLimits: {}
103
- };
104
-
105
- // Define global variables
106
- let contributions, lastFetch = 0, requests = 0, requestLimit, resetTime = Date.now() + 3600000;
107
-
108
- // ----------- ----------- MIDDLEWARES SETUP ----------- ----------- //
109
-
110
- dotenv.config();
111
-
112
- // CORS & Express setup
113
- app.set('trust proxy', 1);
114
- app.use(cors({ methods: ['GET', 'POST'] }));
115
- app.use(urlencoded({ extended: true }));
116
- app.use(json());
117
-
118
- // Set favicon for API
119
- app.use('/favicon.ico', express.static(join(__dirname, 'src', 'favicon.ico')));
120
-
121
- // Display robots.txt
122
- app.use('/robots.txt', express.static(join(__dirname, 'robots.txt')));
123
-
124
- // Return formatted JSON
125
- app.use((req, res, next) => {
126
- res.setHeader('Content-Type', 'application/json');
127
- res.jsonResponse = (data) => {
128
- res.send(JSON.stringify(data, null, 2));
129
- };
130
- next();
131
- });
132
-
133
- // Too many requests
134
- app.use((req, res, next) => {
135
- const ip = req.headers['cf-connecting-ip'] || req.socket.remoteAddress;
136
- const token = req.headers.authorization?.split(' ')[1] || '';
137
-
138
- const now = Date.now();
139
- const minute = Math.floor(now / 60000) % 60;
140
- const hour = Math.floor(now / 3600000) % 24;
141
-
142
- const business = process.env.BUSINESS_TOKEN_LIST?.split(' ') || [];
143
- const pro = process.env.PRO_TOKEN_LIST?.split(' ') || [];
144
- const advanced = process.env.ADVANCED_TOKEN_LIST?.split(' ') || [];
145
-
146
- if (token && ![...business, ...pro, ...advanced].includes(token) || token === 'undefined') {
147
- return res.status(401).jsonResponse({
148
- message: 'Unauthorized',
149
- error: 'Invalid token.',
150
- status: '401'
151
- });
152
- }
153
-
154
- if (business.includes(token) && !business.includes('undefined')) requestLimit = process.env.BUSINESS_LIMIT;
155
- else if (pro.includes(token) && !pro.includes('undefined')) requestLimit = process.env.PRO_LIMIT;
156
- else if (advanced.includes(token) && !advanced.includes('undefined')) requestLimit = process.env.ADVANCED_LIMIT;
157
- else requestLimit = process.env.DEFAULT_LIMIT;
158
-
159
- if (now > resetTime) requests = 0, resetTime = now + 3600000;
160
- if (++requests > Math.max(process.env.GLOBAL_LIMIT, requestLimit)) {
161
- return res.status(429).jsonResponse({ message: 'Too Many Requests' });
162
- }
163
-
164
- if (['__proto__', 'constructor', 'prototype'].includes(ip)) {
165
- return res.status(400).jsonResponse({ message: 'Invalid IP address' });
166
- }
167
-
168
- if (!ipLimits[ip]) ipLimits[ip] = {};
169
- if (!ipLimits[ip][hour]) ipLimits[ip][hour] = {};
170
- ipLimits[ip][hour][minute] = (ipLimits[ip][hour][minute] || 0) + 1;
171
-
172
- if (ipLimits[ip][hour][minute] > requestLimit) {
173
- return res.status(429).jsonResponse({
174
- message: 'Too Many Requests (IP limited), authenticate to increase the limit',
175
- error: `You have exceeded the limit of ${requestLimit} requests per hour.`,
176
- reset: `Reset in ${((resetTime - now) / 60000).toFixed(0)} minutes.`,
177
- status: '429'
178
- });
179
- }
180
-
181
- Object.keys(ipLimits[ip]).forEach(h => { if (h != hour) delete ipLimits[ip][h]; });
182
-
183
- next();
184
- });
185
-
186
- // Save and send logs
187
- app.use((req, res, next) => {
188
- if (req.method === 'HEAD') return next();
189
- if (req.originalUrl === '/logs') return next();
190
-
191
- const ip = req.headers['cf-connecting-ip'] || req.socket.remoteAddress;
192
-
193
- const startTime = Date.now();
194
- const timestamp = new Date().toISOString();
195
- const method = req.method;
196
- const url = req.originalUrl;
197
- const platform = req.headers['sec-ch-ua-platform']?.replace(/"/g, '');
198
-
199
- res.on('finish', () => {
200
- const status = res.statusCode === 304 ? 200 : res.statusCode;
201
- const duration = `${Date.now() - startTime}ms`;
202
-
203
- logs.push({ timestamp, method, url, status, duration, platform });
204
- console.log(`[${new Date().toISOString()}] ${method} ${url} ${res.statusCode} - ${duration} - ${ip}`);
205
- if (logs.length > 1000) logs.shift();
206
- });
207
- next();
208
- });
209
-
210
- // Internal Server Error
211
- app.use((err, req, res, next) => {
212
- console.error(err.stack);
213
- res.status(500).jsonResponse({
214
- message: 'Internal Server Error',
215
- error: err.message,
216
- documentation: 'https://docs.sylvain.pro',
217
- status: '500'
218
- });
219
- });
220
-
221
- // Check if version exists
222
- app.use('/:version', (req, res, next) => {
223
- const { version } = req.params;
224
- const latest = Object.keys(versions).pop();
225
- const endpoint = req.originalUrl.split('/').slice(2).join('/');
226
-
227
- req.version = version;
228
- req.latest = latest;
229
-
230
- if (['latest', 'fr', 'en'].includes(version)) {
231
- return res.redirect(endpoint ? `/${latest}/${endpoint}` : `/${latest}`);
232
- }
233
-
234
- if (version === 'logs') return res.jsonResponse(logs);
235
-
236
- if (!versions[version] && version !== 'logs') {
237
- return res.status(404).jsonResponse({
238
- message: 'Not Found',
239
- error: `Invalid API version (${version}).`,
240
- documentation: `https://docs.sylvain.pro/${latest}`,
241
- status: '404'
242
- });
243
- }
244
-
245
- req.module = versions[version].modules;
246
-
247
- if (!req.module) {
248
- return res.status(404).jsonResponse({
249
- message: 'Not Found',
250
- error: `Module not found for version ${version}.`,
251
- documentation: `https://docs.sylvain.pro/${latest}`,
252
- status: '404'
253
- });
254
- }
255
-
256
- next();
257
- });
258
-
259
- // Check if endpoint exists
260
- app.use('/:version/:endpoint', (req, res, next) => {
261
- const { version, endpoint } = req.params;
262
-
263
- req.version = version;
264
- req.endpoint = endpoint;
265
-
266
- if (!req.endpoint) {
267
- return res.status(404).jsonResponse({
268
- message: 'Not Found',
269
- error: `Endpoint '${endpoint}' does not exist in ${req.version}.`,
270
- documentation: `https://docs.sylvain.pro/${versions[version.length - 1]}`,
271
- status: '404'
272
- });
273
- }
274
- next();
275
- });
276
-
277
- // ----------- ----------- MAIN ENDPOINTS ----------- ----------- //
278
-
279
- // Main route
280
- app.get('/', (req, res) => {
281
- const base = `${req.protocol}://${req.get('host')}`;
282
- const links = Object.keys(versions).reduce((link, version) => {
283
- link[version] = `${base}/${version}`;
284
- return link;
285
- }, {});
286
-
287
- res.jsonResponse({
288
- documentation: 'https://docs.sylvain.pro',
289
- latest: `${base}/latest`,
290
- logs: `${base}/logs`,
291
- versions: links
292
- });
293
- });
294
-
295
- // Display version information
296
- app.get('/:version', (req, res) => {
297
- const { version } = req.params;
298
-
299
- if (!versions[version]) {
300
- return res.status(404).jsonResponse({
301
- message: 'Not Found',
302
- error: `Invalid API version (${version}).`,
303
- documentation: `https://docs.sylvain.pro/${Object.keys(versions).pop()}`,
304
- status: '404'
305
- });
306
- }
307
-
308
- const endpoints = Object.keys(versions[version].endpoints).reduce((acc, method) => {
309
- acc[method] = versions[version].endpoints[method]
310
- .filter(({ name }) => name !== 'website')
311
- .sort((a, b) => a.name.localeCompare(b.name))
312
- .reduce((group, endpoint) => {
313
- if (endpoint.children) {
314
- group[endpoint.name] = Object.keys(endpoint.children)
315
- .sort((a, b) => a.localeCompare(b))
316
- .reduce((childGroup, childName) => {
317
- childGroup[childName] = `/${version}${endpoint.children[childName]}`;
318
- return childGroup;
319
- }, {});
320
- } else {
321
- group[endpoint.name] = `/${version}${endpoint.path}`;
322
- }
323
- return group;
324
- }, {});
325
- return acc;
326
- }, {});
327
-
328
- res.jsonResponse({
329
- version,
330
- documentation: `https://docs.sylvain.pro/${version}`,
331
- endpoints
332
- });
333
- });
334
-
335
- // ----------- ----------- GET ENDPOINTS ----------- ----------- //
336
-
337
- // Algorithms
338
- app.get('/:version/algorithms', (req, res) => {
339
- const { method, value, value2 } = req.query;
340
- const { version } = req.params;
341
-
342
- if (!req.module.algorithms || !req.module.algorithms[method]) {
343
- return res.jsonResponse({
344
- error: 'Please provide a valid algorithm (?method={algorithm})',
345
- documentation: `https://docs.sylvain.pro/${version}/en/algorithms`
346
- });
347
- }
348
-
349
- try {
350
- const answer = req.module.algorithms[method](value, value2);
351
- res.jsonResponse({ answer });
352
- } catch (err) {
353
- res.jsonResponse({
354
- error: err.message,
355
- documentation: `https://docs.sylvain.pro/${version}/en/algorithms`
356
- });
357
- }
358
- });
359
-
360
- // Generate captcha
361
- app.get('/:version/captcha', (req, res) => {
362
- const text = req.query.text;
363
-
364
- if (!text) return res.jsonResponse({ error: 'Please provide a valid argument (?text={text})' });
365
-
366
- try {
367
- const result = req.module.captcha(text);
368
- res.type('png').send(result);
369
- } catch (err) {
370
- res.jsonResponse({
371
- error: err.message,
372
- documentation: `https://docs.sylvain.pro/${req.version}/en/captcha`
373
- });
374
- }
375
- });
376
-
377
- // Display stored data
378
- app.get('/:version/chat', (req, res) => {
379
- try {
380
- const messages = req.module.chat('fetch', {
381
- username: 'system',
382
- storage: chatStorage
383
- });
384
- res.jsonResponse(messages);
385
- } catch (err) {
386
- res.jsonResponse({ error: err.message });
387
- }
388
- });
389
-
390
- // GET private chat error
391
- app.get('/:version/chat/private', (req, res) => {
392
- res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
393
- });
394
-
395
- // Generate color
396
- app.get('/:version/color', (req, res) => {
397
- try {
398
- const result = req.module.color();
399
- res.jsonResponse(result);
400
- } catch (err) {
401
- res.jsonResponse({
402
- error: err.message,
403
- documentation: `https://docs.sylvain.pro/${req.version}/en/color`
404
- });
405
- }
406
- });
407
-
408
- // Convert units
409
- app.get('/:version/convert', (req, res) => {
410
- const { value, from, to } = req.query;
411
-
412
- if (!value || isNaN(value)) return res.jsonResponse({ error: 'Please provide a valid value (?value={value})' });
413
- if (!from) return res.jsonResponse({ error: 'Please provide a valid source unit (&from={unit})' });
414
- if (!to) return res.jsonResponse({ error: 'Please provide a valid target unit (&to={unit})' });
415
-
416
- try {
417
- const result = req.module.convert(value, from, to);
418
- res.jsonResponse(result);
419
- } catch (err) {
420
- res.jsonResponse({
421
- error: err.message,
422
- documentation: `https://docs.sylvain.pro/${req.version}/en/convert`
423
- });
424
- }
425
- });
426
-
427
- // Generate domain informations
428
- app.get('/:version/domain', (req, res) => {
429
- try {
430
- const result = req.module.domain();
431
- res.jsonResponse(result);
432
- } catch (err) {
433
- res.jsonResponse({
434
- error: err.message,
435
- documentation: `https://docs.sylvain.pro/${req.version}/en/domain`
436
- });
437
- }
438
- });
439
-
440
- // GET planning error
441
- app.get('/:version/hyperplanning', (req, res) => {
442
- res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
443
- });
444
-
445
- // GET hash error
446
- app.get('/:version/hash', (req, res) => {
447
- res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
448
- });
449
-
450
- // Display API informations
451
- app.get('/:version/infos', (req, res) => {
452
- const endpoints = Object.values(versions[req.version].endpoints).flat();
453
-
454
- const paths = endpoints.flatMap(e => e.children
455
- ? Object.values(e.children)
456
- : (e.path ? [e.path] : []));
457
-
458
- res.jsonResponse({
459
- endpoints: new Set(paths).size,
460
- last_version: Object.keys(versions).pop(),
461
- documentation: 'https://docs.sylvain.pro',
462
- github: 'https://github.com/20syldev/api',
463
- creation: 'November 25th 2024',
464
- });
465
- });
466
-
467
- // Calculate Levenshtein distance
468
- app.get('/:version/levenshtein', (req, res) => {
469
- const { str1, str2 } = req.query;
470
-
471
- if (!str1 || typeof str1 !== 'string') return res.jsonResponse({ error: 'Please provide a first string (?str1={string})' });
472
- if (!str2 || typeof str2 !== 'string') return res.jsonResponse({ error: 'Please provide a second string (&str2={string})' });
473
-
474
- try {
475
- const result = req.module.levenshtein(str1, str2);
476
- res.jsonResponse(result);
477
- } catch (err) {
478
- res.jsonResponse({
479
- error: err.message,
480
- documentation: `https://docs.sylvain.pro/${req.version}/en/levenshtein`
481
- });
482
- }
483
- });
484
-
485
- // Generate personal data
486
- app.get('/:version/personal', (req, res) => {
487
- try {
488
- const result = req.module.personal();
489
- res.jsonResponse(result);
490
- } catch (err) {
491
- res.jsonResponse({
492
- error: err.message,
493
- documentation: `https://docs.sylvain.pro/${req.version}/en/personal`
494
- });
495
- }
496
- });
497
-
498
- // Generate QR Code
499
- app.get('/:version/qrcode', async (req, res) => {
500
- const { url } = req.query;
501
-
502
- if (!url) return res.jsonResponse({ error: 'Please provide a valid url (?url={URL})' });
503
-
504
- try {
505
- const result = await req.module.qrcode(url);
506
- res.jsonResponse(result);
507
- } catch (err) {
508
- res.jsonResponse({
509
- error: err.message,
510
- documentation: `https://docs.sylvain.pro/${req.version}/en/qrcode`
511
- });
512
- }
513
- });
514
-
515
- // GET tic-tac-toe game error
516
- app.get('/:version/tic-tac-toe', (req, res) => {
517
- res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
518
- });
519
-
520
- // GET tic-tac-toe fetch error
521
- app.get('/:version/tic-tac-toe/fetch', (req, res) => {
522
- res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
523
- });
524
-
525
- // GET tic-tac-toe list error
526
- app.get('/:version/tic-tac-toe/list', (req, res) => {
527
- res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
528
- });
529
-
530
- // Display or generate time informations
531
- app.get('/:version/time', (req, res) => {
532
- const { type = 'live', start, end, format, timezone } = req.query;
533
-
534
- const validFormats = ['iso', 'utc', 'timestamp', 'locale', 'date', 'time', 'year', 'month', 'day', 'hour', 'minute', 'second', 'ms', 'dayOfWeek', 'dayOfYear', 'weekNumber', 'timezone', 'timezoneOffset'];
535
- const validTimezones = ['UTC', 'America/New_York', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'];
536
-
537
- if (type !== 'live' && type !== 'random') return res.jsonResponse({ error: 'Please provide a valid type (?type={type})' });
538
- if (start && !Date.parse(start)) return res.jsonResponse({ error: 'Please provide a valid start date (?start={YYYY-MM-DD})' });
539
- if (end && !Date.parse(end)) return res.jsonResponse({ error: 'Please provide a valid end date (?end={YYYY-MM-DD})' });
540
- if (format && !validFormats.includes(format)) return res.jsonResponse({ error: 'Please provide a valid format (?format={format})' });
541
- if (timezone && !validTimezones.includes(timezone)) return res.jsonResponse({ error: 'Please provide a valid timezone (?timezone={timezone})' });
542
-
543
- try {
544
- const time = req.module.time(type, start, end, format, timezone);
545
- res.jsonResponse(time);
546
- } catch (err) {
547
- res.jsonResponse({
548
- error: err.message,
549
- documentation: `https://docs.sylvain.pro/${req.version}/en/time`
550
- });
551
- }
552
- });
553
-
554
- // GET token error
555
- app.get('/:version/token', (req, res) => {
556
- res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
557
- });
558
-
559
- // Generate username
560
- app.get('/:version/username', (req, res) => {
561
- try {
562
- const result = req.module.username();
563
- res.jsonResponse(result);
564
- } catch (err) {
565
- res.jsonResponse({
566
- error: err.message,
567
- documentation: `https://docs.sylvain.pro/${req.version}/en/username`
568
- });
569
- }
570
- });
571
-
572
- // Display informations for owner's website
573
- app.get('/:version/website', async (req, res) => {
574
- const currentTime = Date.now();
575
-
576
- if (currentTime - lastFetch >= 10 * 60 * 1000) {
577
- try {
578
- const username = '20syldev';
579
- const token = process.env.GITHUB_TOKEN;
580
- const today = new Date().toISOString().split('T')[0];
581
- const monthFirst = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0];
582
- const lastYear = new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString().split('T')[0];
583
-
584
- const query = `
585
- {
586
- user(login: "${username}") {
587
- contributionsCollection(from: "${today}T00:00:00Z") {
588
- contributionCalendar {
589
- totalContributions
590
- }
591
- }
592
- contributions_month: contributionsCollection(from: "${monthFirst}T00:00:00Z") {
593
- contributionCalendar {
594
- totalContributions
595
- }
596
- }
597
- contributions_year: contributionsCollection(from: "${lastYear}T00:00:00Z") {
598
- contributionCalendar {
599
- totalContributions
600
- }
601
- }
602
- }
603
- }`;
604
-
605
- const apiResponse = await fetch('https://api.github.com/graphql', {
606
- method: 'POST',
607
- headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
608
- body: JSON.stringify({ query })
609
- });
610
-
611
- if (!apiResponse.ok) throw new Error('Error fetching data.');
612
-
613
- const data = await apiResponse.json();
614
- const user = data?.data?.user;
615
-
616
- contributions = {
617
- today: user?.contributionsCollection?.contributionCalendar?.totalContributions || 0,
618
- month: user?.contributions_month?.contributionCalendar?.totalContributions || 0,
619
- year: user?.contributions_year?.contributionCalendar?.totalContributions || 0,
620
- };
621
-
622
- lastFetch = currentTime;
623
- } catch { contributions = { today: 0, month: 0, year: 0 }; }
624
- }
625
-
626
- res.jsonResponse({
627
- versions: {
628
- api: process.env.API,
629
- cdn: process.env.CDN,
630
- coop_api: process.env.COOP_API,
631
- coop_status: process.env.COOP_STATUS,
632
- chat: process.env.CHAT,
633
- digit: process.env.DIGIT,
634
- doc_coopbot: process.env.DOC_COOPBOT,
635
- docs: process.env.DOCS,
636
- donut: process.env.DONUT,
637
- drawio_plugin: process.env.DRAWIO_PLUGIN,
638
- flowers: process.env.FLOWERS,
639
- g_2048: process.env.G_2048,
640
- gemsync: process.env.GEMSYNC,
641
- gitsite: process.env.GITSITE,
642
- lebonchar: process.env.LEBONCHAR,
643
- logs: process.env.LOGS,
644
- logvault: process.env.LOGVAULT,
645
- lyah: process.env.LYAH,
646
- minify: process.env.MINIFY,
647
- monitoring: process.env.MONITORING,
648
- morpion: process.env.MORPION,
649
- nitrogen: process.env.NITROGEN,
650
- old_database: process.env.OLD_DATABASE,
651
- password: process.env.PASSWORD,
652
- php: process.env.PHP,
653
- ping: process.env.PING,
654
- portfolio: process.env.PORTFOLIO,
655
- python_api: process.env.PYTHON_API,
656
- readme: process.env.README,
657
- timestamp: process.env.TIMESTAMP,
658
- terminal: process.env.TERMINAL,
659
- wrkit: process.env.WRKIT,
660
- zpki: process.env.ZPKI
661
- },
662
- patched_projects: process.env.PATCH?.split(' ') || [],
663
- updated_projects: process.env.RECENT?.split(' ') || [],
664
- new_projects: process.env.NEW?.split(' ') || [],
665
- sub_domains: process.env.DOMAINS?.split(' ') || [],
666
- stats: {
667
- 1: process.env.STATS1,
668
- 2: process.env.STATS2,
669
- 3: process.env.STATS3,
670
- 4: process.env.STATS4,
671
- 5: Object.keys(ipLimits).length,
672
- today: contributions.today.toString(),
673
- this_month: contributions.month.toString(),
674
- last_year: contributions.year.toString(),
675
- },
676
- notif_tag: process.env.TAG,
677
- active: process.env.ACTIVE
678
- });
679
- });
680
-
681
- // ----------- ----------- POST ENDPOINTS ----------- ----------- //
682
-
683
- // Store chat messages
684
- app.post('/:version/chat', (req, res) => {
685
- const { username, message, timestamp, session, token } = req.body;
686
-
687
- if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
688
- if (!message) return res.jsonResponse({ error: 'Please provide a message (&message={message})' });
689
- if (!session) return res.jsonResponse({ error: 'Please provide a valid session ID (&session={ID})' });
690
-
691
- try {
692
- const result = req.module.chat('message', {
693
- username,
694
- message,
695
- timestamp,
696
- session,
697
- token,
698
- storage: chatStorage
699
- });
700
- res.jsonResponse(result);
701
- } catch (err) {
702
- res.jsonResponse({ error: err.message });
703
- }
704
- });
705
-
706
- // Display a private chat with a token
707
- app.post('/:version/chat/private', (req, res) => {
708
- const { username, token } = req.body;
709
-
710
- if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
711
- if (!token) return res.jsonResponse({ error: 'Please provide a valid token (&token={key}).' });
712
-
713
- try {
714
- const messages = req.module.chat('private', {
715
- username,
716
- token,
717
- storage: chatStorage
718
- });
719
- res.jsonResponse(messages);
720
- } catch (err) {
721
- res.jsonResponse({ error: err.message });
722
- }
723
- });
724
-
725
- // Generate hash
726
- app.post('/:version/hash', (req, res) => {
727
- const { text, method } = req.body;
728
-
729
- if (!text) return res.jsonResponse({ error: 'Please provide a text (?text={text})' });
730
- if (!method) return res.jsonResponse({
731
- error: 'Please provide a valid hash algorithm (&method={algorithm})',
732
- documentation: `https://docs.sylvain.pro/${req.version}/en/hash`
733
- });
734
-
735
- try {
736
- const result = req.module.hash(text, method);
737
- res.jsonResponse(result);
738
- } catch (err) {
739
- res.jsonResponse({
740
- error: err.message,
741
- documentation: `https://docs.sylvain.pro/${req.version}/en/hash`
742
- })
743
- }
744
- });
745
-
746
- // Display a planning from an ICS file
747
- app.post('/:version/hyperplanning', async (req, res) => {
748
- const { url, detail } = req.body;
749
-
750
- if (!url) return res.jsonResponse({ error: 'Please provide a valid ICS file URL (?url={URL})' });
751
-
752
- try {
753
- const hyperplanning = req.module.hyperplanning(url, detail);
754
- res.jsonResponse(hyperplanning);
755
- } catch (err){
756
- res.jsonResponse({
757
- error: 'Failed to parse ICS file.',
758
- documentation: `https://docs.sylvain.pro/${req.version}/en/hyperplanning`
759
- });
760
- }
761
- });
762
-
763
- // Store tic tac toe games
764
- app.post('/:version/tic-tac-toe', (req, res) => {
765
- const { username, move, session, game } = req.body;
766
-
767
- if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
768
- if (!move) return res.jsonResponse({ error: 'Please provide a valid move (&move={move})' });
769
- if (!session) return res.jsonResponse({ error: 'Please provide a valid session ID (&session={ID})' });
770
- if (!game) return res.jsonResponse({ error: 'Please provide a valid game ID (&game={ID})' });
771
-
772
- try {
773
- const result = req.module.tic_tac_toe('play', {
774
- username,
775
- move,
776
- session,
777
- game,
778
- storage: ticTacToeStorage
779
- });
780
- res.jsonResponse(result);
781
- } catch (err) {
782
- res.jsonResponse({ error: err.message });
783
- }
784
- });
785
-
786
- // Display a tic tac toe game with a token
787
- app.post('/:version/tic-tac-toe/fetch', (req, res) => {
788
- const { username, game } = req.body;
789
- const privateGame = req.body.private;
790
-
791
- if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
792
-
793
- try {
794
- const result = req.module.tic_tac_toe('fetch', {
795
- username,
796
- game,
797
- private: privateGame,
798
- storage: ticTacToeStorage
799
- });
800
- res.jsonResponse(result);
801
- } catch (err) {
802
- res.jsonResponse({ error: err.message });
803
- }
804
- });
805
-
806
- // List public tic tac toe games
807
- app.post('/:version/tic-tac-toe/list', (req, res) => {
808
- try {
809
- const result = req.module.tic_tac_toe('list', {
810
- storage: ticTacToeStorage
811
- });
812
- res.jsonResponse(result);
813
- } catch (err) {
814
- res.jsonResponse({ error: err.message });
815
- }
816
- });
817
-
818
- // Generate Token
819
- app.post('/:version/token', (req, res) => {
820
- let { len, type } = req.body;
821
-
822
- len = parseInt(len || 24, 10);
823
- type = type ? type.toLowerCase() : 'alpha';
824
-
825
- if (isNaN(len) || len < 0) return res.jsonResponse({ error: 'Invalid number.' });
826
- if (len > 4096) return res.jsonResponse({ error: 'Length cannot exceed 4096.' });
827
- if (len < 12) return res.jsonResponse({ error: 'Length cannot be less than 12.' });
828
-
829
- try {
830
- const token = req.module.token(len, type);
831
- res.jsonResponse({ token });
832
- } catch (err) {
833
- return res.jsonResponse({
834
- error: err.message,
835
- documentation: `https://docs.sylvain.pro/${req.version}/en/token`
836
- });
837
- }
838
- });
839
-
840
- // ----------- ----------- SERVER SETUP ----------- ----------- //
841
-
842
- app.listen(3000, () => console.log('API is running on\n - http://127.0.0.1:3000\n - http://localhost:3000'));
1
+ // Import modules
2
+ import * as apiv3 from './modules/v3.js';
3
+ import { envList } from './modules/v3/utils.js';
4
+
5
+ // Import dependencies
6
+ import cors from 'cors';
7
+ import dotenv from 'dotenv';
8
+ import express from 'express';
9
+ import fetch from 'node-fetch';
10
+ import { urlencoded, json } from 'express';
11
+ import { dirname, join } from 'path';
12
+ import { fileURLToPath } from 'url';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ const app = express();
17
+
18
+ // Define allowed versions & endpoints for each version
19
+ const v1 = {
20
+ get: [
21
+ { name: 'algorithms', path: '/algorithms?method={algorithm}&value={value}(&value2={value2})' },
22
+ { name: 'captcha', path: '/captcha?text={text}' },
23
+ { name: 'color', path: '/color' },
24
+ { name: 'convert', path: '/convert?value={value}&from={unit}&to={unit}' },
25
+ { name: 'domain', path: '/domain' },
26
+ { name: 'infos', path: '/infos' },
27
+ { name: 'personal', path: '/personal' },
28
+ { name: 'qrcode', path: '/qrcode?url={URL}' },
29
+ { name: 'username', path: '/username' },
30
+ { name: 'website', path: '/website' }
31
+ ],
32
+ post: [
33
+ { name: 'token', path: '/token' }
34
+ ]
35
+ };
36
+ const v2 = {
37
+ get: [
38
+ ...v1.get,
39
+ { name: 'chat', path: '/chat' }
40
+ ],
41
+ post: [
42
+ ...v1.post,
43
+ {
44
+ name: 'chat',
45
+ children: {
46
+ chat: '/chat',
47
+ private: '/chat/private'
48
+ }
49
+ },
50
+ { name: 'hash', path: '/hash' },
51
+ {
52
+ name: 'tic_tac_toe',
53
+ children: {
54
+ tic_tac_toe: '/tic-tac-toe',
55
+ fetch: '/tic-tac-toe/fetch',
56
+ list: '/tic-tac-toe/list'
57
+ }
58
+ },
59
+ { name: 'token', path: '/token' }
60
+ ]
61
+ };
62
+ const v3 = {
63
+ get: [
64
+ ...v2.get,
65
+ { name: 'levenshtein', path: '/levenshtein?str1={string}&str2={string}' },
66
+ { name: 'time', path: '/time(?type={type}&start={timestamp}&end={timestamp}&format={format}&timezone={timezone})' },
67
+ ],
68
+ post: [
69
+ ...v2.post,
70
+ { name: 'hyperplanning', path: '/hyperplanning' }
71
+ ]
72
+ };
73
+ const versions = {
74
+ v1: {
75
+ endpoints: v1,
76
+ modules: apiv3
77
+ },
78
+ v2: {
79
+ endpoints: v2,
80
+ modules: apiv3
81
+ },
82
+ v3: {
83
+ endpoints: v3,
84
+ modules: apiv3
85
+ }
86
+ };
87
+
88
+ // API storage
89
+ const logs = [], ipLimits = {};
90
+
91
+ // Chat storage
92
+ const chatStorage = {
93
+ messages: [],
94
+ privateChats: {},
95
+ sessions: {},
96
+ rateLimits: {}
97
+ };
98
+
99
+ // Tic-Tac-Toe storage
100
+ const ticTacToeStorage = {
101
+ games: {},
102
+ sessions: {},
103
+ rateLimits: {}
104
+ };
105
+
106
+ // Define documentation URL
107
+ const documentation = 'https://docs.sylvain.sh';
108
+
109
+ // Define global variables
110
+ let contributions, lastFetch = 0, requests = 0, requestLimit, resetTime = Date.now() + 3600000;
111
+
112
+ // ----------- ----------- MIDDLEWARES SETUP ----------- ----------- //
113
+
114
+ dotenv.config();
115
+
116
+ // CORS & Express setup
117
+ app.set('trust proxy', 1);
118
+ app.use(cors({ methods: ['GET', 'POST'] }));
119
+ app.use(urlencoded({ extended: true }));
120
+ app.use(json());
121
+
122
+ // Set favicon for API
123
+ app.use('/favicon.ico', express.static(join(__dirname, 'src', 'favicon.ico')));
124
+
125
+ // Display robots.txt
126
+ app.use('/robots.txt', express.static(join(__dirname, 'robots.txt')));
127
+
128
+ // Return formatted JSON
129
+ app.use((req, res, next) => {
130
+ res.setHeader('Content-Type', 'application/json');
131
+ res.jsonResponse = (data) => {
132
+ res.send(JSON.stringify(data, null, 2));
133
+ };
134
+ next();
135
+ });
136
+
137
+ // Too many requests
138
+ app.use((req, res, next) => {
139
+ const ip = req.headers['cf-connecting-ip'] || req.socket.remoteAddress;
140
+ const token = req.headers.authorization?.split(' ')[1] || '';
141
+
142
+ const now = Date.now();
143
+ const minute = Math.floor(now / 60000) % 60;
144
+ const hour = Math.floor(now / 3600000) % 24;
145
+
146
+ const business = process.env.BUSINESS_TOKEN_LIST?.split(' ') || [];
147
+ const pro = process.env.PRO_TOKEN_LIST?.split(' ') || [];
148
+ const advanced = process.env.ADVANCED_TOKEN_LIST?.split(' ') || [];
149
+
150
+ if (token && ![...business, ...pro, ...advanced].includes(token) || token === 'undefined') {
151
+ return res.status(401).jsonResponse({
152
+ message: 'Unauthorized',
153
+ error: 'Invalid token.',
154
+ status: '401'
155
+ });
156
+ }
157
+
158
+ if (business.includes(token) && !business.includes('undefined')) requestLimit = process.env.BUSINESS_LIMIT;
159
+ else if (pro.includes(token) && !pro.includes('undefined')) requestLimit = process.env.PRO_LIMIT;
160
+ else if (advanced.includes(token) && !advanced.includes('undefined')) requestLimit = process.env.ADVANCED_LIMIT;
161
+ else requestLimit = process.env.DEFAULT_LIMIT;
162
+
163
+ if (now > resetTime) requests = 0, resetTime = now + 3600000;
164
+ if (++requests > Math.max(process.env.GLOBAL_LIMIT, requestLimit)) {
165
+ return res.status(429).jsonResponse({ message: 'Too Many Requests' });
166
+ }
167
+
168
+ if (['__proto__', 'constructor', 'prototype'].includes(ip)) {
169
+ return res.status(400).jsonResponse({ message: 'Invalid IP address' });
170
+ }
171
+
172
+ if (!ipLimits[ip]) ipLimits[ip] = {};
173
+ if (!ipLimits[ip][hour]) ipLimits[ip][hour] = {};
174
+ ipLimits[ip][hour][minute] = (ipLimits[ip][hour][minute] || 0) + 1;
175
+
176
+ if (ipLimits[ip][hour][minute] > requestLimit) {
177
+ return res.status(429).jsonResponse({
178
+ message: 'Too Many Requests (IP limited), authenticate to increase the limit',
179
+ error: `You have exceeded the limit of ${requestLimit} requests per hour.`,
180
+ reset: `Reset in ${((resetTime - now) / 60000).toFixed(0)} minutes.`,
181
+ status: '429'
182
+ });
183
+ }
184
+
185
+ Object.keys(ipLimits[ip]).forEach(h => { if (h != hour) delete ipLimits[ip][h]; });
186
+
187
+ next();
188
+ });
189
+
190
+ // Save and send logs
191
+ app.use((req, res, next) => {
192
+ if (req.method === 'HEAD') return next();
193
+ if (req.originalUrl === '/logs') return next();
194
+
195
+ const ip = req.headers['cf-connecting-ip'] || req.socket.remoteAddress;
196
+
197
+ const startTime = Date.now();
198
+ const timestamp = new Date().toISOString();
199
+ const method = req.method;
200
+ const url = req.originalUrl;
201
+ const platform = req.headers['sec-ch-ua-platform']?.replace(/"/g, '');
202
+
203
+ res.on('finish', () => {
204
+ const status = res.statusCode === 304 ? 200 : res.statusCode;
205
+ const duration = `${Date.now() - startTime}ms`;
206
+
207
+ logs.push({ timestamp, method, url, status, duration, platform });
208
+ console.log(`[${new Date().toISOString()}] ${method} ${url} ${res.statusCode} - ${duration} - ${ip}`);
209
+ if (logs.length > 1000) logs.shift();
210
+ });
211
+ next();
212
+ });
213
+
214
+ // Internal Server Error
215
+ app.use((err, req, res, next) => {
216
+ console.error(err.stack);
217
+ res.status(500).jsonResponse({
218
+ message: 'Internal Server Error',
219
+ error: err.message,
220
+ documentation,
221
+ status: '500'
222
+ });
223
+ });
224
+
225
+ // Check if version exists
226
+ app.use('/:version', (req, res, next) => {
227
+ const { version } = req.params;
228
+ const latest = Object.keys(versions).pop();
229
+ const endpoint = req.originalUrl.split('/').slice(2).join('/');
230
+
231
+ req.version = version;
232
+ req.latest = latest;
233
+
234
+ if (['latest', 'fr', 'en'].includes(version)) {
235
+ return res.redirect(endpoint ? `/${latest}/${endpoint}` : `/${latest}`);
236
+ }
237
+
238
+ if (version === 'logs') return res.jsonResponse(logs);
239
+
240
+ if (!versions[version] && version !== 'logs') {
241
+ return res.status(404).jsonResponse({
242
+ message: 'Not Found',
243
+ error: `Invalid API version (${version}).`,
244
+ documentation: `${documentation}/${latest}`,
245
+ status: '404'
246
+ });
247
+ }
248
+
249
+ req.module = versions[version].modules;
250
+
251
+ if (!req.module) {
252
+ return res.status(404).jsonResponse({
253
+ message: 'Not Found',
254
+ error: `Module not found for version ${version}.`,
255
+ documentation: `${documentation}/${latest}`,
256
+ status: '404'
257
+ });
258
+ }
259
+
260
+ next();
261
+ });
262
+
263
+ // Check if endpoint exists
264
+ app.use('/:version/:endpoint', (req, res, next) => {
265
+ const { version, endpoint } = req.params;
266
+
267
+ req.version = version;
268
+ req.endpoint = endpoint;
269
+
270
+ if (!req.endpoint) {
271
+ return res.status(404).jsonResponse({
272
+ message: 'Not Found',
273
+ error: `Endpoint '${endpoint}' does not exist in ${req.version}.`,
274
+ documentation: `${documentation}/${versions[version.length - 1]}`,
275
+ status: '404'
276
+ });
277
+ }
278
+ next();
279
+ });
280
+
281
+ // ----------- ----------- MAIN ENDPOINTS ----------- ----------- //
282
+
283
+ // Main route
284
+ app.get('/', (req, res) => {
285
+ const base = `${req.protocol}://${req.get('host')}`;
286
+ const links = Object.keys(versions).reduce((link, version) => {
287
+ link[version] = `${base}/${version}`;
288
+ return link;
289
+ }, {});
290
+
291
+ res.jsonResponse({
292
+ documentation,
293
+ latest: `${base}/latest`,
294
+ logs: `${base}/logs`,
295
+ versions: links
296
+ });
297
+ });
298
+
299
+ // Display version information
300
+ app.get('/:version', (req, res) => {
301
+ const { version } = req.params;
302
+
303
+ if (!versions[version]) {
304
+ return res.status(404).jsonResponse({
305
+ message: 'Not Found',
306
+ error: `Invalid API version (${version}).`,
307
+ documentation: `${documentation}/${Object.keys(versions).pop()}`,
308
+ status: '404'
309
+ });
310
+ }
311
+
312
+ const endpoints = Object.keys(versions[version].endpoints).reduce((acc, method) => {
313
+ acc[method] = versions[version].endpoints[method]
314
+ .filter(({ name }) => name !== 'website')
315
+ .sort((a, b) => a.name.localeCompare(b.name))
316
+ .reduce((group, endpoint) => {
317
+ if (endpoint.children) {
318
+ group[endpoint.name] = Object.keys(endpoint.children)
319
+ .sort((a, b) => a.localeCompare(b))
320
+ .reduce((childGroup, childName) => {
321
+ childGroup[childName] = `/${version}${endpoint.children[childName]}`;
322
+ return childGroup;
323
+ }, {});
324
+ } else {
325
+ group[endpoint.name] = `/${version}${endpoint.path}`;
326
+ }
327
+ return group;
328
+ }, {});
329
+ return acc;
330
+ }, {});
331
+
332
+ res.jsonResponse({
333
+ version,
334
+ documentation: `${documentation}/${version}`,
335
+ endpoints
336
+ });
337
+ });
338
+
339
+ // ----------- ----------- GET ENDPOINTS ----------- ----------- //
340
+
341
+ // Algorithms
342
+ app.get('/:version/algorithms', (req, res) => {
343
+ const { method, value, value2 } = req.query;
344
+ const { version } = req.params;
345
+
346
+ if (!req.module.algorithms || !req.module.algorithms[method]) {
347
+ return res.jsonResponse({
348
+ error: 'Please provide a valid algorithm (?method={algorithm})',
349
+ documentation: `${documentation}/${version}/en/algorithms`
350
+ });
351
+ }
352
+
353
+ try {
354
+ const answer = req.module.algorithms[method](value, value2);
355
+ res.jsonResponse({ answer });
356
+ } catch (err) {
357
+ res.jsonResponse({
358
+ error: err.message,
359
+ documentation: `${documentation}/${version}/en/algorithms`
360
+ });
361
+ }
362
+ });
363
+
364
+ // Generate captcha
365
+ app.get('/:version/captcha', (req, res) => {
366
+ const text = req.query.text;
367
+
368
+ if (!text) return res.jsonResponse({ error: 'Please provide a valid argument (?text={text})' });
369
+
370
+ try {
371
+ const result = req.module.captcha(text);
372
+ res.type('png').send(result);
373
+ } catch (err) {
374
+ res.jsonResponse({
375
+ error: err.message,
376
+ documentation: `${documentation}/${req.version}/en/captcha`
377
+ });
378
+ }
379
+ });
380
+
381
+ // Display stored data
382
+ app.get('/:version/chat', (req, res) => {
383
+ try {
384
+ const messages = req.module.chat('fetch', {
385
+ username: 'system',
386
+ storage: chatStorage
387
+ });
388
+ res.jsonResponse(messages);
389
+ } catch (err) {
390
+ res.jsonResponse({ error: err.message });
391
+ }
392
+ });
393
+
394
+ // GET private chat error
395
+ app.get('/:version/chat/private', (req, res) => {
396
+ res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
397
+ });
398
+
399
+ // Generate color
400
+ app.get('/:version/color', (req, res) => {
401
+ try {
402
+ const result = req.module.color();
403
+ res.jsonResponse(result);
404
+ } catch (err) {
405
+ res.jsonResponse({
406
+ error: err.message,
407
+ documentation: `${documentation}/${req.version}/en/color`
408
+ });
409
+ }
410
+ });
411
+
412
+ // Convert units
413
+ app.get('/:version/convert', (req, res) => {
414
+ const { value, from, to } = req.query;
415
+
416
+ if (!value || isNaN(value)) return res.jsonResponse({ error: 'Please provide a valid value (?value={value})' });
417
+ if (!from) return res.jsonResponse({ error: 'Please provide a valid source unit (&from={unit})' });
418
+ if (!to) return res.jsonResponse({ error: 'Please provide a valid target unit (&to={unit})' });
419
+
420
+ try {
421
+ const result = req.module.convert(value, from, to);
422
+ res.jsonResponse(result);
423
+ } catch (err) {
424
+ res.jsonResponse({
425
+ error: err.message,
426
+ documentation: `${documentation}/${req.version}/en/convert`
427
+ });
428
+ }
429
+ });
430
+
431
+ // Generate domain informations
432
+ app.get('/:version/domain', (req, res) => {
433
+ try {
434
+ const result = req.module.domain();
435
+ res.jsonResponse(result);
436
+ } catch (err) {
437
+ res.jsonResponse({
438
+ error: err.message,
439
+ documentation: `${documentation}/${req.version}/en/domain`
440
+ });
441
+ }
442
+ });
443
+
444
+ // GET planning error
445
+ app.get('/:version/hyperplanning', (req, res) => {
446
+ res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
447
+ });
448
+
449
+ // GET hash error
450
+ app.get('/:version/hash', (req, res) => {
451
+ res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
452
+ });
453
+
454
+ // Display API informations
455
+ app.get('/:version/infos', (req, res) => {
456
+ const endpoints = Object.values(versions[req.version].endpoints).flat();
457
+
458
+ const paths = endpoints.flatMap(e => e.children
459
+ ? Object.values(e.children)
460
+ : (e.path ? [e.path] : []));
461
+
462
+ res.jsonResponse({
463
+ endpoints: new Set(paths).size,
464
+ last_version: Object.keys(versions).pop(),
465
+ documentation,
466
+ github: 'https://github.com/20syldev/api',
467
+ creation: 'November 25th 2024',
468
+ });
469
+ });
470
+
471
+ // Calculate Levenshtein distance
472
+ app.get('/:version/levenshtein', (req, res) => {
473
+ const { str1, str2 } = req.query;
474
+
475
+ if (!str1 || typeof str1 !== 'string') return res.jsonResponse({ error: 'Please provide a first string (?str1={string})' });
476
+ if (!str2 || typeof str2 !== 'string') return res.jsonResponse({ error: 'Please provide a second string (&str2={string})' });
477
+
478
+ try {
479
+ const result = req.module.levenshtein(str1, str2);
480
+ res.jsonResponse(result);
481
+ } catch (err) {
482
+ res.jsonResponse({
483
+ error: err.message,
484
+ documentation: `${documentation}/${req.version}/en/levenshtein`
485
+ });
486
+ }
487
+ });
488
+
489
+ // Generate personal data
490
+ app.get('/:version/personal', (req, res) => {
491
+ try {
492
+ const result = req.module.personal();
493
+ res.jsonResponse(result);
494
+ } catch (err) {
495
+ res.jsonResponse({
496
+ error: err.message,
497
+ documentation: `${documentation}/${req.version}/en/personal`
498
+ });
499
+ }
500
+ });
501
+
502
+ // Generate QR Code
503
+ app.get('/:version/qrcode', async (req, res) => {
504
+ const { url } = req.query;
505
+
506
+ if (!url) return res.jsonResponse({ error: 'Please provide a valid url (?url={URL})' });
507
+
508
+ try {
509
+ const result = await req.module.qrcode(url);
510
+ res.jsonResponse(result);
511
+ } catch (err) {
512
+ res.jsonResponse({
513
+ error: err.message,
514
+ documentation: `${documentation}/${req.version}/en/qrcode`
515
+ });
516
+ }
517
+ });
518
+
519
+ // GET tic-tac-toe game error
520
+ app.get('/:version/tic-tac-toe', (req, res) => {
521
+ res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
522
+ });
523
+
524
+ // GET tic-tac-toe fetch error
525
+ app.get('/:version/tic-tac-toe/fetch', (req, res) => {
526
+ res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
527
+ });
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
+
534
+ // Display or generate time informations
535
+ app.get('/:version/time', (req, res) => {
536
+ const { type = 'live', start, end, format, timezone } = req.query;
537
+
538
+ const validFormats = ['iso', 'utc', 'timestamp', 'locale', 'date', 'time', 'year', 'month', 'day', 'hour', 'minute', 'second', 'ms', 'dayOfWeek', 'dayOfYear', 'weekNumber', 'timezone', 'timezoneOffset'];
539
+ const validTimezones = ['UTC', 'America/New_York', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'];
540
+
541
+ if (type !== 'live' && type !== 'random') return res.jsonResponse({ error: 'Please provide a valid type (?type={type})' });
542
+ if (start && !Date.parse(start)) return res.jsonResponse({ error: 'Please provide a valid start date (?start={YYYY-MM-DD})' });
543
+ if (end && !Date.parse(end)) return res.jsonResponse({ error: 'Please provide a valid end date (?end={YYYY-MM-DD})' });
544
+ if (format && !validFormats.includes(format)) return res.jsonResponse({ error: 'Please provide a valid format (?format={format})' });
545
+ if (timezone && !validTimezones.includes(timezone)) return res.jsonResponse({ error: 'Please provide a valid timezone (?timezone={timezone})' });
546
+
547
+ try {
548
+ const time = req.module.time(type, start, end, format, timezone);
549
+ res.jsonResponse(time);
550
+ } catch (err) {
551
+ res.jsonResponse({
552
+ error: err.message,
553
+ documentation: `${documentation}/${req.version}/en/time`
554
+ });
555
+ }
556
+ });
557
+
558
+ // GET token error
559
+ app.get('/:version/token', (req, res) => {
560
+ res.jsonResponse({ error: 'This endpoint only supports POST requests.' });
561
+ });
562
+
563
+ // Generate username
564
+ app.get('/:version/username', (req, res) => {
565
+ try {
566
+ const result = req.module.username();
567
+ res.jsonResponse(result);
568
+ } catch (err) {
569
+ res.jsonResponse({
570
+ error: err.message,
571
+ documentation: `${documentation}/${req.version}/en/username`
572
+ });
573
+ }
574
+ });
575
+
576
+ // Display informations for owner's website
577
+ app.get('/:version/website', async (req, res) => {
578
+ const currentTime = Date.now();
579
+
580
+ if (currentTime - lastFetch >= 10 * 60 * 1000) {
581
+ try {
582
+ const username = '20syldev';
583
+ const token = process.env.GITHUB_TOKEN;
584
+ const today = new Date().toISOString().split('T')[0];
585
+ const monthFirst = new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString().split('T')[0];
586
+ const lastYear = new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString().split('T')[0];
587
+
588
+ const query = `
589
+ {
590
+ user(login: "${username}") {
591
+ contributionsCollection(from: "${today}T00:00:00Z") {
592
+ contributionCalendar {
593
+ totalContributions
594
+ }
595
+ }
596
+ contributions_month: contributionsCollection(from: "${monthFirst}T00:00:00Z") {
597
+ contributionCalendar {
598
+ totalContributions
599
+ }
600
+ }
601
+ contributions_year: contributionsCollection(from: "${lastYear}T00:00:00Z") {
602
+ contributionCalendar {
603
+ totalContributions
604
+ }
605
+ }
606
+ }
607
+ }`;
608
+
609
+ const apiResponse = await fetch('https://api.github.com/graphql', {
610
+ method: 'POST',
611
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
612
+ body: JSON.stringify({ query })
613
+ });
614
+
615
+ if (!apiResponse.ok) throw new Error('Error fetching data.');
616
+
617
+ const data = await apiResponse.json();
618
+ const user = data?.data?.user;
619
+
620
+ contributions = {
621
+ today: user?.contributionsCollection?.contributionCalendar?.totalContributions || 0,
622
+ month: user?.contributions_month?.contributionCalendar?.totalContributions || 0,
623
+ year: user?.contributions_year?.contributionCalendar?.totalContributions || 0,
624
+ };
625
+
626
+ lastFetch = currentTime;
627
+ } catch { contributions = { today: 0, month: 0, year: 0 }; }
628
+ }
629
+
630
+ res.jsonResponse({
631
+ versions: {
632
+ api: process.env.API,
633
+ cdn: process.env.CDN,
634
+ coop_api: process.env.COOP_API,
635
+ coop_status: process.env.COOP_STATUS,
636
+ chat: process.env.CHAT,
637
+ digit: process.env.DIGIT,
638
+ doc_coopbot: process.env.DOC_COOPBOT,
639
+ docs: process.env.DOCS,
640
+ donut: process.env.DONUT,
641
+ drawio_plugin: process.env.DRAWIO_PLUGIN,
642
+ flowers: process.env.FLOWERS,
643
+ 2048: process.env["2048"],
644
+ gemsync: process.env.GEMSYNC,
645
+ gft: process.env.GFT,
646
+ gitsite: process.env.GITSITE,
647
+ lebonchar: process.env.LEBONCHAR,
648
+ logs: process.env.LOGS,
649
+ logvault: process.env.LOGVAULT,
650
+ lyah: process.env.LYAH,
651
+ minify: process.env.MINIFY,
652
+ mn: process.env.MN,
653
+ monitoring: process.env.MONITORING,
654
+ morpion: process.env.MORPION,
655
+ nitrogen: process.env.NITROGEN,
656
+ old_database: process.env.OLD_DATABASE,
657
+ password: process.env.PASSWORD,
658
+ php: process.env.PHP,
659
+ planning: process.env.PLANNING,
660
+ ping: process.env.PING,
661
+ portfolio: process.env.PORTFOLIO,
662
+ python_api: process.env.PYTHON_API,
663
+ readme: process.env.README,
664
+ timestamp: process.env.TIMESTAMP,
665
+ terminal: process.env.TERMINAL,
666
+ valentine: process.env.VALENTINE,
667
+ wrkit: process.env.WRKIT,
668
+ zpki: process.env.ZPKI
669
+ },
670
+ patched_projects: envList('PATCH'),
671
+ updated_projects: envList('RECENT'),
672
+ new_projects: envList('NEW'),
673
+ sub_domains: envList('DOMAINS'),
674
+ stats: {
675
+ 1: process.env.STATS1,
676
+ 2: process.env.STATS2,
677
+ 3: process.env.STATS3,
678
+ 4: process.env.STATS4,
679
+ 5: Object.keys(ipLimits).length,
680
+ today: contributions.today.toString(),
681
+ this_month: contributions.month.toString(),
682
+ last_year: contributions.year.toString(),
683
+ },
684
+ tag: process.env.TAG,
685
+ active: process.env.ACTIVE === 'true'
686
+ });
687
+ });
688
+
689
+ // ----------- ----------- POST ENDPOINTS ----------- ----------- //
690
+
691
+ // Store chat messages
692
+ app.post('/:version/chat', (req, res) => {
693
+ const { username, message, timestamp, session, token } = req.body || {};
694
+
695
+ if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
696
+ if (!message) return res.jsonResponse({ error: 'Please provide a message (&message={message})' });
697
+ if (!session) return res.jsonResponse({ error: 'Please provide a valid session ID (&session={ID})' });
698
+
699
+ try {
700
+ const result = req.module.chat('message', {
701
+ username,
702
+ message,
703
+ timestamp,
704
+ session,
705
+ token,
706
+ storage: chatStorage
707
+ });
708
+ res.jsonResponse(result);
709
+ } catch (err) {
710
+ res.jsonResponse({ error: err.message });
711
+ }
712
+ });
713
+
714
+ // Display a private chat with a token
715
+ app.post('/:version/chat/private', (req, res) => {
716
+ const { username, token } = req.body || {};
717
+
718
+ if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
719
+ if (!token) return res.jsonResponse({ error: 'Please provide a valid token (&token={key}).' });
720
+
721
+ try {
722
+ const messages = req.module.chat('private', {
723
+ username,
724
+ token,
725
+ storage: chatStorage
726
+ });
727
+ res.jsonResponse(messages);
728
+ } catch (err) {
729
+ res.jsonResponse({ error: err.message });
730
+ }
731
+ });
732
+
733
+ // Generate hash
734
+ app.post('/:version/hash', (req, res) => {
735
+ const { text, method } = req.body || {};
736
+
737
+ if (!text) return res.jsonResponse({ error: 'Please provide a text (?text={text})' });
738
+ if (!method) return res.jsonResponse({
739
+ error: 'Please provide a valid hash algorithm (&method={algorithm})',
740
+ documentation: `${documentation}/${req.version}/en/hash`
741
+ });
742
+
743
+ try {
744
+ const result = req.module.hash(text, method);
745
+ res.jsonResponse(result);
746
+ } catch (err) {
747
+ res.jsonResponse({
748
+ error: err.message,
749
+ documentation: `${documentation}/${req.version}/en/hash`
750
+ })
751
+ }
752
+ });
753
+
754
+ // Display a planning from an ICS file
755
+ app.post('/:version/hyperplanning', async (req, res) => {
756
+ const { url, detail } = req.body || {};
757
+
758
+ if (!url) return res.jsonResponse({ error: 'Please provide a valid ICS file URL (?url={URL})' });
759
+
760
+ try {
761
+ const hyperplanning = await req.module.hyperplanning(url, detail);
762
+ res.jsonResponse(hyperplanning);
763
+ } catch (err){
764
+ res.jsonResponse({
765
+ error: err.message,
766
+ documentation: `${documentation}/${req.version}/en/hyperplanning`
767
+ });
768
+ }
769
+ });
770
+
771
+ // Store tic tac toe games
772
+ app.post('/:version/tic-tac-toe', (req, res) => {
773
+ const { username, move, session, game } = req.body || {};
774
+
775
+ if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
776
+ if (!move) return res.jsonResponse({ error: 'Please provide a valid move (&move={move})' });
777
+ if (!session) return res.jsonResponse({ error: 'Please provide a valid session ID (&session={ID})' });
778
+ if (!game) return res.jsonResponse({ error: 'Please provide a valid game ID (&game={ID})' });
779
+
780
+ try {
781
+ const result = req.module.tic_tac_toe('play', {
782
+ username,
783
+ move,
784
+ session,
785
+ game,
786
+ storage: ticTacToeStorage
787
+ });
788
+ res.jsonResponse(result);
789
+ } catch (err) {
790
+ res.jsonResponse({ error: err.message });
791
+ }
792
+ });
793
+
794
+ // Display a tic tac toe game with a token
795
+ app.post('/:version/tic-tac-toe/fetch', (req, res) => {
796
+ const { username, game } = req.body || {};
797
+ const privateGame = (req.body || {}).private;
798
+
799
+ if (!username) return res.jsonResponse({ error: 'Please provide a username (?username={username})' });
800
+
801
+ try {
802
+ const result = req.module.tic_tac_toe('fetch', {
803
+ username,
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', {
818
+ storage: ticTacToeStorage
819
+ });
820
+ res.jsonResponse(result);
821
+ } catch (err) {
822
+ res.jsonResponse({ error: err.message });
823
+ }
824
+ });
825
+
826
+ // Generate Token
827
+ app.post('/:version/token', (req, res) => {
828
+ let { len, type } = req.body || {};
829
+
830
+ len = parseInt(len || 24, 10);
831
+ type = type ? type.toLowerCase() : 'alpha';
832
+
833
+ if (isNaN(len) || len < 0) return res.jsonResponse({ error: 'Invalid number.' });
834
+ if (len > 4096) return res.jsonResponse({ error: 'Length cannot exceed 4096.' });
835
+ if (len < 12) return res.jsonResponse({ error: 'Length cannot be less than 12.' });
836
+
837
+ try {
838
+ const token = req.module.token(len, type);
839
+ res.jsonResponse({ token });
840
+ } catch (err) {
841
+ return res.jsonResponse({
842
+ error: err.message,
843
+ documentation: `${documentation}/${req.version}/en/token`
844
+ });
845
+ }
846
+ });
847
+
848
+ // ----------- ----------- SERVER SETUP ----------- ----------- //
849
+
850
+ app.listen(3000, () => console.log('API is running on\n - http://127.0.0.1:3000\n - http://localhost:3000'));