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.
- package/library.js +122 -24
- package/package.json +1 -1
- package/plugin.json +1 -1
package/library.js
CHANGED
|
@@ -1,56 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
1
5
|
/**
|
|
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.
|
|
6
16
|
*/
|
|
7
|
-
function
|
|
8
|
-
if (!str || typeof str !== 'string') return
|
|
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
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
26
|
-
str
|
|
27
|
-
|
|
51
|
+
// Markdown masked links
|
|
52
|
+
str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => {
|
|
53
|
+
push(text, url);
|
|
54
|
+
return _m;
|
|
28
55
|
});
|
|
29
56
|
|
|
30
|
-
//
|
|
31
|
-
str
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
|
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:
|
|
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
package/plugin.json
CHANGED