nodebb-plugin-ezoic-infinite 1.8.68 → 1.8.70

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/library.js CHANGED
@@ -7,18 +7,17 @@ const db = require.main.require('./src/database');
7
7
  const SETTINGS_KEY = 'ezoic-infinite';
8
8
  const plugin = {};
9
9
 
10
- // ── Helpers ──────────────────────────────────────────────────────────────────
10
+ // ── Helpers ────────────────────────────────────────────────────────────────
11
11
 
12
12
  function normalizeExcludedGroups(value) {
13
13
  if (!value) return [];
14
14
  if (Array.isArray(value)) return value;
15
+ // NodeBB stocke les settings multi-valeurs comme string JSON "[\"group1\",\"group2\"]"
15
16
  const s = String(value).trim();
16
17
  if (s.startsWith('[')) {
17
- try {
18
- const parsed = JSON.parse(s);
19
- if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean);
20
- } catch (_) {}
18
+ try { const parsed = JSON.parse(s); if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean); } catch (_) {}
21
19
  }
20
+ // Fallback : séparation par virgule
22
21
  return s.split(',').map(v => v.trim()).filter(Boolean);
23
22
  }
24
23
 
@@ -31,29 +30,25 @@ function parseBool(v, def = false) {
31
30
 
32
31
  async function getAllGroups() {
33
32
  let names = await db.getSortedSetRange('groups:createtime', 0, -1);
34
- if (!names?.length) {
33
+ if (!names || !names.length) {
35
34
  names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
36
35
  }
37
- return (await groups.getGroupsData(
38
- (names || []).filter(name => !groups.isPrivilegeGroup(name))
39
- ))
40
- .filter(g => g?.name)
41
- .sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
36
+ const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
37
+ const data = await groups.getGroupsData(filtered);
38
+ const valid = data.filter(g => g && g.name);
39
+ valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
40
+ return valid;
42
41
  }
43
42
 
44
- // ── Settings cache ───────────────────────────────────────────────────────────
43
+ // ── Settings cache (30s TTL) ────────────────────────────────────────────────
45
44
 
46
45
  let _settingsCache = null;
47
46
  let _settingsCacheAt = 0;
48
47
  const SETTINGS_TTL = 30_000;
49
48
 
50
- const _excludeCache = new Map();
51
- const EXCLUDE_TTL = 60_000;
52
-
53
49
  async function getSettings() {
54
- const t = Date.now();
55
- if (_settingsCache && (t - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
56
-
50
+ const now = Date.now();
51
+ if (_settingsCache && (now - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
57
52
  const s = await meta.settings.get(SETTINGS_KEY);
58
53
  _settingsCacheAt = Date.now();
59
54
  _settingsCache = {
@@ -76,122 +71,52 @@ async function getSettings() {
76
71
 
77
72
  async function isUserExcluded(uid, excludedGroups) {
78
73
  if (!uid || !excludedGroups.length) return false;
79
-
80
- const key = `${uid}|${excludedGroups.join('|')}`;
81
- const t = Date.now();
82
- const hit = _excludeCache.get(key);
83
- if (hit && (t - hit.at) < EXCLUDE_TTL) return hit.value;
84
-
85
- const excludedSet = new Set(excludedGroups);
86
74
  const userGroups = await groups.getUserGroups([uid]);
87
- const value = (userGroups[0] || []).some(g => excludedSet.has(g.name));
88
-
89
- _excludeCache.set(key, { value, at: Date.now() });
90
- if (_excludeCache.size > 1000) {
91
- _excludeCache.delete(_excludeCache.keys().next().value);
92
- }
93
-
94
- return value;
95
- }
96
-
97
- function clearCaches() {
98
- _settingsCache = null;
99
- _settingsCacheAt = 0;
100
- _excludeCache.clear();
75
+ return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
101
76
  }
102
77
 
103
- // ── Client config ────────────────────────────────────────────────────────────
104
-
105
- function buildClientConfig(settings, excluded) {
106
- return {
107
- excluded,
108
- enableBetweenAds: settings.enableBetweenAds,
109
- showFirstTopicAd: settings.showFirstTopicAd,
110
- placeholderIds: settings.placeholderIds,
111
- intervalPosts: settings.intervalPosts,
112
- enableCategoryAds: settings.enableCategoryAds,
113
- showFirstCategoryAd: settings.showFirstCategoryAd,
114
- categoryPlaceholderIds: settings.categoryPlaceholderIds,
115
- intervalCategories: settings.intervalCategories,
116
- enableMessageAds: settings.enableMessageAds,
117
- showFirstMessageAd: settings.showFirstMessageAd,
118
- messagePlaceholderIds: settings.messagePlaceholderIds,
119
- messageIntervalPosts: settings.messageIntervalPosts,
120
- };
121
- }
78
+ // ── Scripts Ezoic ──────────────────────────────────────────────────────────
122
79
 
123
- function serializeInlineConfig(cfg) {
124
- return `<script data-cfasync="false">window.__nbbEzoicCfg=${JSON.stringify(cfg).replace(/</g, '\\u003c')};</script>`;
125
- }
80
+ const EZOIC_SCRIPTS = `<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>
81
+ <script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>
82
+ <script async src="//www.ezojs.com/ezoic/sa.min.js"></script>
83
+ <script>
84
+ window.ezstandalone = window.ezstandalone || {};
85
+ ezstandalone.cmd = ezstandalone.cmd || [];
86
+ </script>`;
126
87
 
127
- // ── Head injection ───────────────────────────────────────────────────────────
128
-
129
- const HEAD_PRECONNECTS = [
130
- '<link rel="preconnect" href="https://g.ezoic.net" crossorigin>',
131
- '<link rel="preconnect" href="https://go.ezoic.net" crossorigin>',
132
- '<link rel="preconnect" href="https://securepubads.g.doubleclick.net" crossorigin>',
133
- '<link rel="preconnect" href="https://pagead2.googlesyndication.com" crossorigin>',
134
- '<link rel="dns-prefetch" href="https://g.ezoic.net">',
135
- '<link rel="dns-prefetch" href="https://securepubads.g.doubleclick.net">',
136
- ].join('\n');
137
-
138
- const EZOIC_SCRIPTS = [
139
- '<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>',
140
- '<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>',
141
- '<script>',
142
- 'window._ezaq = window._ezaq || {};',
143
- 'window.ezstandalone = window.ezstandalone || {};',
144
- 'ezstandalone.cmd = ezstandalone.cmd || [];',
145
- '</script>',
146
- '<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>',
147
- ].join('\n');
148
-
149
- // ── Hooks ────────────────────────────────────────────────────────────────────
88
+ // ── Hooks ──────────────────────────────────────────────────────────────────
150
89
 
151
90
  plugin.onSettingsSet = function (data) {
152
- if (data?.hash === SETTINGS_KEY) clearCaches();
91
+ if (data && data.hash === SETTINGS_KEY) _settingsCache = null;
153
92
  };
154
93
 
155
94
  plugin.addAdminNavigation = async (header) => {
156
95
  header.plugins = header.plugins || [];
157
- header.plugins.push({
158
- route: '/plugins/ezoic-infinite',
159
- icon: 'fa-ad',
160
- name: 'Ezoic Infinite Ads',
161
- });
96
+ header.plugins.push({ route: '/plugins/ezoic-infinite', icon: 'fa-ad', name: 'Ezoic Infinite Ads' });
162
97
  return header;
163
98
  };
164
99
 
100
+ /**
101
+ * Injecte les scripts Ezoic dans le <head> via templateData.customHTML.
102
+ *
103
+ * NodeBB v4 / thème Harmony : header.tpl contient {{customHTML}} dans le <head>
104
+ * (render.js ligne 232 : templateValues.customHTML = meta.config.customHTML).
105
+ * Le hook filter:middleware.renderHeader reçoit templateData = headerFooterData
106
+ * et est rendu via req.app.renderAsync('header', hookReturn.templateData).
107
+ * On préfixe customHTML pour que nos scripts passent AVANT le customHTML admin,
108
+ * tout en préservant ce dernier.
109
+ */
165
110
  plugin.injectEzoicHead = async (data) => {
166
111
  try {
167
112
  const settings = await getSettings();
168
113
  const uid = data.req?.uid ?? 0;
169
114
  const excluded = await isUserExcluded(uid, settings.excludedGroups);
170
-
171
- if (excluded) {
172
- const cfg = buildClientConfig(settings, true);
173
- // Minimal stub for excluded users — prevents ReferenceError if other
174
- // scripts reference _ezaq or ezstandalone
175
- const stub = '<script>'
176
- + 'window._ezaq=window._ezaq||{};'
177
- + 'window.ezstandalone=window.ezstandalone||{};'
178
- + 'window.ezstandalone.cmd=window.ezstandalone.cmd||[];'
179
- + '</script>';
180
- data.templateData.customHTML =
181
- stub + '\n' +
182
- serializeInlineConfig(cfg) +
183
- (data.templateData.customHTML || '');
184
- } else {
185
- const cfg = buildClientConfig(settings, false);
186
- data.templateData.customHTML =
187
- HEAD_PRECONNECTS + '\n' +
188
- EZOIC_SCRIPTS + '\n' +
189
- serializeInlineConfig(cfg) +
190
- (data.templateData.customHTML || '');
115
+ if (!excluded) {
116
+ // Préfixer : nos scripts d'abord, puis le customHTML existant de l'admin
117
+ data.templateData.customHTML = EZOIC_SCRIPTS + (data.templateData.customHTML || '');
191
118
  }
192
- } catch (err) {
193
- console.error('[ezoic-infinite] injectEzoicHead error:', err.message);
194
- }
119
+ } catch (_) {}
195
120
  return data;
196
121
  };
197
122
 
@@ -202,8 +127,7 @@ plugin.init = async ({ router, middleware }) => {
202
127
  res.render('admin/plugins/ezoic-infinite', {
203
128
  title: 'Ezoic Infinite Ads',
204
129
  ...settings,
205
- enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
206
- enableCategoryAds_checked: settings.enableCategoryAds ? 'checked' : '',
130
+ enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
207
131
  enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
208
132
  allGroups,
209
133
  });
@@ -213,14 +137,23 @@ plugin.init = async ({ router, middleware }) => {
213
137
  router.get('/api/admin/plugins/ezoic-infinite', render);
214
138
 
215
139
  router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
216
- try {
217
- const settings = await getSettings();
218
- const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
219
- res.json(buildClientConfig(settings, excluded));
220
- } catch (err) {
221
- console.error('[ezoic-infinite] config API error:', err.message);
222
- res.status(500).json({ error: 'internal' });
223
- }
140
+ const settings = await getSettings();
141
+ const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
142
+ res.json({
143
+ excluded,
144
+ enableBetweenAds: settings.enableBetweenAds,
145
+ showFirstTopicAd: settings.showFirstTopicAd,
146
+ placeholderIds: settings.placeholderIds,
147
+ intervalPosts: settings.intervalPosts,
148
+ enableCategoryAds: settings.enableCategoryAds,
149
+ showFirstCategoryAd: settings.showFirstCategoryAd,
150
+ categoryPlaceholderIds: settings.categoryPlaceholderIds,
151
+ intervalCategories: settings.intervalCategories,
152
+ enableMessageAds: settings.enableMessageAds,
153
+ showFirstMessageAd: settings.showFirstMessageAd,
154
+ messagePlaceholderIds: settings.messagePlaceholderIds,
155
+ messageIntervalPosts: settings.messageIntervalPosts,
156
+ });
224
157
  });
225
158
  };
226
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.8.68",
3
+ "version": "1.8.70",
4
4
  "description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
5
5
  "main": "library.js",
6
6
  "license": "MIT",
@@ -18,4 +18,4 @@
18
18
  "compatibility": "^4.0.0"
19
19
  },
20
20
  "private": false
21
- }
21
+ }