nodebb-plugin-discord-onekite 1.0.13 → 1.0.14
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 +8 -6
- package/lib/controllers.js +0 -1
- package/library.js +52 -12
- package/package.json +1 -1
- package/static/lib/admin.js +28 -17
- package/templates/admin/plugins/discord-onekite.tpl +2 -10
package/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
# nodebb-plugin-discord-onekite v1.1.
|
|
1
|
+
# nodebb-plugin-discord-onekite v1.1.3
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
-
|
|
5
|
-
|
|
3
|
+
ACP:
|
|
4
|
+
- Uses the standard NodeBB save "disk" button (IMPORT admin/partials/save_button.tpl)
|
|
5
|
+
- Saves via AJAX to `/api/admin/plugins/discord-onekite/save` and shows NodeBB toast (bottom-right)
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
-
|
|
7
|
+
Discord message:
|
|
8
|
+
- Notification content starts with the direct post URL
|
|
9
|
+
- Includes an excerpt of the post content (truncated)
|
|
10
|
+
- Keeps embed minimal + fallback to plain content if Discord rejects embeds
|
package/lib/controllers.js
CHANGED
|
@@ -40,7 +40,6 @@ controllers.renderAdminPage = async function (req, res) {
|
|
|
40
40
|
res.render('admin/plugins/discord-onekite', {
|
|
41
41
|
settings: settings || {},
|
|
42
42
|
categories: categoriesForTpl,
|
|
43
|
-
saved: req.query && (req.query.saved === '1' || req.query.saved === 'true'),
|
|
44
43
|
});
|
|
45
44
|
};
|
|
46
45
|
|
package/library.js
CHANGED
|
@@ -7,6 +7,7 @@ const meta = require.main.require('./src/meta');
|
|
|
7
7
|
const middleware = require.main.require('./src/middleware');
|
|
8
8
|
|
|
9
9
|
const topics = require.main.require('./src/topics');
|
|
10
|
+
const posts = require.main.require('./src/posts');
|
|
10
11
|
const user = require.main.require('./src/user');
|
|
11
12
|
|
|
12
13
|
const controllers = require('./lib/controllers');
|
|
@@ -46,6 +47,27 @@ function isValidHttpUrl(s) {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
function stripHtml(html) {
|
|
51
|
+
if (!html) return '';
|
|
52
|
+
return String(html)
|
|
53
|
+
.replace(/<br\s*\/?>/gi, '\n')
|
|
54
|
+
.replace(/<\/p>/gi, '\n')
|
|
55
|
+
.replace(/<[^>]*>/g, '')
|
|
56
|
+
.replace(/ /g, ' ')
|
|
57
|
+
.replace(/&/g, '&')
|
|
58
|
+
.replace(/</g, '<')
|
|
59
|
+
.replace(/>/g, '>')
|
|
60
|
+
.replace(/'/g, "'")
|
|
61
|
+
.replace(/"/g, '"')
|
|
62
|
+
.trim();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function truncate(s, n) {
|
|
66
|
+
const str = String(s || '');
|
|
67
|
+
if (str.length <= n) return str;
|
|
68
|
+
return str.slice(0, n - 1) + '…';
|
|
69
|
+
}
|
|
70
|
+
|
|
49
71
|
async function getSettings() {
|
|
50
72
|
const s = await meta.settings.get(SETTINGS_KEY);
|
|
51
73
|
return {
|
|
@@ -88,15 +110,24 @@ async function sendDiscord(webhookUrl, payload, fallbackContent) {
|
|
|
88
110
|
await postToDiscord(webhookUrl, { content: fallbackContent });
|
|
89
111
|
return;
|
|
90
112
|
} catch (e2) {
|
|
91
|
-
// eslint-disable-next-line no-console
|
|
92
113
|
console.error(e2);
|
|
93
114
|
}
|
|
94
115
|
}
|
|
95
|
-
// eslint-disable-next-line no-console
|
|
96
116
|
console.error(e);
|
|
97
117
|
}
|
|
98
118
|
}
|
|
99
119
|
|
|
120
|
+
async function getPostExcerpt(pid) {
|
|
121
|
+
if (!pid) return '';
|
|
122
|
+
try {
|
|
123
|
+
const p = await posts.getPostFields(pid, ['content']);
|
|
124
|
+
const text = stripHtml(p && p.content);
|
|
125
|
+
return truncate(text, 350);
|
|
126
|
+
} catch {
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
100
131
|
async function buildPayload({ tid, pid, isReply }) {
|
|
101
132
|
const baseUrl = normalizeBaseUrl(meta.config.url);
|
|
102
133
|
|
|
@@ -104,24 +135,32 @@ async function buildPayload({ tid, pid, isReply }) {
|
|
|
104
135
|
if (!topicData) return null;
|
|
105
136
|
|
|
106
137
|
const topicUrl = ensureAbsoluteUrl(baseUrl, `/topic/${topicData.slug || topicData.tid}`);
|
|
107
|
-
const
|
|
138
|
+
const postUrl = isReply && pid ? `${topicUrl}/${pid}` : `${topicUrl}/${topicData.mainPid || ''}`.replace(/\/$/, '');
|
|
108
139
|
|
|
109
140
|
const u = await user.getUserFields(topicData.uid, ['username']);
|
|
110
141
|
const title = (topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet')).toString().slice(0, 256);
|
|
111
142
|
const authorName = (u && u.username ? String(u.username) : 'Utilisateur').slice(0, 256);
|
|
112
143
|
|
|
144
|
+
const excerpt = await getPostExcerpt(isReply ? pid : topicData.mainPid);
|
|
145
|
+
const description = truncate(excerpt || (isReply ? `Réponse de ${authorName}` : `Sujet créé par ${authorName}`), 350);
|
|
146
|
+
|
|
113
147
|
const embed = {
|
|
114
148
|
title,
|
|
115
|
-
description
|
|
149
|
+
description,
|
|
116
150
|
};
|
|
117
151
|
|
|
118
|
-
if (isValidHttpUrl(
|
|
119
|
-
embed.url =
|
|
152
|
+
if (isValidHttpUrl(postUrl)) {
|
|
153
|
+
embed.url = postUrl;
|
|
120
154
|
}
|
|
121
155
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
156
|
+
// "Lien vers le message directement en début de notification"
|
|
157
|
+
const contentLines = [
|
|
158
|
+
postUrl,
|
|
159
|
+
isReply ? `🗨️ Nouvelle réponse : ${title}` : `🆕 Nouveau sujet : ${title}`,
|
|
160
|
+
];
|
|
161
|
+
if (description) contentLines.push(description);
|
|
162
|
+
|
|
163
|
+
const content = contentLines.join('\n');
|
|
125
164
|
|
|
126
165
|
return { topicData, embed, content };
|
|
127
166
|
}
|
|
@@ -142,7 +181,8 @@ Plugin.init = async ({ router }) => {
|
|
|
142
181
|
controllers.renderAdminPage
|
|
143
182
|
);
|
|
144
183
|
|
|
145
|
-
|
|
184
|
+
// AJAX save endpoint for ACP (used by admin.js)
|
|
185
|
+
router.post('/api/admin/plugins/discord-onekite/save',
|
|
146
186
|
middleware.admin.checkPrivileges,
|
|
147
187
|
async (req, res) => {
|
|
148
188
|
try {
|
|
@@ -152,11 +192,11 @@ Plugin.init = async ({ router }) => {
|
|
|
152
192
|
cids: req.body.cids || '',
|
|
153
193
|
};
|
|
154
194
|
await meta.settings.set(SETTINGS_KEY, payload);
|
|
195
|
+
res.json({ ok: true });
|
|
155
196
|
} catch (e) {
|
|
156
|
-
// eslint-disable-next-line no-console
|
|
157
197
|
console.error('[discord-onekite] save failed', e);
|
|
198
|
+
res.status(500).json({ ok: false });
|
|
158
199
|
}
|
|
159
|
-
res.redirect('/admin/plugins/discord-onekite?saved=1');
|
|
160
200
|
}
|
|
161
201
|
);
|
|
162
202
|
};
|
package/package.json
CHANGED
package/static/lib/admin.js
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
/* global app */
|
|
2
|
+
/* global $, app */
|
|
3
3
|
|
|
4
|
-
define('admin/plugins/discord-onekite', [], function () {
|
|
4
|
+
define('admin/plugins/discord-onekite', ['api'], function (api) {
|
|
5
5
|
const ACP = {};
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (saved === '1' || saved === 'true') {
|
|
12
|
-
if (window.app && typeof app.alertSuccess === 'function') {
|
|
13
|
-
app.alertSuccess('Paramètres enregistrés !');
|
|
14
|
-
}
|
|
15
|
-
url.searchParams.delete('saved');
|
|
16
|
-
window.history.replaceState({}, document.title, url.toString());
|
|
17
|
-
}
|
|
18
|
-
} catch (e) {
|
|
19
|
-
// ignore
|
|
20
|
-
}
|
|
7
|
+
function getMultiSelectValues(selector) {
|
|
8
|
+
const el = document.querySelector(selector);
|
|
9
|
+
if (!el) return [];
|
|
10
|
+
return Array.from(el.selectedOptions || []).map(o => o.value);
|
|
21
11
|
}
|
|
22
12
|
|
|
23
13
|
ACP.init = function () {
|
|
24
|
-
|
|
14
|
+
const $form = $('.discord-onekite-settings');
|
|
15
|
+
if (!$form.length) return;
|
|
16
|
+
|
|
17
|
+
$('#save').off('click.discordOnekite').on('click.discordOnekite', function () {
|
|
18
|
+
const payload = {
|
|
19
|
+
webhookUrl: String($('#webhookUrl').val() || '').trim(),
|
|
20
|
+
notifyReplies: $('#notifyReplies').is(':checked'),
|
|
21
|
+
cids: getMultiSelectValues('#cids'),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
api.post('/admin/plugins/discord-onekite/save', payload).then(function () {
|
|
25
|
+
if (window.app && typeof app.alertSuccess === 'function') {
|
|
26
|
+
app.alertSuccess('Paramètres enregistrés !');
|
|
27
|
+
}
|
|
28
|
+
}).catch(function (err) {
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.error(err);
|
|
31
|
+
if (window.app && typeof app.alertError === 'function') {
|
|
32
|
+
app.alertError('Erreur lors de l’enregistrement');
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
});
|
|
25
36
|
};
|
|
26
37
|
|
|
27
38
|
return ACP;
|
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
Notifications Discord via webhook.
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
<form role="form"
|
|
8
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}" />
|
|
9
|
-
|
|
7
|
+
<form role="form" class="discord-onekite-settings">
|
|
10
8
|
<div class="mb-3">
|
|
11
9
|
<label class="form-label" for="webhookUrl">Discord Webhook URL</label>
|
|
12
10
|
<input type="text" class="form-control" id="webhookUrl" name="webhookUrl" value="{settings.webhookUrl}" placeholder="https://discord.com/api/webhooks/..." />
|
|
@@ -31,12 +29,6 @@
|
|
|
31
29
|
</p>
|
|
32
30
|
</div>
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
<!-- IMPORT admin/partials/save_button.tpl -->
|
|
35
33
|
</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
34
|
</div>
|