nodebb-plugin-ezoic-infinite 1.8.33 → 1.8.35
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 +102 -80
- package/package.json +2 -2
- package/public/client.js +719 -508
- package/public/style.css +9 -30
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,61 +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
|
-
|
|
45
|
-
let _excludeCache = new Map();
|
|
46
|
-
const EXCLUDE_TTL = 60_000;
|
|
47
|
-
|
|
48
|
-
function excludedKey(uid, excludedGroups) {
|
|
49
|
-
return `${uid}|${(excludedGroups || []).join('|')}`;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function buildClientConfigPayload(settings, excluded) {
|
|
53
|
-
return {
|
|
54
|
-
excluded,
|
|
55
|
-
enableBetweenAds: settings.enableBetweenAds,
|
|
56
|
-
showFirstTopicAd: settings.showFirstTopicAd,
|
|
57
|
-
placeholderIds: settings.placeholderIds,
|
|
58
|
-
intervalPosts: settings.intervalPosts,
|
|
59
|
-
enableCategoryAds: settings.enableCategoryAds,
|
|
60
|
-
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
61
|
-
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
62
|
-
intervalCategories: settings.intervalCategories,
|
|
63
|
-
enableMessageAds: settings.enableMessageAds,
|
|
64
|
-
showFirstMessageAd: settings.showFirstMessageAd,
|
|
65
|
-
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
66
|
-
messageIntervalPosts: settings.messageIntervalPosts,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function serializeInlineConfigScript(cfg) {
|
|
71
|
-
return `<script>window.__nbbEzoicCfg=${JSON.stringify(cfg).replace(/</g, '\\u003c')};</script>`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const HEAD_PRECONNECTS = `<link rel="preconnect" href="https://g.ezoic.net" crossorigin>
|
|
75
|
-
<link rel="preconnect" href="https://go.ezoic.net" crossorigin>
|
|
76
|
-
<link rel="preconnect" href="https://securepubads.g.doubleclick.net" crossorigin>
|
|
77
|
-
<link rel="preconnect" href="https://pagead2.googlesyndication.com" crossorigin>
|
|
78
|
-
<link rel="dns-prefetch" href="https://g.ezoic.net">
|
|
79
|
-
<link rel="dns-prefetch" href="https://securepubads.g.doubleclick.net">`;
|
|
44
|
+
// ── Settings cache ───────────────────────────────────────────────────────────
|
|
80
45
|
|
|
81
46
|
let _settingsCache = null;
|
|
82
47
|
let _settingsCacheAt = 0;
|
|
83
48
|
const SETTINGS_TTL = 30_000;
|
|
84
49
|
|
|
50
|
+
const _excludeCache = new Map();
|
|
51
|
+
const EXCLUDE_TTL = 60_000;
|
|
52
|
+
|
|
85
53
|
async function getSettings() {
|
|
86
|
-
const
|
|
87
|
-
if (_settingsCache && (
|
|
54
|
+
const t = Date.now();
|
|
55
|
+
if (_settingsCache && (t - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
|
|
56
|
+
|
|
88
57
|
const s = await meta.settings.get(SETTINGS_KEY);
|
|
89
58
|
_settingsCacheAt = Date.now();
|
|
90
59
|
_settingsCache = {
|
|
@@ -107,56 +76,97 @@ async function getSettings() {
|
|
|
107
76
|
|
|
108
77
|
async function isUserExcluded(uid, excludedGroups) {
|
|
109
78
|
if (!uid || !excludedGroups.length) return false;
|
|
110
|
-
|
|
111
|
-
const
|
|
79
|
+
|
|
80
|
+
const key = `${uid}|${excludedGroups.join('|')}`;
|
|
81
|
+
const t = Date.now();
|
|
112
82
|
const hit = _excludeCache.get(key);
|
|
113
|
-
if (hit && (
|
|
83
|
+
if (hit && (t - hit.at) < EXCLUDE_TTL) return hit.value;
|
|
114
84
|
|
|
115
85
|
const excludedSet = new Set(excludedGroups);
|
|
116
86
|
const userGroups = await groups.getUserGroups([uid]);
|
|
117
87
|
const value = (userGroups[0] || []).some(g => excludedSet.has(g.name));
|
|
118
88
|
|
|
119
|
-
_excludeCache.set(key, { value, at: now });
|
|
89
|
+
_excludeCache.set(key, { value, at: Date.now() });
|
|
90
|
+
|
|
91
|
+
// Limit cache size to prevent unbounded growth
|
|
92
|
+
if (_excludeCache.size > 1000) {
|
|
93
|
+
const oldest = _excludeCache.keys().next().value;
|
|
94
|
+
_excludeCache.delete(oldest);
|
|
95
|
+
}
|
|
96
|
+
|
|
120
97
|
return value;
|
|
121
98
|
}
|
|
122
99
|
|
|
123
100
|
function clearCaches() {
|
|
124
101
|
_settingsCache = null;
|
|
125
102
|
_settingsCacheAt = 0;
|
|
126
|
-
_excludeCache
|
|
103
|
+
_excludeCache.clear();
|
|
127
104
|
}
|
|
128
105
|
|
|
129
|
-
// ──
|
|
106
|
+
// ── Client config ────────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
function buildClientConfig(settings, excluded) {
|
|
109
|
+
return {
|
|
110
|
+
excluded,
|
|
111
|
+
enableBetweenAds: settings.enableBetweenAds,
|
|
112
|
+
showFirstTopicAd: settings.showFirstTopicAd,
|
|
113
|
+
placeholderIds: settings.placeholderIds,
|
|
114
|
+
intervalPosts: settings.intervalPosts,
|
|
115
|
+
enableCategoryAds: settings.enableCategoryAds,
|
|
116
|
+
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
117
|
+
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
118
|
+
intervalCategories: settings.intervalCategories,
|
|
119
|
+
enableMessageAds: settings.enableMessageAds,
|
|
120
|
+
showFirstMessageAd: settings.showFirstMessageAd,
|
|
121
|
+
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
122
|
+
messageIntervalPosts: settings.messageIntervalPosts,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
130
125
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<script>
|
|
135
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
136
|
-
ezstandalone.cmd = ezstandalone.cmd || [];
|
|
137
|
-
</script>`;
|
|
126
|
+
function serializeInlineConfig(cfg) {
|
|
127
|
+
return `<script>window.__nbbEzoicCfg=${JSON.stringify(cfg).replace(/</g, '\\u003c')};</script>`;
|
|
128
|
+
}
|
|
138
129
|
|
|
139
|
-
// ──
|
|
130
|
+
// ── Head injection ───────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
const HEAD_PRECONNECTS = [
|
|
133
|
+
'<link rel="preconnect" href="https://g.ezoic.net" crossorigin>',
|
|
134
|
+
'<link rel="preconnect" href="https://go.ezoic.net" crossorigin>',
|
|
135
|
+
'<link rel="preconnect" href="https://securepubads.g.doubleclick.net" crossorigin>',
|
|
136
|
+
'<link rel="preconnect" href="https://pagead2.googlesyndication.com" crossorigin>',
|
|
137
|
+
'<link rel="dns-prefetch" href="https://g.ezoic.net">',
|
|
138
|
+
'<link rel="dns-prefetch" href="https://securepubads.g.doubleclick.net">',
|
|
139
|
+
].join('\n');
|
|
140
|
+
|
|
141
|
+
const EZOIC_SCRIPTS = [
|
|
142
|
+
'<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>',
|
|
143
|
+
'<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>',
|
|
144
|
+
'<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>',
|
|
145
|
+
'<script>',
|
|
146
|
+
'window.ezstandalone = window.ezstandalone || {};',
|
|
147
|
+
'ezstandalone.cmd = ezstandalone.cmd || [];',
|
|
148
|
+
'</script>',
|
|
149
|
+
].join('\n');
|
|
150
|
+
|
|
151
|
+
// ── Hooks ────────────────────────────────────────────────────────────────────
|
|
140
152
|
|
|
141
153
|
plugin.onSettingsSet = function (data) {
|
|
142
|
-
if (data
|
|
154
|
+
if (data?.hash === SETTINGS_KEY) clearCaches();
|
|
143
155
|
};
|
|
144
156
|
|
|
145
157
|
plugin.addAdminNavigation = async (header) => {
|
|
146
158
|
header.plugins = header.plugins || [];
|
|
147
|
-
header.plugins.push({
|
|
159
|
+
header.plugins.push({
|
|
160
|
+
route: '/plugins/ezoic-infinite',
|
|
161
|
+
icon: 'fa-ad',
|
|
162
|
+
name: 'Ezoic Infinite Ads',
|
|
163
|
+
});
|
|
148
164
|
return header;
|
|
149
165
|
};
|
|
150
166
|
|
|
151
167
|
/**
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* NodeBB v4 / thème Harmony : header.tpl contient {{customHTML}} dans le <head>
|
|
155
|
-
* (render.js ligne 232 : templateValues.customHTML = meta.config.customHTML).
|
|
156
|
-
* Le hook filter:middleware.renderHeader reçoit templateData = headerFooterData
|
|
157
|
-
* et est rendu via req.app.renderAsync('header', hookReturn.templateData).
|
|
158
|
-
* On préfixe customHTML pour que nos scripts passent AVANT le customHTML admin,
|
|
159
|
-
* tout en préservant ce dernier.
|
|
168
|
+
* Inject Ezoic scripts into <head> via templateData.customHTML.
|
|
169
|
+
* NodeBB v4/Harmony: header.tpl has {{customHTML}} in <head>.
|
|
160
170
|
*/
|
|
161
171
|
plugin.injectEzoicHead = async (data) => {
|
|
162
172
|
try {
|
|
@@ -164,11 +174,17 @@ plugin.injectEzoicHead = async (data) => {
|
|
|
164
174
|
const uid = data.req?.uid ?? 0;
|
|
165
175
|
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
166
176
|
if (!excluded) {
|
|
167
|
-
const
|
|
177
|
+
const cfg = buildClientConfig(settings, false);
|
|
168
178
|
data.templateData.customHTML =
|
|
169
|
-
HEAD_PRECONNECTS +
|
|
179
|
+
HEAD_PRECONNECTS + '\n' +
|
|
180
|
+
EZOIC_SCRIPTS + '\n' +
|
|
181
|
+
serializeInlineConfig(cfg) +
|
|
182
|
+
(data.templateData.customHTML || '');
|
|
170
183
|
}
|
|
171
|
-
} catch (
|
|
184
|
+
} catch (err) {
|
|
185
|
+
// Log but don't break rendering
|
|
186
|
+
console.error('[ezoic-infinite] injectEzoicHead error:', err.message);
|
|
187
|
+
}
|
|
172
188
|
return data;
|
|
173
189
|
};
|
|
174
190
|
|
|
@@ -179,7 +195,8 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
179
195
|
res.render('admin/plugins/ezoic-infinite', {
|
|
180
196
|
title: 'Ezoic Infinite Ads',
|
|
181
197
|
...settings,
|
|
182
|
-
enableBetweenAds_checked:
|
|
198
|
+
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
199
|
+
enableCategoryAds_checked: settings.enableCategoryAds ? 'checked' : '',
|
|
183
200
|
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
184
201
|
allGroups,
|
|
185
202
|
});
|
|
@@ -189,9 +206,14 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
189
206
|
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
190
207
|
|
|
191
208
|
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
try {
|
|
210
|
+
const settings = await getSettings();
|
|
211
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
212
|
+
res.json(buildClientConfig(settings, excluded));
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.error('[ezoic-infinite] config API error:', err.message);
|
|
215
|
+
res.status(500).json({ error: 'internal' });
|
|
216
|
+
}
|
|
195
217
|
});
|
|
196
218
|
};
|
|
197
219
|
|
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.35",
|
|
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
|
+
}
|