nodebb-plugin-discord-onekite 1.0.12 → 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 +9 -4
- package/lib/controllers.js +0 -1
- package/library.js +52 -16
- package/package.json +1 -1
- package/plugin.json +3 -3
- package/static/lib/admin.js +39 -0
- package/templates/admin/plugins/discord-onekite.tpl +3 -11
- package/static/lib/acp-toast.js +0 -23
package/README.md
CHANGED
|
@@ -1,5 +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
|
+
|
|
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 {
|
|
@@ -83,22 +105,29 @@ async function sendDiscord(webhookUrl, payload, fallbackContent) {
|
|
|
83
105
|
try {
|
|
84
106
|
await postToDiscord(webhookUrl, payload);
|
|
85
107
|
} catch (e) {
|
|
86
|
-
// Discord embed validation error is commonly 400 with {"embeds":["0"]}
|
|
87
|
-
// Retry with plain content so notifications still arrive.
|
|
88
108
|
if (e && e.statusCode === 400 && fallbackContent) {
|
|
89
109
|
try {
|
|
90
110
|
await postToDiscord(webhookUrl, { content: fallbackContent });
|
|
91
111
|
return;
|
|
92
112
|
} catch (e2) {
|
|
93
|
-
// eslint-disable-next-line no-console
|
|
94
113
|
console.error(e2);
|
|
95
114
|
}
|
|
96
115
|
}
|
|
97
|
-
// eslint-disable-next-line no-console
|
|
98
116
|
console.error(e);
|
|
99
117
|
}
|
|
100
118
|
}
|
|
101
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
|
+
|
|
102
131
|
async function buildPayload({ tid, pid, isReply }) {
|
|
103
132
|
const baseUrl = normalizeBaseUrl(meta.config.url);
|
|
104
133
|
|
|
@@ -106,26 +135,32 @@ async function buildPayload({ tid, pid, isReply }) {
|
|
|
106
135
|
if (!topicData) return null;
|
|
107
136
|
|
|
108
137
|
const topicUrl = ensureAbsoluteUrl(baseUrl, `/topic/${topicData.slug || topicData.tid}`);
|
|
109
|
-
const
|
|
138
|
+
const postUrl = isReply && pid ? `${topicUrl}/${pid}` : `${topicUrl}/${topicData.mainPid || ''}`.replace(/\/$/, '');
|
|
110
139
|
|
|
111
140
|
const u = await user.getUserFields(topicData.uid, ['username']);
|
|
112
|
-
|
|
113
141
|
const title = (topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet')).toString().slice(0, 256);
|
|
114
142
|
const authorName = (u && u.username ? String(u.username) : 'Utilisateur').slice(0, 256);
|
|
115
143
|
|
|
116
|
-
|
|
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
|
+
|
|
117
147
|
const embed = {
|
|
118
148
|
title,
|
|
119
|
-
description
|
|
149
|
+
description,
|
|
120
150
|
};
|
|
121
151
|
|
|
122
|
-
if (isValidHttpUrl(
|
|
123
|
-
embed.url =
|
|
152
|
+
if (isValidHttpUrl(postUrl)) {
|
|
153
|
+
embed.url = postUrl;
|
|
124
154
|
}
|
|
125
155
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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');
|
|
129
164
|
|
|
130
165
|
return { topicData, embed, content };
|
|
131
166
|
}
|
|
@@ -146,7 +181,8 @@ Plugin.init = async ({ router }) => {
|
|
|
146
181
|
controllers.renderAdminPage
|
|
147
182
|
);
|
|
148
183
|
|
|
149
|
-
|
|
184
|
+
// AJAX save endpoint for ACP (used by admin.js)
|
|
185
|
+
router.post('/api/admin/plugins/discord-onekite/save',
|
|
150
186
|
middleware.admin.checkPrivileges,
|
|
151
187
|
async (req, res) => {
|
|
152
188
|
try {
|
|
@@ -156,11 +192,11 @@ Plugin.init = async ({ router }) => {
|
|
|
156
192
|
cids: req.body.cids || '',
|
|
157
193
|
};
|
|
158
194
|
await meta.settings.set(SETTINGS_KEY, payload);
|
|
195
|
+
res.json({ ok: true });
|
|
159
196
|
} catch (e) {
|
|
160
|
-
// eslint-disable-next-line no-console
|
|
161
197
|
console.error('[discord-onekite] save failed', e);
|
|
198
|
+
res.status(500).json({ ok: false });
|
|
162
199
|
}
|
|
163
|
-
res.redirect('/admin/plugins/discord-onekite?saved=1');
|
|
164
200
|
}
|
|
165
201
|
);
|
|
166
202
|
};
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* global $, app */
|
|
3
|
+
|
|
4
|
+
define('admin/plugins/discord-onekite', ['api'], function (api) {
|
|
5
|
+
const ACP = {};
|
|
6
|
+
|
|
7
|
+
function getMultiSelectValues(selector) {
|
|
8
|
+
const el = document.querySelector(selector);
|
|
9
|
+
if (!el) return [];
|
|
10
|
+
return Array.from(el.selectedOptions || []).map(o => o.value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
ACP.init = function () {
|
|
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
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return ACP;
|
|
39
|
+
});
|
|
@@ -1,18 +1,10 @@
|
|
|
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
|
-
|
|
8
|
-
<div class="alert alert-success" role="alert">
|
|
9
|
-
Paramètres enregistrés !
|
|
10
|
-
</div>
|
|
11
|
-
<!-- ENDIF saved -->
|
|
12
|
-
|
|
13
|
-
<form role="form" method="post" action="/admin/plugins/discord-onekite/save">
|
|
14
|
-
<input type="hidden" name="_csrf" value="{config.csrf_token}" />
|
|
15
|
-
|
|
7
|
+
<form role="form" class="discord-onekite-settings">
|
|
16
8
|
<div class="mb-3">
|
|
17
9
|
<label class="form-label" for="webhookUrl">Discord Webhook URL</label>
|
|
18
10
|
<input type="text" class="form-control" id="webhookUrl" name="webhookUrl" value="{settings.webhookUrl}" placeholder="https://discord.com/api/webhooks/..." />
|
|
@@ -37,6 +29,6 @@
|
|
|
37
29
|
</p>
|
|
38
30
|
</div>
|
|
39
31
|
|
|
40
|
-
|
|
32
|
+
<!-- IMPORT admin/partials/save_button.tpl -->
|
|
41
33
|
</form>
|
|
42
34
|
</div>
|
package/static/lib/acp-toast.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/* global $, app, ajaxify */
|
|
3
|
-
|
|
4
|
-
(function () {
|
|
5
|
-
function showToastIfSaved() {
|
|
6
|
-
if (!ajaxify || !ajaxify.data || ajaxify.data.template !== 'admin/plugins/discord-onekite') return;
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
const url = new URL(window.location.href);
|
|
10
|
-
const saved = url.searchParams.get('saved');
|
|
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
|
-
}
|
|
20
|
-
|
|
21
|
-
$(window).on('action:ajaxify.end', showToastIfSaved);
|
|
22
|
-
$(showToastIfSaved);
|
|
23
|
-
})();
|