nodebb-plugin-ezoic-infinite 1.8.69 → 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,120 +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.ezstandalone = window.ezstandalone || {};',
143
- 'ezstandalone.cmd = ezstandalone.cmd || [];',
144
- '</script>',
145
- '<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>',
146
- ].join('\n');
147
-
148
- // ── Hooks ────────────────────────────────────────────────────────────────────
88
+ // ── Hooks ──────────────────────────────────────────────────────────────────
149
89
 
150
90
  plugin.onSettingsSet = function (data) {
151
- if (data?.hash === SETTINGS_KEY) clearCaches();
91
+ if (data && data.hash === SETTINGS_KEY) _settingsCache = null;
152
92
  };
153
93
 
154
94
  plugin.addAdminNavigation = async (header) => {
155
95
  header.plugins = header.plugins || [];
156
- header.plugins.push({
157
- route: '/plugins/ezoic-infinite',
158
- icon: 'fa-ad',
159
- name: 'Ezoic Infinite Ads',
160
- });
96
+ header.plugins.push({ route: '/plugins/ezoic-infinite', icon: 'fa-ad', name: 'Ezoic Infinite Ads' });
161
97
  return header;
162
98
  };
163
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
+ */
164
110
  plugin.injectEzoicHead = async (data) => {
165
111
  try {
166
112
  const settings = await getSettings();
167
113
  const uid = data.req?.uid ?? 0;
168
114
  const excluded = await isUserExcluded(uid, settings.excludedGroups);
169
-
170
- if (excluded) {
171
- const cfg = buildClientConfig(settings, true);
172
- // Minimal stub for excluded users — prevents ReferenceError if other
173
- // scripts reference _ezaq or ezstandalone
174
- const stub = '<script>'
175
- + 'window.ezstandalone=window.ezstandalone||{};'
176
- + 'window.ezstandalone.cmd=window.ezstandalone.cmd||[];'
177
- + '</script>';
178
- data.templateData.customHTML =
179
- stub + '\n' +
180
- serializeInlineConfig(cfg) +
181
- (data.templateData.customHTML || '');
182
- } else {
183
- const cfg = buildClientConfig(settings, false);
184
- data.templateData.customHTML =
185
- HEAD_PRECONNECTS + '\n' +
186
- EZOIC_SCRIPTS + '\n' +
187
- serializeInlineConfig(cfg) +
188
- (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 || '');
189
118
  }
190
- } catch (err) {
191
- console.error('[ezoic-infinite] injectEzoicHead error:', err.message);
192
- }
119
+ } catch (_) {}
193
120
  return data;
194
121
  };
195
122
 
@@ -200,8 +127,7 @@ plugin.init = async ({ router, middleware }) => {
200
127
  res.render('admin/plugins/ezoic-infinite', {
201
128
  title: 'Ezoic Infinite Ads',
202
129
  ...settings,
203
- enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
204
- enableCategoryAds_checked: settings.enableCategoryAds ? 'checked' : '',
130
+ enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
205
131
  enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
206
132
  allGroups,
207
133
  });
@@ -211,14 +137,23 @@ plugin.init = async ({ router, middleware }) => {
211
137
  router.get('/api/admin/plugins/ezoic-infinite', render);
212
138
 
213
139
  router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
214
- try {
215
- const settings = await getSettings();
216
- const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
217
- res.json(buildClientConfig(settings, excluded));
218
- } catch (err) {
219
- console.error('[ezoic-infinite] config API error:', err.message);
220
- res.status(500).json({ error: 'internal' });
221
- }
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
+ });
222
157
  });
223
158
  };
224
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-ezoic-infinite",
3
- "version": "1.8.69",
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
+ }