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 CHANGED
@@ -1,7 +1,8 @@
1
- # nodebb-plugin-discord-onekite (NodeBB v4.x) — v1.0.4
1
+ # nodebb-plugin-discord-onekite — v1.0.5 (server-side ACP)
2
2
 
3
- This version follows the most compatible ACP pattern used by many official/community plugins:
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
- - Templates are in `templates/` (not `static/templates/`)
6
- - ACP script is loaded via `acpScripts` and runs globally on `action:ajaxify.end`
7
- - No reliance on `modules` name matching
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.5",
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,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 réponses, filtrable par catégories (NodeBB v4.x uniquement).",
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
- { "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": "templates",
13
- "acpScripts": ["static/lib/admin.js"]
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" 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,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"></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
32
  </div>
27
33
 
28
- <!-- IMPORT admin/partials/save_button.tpl -->
34
+ <button type="submit" class="btn btn-primary">Enregistrer</button>
29
35
  </form>
30
36
  </div>
@@ -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
- })();