nodebb-plugin-ezoic-infinite 1.8.70 → 1.8.71
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 +111 -50
- 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,116 @@ 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;
|
|
76
95
|
}
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
function clearCaches() {
|
|
98
|
+
_settingsCache = null;
|
|
99
|
+
_settingsCacheAt = 0;
|
|
100
|
+
_excludeCache.clear();
|
|
101
|
+
}
|
|
79
102
|
|
|
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
|
+
}
|
|
122
|
+
|
|
123
|
+
function serializeInlineConfig(cfg) {
|
|
124
|
+
return `<script data-cfasync="false">window.__nbbEzoicCfg=${JSON.stringify(cfg).replace(/</g, '\\u003c')};</script>`;
|
|
125
|
+
}
|
|
126
|
+
|
|
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
|
+
// Exact v50 script block — DO NOT change order or attributes.
|
|
139
|
+
// The inline stub AFTER sa.min.js is required: client.js uses ezstandalone.cmd
|
|
140
|
+
// before sa.min.js may have initialized it. _ezaq is NOT stubbed — sa.min.js
|
|
141
|
+
// defines it itself and pre-defining it can change Ezoic's init path.
|
|
80
142
|
const EZOIC_SCRIPTS = `<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>
|
|
81
143
|
<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>
|
|
82
144
|
<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>
|
|
145
|
+
<script src="//ezoicanalytics.com/analytics.js"></script>
|
|
83
146
|
<script>
|
|
84
147
|
window.ezstandalone = window.ezstandalone || {};
|
|
85
148
|
ezstandalone.cmd = ezstandalone.cmd || [];
|
|
86
149
|
</script>`;
|
|
87
150
|
|
|
88
|
-
// ── Hooks
|
|
151
|
+
// ── Hooks ────────────────────────────────────────────────────────────────────
|
|
89
152
|
|
|
90
153
|
plugin.onSettingsSet = function (data) {
|
|
91
|
-
if (data
|
|
154
|
+
if (data?.hash === SETTINGS_KEY) clearCaches();
|
|
92
155
|
};
|
|
93
156
|
|
|
94
157
|
plugin.addAdminNavigation = async (header) => {
|
|
95
158
|
header.plugins = header.plugins || [];
|
|
96
|
-
header.plugins.push({
|
|
159
|
+
header.plugins.push({
|
|
160
|
+
route: '/plugins/ezoic-infinite',
|
|
161
|
+
icon: 'fa-ad',
|
|
162
|
+
name: 'Ezoic Infinite Ads',
|
|
163
|
+
});
|
|
97
164
|
return header;
|
|
98
165
|
};
|
|
99
166
|
|
|
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
167
|
plugin.injectEzoicHead = async (data) => {
|
|
111
168
|
try {
|
|
112
169
|
const settings = await getSettings();
|
|
113
170
|
const uid = data.req?.uid ?? 0;
|
|
114
171
|
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
172
|
+
|
|
173
|
+
if (excluded) {
|
|
174
|
+
// v50: did nothing for excluded users. Keep it simple.
|
|
175
|
+
} else {
|
|
176
|
+
const cfg = buildClientConfig(settings, false);
|
|
177
|
+
// v50 order: EZOIC_SCRIPTS first, then existing customHTML.
|
|
178
|
+
// Preconnects added after scripts (link tags don't block parsing).
|
|
179
|
+
// Inline config after everything (optimization: avoids API fetch).
|
|
180
|
+
data.templateData.customHTML =
|
|
181
|
+
EZOIC_SCRIPTS +
|
|
182
|
+
HEAD_PRECONNECTS + '\n' +
|
|
183
|
+
serializeInlineConfig(cfg) +
|
|
184
|
+
(data.templateData.customHTML || '');
|
|
118
185
|
}
|
|
119
|
-
} catch (
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.error('[ezoic-infinite] injectEzoicHead error:', err.message);
|
|
188
|
+
}
|
|
120
189
|
return data;
|
|
121
190
|
};
|
|
122
191
|
|
|
@@ -127,7 +196,8 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
127
196
|
res.render('admin/plugins/ezoic-infinite', {
|
|
128
197
|
title: 'Ezoic Infinite Ads',
|
|
129
198
|
...settings,
|
|
130
|
-
enableBetweenAds_checked:
|
|
199
|
+
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
200
|
+
enableCategoryAds_checked: settings.enableCategoryAds ? 'checked' : '',
|
|
131
201
|
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
132
202
|
allGroups,
|
|
133
203
|
});
|
|
@@ -137,23 +207,14 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
137
207
|
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
138
208
|
|
|
139
209
|
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
|
-
});
|
|
210
|
+
try {
|
|
211
|
+
const settings = await getSettings();
|
|
212
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
213
|
+
res.json(buildClientConfig(settings, excluded));
|
|
214
|
+
} catch (err) {
|
|
215
|
+
console.error('[ezoic-infinite] config API error:', err.message);
|
|
216
|
+
res.status(500).json({ error: 'internal' });
|
|
217
|
+
}
|
|
157
218
|
});
|
|
158
219
|
};
|
|
159
220
|
|
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.71",
|
|
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
|
+
}
|