nodebb-plugin-ezoic-infinite 1.8.70 → 1.8.72
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 +123 -57
- package/package.json +2 -2
- package/public/client.js +433 -374
- package/public/style.css +95 -19
package/library.js
CHANGED
|
@@ -7,17 +7,18 @@ 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\"]"
|
|
16
15
|
const s = String(value).trim();
|
|
17
16
|
if (s.startsWith('[')) {
|
|
18
|
-
try {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(s);
|
|
19
|
+
if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean);
|
|
20
|
+
} catch (_) {}
|
|
19
21
|
}
|
|
20
|
-
// Fallback : séparation par virgule
|
|
21
22
|
return s.split(',').map(v => v.trim()).filter(Boolean);
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -30,25 +31,29 @@ function parseBool(v, def = false) {
|
|
|
30
31
|
|
|
31
32
|
async function getAllGroups() {
|
|
32
33
|
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
33
|
-
if (!names
|
|
34
|
+
if (!names?.length) {
|
|
34
35
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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' }));
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
// ── Settings cache
|
|
44
|
+
// ── Settings cache ───────────────────────────────────────────────────────────
|
|
44
45
|
|
|
45
46
|
let _settingsCache = null;
|
|
46
47
|
let _settingsCacheAt = 0;
|
|
47
48
|
const SETTINGS_TTL = 30_000;
|
|
48
49
|
|
|
50
|
+
const _excludeCache = new Map();
|
|
51
|
+
const EXCLUDE_TTL = 60_000;
|
|
52
|
+
|
|
49
53
|
async function getSettings() {
|
|
50
|
-
const
|
|
51
|
-
if (_settingsCache && (
|
|
54
|
+
const t = Date.now();
|
|
55
|
+
if (_settingsCache && (t - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
|
|
56
|
+
|
|
52
57
|
const s = await meta.settings.get(SETTINGS_KEY);
|
|
53
58
|
_settingsCacheAt = Date.now();
|
|
54
59
|
_settingsCache = {
|
|
@@ -71,52 +76,121 @@ async function getSettings() {
|
|
|
71
76
|
|
|
72
77
|
async function isUserExcluded(uid, excludedGroups) {
|
|
73
78
|
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);
|
|
74
86
|
const userGroups = await groups.getUserGroups([uid]);
|
|
75
|
-
|
|
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();
|
|
76
101
|
}
|
|
77
102
|
|
|
78
|
-
// ──
|
|
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
|
+
}
|
|
79
122
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<script>
|
|
84
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
85
|
-
ezstandalone.cmd = ezstandalone.cmd || [];
|
|
86
|
-
</script>`;
|
|
123
|
+
function serializeInlineConfig(cfg) {
|
|
124
|
+
return `<script data-cfasync="false">window.__nbbEzoicCfg=${JSON.stringify(cfg).replace(/</g, '\\u003c')};</script>`;
|
|
125
|
+
}
|
|
87
126
|
|
|
88
|
-
// ──
|
|
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
|
+
// Match v50 exactly: CMP → sa.min.js, no stubs.
|
|
139
|
+
// sa.min.js defines _ezaq and ezstandalone itself.
|
|
140
|
+
// Placing stubs before or alongside sa.min.js changes Ezoic's init path
|
|
141
|
+
// and causes "Timed out waiting for loadingStatus" errors.
|
|
142
|
+
const EZOIC_SCRIPTS = [
|
|
143
|
+
'<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>',
|
|
144
|
+
'<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>',
|
|
145
|
+
'<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>',
|
|
146
|
+
].join('\n');
|
|
147
|
+
|
|
148
|
+
// ── Hooks ────────────────────────────────────────────────────────────────────
|
|
89
149
|
|
|
90
150
|
plugin.onSettingsSet = function (data) {
|
|
91
|
-
if (data
|
|
151
|
+
if (data?.hash === SETTINGS_KEY) clearCaches();
|
|
92
152
|
};
|
|
93
153
|
|
|
94
154
|
plugin.addAdminNavigation = async (header) => {
|
|
95
155
|
header.plugins = header.plugins || [];
|
|
96
|
-
header.plugins.push({
|
|
156
|
+
header.plugins.push({
|
|
157
|
+
route: '/plugins/ezoic-infinite',
|
|
158
|
+
icon: 'fa-ad',
|
|
159
|
+
name: 'Ezoic Infinite Ads',
|
|
160
|
+
});
|
|
97
161
|
return header;
|
|
98
162
|
};
|
|
99
163
|
|
|
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
|
-
*/
|
|
110
164
|
plugin.injectEzoicHead = async (data) => {
|
|
111
165
|
try {
|
|
112
166
|
const settings = await getSettings();
|
|
113
167
|
const uid = data.req?.uid ?? 0;
|
|
114
168
|
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 data-cfasync="false">'
|
|
175
|
+
+ 'window._ezaq=window._ezaq||{};'
|
|
176
|
+
+ 'window.ezstandalone=window.ezstandalone||{};'
|
|
177
|
+
+ 'window.ezstandalone.cmd=window.ezstandalone.cmd||[];'
|
|
178
|
+
+ '</script>';
|
|
179
|
+
data.templateData.customHTML =
|
|
180
|
+
stub + '\n' +
|
|
181
|
+
serializeInlineConfig(cfg) +
|
|
182
|
+
(data.templateData.customHTML || '');
|
|
183
|
+
} else {
|
|
184
|
+
const cfg = buildClientConfig(settings, false);
|
|
185
|
+
data.templateData.customHTML =
|
|
186
|
+
HEAD_PRECONNECTS + '\n' +
|
|
187
|
+
EZOIC_SCRIPTS + '\n' +
|
|
188
|
+
serializeInlineConfig(cfg) +
|
|
189
|
+
(data.templateData.customHTML || '');
|
|
118
190
|
}
|
|
119
|
-
} catch (
|
|
191
|
+
} catch (err) {
|
|
192
|
+
console.error('[ezoic-infinite] injectEzoicHead error:', err.message);
|
|
193
|
+
}
|
|
120
194
|
return data;
|
|
121
195
|
};
|
|
122
196
|
|
|
@@ -127,7 +201,8 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
127
201
|
res.render('admin/plugins/ezoic-infinite', {
|
|
128
202
|
title: 'Ezoic Infinite Ads',
|
|
129
203
|
...settings,
|
|
130
|
-
enableBetweenAds_checked:
|
|
204
|
+
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
205
|
+
enableCategoryAds_checked: settings.enableCategoryAds ? 'checked' : '',
|
|
131
206
|
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
132
207
|
allGroups,
|
|
133
208
|
});
|
|
@@ -137,23 +212,14 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
137
212
|
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
138
213
|
|
|
139
214
|
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
excluded
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
});
|
|
215
|
+
try {
|
|
216
|
+
const settings = await getSettings();
|
|
217
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
218
|
+
res.json(buildClientConfig(settings, excluded));
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.error('[ezoic-infinite] config API error:', err.message);
|
|
221
|
+
res.status(500).json({ error: 'internal' });
|
|
222
|
+
}
|
|
157
223
|
});
|
|
158
224
|
};
|
|
159
225
|
|
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.72",
|
|
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
|
+
}
|