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 +57 -122
- 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,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
|
-
|
|
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.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
|
|
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
|
-
|
|
171
|
-
|
|
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 (
|
|
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:
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
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
|
+
}
|