nodebb-plugin-discord-onekite 1.0.5 → 1.0.6
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 +6 -5
- package/lib/controllers.js +47 -1
- package/library.js +23 -0
- package/package.json +9 -3
- package/plugin.json +19 -8
- package/templates/admin/plugins/discord-onekite.tpl +12 -6
- package/static/lib/admin.js +0 -67
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
# nodebb-plugin-discord-onekite
|
|
1
|
+
# nodebb-plugin-discord-onekite — v1.0.5 (server-side ACP)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
If your ACP JS is not loading for any reason (custom admin theme, aggressive bundling, CSP, etc.),
|
|
4
|
+
this version avoids it entirely:
|
|
4
5
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
6
|
+
- Categories are rendered server-side.
|
|
7
|
+
- Settings are saved via a normal POST form submit.
|
|
8
|
+
- CSRF token is provided via `{config.csrf_token}`.
|
package/lib/controllers.js
CHANGED
|
@@ -1,9 +1,55 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const meta = require.main.require('./src/meta');
|
|
4
|
+
const categories = require.main.require('./src/categories');
|
|
5
|
+
|
|
6
|
+
const SETTINGS_KEY = 'discord-onekite';
|
|
7
|
+
|
|
8
|
+
function normalizeCids(v) {
|
|
9
|
+
if (!v) return [];
|
|
10
|
+
if (Array.isArray(v)) return v.map(String).filter(Boolean);
|
|
11
|
+
if (typeof v === 'string') return v.split(',').map(s => s.trim()).filter(Boolean);
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function getCategoriesForAdmin(uid) {
|
|
16
|
+
// This API exists in NodeBB and is used in various plugins/examples.
|
|
17
|
+
// If it ever changes, the fallback is to just return an empty array.
|
|
18
|
+
return await new Promise((resolve) => {
|
|
19
|
+
try {
|
|
20
|
+
categories.getCategoriesByPrivilege('categories:cid', uid, 'read', (err, cids) => {
|
|
21
|
+
if (err || !Array.isArray(cids)) return resolve([]);
|
|
22
|
+
categories.getCategoriesData(cids, uid, (err2, data) => {
|
|
23
|
+
if (err2 || !Array.isArray(data)) return resolve([]);
|
|
24
|
+
resolve(data);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
} catch (e) {
|
|
28
|
+
resolve([]);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
3
33
|
const controllers = {};
|
|
4
34
|
|
|
5
35
|
controllers.renderAdminPage = async function (req, res) {
|
|
6
|
-
|
|
36
|
+
const settings = await meta.settings.get(SETTINGS_KEY);
|
|
37
|
+
const savedCids = normalizeCids(settings && settings.cids);
|
|
38
|
+
|
|
39
|
+
const cats = await getCategoriesForAdmin(req.uid);
|
|
40
|
+
const categoriesForTpl = (cats || [])
|
|
41
|
+
.filter(c => c && typeof c.cid !== 'undefined' && c.name)
|
|
42
|
+
.map(c => ({
|
|
43
|
+
cid: String(c.cid),
|
|
44
|
+
name: c.name,
|
|
45
|
+
selected: savedCids.includes(String(c.cid)),
|
|
46
|
+
}))
|
|
47
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
48
|
+
|
|
49
|
+
res.render('admin/plugins/discord-onekite', {
|
|
50
|
+
settings: settings || {},
|
|
51
|
+
categories: categoriesForTpl,
|
|
52
|
+
});
|
|
7
53
|
};
|
|
8
54
|
|
|
9
55
|
module.exports = controllers;
|
package/library.js
CHANGED
|
@@ -4,6 +4,7 @@ const { request } = require('undici');
|
|
|
4
4
|
|
|
5
5
|
const routeHelpers = require.main.require('./src/routes/helpers');
|
|
6
6
|
const meta = require.main.require('./src/meta');
|
|
7
|
+
const middleware = require.main.require('./src/middleware');
|
|
7
8
|
|
|
8
9
|
const topics = require.main.require('./src/topics');
|
|
9
10
|
const user = require.main.require('./src/user');
|
|
@@ -74,12 +75,34 @@ async function buildTopicEmbed({ tid, pid, type }) {
|
|
|
74
75
|
const Plugin = {};
|
|
75
76
|
|
|
76
77
|
Plugin.init = async ({ router }) => {
|
|
78
|
+
// GET admin page
|
|
77
79
|
routeHelpers.setupAdminPageRoute(
|
|
78
80
|
router,
|
|
79
81
|
'/admin/plugins/discord-onekite',
|
|
80
82
|
[],
|
|
81
83
|
controllers.renderAdminPage
|
|
82
84
|
);
|
|
85
|
+
|
|
86
|
+
// POST save (server-side, no ACP JS needed)
|
|
87
|
+
// Protect via admin middleware; CSRF is checked globally by NodeBB when _csrf is present.
|
|
88
|
+
router.post('/admin/plugins/discord-onekite/save',
|
|
89
|
+
middleware.admin.checkPrivileges,
|
|
90
|
+
async (req, res) => {
|
|
91
|
+
try {
|
|
92
|
+
const payload = {
|
|
93
|
+
webhookUrl: req.body.webhookUrl || '',
|
|
94
|
+
notifyReplies: req.body.notifyReplies ? 'on' : '',
|
|
95
|
+
// multiple select may come as string or array
|
|
96
|
+
cids: req.body.cids || '',
|
|
97
|
+
};
|
|
98
|
+
await meta.settings.set(SETTINGS_KEY, payload);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.error('[discord-onekite] save failed', e);
|
|
102
|
+
}
|
|
103
|
+
res.redirect(`${meta.config.relative_path}/admin/plugins/discord-onekite`);
|
|
104
|
+
}
|
|
105
|
+
);
|
|
83
106
|
};
|
|
84
107
|
|
|
85
108
|
Plugin.addAdminNavigation = (header) => {
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-discord-onekite",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Discord webhook notifier for Onekite (NodeBB v4.x only)",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"keywords": [
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nodebb",
|
|
9
|
+
"plugin",
|
|
10
|
+
"discord",
|
|
11
|
+
"webhook",
|
|
12
|
+
"notifications"
|
|
13
|
+
],
|
|
8
14
|
"dependencies": {
|
|
9
15
|
"undici": "^6.0.0"
|
|
10
16
|
},
|
|
11
17
|
"nbbpm": {
|
|
12
18
|
"compatibility": "^4.0.0"
|
|
13
19
|
}
|
|
14
|
-
}
|
|
20
|
+
}
|
package/plugin.json
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "nodebb-plugin-discord-onekite",
|
|
3
3
|
"name": "Discord Onekite Notifier",
|
|
4
|
-
"description": "Notifie Discord via webhook pour nouveaux sujets et/ou
|
|
4
|
+
"description": "Notifie Discord via webhook pour nouveaux sujets et/ou r\u00e9ponses, filtrable par cat\u00e9gories (NodeBB v4.x uniquement).",
|
|
5
5
|
"library": "./library.js",
|
|
6
6
|
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
{
|
|
8
|
+
"hook": "static:app.load",
|
|
9
|
+
"method": "init"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"hook": "filter:admin.header.build",
|
|
13
|
+
"method": "addAdminNavigation"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"hook": "action:topic.save",
|
|
17
|
+
"method": "onTopicSave"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"hook": "action:post.save",
|
|
21
|
+
"method": "onPostSave"
|
|
22
|
+
}
|
|
11
23
|
],
|
|
12
|
-
"templates": "templates"
|
|
13
|
-
|
|
14
|
-
}
|
|
24
|
+
"templates": "templates"
|
|
25
|
+
}
|
|
@@ -1,17 +1,19 @@
|
|
|
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 (sans JS côté ACP).
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
<form role="form"
|
|
7
|
+
<form role="form" method="post" action="{config.relative_path}/admin/plugins/discord-onekite/save">
|
|
8
|
+
<input type="hidden" name="_csrf" value="{config.csrf_token}" />
|
|
9
|
+
|
|
8
10
|
<div class="mb-3">
|
|
9
11
|
<label class="form-label" for="webhookUrl">Discord Webhook URL</label>
|
|
10
|
-
<input type="text" class="form-control" id="webhookUrl" name="webhookUrl" placeholder="https://discord.com/api/webhooks/..." />
|
|
12
|
+
<input type="text" class="form-control" id="webhookUrl" name="webhookUrl" value="{settings.webhookUrl}" placeholder="https://discord.com/api/webhooks/..." />
|
|
11
13
|
</div>
|
|
12
14
|
|
|
13
15
|
<div class="form-check mb-3">
|
|
14
|
-
<input class="form-check-input" type="checkbox" id="notifyReplies" name="notifyReplies"
|
|
16
|
+
<input class="form-check-input" type="checkbox" id="notifyReplies" name="notifyReplies" <!-- IF settings.notifyReplies -->checked<!-- ENDIF settings.notifyReplies -->>
|
|
15
17
|
<label class="form-check-label" for="notifyReplies">
|
|
16
18
|
Notifier aussi les réponses (si décoché : uniquement les nouveaux sujets)
|
|
17
19
|
</label>
|
|
@@ -19,12 +21,16 @@
|
|
|
19
21
|
|
|
20
22
|
<div class="mb-3">
|
|
21
23
|
<label class="form-label" for="cids">Catégories à notifier</label>
|
|
22
|
-
<select class="form-select" id="cids" name="cids" multiple size="12"
|
|
24
|
+
<select class="form-select" id="cids" name="cids" multiple size="12">
|
|
25
|
+
<!-- BEGIN categories -->
|
|
26
|
+
<option value="{categories.cid}" <!-- IF categories.selected -->selected<!-- ENDIF categories.selected -->>{categories.name}</option>
|
|
27
|
+
<!-- END categories -->
|
|
28
|
+
</select>
|
|
23
29
|
<p class="form-text text-muted">
|
|
24
30
|
Si aucune catégorie n’est sélectionnée : <strong>toutes les catégories</strong> seront notifiées.
|
|
25
31
|
</p>
|
|
26
32
|
</div>
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
|
29
35
|
</form>
|
|
30
36
|
</div>
|
package/static/lib/admin.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/* global $, app, ajaxify */
|
|
3
|
-
|
|
4
|
-
(function () {
|
|
5
|
-
function initOnekiteACP() {
|
|
6
|
-
if (!ajaxify || !ajaxify.data || ajaxify.data.template !== 'admin/plugins/discord-onekite') {
|
|
7
|
-
return;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Use NodeBB's AMD loader to access core helpers
|
|
11
|
-
require(['settings', 'api'], function (settings, api) {
|
|
12
|
-
const $form = $('.discord-onekite-settings');
|
|
13
|
-
if (!$form.length) return;
|
|
14
|
-
|
|
15
|
-
// Avoid double-binding
|
|
16
|
-
if ($form.data('onekite-initialized')) return;
|
|
17
|
-
$form.data('onekite-initialized', true);
|
|
18
|
-
|
|
19
|
-
function normalizeCids(v) {
|
|
20
|
-
if (!v) return [];
|
|
21
|
-
if (Array.isArray(v)) return v.map(String).filter(Boolean);
|
|
22
|
-
if (typeof v === 'string') return v.split(',').map(s => s.trim()).filter(Boolean);
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Load saved settings into the form
|
|
27
|
-
settings.load('discord-onekite', $form);
|
|
28
|
-
|
|
29
|
-
// Get raw settings so we can pre-select categories
|
|
30
|
-
settings.get('discord-onekite', function (saved) {
|
|
31
|
-
saved = saved || {};
|
|
32
|
-
const savedCids = normalizeCids(saved.cids);
|
|
33
|
-
|
|
34
|
-
// Load categories (admin should have access)
|
|
35
|
-
api.get('/categories').then(function (res) {
|
|
36
|
-
const categories = (res && res.categories) ? res.categories : [];
|
|
37
|
-
const $select = $('#cids');
|
|
38
|
-
$select.empty();
|
|
39
|
-
|
|
40
|
-
categories
|
|
41
|
-
.filter(c => c && typeof c.cid !== 'undefined' && c.name)
|
|
42
|
-
.forEach(c => {
|
|
43
|
-
const cid = String(c.cid);
|
|
44
|
-
const $opt = $('<option/>').val(cid).text(c.name);
|
|
45
|
-
if (savedCids.includes(cid)) $opt.prop('selected', true);
|
|
46
|
-
$opt.appendTo($select);
|
|
47
|
-
});
|
|
48
|
-
}).catch(function (e) {
|
|
49
|
-
// eslint-disable-next-line no-console
|
|
50
|
-
console.error('[discord-onekite] GET /api/categories failed', e);
|
|
51
|
-
if (app && app.alertError) app.alertError('Impossible de charger les catégories (GET /api/categories).');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Save
|
|
56
|
-
$('#save').off('click.discordOnekite').on('click.discordOnekite', function () {
|
|
57
|
-
settings.save('discord-onekite', $form, function () {
|
|
58
|
-
if (app && app.alertSuccess) app.alertSuccess('Paramètres enregistrés !');
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Run on initial load and after ajax navigation in ACP
|
|
65
|
-
$(window).on('action:ajaxify.end', initOnekiteACP);
|
|
66
|
-
$(initOnekiteACP);
|
|
67
|
-
})();
|