nodebb-plugin-discord-onekite 1.0.13 → 1.0.14

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,10 @@
1
- # nodebb-plugin-discord-onekite v1.1.2
1
+ # nodebb-plugin-discord-onekite v1.1.3
2
2
 
3
- Fix build error:
4
- - Removed `acpScripts` and instead provides a proper AMD admin module via `modules` mapping.
5
- This avoids RequireJS build failures.
3
+ ACP:
4
+ - Uses the standard NodeBB save "disk" button (IMPORT admin/partials/save_button.tpl)
5
+ - Saves via AJAX to `/api/admin/plugins/discord-onekite/save` and shows NodeBB toast (bottom-right)
6
6
 
7
- - Toast "classique NodeBB" via app.alertSuccess when `?saved=1`.
8
- - Discord notifications: minimal embed + fallback to plain content if Discord rejects embed.
7
+ Discord message:
8
+ - Notification content starts with the direct post URL
9
+ - Includes an excerpt of the post content (truncated)
10
+ - Keeps embed minimal + fallback to plain content if Discord rejects embeds
@@ -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
@@ -7,6 +7,7 @@ const meta = require.main.require('./src/meta');
7
7
  const middleware = require.main.require('./src/middleware');
8
8
 
9
9
  const topics = require.main.require('./src/topics');
10
+ const posts = require.main.require('./src/posts');
10
11
  const user = require.main.require('./src/user');
11
12
 
12
13
  const controllers = require('./lib/controllers');
@@ -46,6 +47,27 @@ function isValidHttpUrl(s) {
46
47
  }
47
48
  }
48
49
 
50
+ function stripHtml(html) {
51
+ if (!html) return '';
52
+ return String(html)
53
+ .replace(/<br\s*\/?>/gi, '\n')
54
+ .replace(/<\/p>/gi, '\n')
55
+ .replace(/<[^>]*>/g, '')
56
+ .replace(/&nbsp;/g, ' ')
57
+ .replace(/&amp;/g, '&')
58
+ .replace(/&lt;/g, '<')
59
+ .replace(/&gt;/g, '>')
60
+ .replace(/&#39;/g, "'")
61
+ .replace(/&quot;/g, '"')
62
+ .trim();
63
+ }
64
+
65
+ function truncate(s, n) {
66
+ const str = String(s || '');
67
+ if (str.length <= n) return str;
68
+ return str.slice(0, n - 1) + '…';
69
+ }
70
+
49
71
  async function getSettings() {
50
72
  const s = await meta.settings.get(SETTINGS_KEY);
51
73
  return {
@@ -88,15 +110,24 @@ async function sendDiscord(webhookUrl, payload, fallbackContent) {
88
110
  await postToDiscord(webhookUrl, { content: fallbackContent });
89
111
  return;
90
112
  } catch (e2) {
91
- // eslint-disable-next-line no-console
92
113
  console.error(e2);
93
114
  }
94
115
  }
95
- // eslint-disable-next-line no-console
96
116
  console.error(e);
97
117
  }
98
118
  }
99
119
 
120
+ async function getPostExcerpt(pid) {
121
+ if (!pid) return '';
122
+ try {
123
+ const p = await posts.getPostFields(pid, ['content']);
124
+ const text = stripHtml(p && p.content);
125
+ return truncate(text, 350);
126
+ } catch {
127
+ return '';
128
+ }
129
+ }
130
+
100
131
  async function buildPayload({ tid, pid, isReply }) {
101
132
  const baseUrl = normalizeBaseUrl(meta.config.url);
102
133
 
@@ -104,24 +135,32 @@ async function buildPayload({ tid, pid, isReply }) {
104
135
  if (!topicData) return null;
105
136
 
106
137
  const topicUrl = ensureAbsoluteUrl(baseUrl, `/topic/${topicData.slug || topicData.tid}`);
107
- const url = isReply && pid ? `${topicUrl}/${pid}` : topicUrl;
138
+ const postUrl = isReply && pid ? `${topicUrl}/${pid}` : `${topicUrl}/${topicData.mainPid || ''}`.replace(/\/$/, '');
108
139
 
109
140
  const u = await user.getUserFields(topicData.uid, ['username']);
110
141
  const title = (topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet')).toString().slice(0, 256);
111
142
  const authorName = (u && u.username ? String(u.username) : 'Utilisateur').slice(0, 256);
112
143
 
144
+ const excerpt = await getPostExcerpt(isReply ? pid : topicData.mainPid);
145
+ const description = truncate(excerpt || (isReply ? `Réponse de ${authorName}` : `Sujet créé par ${authorName}`), 350);
146
+
113
147
  const embed = {
114
148
  title,
115
- description: isReply ? `Réponse de **${authorName}**` : `Sujet créé par **${authorName}**`,
149
+ description,
116
150
  };
117
151
 
118
- if (isValidHttpUrl(url)) {
119
- embed.url = url;
152
+ if (isValidHttpUrl(postUrl)) {
153
+ embed.url = postUrl;
120
154
  }
121
155
 
122
- const content = isReply
123
- ? `🗨️ Nouvelle réponse : ${title}\n${url}`
124
- : `🆕 Nouveau sujet : ${title}\n${url}`;
156
+ // "Lien vers le message directement en début de notification"
157
+ const contentLines = [
158
+ postUrl,
159
+ isReply ? `🗨️ Nouvelle réponse : ${title}` : `🆕 Nouveau sujet : ${title}`,
160
+ ];
161
+ if (description) contentLines.push(description);
162
+
163
+ const content = contentLines.join('\n');
125
164
 
126
165
  return { topicData, embed, content };
127
166
  }
@@ -142,7 +181,8 @@ Plugin.init = async ({ router }) => {
142
181
  controllers.renderAdminPage
143
182
  );
144
183
 
145
- router.post('/admin/plugins/discord-onekite/save',
184
+ // AJAX save endpoint for ACP (used by admin.js)
185
+ router.post('/api/admin/plugins/discord-onekite/save',
146
186
  middleware.admin.checkPrivileges,
147
187
  async (req, res) => {
148
188
  try {
@@ -152,11 +192,11 @@ Plugin.init = async ({ router }) => {
152
192
  cids: req.body.cids || '',
153
193
  };
154
194
  await meta.settings.set(SETTINGS_KEY, payload);
195
+ res.json({ ok: true });
155
196
  } catch (e) {
156
- // eslint-disable-next-line no-console
157
197
  console.error('[discord-onekite] save failed', e);
198
+ res.status(500).json({ ok: false });
158
199
  }
159
- res.redirect('/admin/plugins/discord-onekite?saved=1');
160
200
  }
161
201
  );
162
202
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-discord-onekite",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
4
4
  "description": "Discord webhook notifier for Onekite (NodeBB v4.x only)",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
@@ -1,27 +1,38 @@
1
1
  'use strict';
2
- /* global app */
2
+ /* global $, app */
3
3
 
4
- define('admin/plugins/discord-onekite', [], function () {
4
+ define('admin/plugins/discord-onekite', ['api'], function (api) {
5
5
  const ACP = {};
6
6
 
7
- function toastIfSaved() {
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
- url.searchParams.delete('saved');
16
- window.history.replaceState({}, document.title, url.toString());
17
- }
18
- } catch (e) {
19
- // ignore
20
- }
7
+ function getMultiSelectValues(selector) {
8
+ const el = document.querySelector(selector);
9
+ if (!el) return [];
10
+ return Array.from(el.selectedOptions || []).map(o => o.value);
21
11
  }
22
12
 
23
13
  ACP.init = function () {
24
- toastIfSaved();
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
+ });
25
36
  };
26
37
 
27
38
  return ACP;
@@ -4,9 +4,7 @@
4
4
  Notifications Discord via webhook.
5
5
  </p>
6
6
 
7
- <form role="form" method="post" action="/admin/plugins/discord-onekite/save">
8
- <input type="hidden" name="_csrf" value="{config.csrf_token}" />
9
-
7
+ <form role="form" class="discord-onekite-settings">
10
8
  <div class="mb-3">
11
9
  <label class="form-label" for="webhookUrl">Discord Webhook URL</label>
12
10
  <input type="text" class="form-control" id="webhookUrl" name="webhookUrl" value="{settings.webhookUrl}" placeholder="https://discord.com/api/webhooks/..." />
@@ -31,12 +29,6 @@
31
29
  </p>
32
30
  </div>
33
31
 
34
- <button type="submit" class="btn btn-primary">Enregistrer</button>
32
+ <!-- IMPORT admin/partials/save_button.tpl -->
35
33
  </form>
36
-
37
- <noscript>
38
- <!-- IF saved -->
39
- <div class="alert alert-success mt-3" role="alert">Paramètres enregistrés !</div>
40
- <!-- ENDIF saved -->
41
- </noscript>
42
34
  </div>