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 +57 -124
- package/package.json +2 -2
- package/public/client.js +374 -433
- package/public/style.css +19 -95
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
|
|
33
|
+
if (!names || !names.length) {
|
|
35
34
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
36
35
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
55
|
-
if (_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
|
-
|
|
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
|
-
// ──
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
// ──
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
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 (
|
|
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:
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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.
|
|
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
|
+
}
|