nodebb-plugin-onekite-discord 1.0.14 → 1.0.15

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 +16 -120
  2. package/package.json +1 -1
  3. package/plugin.json +1 -1
package/library.js CHANGED
@@ -2,25 +2,22 @@
2
2
 
3
3
 
4
4
 
5
-
6
-
7
5
  /**
8
- * Flatten any link-like markup to plain URLs so Discord auto-linkifies them.
9
- * Supported:
10
- * - [text](url)
11
- * - <a href="url">text</a>
12
- * - text(url) / text (url)
13
- * - bare URLs and www.*
6
+ * Discord embed descriptions do not support masked links like [text](url).
7
+ * To keep links clickable, flatten any link-like markup into plain URLs,
8
+ * which Discord auto-linkifies.
14
9
  */
15
10
  function flattenLinksToUrls(str) {
16
11
  if (!str || typeof str !== 'string') return str;
17
12
 
18
13
  const normalizeUrl = (u) => {
19
- if (!u) return u;
14
+ if (!u) return '';
20
15
  let url = String(u).trim();
21
16
  url = url.replace(/^<(.+)>$/, '$1');
17
+ // fix common typos
22
18
  url = url.replace(/^https:\//i, 'https://');
23
19
  url = url.replace(/^http:\//i, 'http://');
20
+ // add scheme for bare www.
24
21
  if (/^www\./i.test(url)) url = 'https://' + url;
25
22
  return url;
26
23
  };
@@ -30,85 +27,21 @@ function flattenLinksToUrls(str) {
30
27
  return normalizeUrl(href);
31
28
  });
32
29
 
33
- // Markdown [text](url) -> URL
30
+ // Markdown masked links -> URL
34
31
  str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, _text, url) => {
35
32
  return normalizeUrl(url);
36
33
  });
37
34
 
38
- // text(url) or text (url) -> URL
39
- str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\\s]+)\)/g, (_m, _text, url) => {
40
- return normalizeUrl(url);
41
- });
42
-
43
- return str;
44
- }
45
-
46
- ;
47
-
48
- const push = (text, url) => {
49
- const u = normalizeUrl(url);
50
- if (!u) return;
51
- const key = `${text || ''}||${u}`;
52
- if (seen.has(key)) return;
53
- seen.add(key);
54
- links.push({ text: (text || '').trim(), url: u });
55
- };
56
-
57
- // HTML links
58
- str.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi, (_m, href, text) => {
59
- push(text, href);
60
- return _m;
61
- });
62
-
63
- // Markdown masked links
64
- str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => {
65
- push(text, url);
66
- return _m;
67
- });
68
-
69
- // NodeBB rendered: "texte du lien(url)" or "texte du lien (url)"
70
- str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text, url) => {
71
- push(text, url);
72
- return _m;
73
- });
74
-
75
- // Bare URLs (avoid trailing punctuation)
76
- str.replace(/\b(https?:\/\/[^\s<>()]+)\b/g, (_m, url) => {
77
- push('', url.replace(/[),.;!?]+$/g, ''));
78
- return _m;
79
- });
80
- str.replace(/\b(www\.[^\s<>()]+)\b/g, (_m, url) => {
81
- push('', url.replace(/[),.;!?]+$/g, ''));
82
- return _m;
83
- });
84
-
85
- return links;
86
- }
87
-
88
- ?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text) => String(text).trim());
89
-
90
- return str;
91
- }
92
-
93
- ;
94
-
95
- // HTML <a href="...">...</a> -> URL
96
- str = str.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>.*?<\/a>/gi, (_m, href) => {
97
- return normalizeUrl(href);
98
- });
99
-
100
- // Markdown [text](url) -> URL
101
- str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, _text, url) => {
35
+ // NodeBB rendered: "texte du lien(url)" or "texte du lien (url)" -> URL
36
+ str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, _text, url) => {
102
37
  return normalizeUrl(url);
103
38
  });
104
39
 
105
- // Already-rendered form from NodeBB: texte du lien(url) or texte du lien (url)
106
- // Convert to: "texte du lien https://url" so Discord auto-linkifies.
107
- str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, text, url) => {
108
- const cleanText = String(text).trim();
109
- const cleanUrl = normalizeUrl(url);
110
- if (!cleanUrl) return _m;
111
- return cleanText ? `${cleanText} ${cleanUrl}` : cleanUrl;
40
+ // Ensure bare "www.xxx" tokens become "https://www.xxx"
41
+ str = str.replace(/\b(www\.[^\s<>()]+)\b/g, (_m, url) => {
42
+ // strip trailing punctuation
43
+ const clean = url.replace(/[),.;!?]+$/g, '');
44
+ return normalizeUrl(clean);
112
45
  });
113
46
 
114
47
  return str;
@@ -155,26 +88,6 @@ function ensureAbsoluteUrl(baseUrl, maybeUrl) {
155
88
  return s;
156
89
  }
157
90
 
158
-
159
- function normalizeExcerptLinks(input) {
160
- if (!input) return '';
161
- let s = String(input);
162
-
163
- // Convert HTML anchors to "text (url)" before stripping HTML
164
- s = s.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi, (m, href, inner) => {
165
- const text = stripHtml(inner).trim() || href;
166
- return `${text} (${href})`;
167
- });
168
-
169
- // Convert Markdown links [text](url) to "text (url)"
170
- s = s.replace(/\[([^\]]+)\]\(([^\s)]+)\)/g, (m, label, url) => `${label} (${url})`);
171
-
172
- // Fix common malformed scheme "https:/example.com" -> "https://example.com"
173
- s = s.replace(/\b(https?):\/(?!\/)/g, '$1://');
174
-
175
- return s;
176
- }
177
-
178
91
  function stripHtml(html) {
179
92
  if (!html) return '';
180
93
  return String(html)
@@ -276,8 +189,7 @@ async function getPostExcerpt(pid) {
276
189
  if (!pid) return '';
277
190
  try {
278
191
  const p = await posts.getPostFields(pid, ['content']);
279
- const normalized = normalizeExcerptLinks(p && p.content);
280
- const text = stripHtml(normalized);
192
+ const text = stripHtml(p && p.content);
281
193
  return truncate(text, 500);
282
194
  } catch {
283
195
  return '';
@@ -308,22 +220,6 @@ async function buildEmbed({ tid, pid, isReply }) {
308
220
 
309
221
  const excerpt = await getPostExcerpt(isReply ? pid : topicData.mainPid);
310
222
 
311
- // Extract all links and put them in embed fields (Discord supports masked links in fields)
312
- const extractedLinks = extractDiscordLinks(excerpt || '');
313
- const linkFields = [];
314
- const maxFields = 25; // Discord limit
315
- for (let i = 0; i < extractedLinks.length && linkFields.length < maxFields; i += 1) {
316
- const { text, url } = extractedLinks[i];
317
- const label = text && text.length ? text : url;
318
- // Field values have a 1024 char limit; keep it safe
319
- const value = `[${label.substring(0, 200)}](${url})`;
320
- linkFields.push({
321
- name: `Lien ${linkFields.length + 1}`,
322
- value: value.substring(0, 1024),
323
- inline: false,
324
- });
325
- }
326
-
327
223
  const postForUser = await posts.getPostFields(isReply ? pid : topicData.mainPid, ['uid']);
328
224
  const username = await user.getUserField(postForUser.uid, 'username');
329
225
 
@@ -331,7 +227,7 @@ for (let i = 0; i < extractedLinks.length && linkFields.length < maxFields; i +=
331
227
  const embedTitle = `${replyIcon}${safeTitle} – ${username}`.slice(0, 256);
332
228
 
333
229
  // Embed title becomes clickable via embed.url
334
- return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: flattenLinksToUrls(excerpt || ''), fields: linkFields } };
230
+ return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: flattenLinksToUrls(excerpt || '') } };
335
231
  }
336
232
 
337
233
  function extractTidPid(data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-onekite-discord",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
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.14"
34
+ "version": "1.0.15"
35
35
  }