nodebb-plugin-discord-onekite 1.0.11 → 1.0.13
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 +7 -3
- package/library.js +89 -47
- package/package.json +1 -1
- package/plugin.json +3 -3
- package/static/lib/{acp-toast.js → admin.js} +10 -8
- package/templates/admin/plugins/discord-onekite.tpl +7 -7
package/README.md
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
# nodebb-plugin-discord-onekite v1.
|
|
1
|
+
# nodebb-plugin-discord-onekite v1.1.2
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
-
|
|
3
|
+
Fix build error:
|
|
4
|
+
- Removed `acpScripts` and instead provides a proper AMD admin module via `modules` mapping.
|
|
5
|
+
This avoids RequireJS build failures.
|
|
6
|
+
|
|
7
|
+
- Toast "classique NodeBB" via app.alertSuccess when `?saved=1`.
|
|
8
|
+
- Discord notifications: minimal embed + fallback to plain content if Discord rejects embed.
|
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,69 @@ 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;
|
|
49
77
|
}
|
|
50
78
|
}
|
|
51
79
|
|
|
52
|
-
async function
|
|
53
|
-
|
|
80
|
+
async function sendDiscord(webhookUrl, payload, fallbackContent) {
|
|
81
|
+
if (!webhookUrl) return;
|
|
54
82
|
|
|
55
|
-
|
|
83
|
+
try {
|
|
84
|
+
await postToDiscord(webhookUrl, payload);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (e && e.statusCode === 400 && fallbackContent) {
|
|
87
|
+
try {
|
|
88
|
+
await postToDiscord(webhookUrl, { content: fallbackContent });
|
|
89
|
+
return;
|
|
90
|
+
} catch (e2) {
|
|
91
|
+
// eslint-disable-next-line no-console
|
|
92
|
+
console.error(e2);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// eslint-disable-next-line no-console
|
|
96
|
+
console.error(e);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function buildPayload({ tid, pid, isReply }) {
|
|
101
|
+
const baseUrl = normalizeBaseUrl(meta.config.url);
|
|
102
|
+
|
|
103
|
+
const topicData = await topics.getTopicFields(tid, ['tid', 'uid', 'cid', 'title', 'slug', 'mainPid']);
|
|
56
104
|
if (!topicData) return null;
|
|
57
105
|
|
|
58
|
-
const topicUrl =
|
|
59
|
-
const
|
|
106
|
+
const topicUrl = ensureAbsoluteUrl(baseUrl, `/topic/${topicData.slug || topicData.tid}`);
|
|
107
|
+
const url = isReply && pid ? `${topicUrl}/${pid}` : topicUrl;
|
|
108
|
+
|
|
109
|
+
const u = await user.getUserFields(topicData.uid, ['username']);
|
|
110
|
+
const title = (topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet')).toString().slice(0, 256);
|
|
111
|
+
const authorName = (u && u.username ? String(u.username) : 'Utilisateur').slice(0, 256);
|
|
60
112
|
|
|
61
113
|
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)' },
|
|
114
|
+
title,
|
|
115
|
+
description: isReply ? `Réponse de **${authorName}**` : `Sujet créé par **${authorName}**`,
|
|
67
116
|
};
|
|
68
117
|
|
|
69
|
-
if (
|
|
118
|
+
if (isValidHttpUrl(url)) {
|
|
119
|
+
embed.url = url;
|
|
120
|
+
}
|
|
70
121
|
|
|
71
|
-
|
|
122
|
+
const content = isReply
|
|
123
|
+
? `🗨️ Nouvelle réponse : ${title}\n${url}`
|
|
124
|
+
: `🆕 Nouveau sujet : ${title}\n${url}`;
|
|
125
|
+
|
|
126
|
+
return { topicData, embed, content };
|
|
72
127
|
}
|
|
73
128
|
|
|
74
129
|
function extractTidPid(data) {
|
|
@@ -115,50 +170,37 @@ Plugin.addAdminNavigation = (header) => {
|
|
|
115
170
|
return header;
|
|
116
171
|
};
|
|
117
172
|
|
|
118
|
-
// Fired when a new topic is posted (creation)
|
|
119
173
|
Plugin.onTopicPost = async (data) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!settings.webhookUrl) return;
|
|
174
|
+
const settings = await getSettings();
|
|
175
|
+
if (!settings.webhookUrl) return;
|
|
123
176
|
|
|
124
|
-
|
|
125
|
-
|
|
177
|
+
const { tid } = extractTidPid(data);
|
|
178
|
+
if (!tid) return;
|
|
126
179
|
|
|
127
|
-
|
|
128
|
-
|
|
180
|
+
const built = await buildPayload({ tid, isReply: false });
|
|
181
|
+
if (!built) return;
|
|
129
182
|
|
|
130
|
-
|
|
183
|
+
if (!cidAllowed(built.topicData.cid, settings.cids)) return;
|
|
131
184
|
|
|
132
|
-
|
|
133
|
-
} catch (err) {
|
|
134
|
-
// eslint-disable-next-line no-console
|
|
135
|
-
console.error(err);
|
|
136
|
-
}
|
|
185
|
+
await sendDiscord(settings.webhookUrl, { embeds: [built.embed] }, built.content);
|
|
137
186
|
};
|
|
138
187
|
|
|
139
|
-
// Fired when a reply is made in a topic
|
|
140
188
|
Plugin.onTopicReply = async (data) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (!settings.webhookUrl) return;
|
|
189
|
+
const settings = await getSettings();
|
|
190
|
+
if (!settings.notifyReplies) return;
|
|
191
|
+
if (!settings.webhookUrl) return;
|
|
145
192
|
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
const { tid, pid } = extractTidPid(data);
|
|
194
|
+
if (!tid || !pid) return;
|
|
148
195
|
|
|
149
|
-
|
|
150
|
-
|
|
196
|
+
const built = await buildPayload({ tid, pid, isReply: true });
|
|
197
|
+
if (!built) return;
|
|
151
198
|
|
|
152
|
-
|
|
153
|
-
if (built.topicData?.mainPid && String(built.topicData.mainPid) === String(pid)) return;
|
|
199
|
+
if (built.topicData?.mainPid && String(built.topicData.mainPid) === String(pid)) return;
|
|
154
200
|
|
|
155
|
-
|
|
201
|
+
if (!cidAllowed(built.topicData.cid, settings.cids)) return;
|
|
156
202
|
|
|
157
|
-
|
|
158
|
-
} catch (err) {
|
|
159
|
-
// eslint-disable-next-line no-console
|
|
160
|
-
console.error(err);
|
|
161
|
-
}
|
|
203
|
+
await sendDiscord(settings.webhookUrl, { embeds: [built.embed] }, built.content);
|
|
162
204
|
};
|
|
163
205
|
|
|
164
206
|
module.exports = Plugin;
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
/* global
|
|
2
|
+
/* global app */
|
|
3
3
|
|
|
4
|
-
(function () {
|
|
5
|
-
|
|
6
|
-
if (!ajaxify || !ajaxify.data || ajaxify.data.template !== 'admin/plugins/discord-onekite') return;
|
|
4
|
+
define('admin/plugins/discord-onekite', [], function () {
|
|
5
|
+
const ACP = {};
|
|
7
6
|
|
|
7
|
+
function toastIfSaved() {
|
|
8
8
|
try {
|
|
9
9
|
const url = new URL(window.location.href);
|
|
10
10
|
const saved = url.searchParams.get('saved');
|
|
@@ -12,7 +12,6 @@
|
|
|
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
|
}
|
|
@@ -21,6 +20,9 @@
|
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
23
|
+
ACP.init = function () {
|
|
24
|
+
toastIfSaved();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return ACP;
|
|
28
|
+
});
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
<div class="acp-page-container">
|
|
2
2
|
<h4>Discord Onekite</h4>
|
|
3
3
|
<p class="text-muted">
|
|
4
|
-
Notifications Discord via webhook
|
|
4
|
+
Notifications Discord via webhook.
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
<!-- IF saved -->
|
|
8
|
-
<div class="alert alert-success" role="alert">
|
|
9
|
-
Paramètres enregistrés !
|
|
10
|
-
</div>
|
|
11
|
-
<!-- ENDIF saved -->
|
|
12
|
-
|
|
13
7
|
<form role="form" method="post" action="/admin/plugins/discord-onekite/save">
|
|
14
8
|
<input type="hidden" name="_csrf" value="{config.csrf_token}" />
|
|
15
9
|
|
|
@@ -39,4 +33,10 @@
|
|
|
39
33
|
|
|
40
34
|
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
|
41
35
|
</form>
|
|
36
|
+
|
|
37
|
+
<noscript>
|
|
38
|
+
<!-- IF saved -->
|
|
39
|
+
<div class="alert alert-success mt-3" role="alert">Paramètres enregistrés !</div>
|
|
40
|
+
<!-- ENDIF saved -->
|
|
41
|
+
</noscript>
|
|
42
42
|
</div>
|