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.
- package/library.js +119 -4
- package/package.json +1 -1
- 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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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:
|
|
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
package/plugin.json
CHANGED