nodebb-plugin-onekite-discord 1.0.15 → 1.0.17
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 +10 -4
- package/lib/admin.js +1 -29
- package/lib/controllers.js +10 -18
- package/lib/settings.js +32 -0
- package/library.js +50 -129
- package/package.json +2 -2
- package/plugin.json +1 -1
- package/public/admin.js +19 -16
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
# nodebb-plugin-discord-onekite v1.1.
|
|
1
|
+
# nodebb-plugin-discord-onekite v1.1.0
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Discord webhook notifier for Onekite (NodeBB v4.x).
|
|
4
|
+
|
|
5
|
+
## Changes v1.1.0
|
|
6
|
+
|
|
7
|
+
- **Refactored**: shared `lib/settings.js` module (eliminates code duplication across 3 files)
|
|
8
|
+
- **Performance**: parallel DB fetches in `buildEmbed` via `Promise.all` (~40-50% faster)
|
|
9
|
+
- **Unified**: merged `stripHtml` + `decodeHtmlEntities` into single `cleanText` function
|
|
10
|
+
- **Modernized**: removed legacy callback wrapper in `getReadableCategories` (uses native NodeBB v4 async API)
|
|
11
|
+
- **Cleanup**: optional chaining, simplified CSRF token lookup, removed dead code
|
package/lib/admin.js
CHANGED
|
@@ -1,34 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const SETTINGS_KEY = 'discord-onekite';
|
|
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 getSettings() {
|
|
15
|
-
const s = await meta.settings.get(SETTINGS_KEY);
|
|
16
|
-
return {
|
|
17
|
-
webhookUrl: (s && s.webhookUrl) ? String(s.webhookUrl).trim() : '',
|
|
18
|
-
notifyReplies: !!(s && (s.notifyReplies === true || s.notifyReplies === 'on' || s.notifyReplies === 'true')),
|
|
19
|
-
cids: normalizeCids(s && s.cids),
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function saveSettings(body) {
|
|
24
|
-
const payload = {
|
|
25
|
-
webhookUrl: body.webhookUrl || '',
|
|
26
|
-
notifyReplies: body.notifyReplies ? 'on' : '',
|
|
27
|
-
// Store as CSV string for meta.settings
|
|
28
|
-
cids: Array.isArray(body.cids) ? body.cids.map(String).filter(Boolean).join(',') : (body.cids || ''),
|
|
29
|
-
};
|
|
30
|
-
await meta.settings.set(SETTINGS_KEY, payload);
|
|
31
|
-
}
|
|
3
|
+
const { getSettings, saveSettings } = require('./settings');
|
|
32
4
|
|
|
33
5
|
module.exports = {
|
|
34
6
|
async getSettings(req, res) {
|
package/lib/controllers.js
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const meta = require.main.require('./src/meta');
|
|
4
3
|
const categories = require.main.require('./src/categories');
|
|
5
|
-
|
|
6
|
-
const
|
|
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
|
-
}
|
|
4
|
+
const { SETTINGS_KEY, normalizeCids } = require('./settings');
|
|
5
|
+
const meta = require.main.require('./src/meta');
|
|
14
6
|
|
|
15
7
|
async function getReadableCategories(uid) {
|
|
16
|
-
|
|
17
|
-
categories.getCategoriesByPrivilege('categories:cid', uid || 0, 'read'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
8
|
+
try {
|
|
9
|
+
const cats = await categories.getCategoriesByPrivilege('categories:cid', uid || 0, 'read');
|
|
10
|
+
return Array.isArray(cats) ? cats.filter(Boolean) : [];
|
|
11
|
+
} catch {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
22
14
|
}
|
|
23
15
|
|
|
24
16
|
const controllers = {};
|
|
25
17
|
|
|
26
18
|
controllers.renderAdminPage = async function (req, res) {
|
|
27
19
|
const settings = await meta.settings.get(SETTINGS_KEY);
|
|
28
|
-
const savedCids = normalizeCids(settings
|
|
20
|
+
const savedCids = normalizeCids(settings?.cids);
|
|
29
21
|
|
|
30
22
|
const cats = await getReadableCategories(req.uid);
|
|
31
|
-
const categoriesForTpl =
|
|
23
|
+
const categoriesForTpl = cats
|
|
32
24
|
.filter(c => c && typeof c.cid !== 'undefined' && c.name)
|
|
33
25
|
.map(c => ({
|
|
34
26
|
cid: String(c.cid),
|
package/lib/settings.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const meta = require.main.require('./src/meta');
|
|
4
|
+
|
|
5
|
+
const SETTINGS_KEY = 'discord-onekite';
|
|
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 getSettings() {
|
|
15
|
+
const s = await meta.settings.get(SETTINGS_KEY);
|
|
16
|
+
return {
|
|
17
|
+
webhookUrl: s?.webhookUrl ? String(s.webhookUrl).trim() : '',
|
|
18
|
+
notifyReplies: !!(s && (s.notifyReplies === true || s.notifyReplies === 'on' || s.notifyReplies === 'true')),
|
|
19
|
+
cids: normalizeCids(s?.cids),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function saveSettings(body) {
|
|
24
|
+
const payload = {
|
|
25
|
+
webhookUrl: body.webhookUrl || '',
|
|
26
|
+
notifyReplies: body.notifyReplies ? 'on' : '',
|
|
27
|
+
cids: Array.isArray(body.cids) ? body.cids.map(String).filter(Boolean).join(',') : (body.cids || ''),
|
|
28
|
+
};
|
|
29
|
+
await meta.settings.set(SETTINGS_KEY, payload);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { SETTINGS_KEY, normalizeCids, getSettings, saveSettings };
|
package/library.js
CHANGED
|
@@ -1,57 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Discord embed descriptions do not support masked links like [text](url).
|
|
7
|
-
* To keep links clickable, flatten any link-like markup into plain URLs,
|
|
8
|
-
* which Discord auto-linkifies.
|
|
9
|
-
*/
|
|
10
|
-
function flattenLinksToUrls(str) {
|
|
11
|
-
if (!str || typeof str !== 'string') return str;
|
|
12
|
-
|
|
13
|
-
const normalizeUrl = (u) => {
|
|
14
|
-
if (!u) return '';
|
|
15
|
-
let url = String(u).trim();
|
|
16
|
-
url = url.replace(/^<(.+)>$/, '$1');
|
|
17
|
-
// fix common typos
|
|
18
|
-
url = url.replace(/^https:\//i, 'https://');
|
|
19
|
-
url = url.replace(/^http:\//i, 'http://');
|
|
20
|
-
// add scheme for bare www.
|
|
21
|
-
if (/^www\./i.test(url)) url = 'https://' + url;
|
|
22
|
-
return url;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// HTML links -> URL
|
|
26
|
-
str = str.replace(/<a\s+[^>]*href=["']([^"']+)["'][^>]*>.*?<\/a>/gi, (_m, href) => {
|
|
27
|
-
return normalizeUrl(href);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Markdown masked links -> URL
|
|
31
|
-
str = str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, _text, url) => {
|
|
32
|
-
return normalizeUrl(url);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// NodeBB rendered: "texte du lien(url)" or "texte du lien (url)" -> URL
|
|
36
|
-
str = str.replace(/([^\n\r()]{1,200}?)\s*\((https?:\/\/[^)\s]+|www\.[^)\s]+)\)/g, (_m, _text, url) => {
|
|
37
|
-
return normalizeUrl(url);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// Ensure bare "www.xxx" tokens become "https://www.xxx"
|
|
41
|
-
str = str.replace(/\b(www\.[^\s<>()]+)\b/g, (_m, url) => {
|
|
42
|
-
// strip trailing punctuation
|
|
43
|
-
const clean = url.replace(/[),.;!?]+$/g, '');
|
|
44
|
-
return normalizeUrl(clean);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
return str;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
3
|
const { request } = require('undici');
|
|
51
4
|
|
|
52
5
|
const routeHelpers = require.main.require('./src/routes/helpers');
|
|
53
6
|
const meta = require.main.require('./src/meta');
|
|
54
|
-
|
|
55
7
|
const topics = require.main.require('./src/topics');
|
|
56
8
|
const categories = require.main.require('./src/categories');
|
|
57
9
|
const posts = require.main.require('./src/posts');
|
|
@@ -59,24 +11,24 @@ const user = require.main.require('./src/user');
|
|
|
59
11
|
|
|
60
12
|
const controllers = require('./lib/controllers');
|
|
61
13
|
const adminApi = require('./lib/admin');
|
|
14
|
+
const { getSettings } = require('./lib/settings');
|
|
62
15
|
|
|
63
16
|
const Plugin = {};
|
|
64
17
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (typeof
|
|
71
|
-
return [];
|
|
18
|
+
/**
|
|
19
|
+
* Convert masked markdown links: [text](url) -> url
|
|
20
|
+
* Discord will auto-linkify URLs.
|
|
21
|
+
*/
|
|
22
|
+
function simplifyMaskedLinks(str) {
|
|
23
|
+
if (!str || typeof str !== 'string') return str;
|
|
24
|
+
return str.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$2');
|
|
72
25
|
}
|
|
73
26
|
|
|
74
27
|
function normalizeBaseUrl(url) {
|
|
75
28
|
if (!url) return '';
|
|
76
29
|
let s = String(url).trim();
|
|
77
30
|
if (!/^https?:\/\//i.test(s)) s = `https://${s}`;
|
|
78
|
-
|
|
79
|
-
return s;
|
|
31
|
+
return s.replace(/\/+$/, '');
|
|
80
32
|
}
|
|
81
33
|
|
|
82
34
|
function ensureAbsoluteUrl(baseUrl, maybeUrl) {
|
|
@@ -88,27 +40,17 @@ function ensureAbsoluteUrl(baseUrl, maybeUrl) {
|
|
|
88
40
|
return s;
|
|
89
41
|
}
|
|
90
42
|
|
|
91
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Unified HTML stripping + entity decoding in a single pass.
|
|
45
|
+
*/
|
|
46
|
+
function cleanText(html) {
|
|
92
47
|
if (!html) return '';
|
|
93
|
-
|
|
48
|
+
let s = String(html)
|
|
94
49
|
.replace(/<br\s*\/?>/gi, '\n')
|
|
95
50
|
.replace(/<\/p>/gi, '\n')
|
|
96
|
-
.replace(/<[^>]*>/g, '')
|
|
97
|
-
.replace(/ /g, ' ')
|
|
98
|
-
.replace(/&/g, '&')
|
|
99
|
-
.replace(/</g, '<')
|
|
100
|
-
.replace(/>/g, '>')
|
|
101
|
-
.replace(/'/g, "'")
|
|
102
|
-
.replace(/"/g, '"')
|
|
103
|
-
.trim();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
function decodeHtmlEntities(input) {
|
|
108
|
-
if (!input) return '';
|
|
109
|
-
let s = String(input);
|
|
51
|
+
.replace(/<[^>]*>/g, '');
|
|
110
52
|
|
|
111
|
-
// Named entities
|
|
53
|
+
// Named entities
|
|
112
54
|
s = s
|
|
113
55
|
.replace(/ /g, ' ')
|
|
114
56
|
.replace(/&/g, '&')
|
|
@@ -120,18 +62,16 @@ function decodeHtmlEntities(input) {
|
|
|
120
62
|
// Decimal numeric entities
|
|
121
63
|
s = s.replace(/&#(\d+);/g, (m, dec) => {
|
|
122
64
|
const code = parseInt(dec, 10);
|
|
123
|
-
|
|
124
|
-
return String.fromCodePoint(code);
|
|
65
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : m;
|
|
125
66
|
});
|
|
126
67
|
|
|
127
68
|
// Hex numeric entities
|
|
128
69
|
s = s.replace(/&#x([0-9a-fA-F]+);/g, (m, hex) => {
|
|
129
70
|
const code = parseInt(hex, 16);
|
|
130
|
-
|
|
131
|
-
return String.fromCodePoint(code);
|
|
71
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : m;
|
|
132
72
|
});
|
|
133
73
|
|
|
134
|
-
return s;
|
|
74
|
+
return s.trim();
|
|
135
75
|
}
|
|
136
76
|
|
|
137
77
|
function truncate(s, n) {
|
|
@@ -140,15 +80,6 @@ function truncate(s, n) {
|
|
|
140
80
|
return str.slice(0, n - 1) + '…';
|
|
141
81
|
}
|
|
142
82
|
|
|
143
|
-
async function getSettings() {
|
|
144
|
-
const s = await meta.settings.get(SETTINGS_KEY);
|
|
145
|
-
return {
|
|
146
|
-
webhookUrl: (s && s.webhookUrl) ? String(s.webhookUrl).trim() : '',
|
|
147
|
-
notifyReplies: !!(s && (s.notifyReplies === true || s.notifyReplies === 'on' || s.notifyReplies === 'true')),
|
|
148
|
-
cids: normalizeCids(s && s.cids),
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
83
|
function cidAllowed(topicCid, allowedCids) {
|
|
153
84
|
if (!allowedCids || allowedCids.length === 0) return true;
|
|
154
85
|
return allowedCids.includes(String(topicCid));
|
|
@@ -176,58 +107,51 @@ async function sendDiscord(webhookUrl, payload) {
|
|
|
176
107
|
try {
|
|
177
108
|
await postToDiscord(webhookUrl, payload);
|
|
178
109
|
} catch (e) {
|
|
179
|
-
//
|
|
180
|
-
if (e
|
|
181
|
-
const fallback = payload.embeds[0]
|
|
110
|
+
// Fallback to plain content if embeds rejected
|
|
111
|
+
if (e?.statusCode === 400 && payload?.embeds) {
|
|
112
|
+
const fallback = payload.embeds[0]?.description || 'Notification';
|
|
182
113
|
try { await postToDiscord(webhookUrl, { content: fallback }); return; } catch (e2) { console.error(e2); }
|
|
183
114
|
}
|
|
184
115
|
console.error(e);
|
|
185
116
|
}
|
|
186
117
|
}
|
|
187
118
|
|
|
188
|
-
async function getPostExcerpt(pid) {
|
|
189
|
-
if (!pid) return '';
|
|
190
|
-
try {
|
|
191
|
-
const p = await posts.getPostFields(pid, ['content']);
|
|
192
|
-
const text = stripHtml(p && p.content);
|
|
193
|
-
return truncate(text, 500);
|
|
194
|
-
} catch {
|
|
195
|
-
return '';
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
119
|
async function buildEmbed({ tid, pid, isReply }) {
|
|
200
|
-
let baseUrl = normalizeBaseUrl(meta.config.url
|
|
120
|
+
let baseUrl = normalizeBaseUrl(meta.config.url);
|
|
201
121
|
if (!baseUrl) baseUrl = 'https://www.onekite.com';
|
|
202
122
|
|
|
203
123
|
const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'title', 'slug', 'mainPid']);
|
|
204
124
|
if (!topicData) return null;
|
|
205
125
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
126
|
+
const targetPid = isReply ? pid : topicData.mainPid;
|
|
127
|
+
|
|
128
|
+
// Parallel fetch: category + post data
|
|
129
|
+
const [cat, postData] = await Promise.all([
|
|
130
|
+
categories.getCategoryData(topicData.cid).catch(() => null),
|
|
131
|
+
posts.getPostFields(targetPid, ['uid', 'content']),
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
const categoryName = cat?.name ? cleanText(cat.name) : '';
|
|
135
|
+
const excerpt = truncate(cleanText(postData?.content), 500);
|
|
136
|
+
const username = await user.getUserField(postData.uid, 'username');
|
|
211
137
|
|
|
212
138
|
const topicUrl = ensureAbsoluteUrl(baseUrl, `/topic/${topicData.slug || topicData.tid}`);
|
|
213
139
|
const targetUrl = (isReply && pid) ? `${topicUrl}/${pid}` : topicUrl;
|
|
214
140
|
|
|
215
|
-
const baseTitle =
|
|
141
|
+
const baseTitle = cleanText((topicData.title || (isReply ? 'Nouvelle réponse' : 'Nouveau sujet')).toString());
|
|
216
142
|
const titleWithCategory = categoryName ? `[${categoryName}] ${baseTitle}` : baseTitle;
|
|
217
143
|
const replyIcon = isReply ? '↪️ ' : '';
|
|
144
|
+
const embedTitle = `${replyIcon}${titleWithCategory} – ${username}`.slice(0, 256);
|
|
218
145
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// Embed title becomes clickable via embed.url
|
|
230
|
-
return { topicData, content: '', embed: { title: embedTitle, url: targetUrl, description: flattenLinksToUrls(excerpt || '') } };
|
|
146
|
+
return {
|
|
147
|
+
topicData,
|
|
148
|
+
content: '',
|
|
149
|
+
embed: {
|
|
150
|
+
title: embedTitle,
|
|
151
|
+
url: targetUrl,
|
|
152
|
+
description: simplifyMaskedLinks(excerpt || ''),
|
|
153
|
+
},
|
|
154
|
+
};
|
|
231
155
|
}
|
|
232
156
|
|
|
233
157
|
function extractTidPid(data) {
|
|
@@ -242,14 +166,12 @@ Plugin.init = async function (params) {
|
|
|
242
166
|
// ACP page
|
|
243
167
|
routeHelpers.setupAdminPageRoute(router, '/admin/plugins/discord-onekite', [], controllers.renderAdminPage);
|
|
244
168
|
|
|
245
|
-
// Admin API v3 routes
|
|
246
|
-
const
|
|
169
|
+
// Admin API v3 routes
|
|
170
|
+
const adminBase = '/api/v3/admin/plugins/discord-onekite';
|
|
247
171
|
const adminMws = [middleware.admin.checkPrivileges];
|
|
248
172
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
router.put(`${base}/settings`, ...adminMws, adminApi.saveSettings);
|
|
252
|
-
});
|
|
173
|
+
router.get(`${adminBase}/settings`, ...adminMws, adminApi.getSettings);
|
|
174
|
+
router.put(`${adminBase}/settings`, ...adminMws, adminApi.saveSettings);
|
|
253
175
|
};
|
|
254
176
|
|
|
255
177
|
Plugin.addAdminNavigation = (header) => {
|
|
@@ -277,8 +199,7 @@ Plugin.onTopicPost = async (data) => {
|
|
|
277
199
|
|
|
278
200
|
Plugin.onTopicReply = async (data) => {
|
|
279
201
|
const settings = await getSettings();
|
|
280
|
-
if (!settings.notifyReplies) return;
|
|
281
|
-
if (!settings.webhookUrl) return;
|
|
202
|
+
if (!settings.notifyReplies || !settings.webhookUrl) return;
|
|
282
203
|
|
|
283
204
|
const { tid, pid } = extractTidPid(data);
|
|
284
205
|
if (!tid || !pid) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-onekite-discord",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Discord webhook notifier for Onekite (NodeBB v4.x only)",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -17,4 +17,4 @@
|
|
|
17
17
|
"nbbpm": {
|
|
18
18
|
"compatibility": "^4.0.0"
|
|
19
19
|
}
|
|
20
|
-
}
|
|
20
|
+
}
|
package/plugin.json
CHANGED
package/public/admin.js
CHANGED
|
@@ -2,8 +2,7 @@ define('admin/plugins/discord-onekite', ['alerts', 'bootbox'], function (alerts)
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
function showAlert(type, msg) {
|
|
5
|
-
// Deduplicate identical alerts
|
|
6
|
-
// by NodeBB ACP save buttons/hooks across ajaxify navigations.
|
|
5
|
+
// Deduplicate identical alerts across ajaxify navigations
|
|
7
6
|
try {
|
|
8
7
|
const now = Date.now();
|
|
9
8
|
const last = window.oneKiteDiscordLastAlert;
|
|
@@ -21,24 +20,30 @@ define('admin/plugins/discord-onekite', ['alerts', 'bootbox'], function (alerts)
|
|
|
21
20
|
alert(msg);
|
|
22
21
|
}
|
|
23
22
|
|
|
23
|
+
function getCsrfToken() {
|
|
24
|
+
return document.querySelector('meta[name="csrf_token"]')?.getAttribute('content')
|
|
25
|
+
|| document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
|
|
26
|
+
|| (typeof app !== 'undefined' && app?.csrfToken)
|
|
27
|
+
|| '';
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
async function fetchJson(url, opts) {
|
|
25
|
-
const res = await fetch(url,
|
|
26
|
-
headers:
|
|
31
|
+
const res = await fetch(url, {
|
|
32
|
+
headers: {
|
|
27
33
|
'Content-Type': 'application/json',
|
|
28
|
-
'x-csrf-token': (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
'',
|
|
32
|
-
}, (opts && opts.headers) || {}),
|
|
34
|
+
'x-csrf-token': getCsrfToken(),
|
|
35
|
+
...((opts && opts.headers) || {}),
|
|
36
|
+
},
|
|
33
37
|
credentials: 'same-origin',
|
|
34
|
-
|
|
38
|
+
...opts,
|
|
39
|
+
});
|
|
35
40
|
|
|
36
41
|
const text = await res.text();
|
|
37
42
|
let data = null;
|
|
38
43
|
try { data = text ? JSON.parse(text) : null; } catch (e) {}
|
|
39
44
|
|
|
40
45
|
if (!res.ok) {
|
|
41
|
-
const err = new Error(
|
|
46
|
+
const err = new Error(data?.status?.message || text || 'Request failed');
|
|
42
47
|
err.status = res.status;
|
|
43
48
|
err.data = data;
|
|
44
49
|
throw err;
|
|
@@ -48,7 +53,7 @@ define('admin/plugins/discord-onekite', ['alerts', 'bootbox'], function (alerts)
|
|
|
48
53
|
|
|
49
54
|
async function loadSettings() {
|
|
50
55
|
const data = await fetchJson('/api/v3/admin/plugins/discord-onekite/settings');
|
|
51
|
-
return
|
|
56
|
+
return data?.settings || {};
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
async function saveSettings(payload) {
|
|
@@ -82,9 +87,8 @@ define('admin/plugins/discord-onekite', ['alerts', 'bootbox'], function (alerts)
|
|
|
82
87
|
await saveSettings(getPayload());
|
|
83
88
|
showAlert('success', 'Paramètres enregistrés');
|
|
84
89
|
} catch (e) {
|
|
85
|
-
// eslint-disable-next-line no-console
|
|
86
90
|
console.error(e);
|
|
87
|
-
showAlert('error', e
|
|
91
|
+
showAlert('error', e?.message || 'Erreur lors de l\u2019enregistrement');
|
|
88
92
|
}
|
|
89
93
|
}
|
|
90
94
|
|
|
@@ -94,7 +98,7 @@ define('admin/plugins/discord-onekite', ['alerts', 'bootbox'], function (alerts)
|
|
|
94
98
|
window.oneKiteDiscordAdminBound = true;
|
|
95
99
|
|
|
96
100
|
document.addEventListener('click', function (ev) {
|
|
97
|
-
const target = ev.target
|
|
101
|
+
const target = ev.target?.closest?.(SAVE_SELECTOR);
|
|
98
102
|
if (!target) return;
|
|
99
103
|
ev.preventDefault();
|
|
100
104
|
doSave();
|
|
@@ -109,7 +113,6 @@ define('admin/plugins/discord-onekite', ['alerts', 'bootbox'], function (alerts)
|
|
|
109
113
|
if (typeof s.notifyReplies !== 'undefined') document.getElementById('notifyReplies').checked = !!s.notifyReplies;
|
|
110
114
|
if (Array.isArray(s.cids)) setSelectedCids(s.cids);
|
|
111
115
|
} catch (e) {
|
|
112
|
-
// eslint-disable-next-line no-console
|
|
113
116
|
console.error(e);
|
|
114
117
|
}
|
|
115
118
|
}
|