nodebb-plugin-onekite-calendar 1.0.15 → 1.0.21
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/CHANGELOG.md +2 -0
- package/lib/api.js +113 -15
- package/lib/scheduler.js +32 -13
- package/package.json +1 -1
- package/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
- Suppression du texte d’archivage dans le toast de purge (plus de « 0 archivés »)
|
|
5
5
|
- Renommage du plugin : nodebb-plugin-onekite-calendar
|
|
6
6
|
- Discord : notification « ❌ Réservation annulée » (annulation manuelle + annulation automatique) + option ACP
|
|
7
|
+
- Groupes ACP : les champs CSV acceptent aussi ; et retours à la ligne
|
|
8
|
+
- Notifications email (groupes) : résolution robuste nom/slug et normalisation des membres (uids)
|
|
7
9
|
|
|
8
10
|
## 1.0.2
|
|
9
11
|
- Purge calendrier : suppression réelle des réservations (aucune logique d’archivage)
|
package/lib/api.js
CHANGED
|
@@ -12,6 +12,83 @@ const logger = require.main.require('./src/logger');
|
|
|
12
12
|
|
|
13
13
|
const dbLayer = require('./db');
|
|
14
14
|
|
|
15
|
+
// Resolve group identifiers from ACP.
|
|
16
|
+
// Admins may enter a group *name* ("Test") or a *slug* ("onekite-ffvl-2026").
|
|
17
|
+
// Depending on NodeBB version and group type (system/custom), some core methods
|
|
18
|
+
// accept one or the other. We try both to be tolerant.
|
|
19
|
+
async function getMembersByGroupIdentifier(groupIdentifier) {
|
|
20
|
+
const id = String(groupIdentifier || '').trim();
|
|
21
|
+
if (!id) return [];
|
|
22
|
+
|
|
23
|
+
const toUids = (arr) => {
|
|
24
|
+
if (!Array.isArray(arr)) return [];
|
|
25
|
+
// NodeBB may return an array of uids (numbers/strings), or an array of user objects (with uid).
|
|
26
|
+
const uids = arr.map((v) => {
|
|
27
|
+
if (v == null) return null;
|
|
28
|
+
if (typeof v === 'number') return v;
|
|
29
|
+
if (typeof v === 'string') {
|
|
30
|
+
const n = parseInt(v, 10);
|
|
31
|
+
return Number.isFinite(n) ? n : null;
|
|
32
|
+
}
|
|
33
|
+
if (typeof v === 'object') {
|
|
34
|
+
const maybe = v.uid ?? v.userId ?? v.id;
|
|
35
|
+
const n = parseInt(String(maybe), 10);
|
|
36
|
+
return Number.isFinite(n) ? n : null;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}).filter((n) => Number.isFinite(n));
|
|
40
|
+
// de-dupe
|
|
41
|
+
return [...new Set(uids)];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// First try direct.
|
|
45
|
+
let members = [];
|
|
46
|
+
try {
|
|
47
|
+
members = await groups.getMembers(id, 0, -1);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
members = [];
|
|
50
|
+
}
|
|
51
|
+
const directUids = toUids(members);
|
|
52
|
+
if (directUids.length) return directUids;
|
|
53
|
+
|
|
54
|
+
// Some NodeBB builds expose getMembersByGroupSlug (slug -> uids)
|
|
55
|
+
if (typeof groups.getMembersByGroupSlug === 'function') {
|
|
56
|
+
try {
|
|
57
|
+
const bySlug = await groups.getMembersByGroupSlug(id, 0, -1);
|
|
58
|
+
const bySlugUids = toUids(bySlug);
|
|
59
|
+
if (bySlugUids.length) return bySlugUids;
|
|
60
|
+
} catch (e) {}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Then try slug -> groupName mapping when available.
|
|
64
|
+
if (typeof groups.getGroupNameByGroupSlug === 'function') {
|
|
65
|
+
let groupName = null;
|
|
66
|
+
try {
|
|
67
|
+
if (groups.getGroupNameByGroupSlug.length >= 2) {
|
|
68
|
+
groupName = await new Promise((resolve) => {
|
|
69
|
+
groups.getGroupNameByGroupSlug(id, (err, name) => resolve(err ? null : name));
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
groupName = await groups.getGroupNameByGroupSlug(id);
|
|
73
|
+
}
|
|
74
|
+
} catch (e) {
|
|
75
|
+
groupName = null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (groupName && String(groupName).trim() && String(groupName).trim() !== id) {
|
|
79
|
+
try {
|
|
80
|
+
members = await groups.getMembers(String(groupName).trim(), 0, -1);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
members = [];
|
|
83
|
+
}
|
|
84
|
+
const byNameUids = toUids(members);
|
|
85
|
+
if (byNameUids.length) return byNameUids;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
|
|
15
92
|
// Fast membership check without N calls to groups.isMember.
|
|
16
93
|
// NodeBB's groups.getUserGroups([uid]) returns an array (per uid) of group objects.
|
|
17
94
|
// We compare against both group slugs and names to be tolerant with older settings.
|
|
@@ -43,7 +120,8 @@ function normalizeAllowedGroups(raw) {
|
|
|
43
120
|
if (Array.isArray(parsed)) return parsed.map(v => String(v).trim()).filter(Boolean);
|
|
44
121
|
} catch (e) {}
|
|
45
122
|
}
|
|
46
|
-
|
|
123
|
+
// Accept commas, semicolons, or new lines as separators
|
|
124
|
+
return s.split(/[,;\n]+/).map(v => String(v).trim().replace(/^"+|"+$/g, '')).filter(Boolean);
|
|
47
125
|
}
|
|
48
126
|
|
|
49
127
|
// NOTE: Avoid per-group async checks (groups.isMember) when possible.
|
|
@@ -56,30 +134,50 @@ const discord = require('./discord');
|
|
|
56
134
|
// We try the common forms. Any failure is logged for debugging.
|
|
57
135
|
async function sendEmail(template, toEmail, subject, data) {
|
|
58
136
|
if (!toEmail) return;
|
|
137
|
+
|
|
138
|
+
// Language can come from plugin settings or NodeBB config depending on version.
|
|
139
|
+
let lang = 'fr';
|
|
140
|
+
try {
|
|
141
|
+
const s = await meta.settings.get('calendar-onekite').catch(() => ({}));
|
|
142
|
+
lang = (s && s.defaultLang) || (meta && meta.config && (meta.config.defaultLang || meta.config.defaultLanguage)) || 'fr';
|
|
143
|
+
} catch (e) {
|
|
144
|
+
lang = (meta && meta.config && (meta.config.defaultLang || meta.config.defaultLanguage)) || 'fr';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Subject is not a positional arg; it must be injected via params.subject.
|
|
148
|
+
const params = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
149
|
+
|
|
150
|
+
// Prefer sendToEmail when available (most consistent across versions).
|
|
59
151
|
try {
|
|
60
|
-
// NodeBB core signature (historically):
|
|
61
|
-
// Emailer.sendToEmail(template, email, language, params[, callback])
|
|
62
|
-
// Subject is not a positional arg; it must be injected (either by NodeBB itself
|
|
63
|
-
// or via filter:email.modify). We always pass it in params.subject.
|
|
64
|
-
const language = (meta && meta.config && (meta.config.defaultLang || meta.config.defaultLanguage)) || 'fr';
|
|
65
|
-
const params = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
66
152
|
if (typeof emailer.sendToEmail === 'function') {
|
|
67
|
-
|
|
153
|
+
// NodeBB: sendToEmail(template, email, language, params)
|
|
154
|
+
if (emailer.sendToEmail.length >= 4) {
|
|
155
|
+
await emailer.sendToEmail(template, toEmail, lang, params);
|
|
156
|
+
} else {
|
|
157
|
+
// Older signature: sendToEmail(template, email, params)
|
|
158
|
+
await emailer.sendToEmail(template, toEmail, params);
|
|
159
|
+
}
|
|
68
160
|
return;
|
|
69
161
|
}
|
|
70
|
-
|
|
162
|
+
} catch (err) {
|
|
163
|
+
// eslint-disable-next-line no-console
|
|
164
|
+
console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String((err && err.message) || err) });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Very old/unusual builds: try send() but only if it clearly accepts an email.
|
|
168
|
+
try {
|
|
71
169
|
if (typeof emailer.send === 'function') {
|
|
72
170
|
// Some builds accept (template, email, language, params)
|
|
73
171
|
if (emailer.send.length >= 4) {
|
|
74
|
-
await emailer.send(template, toEmail,
|
|
75
|
-
|
|
172
|
+
await emailer.send(template, toEmail, lang, params);
|
|
173
|
+
} else {
|
|
174
|
+
// Some builds accept (template, email, params)
|
|
175
|
+
await emailer.send(template, toEmail, params);
|
|
76
176
|
}
|
|
77
|
-
// Some builds accept (template, email, params)
|
|
78
|
-
await emailer.send(template, toEmail, params);
|
|
79
177
|
}
|
|
80
178
|
} catch (err) {
|
|
81
179
|
// eslint-disable-next-line no-console
|
|
82
|
-
console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String(err) });
|
|
180
|
+
console.warn('[calendar-onekite] Failed to send email', { template, toEmail, err: String((err && err.message) || err) });
|
|
83
181
|
}
|
|
84
182
|
}
|
|
85
183
|
|
|
@@ -710,7 +808,7 @@ api.createReservation = async function (req, res) {
|
|
|
710
808
|
const requester = await user.getUserFields(uid, ['username', 'email']);
|
|
711
809
|
const itemsLabel = (resv.itemNames || []).join(', ');
|
|
712
810
|
for (const g of notifyGroups) {
|
|
713
|
-
const members = await
|
|
811
|
+
const members = await getMembersByGroupIdentifier(g);
|
|
714
812
|
const uids = Array.isArray(members) ? members : [];
|
|
715
813
|
|
|
716
814
|
// Batch fetch user email/username when supported by this NodeBB version.
|
package/lib/scheduler.js
CHANGED
|
@@ -59,28 +59,47 @@ async function processAwaitingPayment() {
|
|
|
59
59
|
|
|
60
60
|
async function sendEmail(template, toEmail, subject, data) {
|
|
61
61
|
if (!toEmail) return;
|
|
62
|
+
|
|
63
|
+
// Language can come from plugin settings or NodeBB config depending on version.
|
|
64
|
+
let lang = 'fr';
|
|
62
65
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const params = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
66
|
+
const s = await meta.settings.get('calendar-onekite').catch(() => ({}));
|
|
67
|
+
lang = (s && s.defaultLang) || (meta && meta.config && (meta.config.defaultLang || meta.config.defaultLanguage)) || 'fr';
|
|
68
|
+
} catch (e) {
|
|
69
|
+
lang = (meta && meta.config && (meta.config.defaultLang || meta.config.defaultLanguage)) || 'fr';
|
|
70
|
+
}
|
|
69
71
|
|
|
72
|
+
// Subject is NOT a positional argument; it must be provided in params.subject.
|
|
73
|
+
const params = Object.assign({}, data || {}, subject ? { subject } : {});
|
|
74
|
+
|
|
75
|
+
try {
|
|
70
76
|
if (typeof emailer.sendToEmail === 'function') {
|
|
71
|
-
|
|
77
|
+
// NodeBB: sendToEmail(template, email, language, params)
|
|
78
|
+
if (emailer.sendToEmail.length >= 4) {
|
|
79
|
+
await emailer.sendToEmail(template, toEmail, lang, params);
|
|
80
|
+
} else {
|
|
81
|
+
// Older signature: sendToEmail(template, email, params)
|
|
82
|
+
await emailer.sendToEmail(template, toEmail, params);
|
|
83
|
+
}
|
|
72
84
|
return;
|
|
73
85
|
}
|
|
86
|
+
} catch (err) {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.warn('[calendar-onekite] Failed to send email (scheduler)', {
|
|
89
|
+
template,
|
|
90
|
+
toEmail,
|
|
91
|
+
err: String((err && err.message) || err),
|
|
92
|
+
});
|
|
93
|
+
}
|
|
74
94
|
|
|
75
|
-
|
|
95
|
+
// Very old/unusual builds: try send() but only if it clearly accepts an email.
|
|
96
|
+
try {
|
|
76
97
|
if (typeof emailer.send === 'function') {
|
|
77
|
-
// Some builds accept (template, email, language, params)
|
|
78
98
|
if (emailer.send.length >= 4) {
|
|
79
|
-
await emailer.send(template, toEmail,
|
|
80
|
-
|
|
99
|
+
await emailer.send(template, toEmail, lang, params);
|
|
100
|
+
} else {
|
|
101
|
+
await emailer.send(template, toEmail, params);
|
|
81
102
|
}
|
|
82
|
-
// Some builds accept (template, email, params)
|
|
83
|
-
await emailer.send(template, toEmail, params);
|
|
84
103
|
}
|
|
85
104
|
} catch (err) {
|
|
86
105
|
// eslint-disable-next-line no-console
|
package/package.json
CHANGED
package/plugin.json
CHANGED