nodebb-plugin-onekite-discord 1.0.11 → 1.0.13

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 -5
  2. package/package.json +1 -1
  3. package/plugin.json +1 -1
package/library.js CHANGED
@@ -1,5 +1,99 @@
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
+ };
34
+
35
+ const push = (text, url) => {
36
+ const u = normalizeUrl(url);
37
+ if (!u) return;
38
+ const key = `${text || ''}||${u}`;
39
+ if (seen.has(key)) return;
40
+ seen.add(key);
41
+ links.push({ text: (text || '').trim(), url: u });
42
+ };
43
+
44
+ // HTML links
45
+ str.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi, (_m, href, text) => {
46
+ push(text, href);
47
+ return _m;
48
+ });
49
+
50
+ // Markdown masked links
51
+ str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => {
52
+ push(text, url);
53
+ return _m;
54
+ });
55
+
56
+ // NodeBB rendered: "texte du lien(url)" or "texte du lien (url)"
57
+ str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text, url) => {
58
+ push(text, url);
59
+ return _m;
60
+ });
61
+
62
+ // Bare URLs (avoid trailing punctuation)
63
+ str.replace(/\b(https?:\/\/[^\s<>()]+)\b/g, (_m, url) => {
64
+ push('', url.replace(/[),.;!?]+$/g, ''));
65
+ return _m;
66
+ });
67
+ str.replace(/\b(www\.[^\s<>()]+)\b/g, (_m, url) => {
68
+ push('', url.replace(/[),.;!?]+$/g, ''));
69
+ return _m;
70
+ });
71
+
72
+ return links;
73
+ }
74
+
75
+ /**
76
+ * Remove/flatten link markup from text for embed.description readability.
77
+ * - [text](url) -> text
78
+ * - <a href="url">text</a> -> text
79
+ * - text(url) -> text
80
+ * - leaves bare URLs as-is (optional), but we can also remove them if desired.
81
+ */
82
+ function stripLinkMarkup(str) {
83
+ if (!str || typeof str !== 'string') return str;
84
+
85
+ // HTML links -> text
86
+ str = str.replace(/<a\s+[^>]*href=["'][^"']+["'][^>]*>(.*?)<\/a>/gi, (_m, text) => text);
87
+
88
+ // Markdown masked -> text
89
+ str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text) => text);
90
+
91
+ // NodeBB rendered -> text
92
+ str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text) => String(text).trim());
93
+
94
+ return str;
95
+ }
96
+
3
97
  /**
4
98
  * Normalize links for Discord embeds.
5
99
  * Discord does NOT support masked links in embed descriptions.
@@ -29,9 +123,13 @@ function formatDiscordLinks(str) {
29
123
  return normalizeUrl(url);
30
124
  });
31
125
 
32
- // Already-rendered form: texte(url)
33
- str = str.replace(/([^\s(]+)\((https?:\/\/[^)]+|www\.[^)]+)\)/g, (_m, _text, url) => {
34
- return normalizeUrl(url);
126
+ // Already-rendered form from NodeBB: texte du lien(url) or texte du lien (url)
127
+ // Convert to: "texte du lien https://url" so Discord auto-linkifies.
128
+ str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text, url) => {
129
+ const cleanText = String(text).trim();
130
+ const cleanUrl = normalizeUrl(url);
131
+ if (!cleanUrl) return _m;
132
+ return cleanText ? `${cleanText} ${cleanUrl}` : cleanUrl;
35
133
  });
36
134
 
37
135
  return str;
@@ -231,6 +329,22 @@ async function buildEmbed({ tid, pid, isReply }) {
231
329
 
232
330
  const excerpt = await getPostExcerpt(isReply ? pid : topicData.mainPid);
233
331
 
332
+ // Extract all links and put them in embed fields (Discord supports masked links in fields)
333
+ const extractedLinks = extractDiscordLinks(excerpt || '');
334
+ const linkFields = [];
335
+ const maxFields = 25; // Discord limit
336
+ for (let i = 0; i < extractedLinks.length && linkFields.length < maxFields; i += 1) {
337
+ const { text, url } = extractedLinks[i];
338
+ const label = text && text.length ? text : url;
339
+ // Field values have a 1024 char limit; keep it safe
340
+ const value = `[${label.substring(0, 200)}](${url})`;
341
+ linkFields.push({
342
+ name: `Lien ${linkFields.length + 1}`,
343
+ value: value.substring(0, 1024),
344
+ inline: false,
345
+ });
346
+ }
347
+
234
348
  const postForUser = await posts.getPostFields(isReply ? pid : topicData.mainPid, ['uid']);
235
349
  const username = await user.getUserField(postForUser.uid, 'username');
236
350
 
@@ -238,7 +352,7 @@ async function buildEmbed({ tid, pid, isReply }) {
238
352
  const embedTitle = `${replyIcon}${safeTitle} – ${username}`.slice(0, 256);
239
353
 
240
354
  // Embed title becomes clickable via embed.url
241
- return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: formatDiscordLinks(excerpt || '') } };
355
+ return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: stripLinkMarkup(excerpt || ''), fields: linkFields } };
242
356
  }
243
357
 
244
358
  function extractTidPid(data) {
@@ -301,4 +415,4 @@ Plugin.onTopicReply = async (data) => {
301
415
  await sendDiscord(settings.webhookUrl, { content: built.content, embeds: [built.embed] });
302
416
  };
303
417
 
304
- module.exports = Plugin;
418
+ module.exports = Plugin;
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.13",
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.13"
35
35
  }