nodebb-plugin-discord-onekite 1.0.9 → 1.0.11

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 +1,4 @@
1
- Debug build v1.0.7
1
+ # nodebb-plugin-discord-onekite v1.0.9
2
+
3
+ - Uses action:topic.post for new topics and action:topic.reply for replies (more reliable than action:*save for notifications).
4
+ - Server-side ACP settings + NodeBB-style toast (bottom-right) after save.
@@ -12,28 +12,13 @@ function normalizeCids(v) {
12
12
  return [];
13
13
  }
14
14
 
15
- async function getCategoriesDebug(uid) {
16
- const result = { method: 'getCategoriesByPrivilege', count: 0, error: null, sample: [] };
17
-
18
- const cats = await new Promise((resolve) => {
19
- try {
20
- categories.getCategoriesByPrivilege('categories:cid', uid || 0, 'read', (err, categoriesData) => {
21
- if (err) {
22
- result.error = err.message || String(err);
23
- return resolve([]);
24
- }
25
- if (!Array.isArray(categoriesData)) return resolve([]);
26
- resolve(categoriesData.filter(Boolean));
27
- });
28
- } catch (e) {
29
- result.error = e.message || String(e);
30
- resolve([]);
31
- }
15
+ async function getReadableCategories(uid) {
16
+ return await new Promise((resolve) => {
17
+ categories.getCategoriesByPrivilege('categories:cid', uid || 0, 'read', (err, categoriesData) => {
18
+ if (err || !Array.isArray(categoriesData)) return resolve([]);
19
+ resolve(categoriesData.filter(Boolean));
20
+ });
32
21
  });
33
-
34
- result.count = cats.length;
35
- result.sample = cats.slice(0, 5).map(c => ({ cid: c.cid, name: c.name }));
36
- return { cats, result };
37
22
  }
38
23
 
39
24
  const controllers = {};
@@ -42,7 +27,7 @@ controllers.renderAdminPage = async function (req, res) {
42
27
  const settings = await meta.settings.get(SETTINGS_KEY);
43
28
  const savedCids = normalizeCids(settings && settings.cids);
44
29
 
45
- const { cats, result } = await getCategoriesDebug(req.uid);
30
+ const cats = await getReadableCategories(req.uid);
46
31
  const categoriesForTpl = (cats || [])
47
32
  .filter(c => c && typeof c.cid !== 'undefined' && c.name)
48
33
  .map(c => ({
@@ -52,19 +37,10 @@ controllers.renderAdminPage = async function (req, res) {
52
37
  }))
53
38
  .sort((a, b) => a.name.localeCompare(b.name));
54
39
 
55
- const debug = {
56
- uid: req.uid,
57
- relative_path: meta.config && meta.config.relative_path,
58
- url: meta.config && meta.config.url,
59
- categories: result,
60
- savedCidsCount: savedCids.length,
61
- now: new Date().toISOString(),
62
- };
63
-
64
40
  res.render('admin/plugins/discord-onekite', {
65
41
  settings: settings || {},
66
42
  categories: categoriesForTpl,
67
- debug: JSON.stringify(debug, null, 2),
43
+ saved: req.query && (req.query.saved === '1' || req.query.saved === 'true'),
68
44
  });
69
45
  };
70
46
 
package/library.js CHANGED
@@ -49,7 +49,7 @@ async function postToDiscord(webhookUrl, payload) {
49
49
  }
50
50
  }
51
51
 
52
- async function buildTopicEmbed({ tid, pid, type }) {
52
+ async function buildTopicEmbed({ tid, pid, isReply }) {
53
53
  const baseUrl = meta.config.url;
54
54
 
55
55
  const topicData = await topics.getTopicFields(tid, ['tid', 'uid', 'cid', 'title', 'slug', 'timestamp', 'mainPid']);
@@ -58,11 +58,10 @@ async function buildTopicEmbed({ tid, pid, type }) {
58
58
  const topicUrl = `${baseUrl}/topic/${topicData.slug || topicData.tid}`;
59
59
  const u = await user.getUserFields(topicData.uid, ['username', 'picture']);
60
60
 
61
- const isReply = type === 'reply';
62
61
  const embed = {
63
62
  title: topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet'),
64
63
  url: isReply && pid ? `${topicUrl}/${pid}` : topicUrl,
65
- timestamp: new Date(isReply ? Date.now() : (topicData.timestamp || Date.now())).toISOString(),
64
+ timestamp: new Date(Date.now()).toISOString(),
66
65
  author: u?.username ? { name: u.username, icon_url: u.picture || undefined } : undefined,
67
66
  footer: { text: 'NodeBB → Discord (Onekite)' },
68
67
  };
@@ -72,11 +71,15 @@ async function buildTopicEmbed({ tid, pid, type }) {
72
71
  return { topicData, embed };
73
72
  }
74
73
 
74
+ function extractTidPid(data) {
75
+ const tid = data?.tid || data?.topic?.tid || data?.post?.tid;
76
+ const pid = data?.pid || data?.post?.pid;
77
+ return { tid, pid };
78
+ }
79
+
75
80
  const Plugin = {};
76
81
 
77
82
  Plugin.init = async ({ router }) => {
78
- console.log('[discord-onekite] init hook fired');
79
-
80
83
  routeHelpers.setupAdminPageRoute(
81
84
  router,
82
85
  '/admin/plugins/discord-onekite',
@@ -94,26 +97,11 @@ Plugin.init = async ({ router }) => {
94
97
  cids: req.body.cids || '',
95
98
  };
96
99
  await meta.settings.set(SETTINGS_KEY, payload);
97
- console.log('[discord-onekite] settings saved');
98
100
  } catch (e) {
101
+ // eslint-disable-next-line no-console
99
102
  console.error('[discord-onekite] save failed', e);
100
103
  }
101
- res.redirect('/admin/plugins/discord-onekite');
102
- }
103
- );
104
-
105
- router.get('/admin/plugins/discord-onekite/ping',
106
- middleware.admin.checkPrivileges,
107
- (req, res) => {
108
- res.json({
109
- ok: true,
110
- plugin: 'nodebb-plugin-discord-onekite',
111
- version: '1.0.7',
112
- uid: req.uid,
113
- relative_path: meta.config && meta.config.relative_path,
114
- url: meta.config && meta.config.url,
115
- time: new Date().toISOString(),
116
- });
104
+ res.redirect('/admin/plugins/discord-onekite?saved=1');
117
105
  }
118
106
  );
119
107
  };
@@ -122,58 +110,53 @@ Plugin.addAdminNavigation = (header) => {
122
110
  header.plugins.push({
123
111
  route: '/plugins/discord-onekite',
124
112
  icon: 'fa-bell',
125
- name: 'Discord Onekite (Debug)',
113
+ name: 'Discord Onekite',
126
114
  });
127
115
  return header;
128
116
  };
129
117
 
130
- Plugin.onTopicSave = async (data) => {
118
+ // Fired when a new topic is posted (creation)
119
+ Plugin.onTopicPost = async (data) => {
131
120
  try {
132
121
  const settings = await getSettings();
133
122
  if (!settings.webhookUrl) return;
134
123
 
135
- const tid = data?.tid || data?.topic?.tid;
124
+ const { tid } = extractTidPid(data);
136
125
  if (!tid) return;
137
126
 
138
- const isNew =
139
- data?.isNew === true ||
140
- data?.topic?.isNew === true ||
141
- (typeof data?.topic?.postcount === 'number' && data.topic.postcount === 1);
142
-
143
- if (!isNew) return;
144
-
145
- const built = await buildTopicEmbed({ tid, type: 'topic' });
127
+ const built = await buildTopicEmbed({ tid, isReply: false });
146
128
  if (!built) return;
147
129
 
148
130
  if (!cidAllowed(built.topicData.cid, settings.cids)) return;
149
131
 
150
132
  await postToDiscord(settings.webhookUrl, { embeds: [built.embed] });
151
133
  } catch (err) {
134
+ // eslint-disable-next-line no-console
152
135
  console.error(err);
153
136
  }
154
137
  };
155
138
 
156
- Plugin.onPostSave = async (data) => {
139
+ // Fired when a reply is made in a topic
140
+ Plugin.onTopicReply = async (data) => {
157
141
  try {
158
142
  const settings = await getSettings();
159
143
  if (!settings.notifyReplies) return;
160
144
  if (!settings.webhookUrl) return;
161
145
 
162
- const pid = data?.pid || data?.post?.pid;
163
- const tid = data?.tid || data?.post?.tid;
164
- if (!pid || !tid) return;
146
+ const { tid, pid } = extractTidPid(data);
147
+ if (!tid || !pid) return;
165
148
 
166
- const isNew = data?.isNew === true || data?.post?.isNew === true;
167
- if (!isNew) return;
168
-
169
- const built = await buildTopicEmbed({ tid, pid, type: 'reply' });
149
+ const built = await buildTopicEmbed({ tid, pid, isReply: true });
170
150
  if (!built) return;
171
151
 
152
+ // Safety: don't notify if somehow this is the main post
172
153
  if (built.topicData?.mainPid && String(built.topicData.mainPid) === String(pid)) return;
154
+
173
155
  if (!cidAllowed(built.topicData.cid, settings.cids)) return;
174
156
 
175
157
  await postToDiscord(settings.webhookUrl, { embeds: [built.embed] });
176
158
  } catch (err) {
159
+ // eslint-disable-next-line no-console
177
160
  console.error(err);
178
161
  }
179
162
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nodebb-plugin-discord-onekite",
3
- "version": "1.0.9",
4
- "description": "Discord webhook notifier for Onekite (NodeBB v4.x only) - debug build",
3
+ "version": "1.0.11",
4
+ "description": "Discord webhook notifier for Onekite (NodeBB v4.x only)",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
7
7
  "keywords": [
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "nodebb-plugin-discord-onekite",
3
3
  "name": "Discord Onekite Notifier",
4
- "description": "Notifie Discord via webhook (v4.x). Version debug (ACP server-side).",
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
7
  {
@@ -13,13 +13,16 @@
13
13
  "method": "addAdminNavigation"
14
14
  },
15
15
  {
16
- "hook": "action:topic.save",
17
- "method": "onTopicSave"
16
+ "hook": "action:topic.post",
17
+ "method": "onTopicPost"
18
18
  },
19
19
  {
20
- "hook": "action:post.save",
21
- "method": "onPostSave"
20
+ "hook": "action:topic.reply",
21
+ "method": "onTopicReply"
22
22
  }
23
23
  ],
24
- "templates": "templates"
24
+ "templates": "templates",
25
+ "acpScripts": [
26
+ "static/lib/acp-toast.js"
27
+ ]
25
28
  }
@@ -0,0 +1,26 @@
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
+ // remove the query param to avoid re-toasting on navigation
16
+ url.searchParams.delete('saved');
17
+ window.history.replaceState({}, document.title, url.toString());
18
+ }
19
+ } catch (e) {
20
+ // ignore
21
+ }
22
+ }
23
+
24
+ $(window).on('action:ajaxify.end', showToastIfSaved);
25
+ $(showToastIfSaved);
26
+ })();
@@ -1,9 +1,15 @@
1
1
  <div class="acp-page-container">
2
- <h4>Discord Onekite (Debug)</h4>
2
+ <h4>Discord Onekite</h4>
3
3
  <p class="text-muted">
4
- Cette page est en rendu serveur et inclut des informations de debug.
4
+ Notifications Discord via webhook (rendu serveur).
5
5
  </p>
6
6
 
7
+ <!-- IF saved -->
8
+ <div class="alert alert-success" role="alert">
9
+ Paramètres enregistrés !
10
+ </div>
11
+ <!-- ENDIF saved -->
12
+
7
13
  <form role="form" method="post" action="/admin/plugins/discord-onekite/save">
8
14
  <input type="hidden" name="_csrf" value="{config.csrf_token}" />
9
15
 
@@ -15,7 +21,7 @@
15
21
  <div class="form-check mb-3">
16
22
  <input class="form-check-input" type="checkbox" id="notifyReplies" name="notifyReplies" <!-- IF settings.notifyReplies -->checked<!-- ENDIF settings.notifyReplies -->>
17
23
  <label class="form-check-label" for="notifyReplies">
18
- Notifier aussi les réponses
24
+ Notifier aussi les réponses (si décoché : uniquement les nouveaux sujets)
19
25
  </label>
20
26
  </div>
21
27
 
@@ -33,13 +39,4 @@
33
39
 
34
40
  <button type="submit" class="btn btn-primary">Enregistrer</button>
35
41
  </form>
36
-
37
- <hr />
38
-
39
- <h5>Debug</h5>
40
- <pre style="white-space: pre-wrap;">{debug}</pre>
41
- <p>
42
- Endpoint de test (doit répondre JSON) :
43
- <code>/admin/plugins/discord-onekite/ping</code>
44
- </p>
45
42
  </div>