nodebb-plugin-discord-onekite 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/README.md +4 -3
- package/library.js +93 -47
- package/package.json +1 -1
- package/static/lib/acp-toast.js +1 -4
package/README.md
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
# nodebb-plugin-discord-onekite v1.
|
|
1
|
+
# nodebb-plugin-discord-onekite v1.1.1
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
-
|
|
3
|
+
Fix:
|
|
4
|
+
- Makes embeds ultra-minimal (title/description/url only).
|
|
5
|
+
- On Discord 400 embed validation error, retries with plain `content` so a notification is always delivered.
|
package/library.js
CHANGED
|
@@ -20,6 +20,32 @@ function normalizeCids(value) {
|
|
|
20
20
|
return [];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
function normalizeBaseUrl(url) {
|
|
24
|
+
if (!url) return '';
|
|
25
|
+
let s = String(url).trim();
|
|
26
|
+
if (!/^https?:\/\//i.test(s)) s = `https://${s}`;
|
|
27
|
+
s = s.replace(/\/+$/, '');
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ensureAbsoluteUrl(baseUrl, maybeUrl) {
|
|
32
|
+
if (!maybeUrl) return '';
|
|
33
|
+
const s = String(maybeUrl);
|
|
34
|
+
if (/^https?:\/\//i.test(s)) return s;
|
|
35
|
+
if (s.startsWith('//')) return `https:${s}`;
|
|
36
|
+
if (s.startsWith('/')) return `${baseUrl}${s}`;
|
|
37
|
+
return s;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isValidHttpUrl(s) {
|
|
41
|
+
try {
|
|
42
|
+
const u = new URL(s);
|
|
43
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
23
49
|
async function getSettings() {
|
|
24
50
|
const s = await meta.settings.get(SETTINGS_KEY);
|
|
25
51
|
return {
|
|
@@ -35,40 +61,73 @@ function cidAllowed(topicCid, allowedCids) {
|
|
|
35
61
|
}
|
|
36
62
|
|
|
37
63
|
async function postToDiscord(webhookUrl, payload) {
|
|
38
|
-
if (!webhookUrl) return;
|
|
39
|
-
|
|
40
64
|
const res = await request(webhookUrl, {
|
|
41
65
|
method: 'POST',
|
|
42
66
|
headers: { 'Content-Type': 'application/json' },
|
|
43
67
|
body: JSON.stringify(payload),
|
|
44
68
|
});
|
|
45
69
|
|
|
70
|
+
const bodyText = await res.body.text().catch(() => '');
|
|
71
|
+
|
|
46
72
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
47
|
-
const
|
|
48
|
-
|
|
73
|
+
const err = new Error(`[discord-onekite] Discord webhook HTTP ${res.statusCode}: ${bodyText}`);
|
|
74
|
+
err.statusCode = res.statusCode;
|
|
75
|
+
err.responseBody = bodyText;
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function sendDiscord(webhookUrl, payload, fallbackContent) {
|
|
81
|
+
if (!webhookUrl) return;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await postToDiscord(webhookUrl, payload);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
// Discord embed validation error is commonly 400 with {"embeds":["0"]}
|
|
87
|
+
// Retry with plain content so notifications still arrive.
|
|
88
|
+
if (e && e.statusCode === 400 && fallbackContent) {
|
|
89
|
+
try {
|
|
90
|
+
await postToDiscord(webhookUrl, { content: fallbackContent });
|
|
91
|
+
return;
|
|
92
|
+
} catch (e2) {
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.error(e2);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// eslint-disable-next-line no-console
|
|
98
|
+
console.error(e);
|
|
49
99
|
}
|
|
50
100
|
}
|
|
51
101
|
|
|
52
|
-
async function
|
|
53
|
-
const baseUrl = meta.config.url;
|
|
102
|
+
async function buildPayload({ tid, pid, isReply }) {
|
|
103
|
+
const baseUrl = normalizeBaseUrl(meta.config.url);
|
|
54
104
|
|
|
55
|
-
const topicData = await topics.getTopicFields(tid, ['tid', 'uid', 'cid', 'title', 'slug', '
|
|
105
|
+
const topicData = await topics.getTopicFields(tid, ['tid', 'uid', 'cid', 'title', 'slug', 'mainPid']);
|
|
56
106
|
if (!topicData) return null;
|
|
57
107
|
|
|
58
|
-
const topicUrl =
|
|
59
|
-
const
|
|
108
|
+
const topicUrl = ensureAbsoluteUrl(baseUrl, `/topic/${topicData.slug || topicData.tid}`);
|
|
109
|
+
const url = isReply && pid ? `${topicUrl}/${pid}` : topicUrl;
|
|
110
|
+
|
|
111
|
+
const u = await user.getUserFields(topicData.uid, ['username']);
|
|
112
|
+
|
|
113
|
+
const title = (topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet')).toString().slice(0, 256);
|
|
114
|
+
const authorName = (u && u.username ? String(u.username) : 'Utilisateur').slice(0, 256);
|
|
60
115
|
|
|
116
|
+
// Super-minimal embed to avoid Discord rejecting unknown/invalid fields.
|
|
61
117
|
const embed = {
|
|
62
|
-
title
|
|
63
|
-
|
|
64
|
-
timestamp: new Date(Date.now()).toISOString(),
|
|
65
|
-
author: u?.username ? { name: u.username, icon_url: u.picture || undefined } : undefined,
|
|
66
|
-
footer: { text: 'NodeBB → Discord (Onekite)' },
|
|
118
|
+
title,
|
|
119
|
+
description: isReply ? `Réponse de **${authorName}**` : `Sujet créé par **${authorName}**`,
|
|
67
120
|
};
|
|
68
121
|
|
|
69
|
-
if (
|
|
122
|
+
if (isValidHttpUrl(url)) {
|
|
123
|
+
embed.url = url;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const content = isReply
|
|
127
|
+
? `🗨️ Nouvelle réponse : ${title}\n${url}`
|
|
128
|
+
: `🆕 Nouveau sujet : ${title}\n${url}`;
|
|
70
129
|
|
|
71
|
-
return { topicData, embed };
|
|
130
|
+
return { topicData, embed, content };
|
|
72
131
|
}
|
|
73
132
|
|
|
74
133
|
function extractTidPid(data) {
|
|
@@ -115,50 +174,37 @@ Plugin.addAdminNavigation = (header) => {
|
|
|
115
174
|
return header;
|
|
116
175
|
};
|
|
117
176
|
|
|
118
|
-
// Fired when a new topic is posted (creation)
|
|
119
177
|
Plugin.onTopicPost = async (data) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!settings.webhookUrl) return;
|
|
178
|
+
const settings = await getSettings();
|
|
179
|
+
if (!settings.webhookUrl) return;
|
|
123
180
|
|
|
124
|
-
|
|
125
|
-
|
|
181
|
+
const { tid } = extractTidPid(data);
|
|
182
|
+
if (!tid) return;
|
|
126
183
|
|
|
127
|
-
|
|
128
|
-
|
|
184
|
+
const built = await buildPayload({ tid, isReply: false });
|
|
185
|
+
if (!built) return;
|
|
129
186
|
|
|
130
|
-
|
|
187
|
+
if (!cidAllowed(built.topicData.cid, settings.cids)) return;
|
|
131
188
|
|
|
132
|
-
|
|
133
|
-
} catch (err) {
|
|
134
|
-
// eslint-disable-next-line no-console
|
|
135
|
-
console.error(err);
|
|
136
|
-
}
|
|
189
|
+
await sendDiscord(settings.webhookUrl, { embeds: [built.embed] }, built.content);
|
|
137
190
|
};
|
|
138
191
|
|
|
139
|
-
// Fired when a reply is made in a topic
|
|
140
192
|
Plugin.onTopicReply = async (data) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!settings.webhookUrl) return;
|
|
193
|
+
const settings = await getSettings();
|
|
194
|
+
if (!settings.notifyReplies) return;
|
|
195
|
+
if (!settings.webhookUrl) return;
|
|
145
196
|
|
|
146
|
-
|
|
147
|
-
|
|
197
|
+
const { tid, pid } = extractTidPid(data);
|
|
198
|
+
if (!tid || !pid) return;
|
|
148
199
|
|
|
149
|
-
|
|
150
|
-
|
|
200
|
+
const built = await buildPayload({ tid, pid, isReply: true });
|
|
201
|
+
if (!built) return;
|
|
151
202
|
|
|
152
|
-
|
|
153
|
-
if (built.topicData?.mainPid && String(built.topicData.mainPid) === String(pid)) return;
|
|
203
|
+
if (built.topicData?.mainPid && String(built.topicData.mainPid) === String(pid)) return;
|
|
154
204
|
|
|
155
|
-
|
|
205
|
+
if (!cidAllowed(built.topicData.cid, settings.cids)) return;
|
|
156
206
|
|
|
157
|
-
|
|
158
|
-
} catch (err) {
|
|
159
|
-
// eslint-disable-next-line no-console
|
|
160
|
-
console.error(err);
|
|
161
|
-
}
|
|
207
|
+
await sendDiscord(settings.webhookUrl, { embeds: [built.embed] }, built.content);
|
|
162
208
|
};
|
|
163
209
|
|
|
164
210
|
module.exports = Plugin;
|
package/package.json
CHANGED
package/static/lib/acp-toast.js
CHANGED
|
@@ -12,13 +12,10 @@
|
|
|
12
12
|
if (window.app && typeof app.alertSuccess === 'function') {
|
|
13
13
|
app.alertSuccess('Paramètres enregistrés !');
|
|
14
14
|
}
|
|
15
|
-
// remove the query param to avoid re-toasting on navigation
|
|
16
15
|
url.searchParams.delete('saved');
|
|
17
16
|
window.history.replaceState({}, document.title, url.toString());
|
|
18
17
|
}
|
|
19
|
-
} catch (e) {
|
|
20
|
-
// ignore
|
|
21
|
-
}
|
|
18
|
+
} catch (e) {}
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
$(window).on('action:ajaxify.end', showToastIfSaved);
|