nodebb-plugin-discord-onekite 1.1.9 → 1.1.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,5 +1,9 @@
1
- # nodebb-plugin-discord-onekite v1.1.8
1
+ # nodebb-plugin-discord-onekite v1.1.4.2
2
2
 
3
- This variant avoids RequireJS admin modules (no `modules` / no AMD) to prevent requirejs build failures.
4
- ACP saves with a normal POST + redirect and shows a success banner.
5
- Discord notifications unchanged.
3
+ Based on v1.1.4.
4
+
5
+ Change: Discord notification format is now:
6
+ - Single clickable link line: 🆕 Nouveau sujet : [Titre](URL) (or reply variant)
7
+ - Excerpt below
8
+
9
+ ACP behavior unchanged from v1.1.4.
@@ -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
@@ -8,6 +8,7 @@ const middleware = require.main.require('./src/middleware');
8
8
 
9
9
  const topics = require.main.require('./src/topics');
10
10
  const posts = require.main.require('./src/posts');
11
+ const user = require.main.require('./src/user');
11
12
 
12
13
  const controllers = require('./lib/controllers');
13
14
 
@@ -37,6 +38,15 @@ function ensureAbsoluteUrl(baseUrl, maybeUrl) {
37
38
  return s;
38
39
  }
39
40
 
41
+ function isValidHttpUrl(s) {
42
+ try {
43
+ const u = new URL(s);
44
+ return u.protocol === 'http:' || u.protocol === 'https:';
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
40
50
  function stripHtml(html) {
41
51
  if (!html) return '';
42
52
  return String(html)
@@ -89,14 +99,15 @@ async function postToDiscord(webhookUrl, payload) {
89
99
  }
90
100
  }
91
101
 
92
- async function sendDiscord(webhookUrl, payload) {
102
+ async function sendDiscord(webhookUrl, payload, fallbackContent) {
93
103
  if (!webhookUrl) return;
104
+
94
105
  try {
95
106
  await postToDiscord(webhookUrl, payload);
96
107
  } catch (e) {
97
- if (e && e.statusCode === 400 && payload && payload.content) {
108
+ if (e && e.statusCode === 400 && fallbackContent) {
98
109
  try {
99
- await postToDiscord(webhookUrl, { content: payload.content });
110
+ await postToDiscord(webhookUrl, { content: fallbackContent });
100
111
  return;
101
112
  } catch (e2) {
102
113
  console.error(e2);
@@ -120,24 +131,40 @@ async function getPostExcerpt(pid) {
120
131
  async function buildPayload({ tid, pid, isReply }) {
121
132
  const baseUrl = normalizeBaseUrl(meta.config.url);
122
133
 
123
- const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'title', 'slug', 'mainPid']);
134
+ const topicData = await topics.getTopicFields(tid, ['tid', 'uid', 'cid', 'title', 'slug', 'mainPid']);
124
135
  if (!topicData) return null;
125
136
 
137
+ // Always build the forum link like the example: https://www.onekite.com/topic/<tid or slug>
126
138
  const topicUrl = ensureAbsoluteUrl(baseUrl, `/topic/${topicData.slug || topicData.tid}`);
127
- const targetUrl = (isReply && pid) ? `${topicUrl}/${pid}` : topicUrl;
128
139
 
140
+ // For replies, link directly to the post if possible
141
+ const postUrl = (isReply && pid) ? `${topicUrl}/${pid}` : topicUrl;
142
+
143
+ const u = await user.getUserFields(topicData.uid, ['username']);
129
144
  const title = (topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet')).toString().slice(0, 256);
145
+ const authorName = (u && u.username ? String(u.username) : 'Utilisateur').slice(0, 256);
146
+
130
147
  const excerpt = await getPostExcerpt(isReply ? pid : topicData.mainPid);
148
+ const description = truncate(excerpt || (isReply ? `Réponse de ${authorName}` : `Sujet créé par ${authorName}`), 500);
149
+
150
+ // Minimal embed but with URL so the title is clickable
151
+ const embed = {
152
+ title,
153
+ description,
154
+ };
155
+ if (isValidHttpUrl(postUrl)) embed.url = postUrl;
131
156
 
157
+ // Message format:
158
+ // 1) A single clickable link line: "🆕 Nouveau sujet : [Titre](URL)" (or reply variant)
159
+ // 2) Excerpt below
132
160
  const linkLine = isReply
133
- ? `🗨️ Nouvelle réponse : [${title}](${targetUrl})`
161
+ ? `🗨️ Nouvelle réponse : [${title}](${postUrl})`
134
162
  : `🆕 Nouveau sujet : [${title}](${topicUrl})`;
135
163
 
136
- const content = [linkLine, excerpt].filter(Boolean).join('\n');
137
-
138
- const embed = { title, description: excerpt || '', url: targetUrl };
164
+ const content = [linkLine, description].filter(Boolean).join('
165
+ ');
139
166
 
140
- return { topicData, content, embed };
167
+ return { topicData, embed, content };
141
168
  }
142
169
 
143
170
  function extractTidPid(data) {
@@ -156,7 +183,8 @@ Plugin.init = async ({ router }) => {
156
183
  controllers.renderAdminPage
157
184
  );
158
185
 
159
- router.post('/admin/plugins/discord-onekite/save',
186
+ // AJAX save endpoint for ACP (used by admin.js)
187
+ router.post('/api/admin/plugins/discord-onekite/save',
160
188
  middleware.admin.checkPrivileges,
161
189
  async (req, res) => {
162
190
  try {
@@ -166,10 +194,11 @@ Plugin.init = async ({ router }) => {
166
194
  cids: req.body.cids || '',
167
195
  };
168
196
  await meta.settings.set(SETTINGS_KEY, payload);
197
+ res.json({ ok: true });
169
198
  } catch (e) {
170
199
  console.error('[discord-onekite] save failed', e);
200
+ res.status(500).json({ ok: false });
171
201
  }
172
- res.redirect('/admin/plugins/discord-onekite?saved=1');
173
202
  }
174
203
  );
175
204
  };
@@ -192,9 +221,11 @@ Plugin.onTopicPost = async (data) => {
192
221
 
193
222
  const built = await buildPayload({ tid, isReply: false });
194
223
  if (!built) return;
224
+
195
225
  if (!cidAllowed(built.topicData.cid, settings.cids)) return;
196
226
 
197
- await sendDiscord(settings.webhookUrl, { content: built.content, embeds: [built.embed] });
227
+ // Send content + embed (content ensures clickable link always)
228
+ await sendDiscord(settings.webhookUrl, { content: built.content, embeds: [built.embed] }, built.content);
198
229
  };
199
230
 
200
231
  Plugin.onTopicReply = async (data) => {
@@ -207,9 +238,12 @@ Plugin.onTopicReply = async (data) => {
207
238
 
208
239
  const built = await buildPayload({ tid, pid, isReply: true });
209
240
  if (!built) return;
241
+
242
+ if (built.topicData?.mainPid && String(built.topicData.mainPid) === String(pid)) return;
243
+
210
244
  if (!cidAllowed(built.topicData.cid, settings.cids)) return;
211
245
 
212
- await sendDiscord(settings.webhookUrl, { content: built.content, embeds: [built.embed] });
246
+ await sendDiscord(settings.webhookUrl, { content: built.content, embeds: [built.embed] }, built.content);
213
247
  };
214
248
 
215
249
  module.exports = Plugin;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-discord-onekite",
3
- "version": "1.1.9",
3
+ "version": "1.1.11",
4
4
  "description": "Discord webhook notifier for Onekite (NodeBB v4.x only)",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
package/plugin.json CHANGED
@@ -21,5 +21,8 @@
21
21
  "method": "onTopicReply"
22
22
  }
23
23
  ],
24
- "templates": "templates"
24
+ "templates": "templates",
25
+ "modules": {
26
+ "../admin/plugins/discord-onekite.js": "static/lib/admin.js"
27
+ }
25
28
  }
@@ -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,14 +1,10 @@
1
1
  <div class="acp-page-container">
2
2
  <h4>Discord Onekite</h4>
3
- <p class="text-muted">Notifications Discord via webhook.</p>
4
-
5
- <!-- IF saved -->
6
- <div class="alert alert-success" role="alert">Paramètres enregistrés !</div>
7
- <!-- ENDIF saved -->
8
-
9
- <form role="form" method="post" action="/admin/plugins/discord-onekite/save">
10
- <input type="hidden" name="_csrf" value="{config.csrf_token}" />
3
+ <p class="text-muted">
4
+ Notifications Discord via webhook.
5
+ </p>
11
6
 
7
+ <form role="form" class="discord-onekite-settings">
12
8
  <div class="mb-3">
13
9
  <label class="form-label" for="webhookUrl">Discord Webhook URL</label>
14
10
  <input type="text" class="form-control" id="webhookUrl" name="webhookUrl" value="{settings.webhookUrl}" placeholder="https://discord.com/api/webhooks/..." />
@@ -33,6 +29,6 @@
33
29
  </p>
34
30
  </div>
35
31
 
36
- <button type="submit" class="btn btn-primary"><i class="fa fa-save"></i> Enregistrer</button>
32
+ <!-- IMPORT admin/partials/save_button.tpl -->
37
33
  </form>
38
34
  </div>