nodebb-plugin-discord-onekite 1.0.4 → 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 CHANGED
@@ -1,8 +1,8 @@
1
- # nodebb-plugin-discord-onekite (NodeBB v4.x) — v1.0.3
1
+ # nodebb-plugin-discord-onekite — v1.0.5 (server-side ACP)
2
2
 
3
- This version aligns more closely with the official Quickstart pattern:
4
- - uses `modules` mapping (no leading ./) to expose `admin/plugins/discord-onekite`
5
- - template has no inline script
6
- - admin.js logs `[discord-onekite] admin init` when loaded
3
+ If your ACP JS is not loading for any reason (custom admin theme, aggressive bundling, CSP, etc.),
4
+ this version avoids it entirely:
7
5
 
8
- If you still don't see that log, NodeBB isn't loading the module, so we can narrow it down quickly.
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}`.
@@ -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
- res.render('admin/plugins/discord-onekite', {});
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.4",
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": ["nodebb", "plugin", "discord", "webhook", "notifications"],
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,16 +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 réponses, filtrable par catégories (NodeBB v4.x uniquement).",
5
- "library": "library.js",
4
+ "description": "Notifie Discord via webhook pour nouveaux sujets et/ou r\u00e9ponses, filtrable par cat\u00e9gories (NodeBB v4.x uniquement).",
5
+ "library": "./library.js",
6
6
  "hooks": [
7
- { "hook": "static:app.load", "method": "init" },
8
- { "hook": "filter:admin.header.build", "method": "addAdminNavigation" },
9
- { "hook": "action:topic.save", "method": "onTopicSave" },
10
- { "hook": "action:post.save", "method": "onPostSave" }
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": "static/templates",
13
- "modules": {
14
- "../admin/plugins/discord-onekite.js": "static/lib/admin.js"
15
- }
16
- }
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
- Envoie une notification dans Discord via webhook, avec filtres par catégories.
4
+ Notifications Discord via webhook (sans JS côté ACP).
5
5
  </p>
6
6
 
7
- <form role="form" class="discord-onekite-settings">
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,15 +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"></select>
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
- <p class="form-text">
27
- <em>Astuce debug :</em> ouvre la console navigateur, tu dois voir <code>[discord-onekite] admin init</code>.
28
- </p>
29
32
  </div>
30
33
 
31
- <!-- IMPORT admin/partials/save_button.tpl -->
34
+ <button type="submit" class="btn btn-primary">Enregistrer</button>
32
35
  </form>
33
36
  </div>
@@ -1,70 +0,0 @@
1
- 'use strict';
2
- /* global $, app, ajaxify */
3
-
4
- define('admin/plugins/discord-onekite', ['settings', 'api'], function (settings, api) {
5
- const ACP = {};
6
-
7
- function normalizeCids(v) {
8
- if (!v) return [];
9
- if (Array.isArray(v)) return v.map(String).filter(Boolean);
10
- if (typeof v === 'string') return v.split(',').map(s => s.trim()).filter(Boolean);
11
- return [];
12
- }
13
-
14
- async function fetchCategories() {
15
- // In NodeBB, api.get('/categories') => GET /api/categories
16
- const res = await api.get('/categories');
17
- return (res && res.categories) ? res.categories : [];
18
- }
19
-
20
- ACP.init = async function () {
21
- // This should be called automatically by NodeBB when template admin/plugins/discord-onekite is loaded.
22
- // Also safe to call multiple times.
23
- console.log('[discord-onekite] admin init');
24
-
25
- const $form = $('.discord-onekite-settings');
26
- if (!$form.length) return;
27
-
28
- // Load current settings
29
- const saved = await new Promise((resolve) => {
30
- settings.get('discord-onekite', (data) => resolve(data || {}));
31
- });
32
-
33
- settings.load('discord-onekite', $form);
34
- const savedCids = normalizeCids(saved.cids);
35
-
36
- // Populate categories select
37
- const $select = $('#cids');
38
- try {
39
- const categories = await fetchCategories();
40
- $select.empty();
41
- categories
42
- .filter(c => c && typeof c.cid !== 'undefined' && c.name)
43
- .forEach(c => {
44
- const cid = String(c.cid);
45
- const $opt = $('<option />').val(cid).text(c.name);
46
- if (savedCids.includes(cid)) $opt.prop('selected', true);
47
- $opt.appendTo($select);
48
- });
49
- } catch (e) {
50
- console.error('[discord-onekite] failed to fetch categories', e);
51
- if (app && app.alertError) app.alertError('Impossible de charger la liste des catégories (GET /api/categories).');
52
- }
53
-
54
- // Save
55
- $('#save').off('click.discordOnekite').on('click.discordOnekite', function () {
56
- settings.save('discord-onekite', $form, function () {
57
- if (app && app.alertSuccess) app.alertSuccess('Paramètres enregistrés !');
58
- });
59
- });
60
- };
61
-
62
- // Fallback for ajaxified ACP navigation
63
- $(window).off('action:ajaxify.end.discordOnekite').on('action:ajaxify.end.discordOnekite', function (ev, data) {
64
- if (data && data.template === 'admin/plugins/discord-onekite') {
65
- ACP.init();
66
- }
67
- });
68
-
69
- return ACP;
70
- });