@20syldev/api 3.4.0 → 3.4.1

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.
@@ -1,109 +1,109 @@
1
- import { checkRateLimit } from "./utils.js";
2
-
3
- /**
4
- * Manages a chat system with public and private messages
5
- *
6
- * @param {string} action - The action to perform ('message' or 'private')
7
- * @param {Object} params - The parameters for the action
8
- * @returns {Object} - The result of the action
9
- * @throws {Error} - If parameters are invalid
10
- */
11
- export default function chat(action, params = {}) {
12
- // In-memory storage
13
- const storage = params.storage || {};
14
-
15
- // Initialize storage
16
- storage.messages ??= [];
17
- storage.privateChats ??= {};
18
- storage.sessions ??= {};
19
- storage.rateLimits ??= {};
20
-
21
- // Reference the storage properties
22
- const { messages, privateChats, sessions, rateLimits } = storage;
23
-
24
- // Input validation for common parameters
25
- if (!params.username) throw new Error('Please provide a username');
26
-
27
- const u = params.username.toLowerCase();
28
- const now = Date.now();
29
-
30
- // Rate limiting
31
- checkRateLimit(rateLimits, u, now);
32
-
33
- // Handle different actions
34
- if (action === 'message') {
35
- return sendMessage(params, messages, privateChats, sessions, u, now);
36
- } else if (action === 'private') {
37
- return getPrivateChat(params, privateChats);
38
- } else if (action === 'fetch') {
39
- return fetchMessages(messages);
40
- } else {
41
- throw new Error('Invalid action. Use "message", "private", or "fetch"');
42
- }
43
- }
44
-
45
- /**
46
- * Send a new message
47
- */
48
- function sendMessage(params, messages, privateChats, sessions, u, now) {
49
- const { message, session, token } = params;
50
-
51
- // Input validation for message action
52
- if (!message) throw new Error('Please provide a message');
53
- if (!session) throw new Error('Please provide a valid session ID');
54
-
55
- // Session validation
56
- if (sessions[u] && sessions[u].user !== session) {
57
- throw new Error('Session ID mismatch');
58
- }
59
-
60
- // Create message object
61
- const msg = {
62
- username: params.username,
63
- message,
64
- timestamp: params.timestamp || new Date().toISOString()
65
- };
66
-
67
- // Store message based on whether it's private or public
68
- if (token) {
69
- privateChats[token] = privateChats[token] || [];
70
- privateChats[token].push(msg);
71
- setTimeout(() => { delete privateChats[token]; }, 3600000);
72
- } else {
73
- messages.push(msg);
74
- setTimeout(() => messages.splice(messages.indexOf(msg), 1), 3600000);
75
- }
76
-
77
- // Update session
78
- sessions[u] = sessions[u] || { user: session, last: now };
79
- sessions[u].last = now;
80
-
81
- setTimeout(() => {
82
- if (now - sessions[u].last >= 3600000) delete sessions[u];
83
- }, 3600000);
84
-
85
- return { message: 'Message sent successfully' };
86
- }
87
-
88
- /**
89
- * Get private chat messages
90
- */
91
- function getPrivateChat(params, privateChats) {
92
- const { token } = params;
93
-
94
- // Input validation for private action
95
- if (!token) throw new Error('Please provide a valid token');
96
-
97
- // Return messages if token exists
98
- if (privateChats[token]) return privateChats[token];
99
-
100
- throw new Error('Invalid or expired token.');
101
- }
102
-
103
- /**
104
- * Fetch all public messages
105
- */
106
- function fetchMessages(messages) {
107
- if (messages.length > 0) return messages;
108
- throw new Error('No messages stored.');
1
+ import { checkRateLimit } from "./utils.js";
2
+
3
+ /**
4
+ * Manages a chat system with public and private messages
5
+ *
6
+ * @param {string} action - The action to perform ('message' or 'private')
7
+ * @param {Object} params - The parameters for the action
8
+ * @returns {Object} - The result of the action
9
+ * @throws {Error} - If parameters are invalid
10
+ */
11
+ export default function chat(action, params = {}) {
12
+ // In-memory storage
13
+ const storage = params.storage || {};
14
+
15
+ // Initialize storage
16
+ storage.messages ??= [];
17
+ storage.privateChats ??= {};
18
+ storage.sessions ??= {};
19
+ storage.rateLimits ??= {};
20
+
21
+ // Reference the storage properties
22
+ const { messages, privateChats, sessions, rateLimits } = storage;
23
+
24
+ // Input validation for common parameters
25
+ if (!params.username) throw new Error('Please provide a username');
26
+
27
+ const u = params.username.toLowerCase();
28
+ const now = Date.now();
29
+
30
+ // Rate limiting
31
+ checkRateLimit(rateLimits, u, now);
32
+
33
+ // Handle different actions
34
+ if (action === 'message') {
35
+ return sendMessage(params, messages, privateChats, sessions, u, now);
36
+ } else if (action === 'private') {
37
+ return getPrivateChat(params, privateChats);
38
+ } else if (action === 'fetch') {
39
+ return fetchMessages(messages);
40
+ } else {
41
+ throw new Error('Invalid action. Use "message", "private", or "fetch"');
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Send a new message
47
+ */
48
+ function sendMessage(params, messages, privateChats, sessions, u, now) {
49
+ const { message, session, token } = params;
50
+
51
+ // Input validation for message action
52
+ if (!message) throw new Error('Please provide a message');
53
+ if (!session) throw new Error('Please provide a valid session ID');
54
+
55
+ // Session validation
56
+ if (sessions[u] && sessions[u].user !== session) {
57
+ throw new Error('Session ID mismatch');
58
+ }
59
+
60
+ // Create message object
61
+ const msg = {
62
+ username: params.username,
63
+ message,
64
+ timestamp: params.timestamp || new Date().toISOString()
65
+ };
66
+
67
+ // Store message based on whether it's private or public
68
+ if (token) {
69
+ privateChats[token] = privateChats[token] || [];
70
+ privateChats[token].push(msg);
71
+ setTimeout(() => { delete privateChats[token]; }, 3600000);
72
+ } else {
73
+ messages.push(msg);
74
+ setTimeout(() => messages.splice(messages.indexOf(msg), 1), 3600000);
75
+ }
76
+
77
+ // Update session
78
+ sessions[u] = sessions[u] || { user: session, last: now };
79
+ sessions[u].last = now;
80
+
81
+ setTimeout(() => {
82
+ if (now - sessions[u].last >= 3600000) delete sessions[u];
83
+ }, 3600000);
84
+
85
+ return { message: 'Message sent successfully' };
86
+ }
87
+
88
+ /**
89
+ * Get private chat messages
90
+ */
91
+ function getPrivateChat(params, privateChats) {
92
+ const { token } = params;
93
+
94
+ // Input validation for private action
95
+ if (!token) throw new Error('Please provide a valid token');
96
+
97
+ // Return messages if token exists
98
+ if (privateChats[token]) return privateChats[token];
99
+
100
+ throw new Error('Invalid or expired token.');
101
+ }
102
+
103
+ /**
104
+ * Fetch all public messages
105
+ */
106
+ function fetchMessages(messages) {
107
+ if (messages.length > 0) return messages;
108
+ throw new Error('No messages stored.');
109
109
  }
@@ -1,41 +1,41 @@
1
- /**
2
- * Generate a random color in multiple formats
3
- *
4
- * @returns {Object} Color in various formats (hex, rgb, hsl, hsv, hwb, cmyk)
5
- */
6
- export default function color() {
7
- const r = Math.floor(Math.random() * 256), g = Math.floor(Math.random() * 256), b = Math.floor(Math.random() * 256);
8
-
9
- const hsl = (() => {
10
- 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;
11
- if (max === min) return [0, 0, l * 100];
12
- const d = max - min, s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
13
- let h = { [r1]: (g1 - b1) / d + (g1 < b1 ? 6 : 0), [g1]: (b1 - r1) / d + 2, [b1]: (r1 - g1) / d + 4 }[max];
14
- return [h * 60 % 360, s * 100, l * 100];
15
- })();
16
-
17
- const hsv = (() => {
18
- const max = Math.max(r, g, b), min = Math.min(r, g, b), v = max / 255, s = max ? (max - min) / max : 0;
19
- let h = max === min ? 0 : { [r]: (g - b) / (max - min), [g]: 2 + (b - r) / (max - min), [b]: 4 + (r - g) / (max - min) }[max];
20
- return [h * 60 % 360, s * 100, v * 100];
21
- })();
22
-
23
- const hwb = (() => {
24
- const [h] = hsv, whiteness = Math.min(r, g, b) / 255, blackness = 1 - Math.max(r, g, b) / 255;
25
- return [h, whiteness * 100, blackness * 100];
26
- })();
27
-
28
- const cmyk = (() => {
29
- const k = 1 - Math.max(r, g, b) / 255, c = (1 - r / 255 - k) / (1 - k) || 0, m = (1 - g / 255 - k) / (1 - k) || 0, y = (1 - b / 255 - k) / (1 - k) || 0;
30
- return [c, m, y, k].map(x => x * 100);
31
- })();
32
-
33
- return {
34
- hex: `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`,
35
- rgb: `rgb(${r}, ${g}, ${b})`,
36
- hsl: `hsl(${hsl[0].toFixed(1)}, ${hsl[1].toFixed(1)}%, ${hsl[2].toFixed(1)}%)`,
37
- hsv: `hsv(${hsv[0].toFixed(1)}, ${hsv[1].toFixed(1)}%, ${hsv[2].toFixed(1)}%)`,
38
- hwb: `hwb(${hwb[0].toFixed(1)}, ${hwb[1].toFixed(1)}%, ${hwb[2].toFixed(1)}%)`,
39
- cmyk: `cmyk(${cmyk.map(x => x.toFixed(1)).join('%, ')}%)`
40
- };
1
+ /**
2
+ * Generate a random color in multiple formats
3
+ *
4
+ * @returns {Object} Color in various formats (hex, rgb, hsl, hsv, hwb, cmyk)
5
+ */
6
+ export default function color() {
7
+ const r = Math.floor(Math.random() * 256), g = Math.floor(Math.random() * 256), b = Math.floor(Math.random() * 256);
8
+
9
+ const hsl = (() => {
10
+ 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;
11
+ if (max === min) return [0, 0, l * 100];
12
+ const d = max - min, s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
13
+ let h = { [r1]: (g1 - b1) / d + (g1 < b1 ? 6 : 0), [g1]: (b1 - r1) / d + 2, [b1]: (r1 - g1) / d + 4 }[max];
14
+ return [h * 60 % 360, s * 100, l * 100];
15
+ })();
16
+
17
+ const hsv = (() => {
18
+ const max = Math.max(r, g, b), min = Math.min(r, g, b), v = max / 255, s = max ? (max - min) / max : 0;
19
+ let h = max === min ? 0 : { [r]: (g - b) / (max - min), [g]: 2 + (b - r) / (max - min), [b]: 4 + (r - g) / (max - min) }[max];
20
+ return [h * 60 % 360, s * 100, v * 100];
21
+ })();
22
+
23
+ const hwb = (() => {
24
+ const [h] = hsv, whiteness = Math.min(r, g, b) / 255, blackness = 1 - Math.max(r, g, b) / 255;
25
+ return [h, whiteness * 100, blackness * 100];
26
+ })();
27
+
28
+ const cmyk = (() => {
29
+ const k = 1 - Math.max(r, g, b) / 255, c = (1 - r / 255 - k) / (1 - k) || 0, m = (1 - g / 255 - k) / (1 - k) || 0, y = (1 - b / 255 - k) / (1 - k) || 0;
30
+ return [c, m, y, k].map(x => x * 100);
31
+ })();
32
+
33
+ return {
34
+ hex: `#${[r, g, b].map(x => x.toString(16).padStart(2, '0')).join('')}`,
35
+ rgb: `rgb(${r}, ${g}, ${b})`,
36
+ hsl: `hsl(${hsl[0].toFixed(1)}, ${hsl[1].toFixed(1)}%, ${hsl[2].toFixed(1)}%)`,
37
+ hsv: `hsv(${hsv[0].toFixed(1)}, ${hsv[1].toFixed(1)}%, ${hsv[2].toFixed(1)}%)`,
38
+ hwb: `hwb(${hwb[0].toFixed(1)}, ${hwb[1].toFixed(1)}%, ${hwb[2].toFixed(1)}%)`,
39
+ cmyk: `cmyk(${cmyk.map(x => x.toFixed(1)).join('%, ')}%)`
40
+ };
41
41
  }
@@ -1,39 +1,39 @@
1
- /**
2
- * Converts values between different temperature units
3
- *
4
- * @param {number|string} value - The value to convert
5
- * @param {string} from - The source unit (celsius, fahrenheit, or kelvin)
6
- * @param {string} to - The target unit (celsius, fahrenheit, or kelvin)
7
- * @returns {Object} - Object containing the conversion details
8
- * @throws {Error} - If conversion parameters are invalid
9
- */
10
- export default function convert(value, from, to) {
11
- const conversions = {
12
- celsius: {
13
- fahrenheit: (val) => (val * 9) / 5 + 32,
14
- kelvin: (val) => val + 273.15
15
- },
16
- fahrenheit: {
17
- celsius: (val) => ((val - 32) * 5) / 9,
18
- kelvin: (val) => ((val - 32) * 5) / 9 + 273.15
19
- },
20
- kelvin: {
21
- celsius: (val) => val - 273.15,
22
- fahrenheit: (val) => ((val - 273.15) * 9) / 5 + 32
23
- },
24
- };
25
-
26
- const convert = conversions[from.toLowerCase()]?.[to.toLowerCase()];
27
-
28
- if (!convert) throw new Error('Invalid conversion unit');
29
- if (isNaN(value)) throw new Error('Value must be a number');
30
- if (value < -273.15) throw new Error('Value must be greater than absolute zero');
31
- if (value > 1e6) throw new Error('Value must be less than 1,000,000');
32
-
33
- return {
34
- from,
35
- to,
36
- value: parseFloat(value),
37
- result: convert(parseFloat(value))
38
- };
1
+ /**
2
+ * Converts values between different temperature units
3
+ *
4
+ * @param {number|string} value - The value to convert
5
+ * @param {string} from - The source unit (celsius, fahrenheit, or kelvin)
6
+ * @param {string} to - The target unit (celsius, fahrenheit, or kelvin)
7
+ * @returns {Object} - Object containing the conversion details
8
+ * @throws {Error} - If conversion parameters are invalid
9
+ */
10
+ export default function convert(value, from, to) {
11
+ const conversions = {
12
+ celsius: {
13
+ fahrenheit: (val) => (val * 9) / 5 + 32,
14
+ kelvin: (val) => val + 273.15
15
+ },
16
+ fahrenheit: {
17
+ celsius: (val) => ((val - 32) * 5) / 9,
18
+ kelvin: (val) => ((val - 32) * 5) / 9 + 273.15
19
+ },
20
+ kelvin: {
21
+ celsius: (val) => val - 273.15,
22
+ fahrenheit: (val) => ((val - 273.15) * 9) / 5 + 32
23
+ },
24
+ };
25
+
26
+ const convert = conversions[from.toLowerCase()]?.[to.toLowerCase()];
27
+
28
+ if (!convert) throw new Error('Invalid conversion unit');
29
+ if (isNaN(value)) throw new Error('Value must be a number');
30
+ if (value < -273.15) throw new Error('Value must be greater than absolute zero');
31
+ if (value > 1e6) throw new Error('Value must be less than 1,000,000');
32
+
33
+ return {
34
+ from,
35
+ to,
36
+ value: parseFloat(value),
37
+ result: convert(parseFloat(value))
38
+ };
39
39
  }
@@ -1,39 +1,39 @@
1
- import { random, genIP } from './utils.js';
2
-
3
- /**
4
- * Generate random domain information
5
- *
6
- * @returns {Object} Domain details including DNS, hosting, and SEO metrics
7
- */
8
- export default function domain() {
9
- const subdomains = ['fr.', 'en.', 'docs.', 'api.', 'projects.', 'app.', 'web.', 'info.', 'dev.', 'shop.', 'blog.', 'support.', 'mail.', 'forum.'];
10
- const domains = ['example', 'site', 'test', 'demo', 'page', 'store', 'portfolio', 'platform', 'hub', 'network', 'service', 'cloud', 'solutions', 'company'];
11
- const tlds = ['.com', '.fr', '.eu', '.dev', '.net', '.org', '.io', '.tech', '.biz', '.info', '.co', '.app', '.store', '.online', '.shop', '.tv'];
12
-
13
- const domain = `${random(domains)}${random(tlds)}`;
14
- const fulldomain = `${random(subdomains)}${domain}`;
15
-
16
- const ips = Array.from({ length: Math.floor(Math.random() * 3) + 1 }, genIP);
17
- const dns = Array.from({ length: Math.floor(Math.random() * 5) + 1 }, genIP);
18
-
19
- return {
20
- domain,
21
- full_domain: fulldomain,
22
- ip_address: ips,
23
- ssl_certified: Math.random() > 0.5,
24
- hosting_provider: random(['AWS', 'Bluehost', 'DigitalOcean', 'GitHub', 'HostGator', 'Render', 'SiteGround']),
25
- dns_servers: dns,
26
- dns_provider: random(['AWS Route 53', 'Cloudflare', 'GoDaddy', 'Google DNS', 'Namecheap']),
27
- traffic: `${Math.floor(Math.random() * 10000)} visits/day`,
28
- seo_score: Math.floor(Math.random() * 100),
29
- page_rank: Math.floor(Math.random() * 10),
30
- country: random(['Australia', 'Canada', 'France', 'Germany', 'India', 'Japan', 'UK', 'USA']),
31
- website_type: random(['Blog', 'Community', 'Corporate', 'Educational', 'E-commerce', 'Personal', 'Portfolio']),
32
- random_name: domain.split('.')[0],
33
- random_subdomain: fulldomain.split('.')[0],
34
- random_tld: domain.split('.').pop(),
35
- backlinks_count: Math.floor(Math.random() * 1000),
36
- creation_date: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toISOString(),
37
- expiration_date: new Date(Date.now() + Math.floor(Math.random() * 10000000000)).toISOString(),
38
- };
1
+ import { random, genIP } from './utils.js';
2
+
3
+ /**
4
+ * Generate random domain information
5
+ *
6
+ * @returns {Object} Domain details including DNS, hosting, and SEO metrics
7
+ */
8
+ export default function domain() {
9
+ const subdomains = ['fr.', 'en.', 'docs.', 'api.', 'projects.', 'app.', 'web.', 'info.', 'dev.', 'shop.', 'blog.', 'support.', 'mail.', 'forum.'];
10
+ const domains = ['example', 'site', 'test', 'demo', 'page', 'store', 'portfolio', 'platform', 'hub', 'network', 'service', 'cloud', 'solutions', 'company'];
11
+ const tlds = ['.com', '.fr', '.eu', '.dev', '.net', '.org', '.io', '.tech', '.biz', '.info', '.co', '.app', '.store', '.online', '.shop', '.tv'];
12
+
13
+ const domain = `${random(domains)}${random(tlds)}`;
14
+ const fulldomain = `${random(subdomains)}${domain}`;
15
+
16
+ const ips = Array.from({ length: Math.floor(Math.random() * 3) + 1 }, genIP);
17
+ const dns = Array.from({ length: Math.floor(Math.random() * 5) + 1 }, genIP);
18
+
19
+ return {
20
+ domain,
21
+ full_domain: fulldomain,
22
+ ip_address: ips,
23
+ ssl_certified: Math.random() > 0.5,
24
+ hosting_provider: random(['AWS', 'Bluehost', 'DigitalOcean', 'GitHub', 'HostGator', 'Render', 'SiteGround']),
25
+ dns_servers: dns,
26
+ dns_provider: random(['AWS Route 53', 'Cloudflare', 'GoDaddy', 'Google DNS', 'Namecheap']),
27
+ traffic: `${Math.floor(Math.random() * 10000)} visits/day`,
28
+ seo_score: Math.floor(Math.random() * 100),
29
+ page_rank: Math.floor(Math.random() * 10),
30
+ country: random(['Australia', 'Canada', 'France', 'Germany', 'India', 'Japan', 'UK', 'USA']),
31
+ website_type: random(['Blog', 'Community', 'Corporate', 'Educational', 'E-commerce', 'Personal', 'Portfolio']),
32
+ random_name: domain.split('.')[0],
33
+ random_subdomain: fulldomain.split('.')[0],
34
+ random_tld: domain.split('.').pop(),
35
+ backlinks_count: Math.floor(Math.random() * 1000),
36
+ creation_date: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toISOString(),
37
+ expiration_date: new Date(Date.now() + Math.floor(Math.random() * 10000000000)).toISOString(),
38
+ };
39
39
  }
@@ -1,16 +1,16 @@
1
- import { getHashes, createHash } from 'crypto';
2
-
3
- /**
4
- * Generate a hash of text using the specified algorithm
5
- *
6
- * @param {string} text - The text to hash
7
- * @param {string} method - The hash algorithm to use
8
- * @returns {Object} Object containing the method and resulting hash
9
- */
10
- export default function hash(text, method) {
11
- const methods = getHashes();
12
- if (!methods.includes(method)) return { error: `Unsupported method. Use one of: ${methods.join(', ')}` };
13
-
14
- const hash = createHash(method).update(text).digest('hex');
15
- return { method, hash };
1
+ import { getHashes, createHash } from 'crypto';
2
+
3
+ /**
4
+ * Generate a hash of text using the specified algorithm
5
+ *
6
+ * @param {string} text - The text to hash
7
+ * @param {string} method - The hash algorithm to use
8
+ * @returns {Object} Object containing the method and resulting hash
9
+ */
10
+ export default function hash(text, method) {
11
+ const methods = getHashes();
12
+ if (!methods.includes(method)) return { error: `Unsupported method. Use one of: ${methods.join(', ')}` };
13
+
14
+ const hash = createHash(method).update(text).digest('hex');
15
+ return { method, hash };
16
16
  }
@@ -1,51 +1,51 @@
1
- import { formatDate } from './utils.js';
2
- import ical from 'ical.js';
3
- import fetch from 'node-fetch';
4
-
5
- /**
6
- * Parse an ICS calendar file and extract event information
7
- *
8
- * @param {string} url - URL to the ICS file
9
- * @param {string} [detail] - Detail level of returned information ('full', 'list', or undefined)
10
- * @returns {Promise<Array>} Array of calendar events
11
- * @throws {Error} If the ICS file is invalid or inaccessible
12
- */
13
- export default async function hyperplanning(url, detail) {
14
- const response = await fetch(url);
15
-
16
- if (!response.ok || !(response.headers.get('content-type') || '').includes('text/calendar')) {
17
- throw new Error('Invalid ICS file format.');
18
- }
19
-
20
- const events = new ical.Component(ical.parse(await response.text()))
21
- .getAllSubcomponents('vevent')
22
- .map(e => {
23
- const evt = new ical.Event(e);
24
- const summary = evt.summary.split(' ').filter(part => part !== '-');
25
- const start = formatDate(evt.startDate.toJSDate());
26
- const end = formatDate(evt.endDate.toJSDate());
27
-
28
- if (detail === 'full') {
29
- const desc = evt.description.split('\n').map(l => l.trim());
30
- const extract = (p) => (desc.find(l => l.startsWith(p)) || '').replace(p, '').trim();
31
-
32
- return {
33
- summary,
34
- subject: extract('Matière :'),
35
- teacher: extract('Enseignant :'),
36
- classes: extract('Promotions :').split(', ').map(c => c.trim()),
37
- type: extract('Salle :') || undefined,
38
- start,
39
- end
40
- };
41
- }
42
-
43
- if (detail === 'list') return { summary, start, end };
44
-
45
- return { summary: evt.summary, start, end };
46
- })
47
- .sort((a, b) => new Date(a.start) - new Date(b.start))
48
- .filter(e => new Date(e.end) >= new Date());
49
-
50
- return events;
1
+ import { formatDate } from './utils.js';
2
+ import ical from 'ical.js';
3
+ import fetch from 'node-fetch';
4
+
5
+ /**
6
+ * Parse an ICS calendar file and extract event information
7
+ *
8
+ * @param {string} url - URL to the ICS file
9
+ * @param {string} [detail] - Detail level of returned information ('full', 'list', or undefined)
10
+ * @returns {Promise<Array>} Array of calendar events
11
+ * @throws {Error} If the ICS file is invalid or inaccessible
12
+ */
13
+ export default async function hyperplanning(url, detail) {
14
+ const response = await fetch(url);
15
+
16
+ if (!response.ok || !(response.headers.get('content-type') || '').includes('text/calendar')) {
17
+ throw new Error('Invalid ICS file format.');
18
+ }
19
+
20
+ const events = new ical.Component(ical.parse(await response.text()))
21
+ .getAllSubcomponents('vevent')
22
+ .map(e => {
23
+ const evt = new ical.Event(e);
24
+ const summary = evt.summary.split(' ').filter(part => part !== '-');
25
+ const start = formatDate(evt.startDate.toJSDate());
26
+ const end = formatDate(evt.endDate.toJSDate());
27
+
28
+ if (detail === 'full') {
29
+ const desc = evt.description.split('\n').map(l => l.trim());
30
+ const extract = (p) => (desc.find(l => l.startsWith(p)) || '').replace(p, '').trim();
31
+
32
+ return {
33
+ summary,
34
+ subject: extract('Matière :'),
35
+ teacher: extract('Enseignant :'),
36
+ classes: extract('Promotions :').split(', ').map(c => c.trim()),
37
+ type: extract('Salle :') || undefined,
38
+ start,
39
+ end
40
+ };
41
+ }
42
+
43
+ if (detail === 'list') return { summary, start, end };
44
+
45
+ return { summary: evt.summary, start, end };
46
+ })
47
+ .sort((a, b) => new Date(a.start) - new Date(b.start))
48
+ .filter(e => new Date(e.end) >= new Date());
49
+
50
+ return events;
51
51
  }