nodebb-plugin-onekite-discord 1.0.11 → 1.0.12

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/library.js +119 -4
  2. package/package.json +1 -1
  3. package/plugin.json +1 -1
package/library.js CHANGED
@@ -1,5 +1,100 @@
1
1
  'use strict';
2
2
 
3
+
4
+
5
+ /**
6
+ * Extract links from text in several formats NodeBB may produce.
7
+ * Returns array of { text, url }.
8
+ *
9
+ * Supported inputs:
10
+ * - Markdown: [text](url)
11
+ * - HTML: <a href="url">text</a>
12
+ * - NodeBB rendered: text(url) or text (url)
13
+ * - Bare URLs: https://... or http://... or www.example.com
14
+ *
15
+ * Note: We'll output masked links in embed fields, where Discord supports them.
16
+ */
17
+ function extractDiscordLinks(str) {
18
+ if (!str || typeof str !== 'string') return [];
19
+
20
+ const links = [];
21
+ const seen = new Set();
22
+
23
+ const normalizeUrl = (u) => {
24
+ if (!u) return null;
25
+ let url = String(u).trim();
26
+ url = url.replace(/^<(.+)>$/, '$1');
27
+ url = url.replace(/^https:\//i, 'https://');
28
+ url = url.replace(/^http:\//i, 'http://');
29
+ if (/^www\./i.test(url)) url = 'https://' + url;
30
+ // Very small sanity check
31
+ if (!/^https?:\/\//i.test(url)) return null;
32
+ return url;
33
+ fields: linkFields,
34
+ };
35
+
36
+ const push = (text, url) => {
37
+ const u = normalizeUrl(url);
38
+ if (!u) return;
39
+ const key = `${text || ''}||${u}`;
40
+ if (seen.has(key)) return;
41
+ seen.add(key);
42
+ links.push({ text: (text || '').trim(), url: u });
43
+ };
44
+
45
+ // HTML links
46
+ str.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi, (_m, href, text) => {
47
+ push(text, href);
48
+ return _m;
49
+ });
50
+
51
+ // Markdown masked links
52
+ str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => {
53
+ push(text, url);
54
+ return _m;
55
+ });
56
+
57
+ // NodeBB rendered: "texte du lien(url)" or "texte du lien (url)"
58
+ str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text, url) => {
59
+ push(text, url);
60
+ return _m;
61
+ });
62
+
63
+ // Bare URLs (avoid trailing punctuation)
64
+ str.replace(/\b(https?:\/\/[^\s<>()]+)\b/g, (_m, url) => {
65
+ push('', url.replace(/[),.;!?]+$/g, ''));
66
+ return _m;
67
+ });
68
+ str.replace(/\b(www\.[^\s<>()]+)\b/g, (_m, url) => {
69
+ push('', url.replace(/[),.;!?]+$/g, ''));
70
+ return _m;
71
+ });
72
+
73
+ return links;
74
+ }
75
+
76
+ /**
77
+ * Remove/flatten link markup from text for embed.description readability.
78
+ * - [text](url) -> text
79
+ * - <a href="url">text</a> -> text
80
+ * - text(url) -> text
81
+ * - leaves bare URLs as-is (optional), but we can also remove them if desired.
82
+ */
83
+ function stripLinkMarkup(str) {
84
+ if (!str || typeof str !== 'string') return str;
85
+
86
+ // HTML links -> text
87
+ str = str.replace(/<a\s+[^>]*href=["'][^"']+["'][^>]*>(.*?)<\/a>/gi, (_m, text) => text);
88
+
89
+ // Markdown masked -> text
90
+ str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text) => text);
91
+
92
+ // NodeBB rendered -> text
93
+ str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text) => String(text).trim());
94
+
95
+ return str;
96
+ }
97
+
3
98
  /**
4
99
  * Normalize links for Discord embeds.
5
100
  * Discord does NOT support masked links in embed descriptions.
@@ -29,9 +124,13 @@ function formatDiscordLinks(str) {
29
124
  return normalizeUrl(url);
30
125
  });
31
126
 
32
- // Already-rendered form: texte(url)
33
- str = str.replace(/([^\s(]+)\((https?:\/\/[^)]+|www\.[^)]+)\)/g, (_m, _text, url) => {
34
- return normalizeUrl(url);
127
+ // Already-rendered form from NodeBB: texte du lien(url) or texte du lien (url)
128
+ // Convert to: "texte du lien https://url" so Discord auto-linkifies.
129
+ str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text, url) => {
130
+ const cleanText = String(text).trim();
131
+ const cleanUrl = normalizeUrl(url);
132
+ if (!cleanUrl) return _m;
133
+ return cleanText ? `${cleanText} ${cleanUrl}` : cleanUrl;
35
134
  });
36
135
 
37
136
  return str;
@@ -231,6 +330,22 @@ async function buildEmbed({ tid, pid, isReply }) {
231
330
 
232
331
  const excerpt = await getPostExcerpt(isReply ? pid : topicData.mainPid);
233
332
 
333
+ // Extract all links and put them in embed fields (Discord supports masked links in fields)
334
+ const extractedLinks = extractDiscordLinks(excerpt || '');
335
+ const linkFields = [];
336
+ const maxFields = 25; // Discord limit
337
+ for (let i = 0; i < extractedLinks.length && linkFields.length < maxFields; i += 1) {
338
+ const { text, url } = extractedLinks[i];
339
+ const label = text && text.length ? text : url;
340
+ // Field values have a 1024 char limit; keep it safe
341
+ const value = `[${label.substring(0, 200)}](${url})`;
342
+ linkFields.push({
343
+ name: `Lien ${linkFields.length + 1}`,
344
+ value: value.substring(0, 1024),
345
+ inline: false,
346
+ });
347
+ }
348
+
234
349
  const postForUser = await posts.getPostFields(isReply ? pid : topicData.mainPid, ['uid']);
235
350
  const username = await user.getUserField(postForUser.uid, 'username');
236
351
 
@@ -238,7 +353,7 @@ async function buildEmbed({ tid, pid, isReply }) {
238
353
  const embedTitle = `${replyIcon}${safeTitle} – ${username}`.slice(0, 256);
239
354
 
240
355
  // Embed title becomes clickable via embed.url
241
- return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: formatDiscordLinks(excerpt || '') } };
356
+ return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: stripLinkMarkup(excerpt || '') } };
242
357
  }
243
358
 
244
359
  function extractTidPid(data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-discord",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Discord webhook notifier for Onekite (NodeBB v4.x only)",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "acpScripts": [
32
32
  "public/admin.js"
33
33
  ],
34
- "version": "1.0.11"
34
+ "version": "1.0.12"
35
35
  }