nodebb-plugin-onekite-discord 1.0.10 → 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 +122 -24
  2. package/package.json +1 -1
  3. package/plugin.json +1 -1
package/library.js CHANGED
@@ -1,56 +1,138 @@
1
+ 'use strict';
2
+
3
+
4
+
1
5
  /**
2
- * Normalize links for Discord embeds.
3
- * Discord does NOT support masked links in embed descriptions.
4
- * NodeBB may already render links as: texte(url)
5
- * We always output a plain URL so Discord auto-linkifies it.
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.
6
16
  */
7
- function formatDiscordLinks(str) {
8
- if (!str || typeof str !== 'string') return str;
17
+ function extractDiscordLinks(str) {
18
+ if (!str || typeof str !== 'string') return [];
19
+
20
+ const links = [];
21
+ const seen = new Set();
9
22
 
10
23
  const normalizeUrl = (u) => {
11
- if (!u) return u;
24
+ if (!u) return null;
12
25
  let url = String(u).trim();
13
26
  url = url.replace(/^<(.+)>$/, '$1');
14
27
  url = url.replace(/^https:\//i, 'https://');
15
28
  url = url.replace(/^http:\//i, 'http://');
16
29
  if (/^www\./i.test(url)) url = 'https://' + url;
30
+ // Very small sanity check
31
+ if (!/^https?:\/\//i.test(url)) return null;
17
32
  return url;
33
+ fields: linkFields,
18
34
  };
19
35
 
20
- // HTML <a href>
21
- str = str.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>.*?<\/a>/gi, (_m, href) => {
22
- return normalizeUrl(href);
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;
23
49
  });
24
50
 
25
- // Markdown [text](url)
26
- str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, _text, url) => {
27
- return normalizeUrl(url);
51
+ // Markdown masked links
52
+ str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => {
53
+ push(text, url);
54
+ return _m;
28
55
  });
29
56
 
30
- // Already-rendered form: texte(url)
31
- str = str.replace(/([^\s(]+)\((https?:\/\/[^)]+|www\.[^)]+)\)/g, (_m, _text, url) => {
32
- return normalizeUrl(url);
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;
33
61
  });
34
62
 
35
- return str;
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;
36
74
  }
37
75
 
38
- 'use strict';
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;
39
85
 
86
+ // HTML links -> text
87
+ str = str.replace(/<a\s+[^>]*href=["'][^"']+["'][^>]*>(.*?)<\/a>/gi, (_m, text) => text);
40
88
 
89
+ // Markdown masked -> text
90
+ str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text) => text);
41
91
 
42
- ;
92
+ // NodeBB rendered -> text
93
+ str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text) => String(text).trim());
43
94
 
44
- // HTML links -> URL
45
- str = str.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi, (_m, href, _text) => {
95
+ return str;
96
+ }
97
+
98
+ /**
99
+ * Normalize links for Discord embeds.
100
+ * Discord does NOT support masked links in embed descriptions.
101
+ * NodeBB may already render links as: texte(url)
102
+ * We always output a plain URL so Discord auto-linkifies it.
103
+ */
104
+ function formatDiscordLinks(str) {
105
+ if (!str || typeof str !== 'string') return str;
106
+
107
+ const normalizeUrl = (u) => {
108
+ if (!u) return u;
109
+ let url = String(u).trim();
110
+ url = url.replace(/^<(.+)>$/, '$1');
111
+ url = url.replace(/^https:\//i, 'https://');
112
+ url = url.replace(/^http:\//i, 'http://');
113
+ if (/^www\./i.test(url)) url = 'https://' + url;
114
+ return url;
115
+ };
116
+
117
+ // HTML <a href="...">...</a> -> URL
118
+ str = str.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>.*?<\/a>/gi, (_m, href) => {
46
119
  return normalizeUrl(href);
47
120
  });
48
121
 
49
- // Markdown links [text](url) -> URL
122
+ // Markdown [text](url) -> URL
50
123
  str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, _text, url) => {
51
124
  return normalizeUrl(url);
52
125
  });
53
126
 
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;
134
+ });
135
+
54
136
  return str;
55
137
  }
56
138
 
@@ -248,6 +330,22 @@ async function buildEmbed({ tid, pid, isReply }) {
248
330
 
249
331
  const excerpt = await getPostExcerpt(isReply ? pid : topicData.mainPid);
250
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
+
251
349
  const postForUser = await posts.getPostFields(isReply ? pid : topicData.mainPid, ['uid']);
252
350
  const username = await user.getUserField(postForUser.uid, 'username');
253
351
 
@@ -255,7 +353,7 @@ async function buildEmbed({ tid, pid, isReply }) {
255
353
  const embedTitle = `${replyIcon}${safeTitle} – ${username}`.slice(0, 256);
256
354
 
257
355
  // Embed title becomes clickable via embed.url
258
- return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: formatDiscordLinks(excerpt || '') } };
356
+ return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: stripLinkMarkup(excerpt || '') } };
259
357
  }
260
358
 
261
359
  function extractTidPid(data) {
@@ -318,4 +416,4 @@ Plugin.onTopicReply = async (data) => {
318
416
  await sendDiscord(settings.webhookUrl, { content: built.content, embeds: [built.embed] });
319
417
  };
320
418
 
321
- module.exports = Plugin;
419
+ module.exports = Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-discord",
3
- "version": "1.0.10",
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.10"
34
+ "version": "1.0.12"
35
35
  }