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.
- package/library.js +16 -120
- package/package.json +1 -1
- package/plugin.json +1 -1
package/library.js
CHANGED
|
@@ -2,25 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
5
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
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
|
|
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
|
|
30
|
+
// Markdown masked links -> URL
|
|
34
31
|
str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, _text, url) => {
|
|
35
32
|
return normalizeUrl(url);
|
|
36
33
|
});
|
|
37
34
|
|
|
38
|
-
//
|
|
39
|
-
str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
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
|
|
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 || '')
|
|
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
package/plugin.json
CHANGED