nodebb-plugin-ezoic-infinite 1.7.17 → 1.7.18
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 +12 -68
- package/package.json +4 -13
- package/public/client.js +7 -25
- package/public/style.css +5 -5
- package/public/templates/admin/plugins/ezoic-infinite.tpl +8 -18
package/library.js
CHANGED
|
@@ -9,10 +9,8 @@ const plugin = {};
|
|
|
9
9
|
|
|
10
10
|
function normalizeExcludedGroups(value) {
|
|
11
11
|
if (!value) return [];
|
|
12
|
-
|
|
13
|
-
return
|
|
14
|
-
.map(s => String(s).trim())
|
|
15
|
-
.filter(Boolean);
|
|
12
|
+
if (Array.isArray(value)) return value;
|
|
13
|
+
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
function parseBool(v, def = false) {
|
|
@@ -22,30 +20,18 @@ function parseBool(v, def = false) {
|
|
|
22
20
|
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
23
21
|
}
|
|
24
22
|
|
|
25
|
-
let _groupsCache = null;
|
|
26
|
-
let _groupsCacheAt = 0;
|
|
27
|
-
const GROUPS_TTL = 60000; // 60s
|
|
28
|
-
|
|
29
23
|
async function getAllGroups() {
|
|
30
|
-
const now = Date.now();
|
|
31
|
-
if (_groupsCache && (now - _groupsCacheAt) < GROUPS_TTL) return _groupsCache;
|
|
32
|
-
|
|
33
24
|
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
34
25
|
if (!names || !names.length) {
|
|
35
26
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
36
27
|
}
|
|
37
28
|
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
38
29
|
const data = await groups.getGroupsData(filtered);
|
|
39
|
-
|
|
40
30
|
// Filter out nulls (groups deleted between the sorted-set read and getGroupsData)
|
|
41
|
-
const valid =
|
|
31
|
+
const valid = data.filter(g => g && g.name);
|
|
42
32
|
valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
43
|
-
|
|
44
|
-
_groupsCache = valid;
|
|
45
|
-
_groupsCacheAt = now;
|
|
46
|
-
return _groupsCache;
|
|
33
|
+
return valid;
|
|
47
34
|
}
|
|
48
|
-
|
|
49
35
|
let _settingsCache = null;
|
|
50
36
|
let _settingsCacheAt = 0;
|
|
51
37
|
const SETTINGS_TTL = 30000; // 30s
|
|
@@ -53,13 +39,8 @@ const SETTINGS_TTL = 30000; // 30s
|
|
|
53
39
|
async function getSettings() {
|
|
54
40
|
const now = Date.now();
|
|
55
41
|
if (_settingsCache && (now - _settingsCacheAt) < SETTINGS_TTL) return _settingsCache;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
s = await meta.settings.get(SETTINGS_KEY);
|
|
59
|
-
} catch (err) {
|
|
60
|
-
s = {};
|
|
61
|
-
}
|
|
62
|
-
_settingsCacheAt = now;
|
|
42
|
+
const s = await meta.settings.get(SETTINGS_KEY);
|
|
43
|
+
_settingsCacheAt = Date.now();
|
|
63
44
|
_settingsCache = {
|
|
64
45
|
// Between-post ads (simple blocks) in category topic list
|
|
65
46
|
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
@@ -79,9 +60,6 @@ async function getSettings() {
|
|
|
79
60
|
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
80
61
|
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
81
62
|
|
|
82
|
-
// Avoid globally muting console unless explicitly enabled
|
|
83
|
-
muteEzoicConsole: parseBool(s.muteEzoicConsole, false),
|
|
84
|
-
|
|
85
63
|
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
86
64
|
};
|
|
87
65
|
return _settingsCache;
|
|
@@ -90,8 +68,7 @@ async function getSettings() {
|
|
|
90
68
|
async function isUserExcluded(uid, excludedGroups) {
|
|
91
69
|
if (!uid || !excludedGroups.length) return false;
|
|
92
70
|
const userGroups = await groups.getUserGroups([uid]);
|
|
93
|
-
|
|
94
|
-
return (userGroups[0] || []).some(g => excluded.has(String(g.name).toLowerCase().trim()));
|
|
71
|
+
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
95
72
|
}
|
|
96
73
|
|
|
97
74
|
plugin.onSettingsSet = function (data) {
|
|
@@ -116,53 +93,21 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
116
93
|
const settings = await getSettings();
|
|
117
94
|
const allGroups = await getAllGroups();
|
|
118
95
|
|
|
119
|
-
const excludedSet = new Set((settings.excludedGroups || []).map(n => String(n).toLowerCase().trim()).filter(Boolean));
|
|
120
|
-
const allGroupsWithSelected = (allGroups || []).map(g => ({
|
|
121
|
-
...g,
|
|
122
|
-
selected: excludedSet.has(String(g.name).toLowerCase().trim()) ? 'selected' : '',
|
|
123
|
-
}));
|
|
124
|
-
|
|
125
96
|
res.render('admin/plugins/ezoic-infinite', {
|
|
126
97
|
title: 'Ezoic Infinite Ads',
|
|
127
98
|
...settings,
|
|
128
|
-
|
|
129
|
-
// SSR-friendly checkbox states
|
|
130
99
|
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
131
|
-
showFirstTopicAd_checked: settings.showFirstTopicAd ? 'checked' : '',
|
|
132
|
-
|
|
133
|
-
enableCategoryAds_checked: settings.enableCategoryAds ? 'checked' : '',
|
|
134
|
-
showFirstCategoryAd_checked: settings.showFirstCategoryAd ? 'checked' : '',
|
|
135
|
-
|
|
136
100
|
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
muteEzoicConsole_checked: settings.muteEzoicConsole ? 'checked' : '',
|
|
140
|
-
|
|
141
|
-
allGroups: allGroupsWithSelected,
|
|
101
|
+
allGroups,
|
|
142
102
|
});
|
|
143
103
|
}
|
|
144
104
|
|
|
145
|
-
router.get('/admin/plugins/ezoic-infinite', middleware.
|
|
146
|
-
router.get('/api/admin/plugins/ezoic-infinite',
|
|
105
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
106
|
+
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
147
107
|
|
|
148
108
|
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
settings = await getSettings();
|
|
152
|
-
} catch (err) {
|
|
153
|
-
settings = null;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!settings) {
|
|
157
|
-
return res.json({ excluded: false });
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
let excluded = false;
|
|
161
|
-
try {
|
|
162
|
-
excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
163
|
-
} catch (err) {
|
|
164
|
-
excluded = false;
|
|
165
|
-
}
|
|
109
|
+
const settings = await getSettings();
|
|
110
|
+
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
166
111
|
|
|
167
112
|
res.json({
|
|
168
113
|
excluded,
|
|
@@ -178,7 +123,6 @@ plugin.init = async ({ router, middleware }) => {
|
|
|
178
123
|
showFirstMessageAd: settings.showFirstMessageAd,
|
|
179
124
|
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
180
125
|
messageIntervalPosts: settings.messageIntervalPosts,
|
|
181
|
-
muteEzoicConsole: settings.muteEzoicConsole,
|
|
182
126
|
});
|
|
183
127
|
});
|
|
184
128
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebb-plugin-ezoic-infinite",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.18",
|
|
4
4
|
"description": "Production-ready Ezoic infinite ads integration for NodeBB 4.x",
|
|
5
5
|
"main": "library.js",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,19 +12,10 @@
|
|
|
12
12
|
"infinite-scroll"
|
|
13
13
|
],
|
|
14
14
|
"engines": {
|
|
15
|
-
"nodebb": ">=4.0.0"
|
|
16
|
-
"node": ">=18"
|
|
15
|
+
"nodebb": ">=4.0.0"
|
|
17
16
|
},
|
|
18
17
|
"nbbpm": {
|
|
19
18
|
"compatibility": "^4.0.0"
|
|
20
19
|
},
|
|
21
|
-
"private": false
|
|
22
|
-
|
|
23
|
-
"library.js",
|
|
24
|
-
"plugin.json",
|
|
25
|
-
"public/"
|
|
26
|
-
],
|
|
27
|
-
"scripts": {
|
|
28
|
-
"lint": "node -c library.js && node -c public/admin.js && node -c public/client.js"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
20
|
+
"private": false
|
|
21
|
+
}
|
package/public/client.js
CHANGED
|
@@ -84,7 +84,6 @@
|
|
|
84
84
|
pools: { topics: [], posts: [], categories: [] },
|
|
85
85
|
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
86
86
|
mountedIds: new Set(), // IDs Ezoic actuellement dans le DOM
|
|
87
|
-
reuseSeq: new Map(), // id → compteur pour générer des IDs DOM uniques
|
|
88
87
|
lastShow: new Map(), // id → timestamp dernier show
|
|
89
88
|
|
|
90
89
|
io: null,
|
|
@@ -130,17 +129,14 @@
|
|
|
130
129
|
|
|
131
130
|
function parseIds(raw) {
|
|
132
131
|
const out = [], seen = new Set();
|
|
133
|
-
for (const v of String(raw || '').split(/
|
|
132
|
+
for (const v of String(raw || '').split(/\r?\n/).map(s => s.trim()).filter(Boolean)) {
|
|
134
133
|
const n = parseInt(v, 10);
|
|
135
134
|
if (n > 0 && Number.isFinite(n) && !seen.has(n)) { seen.add(n); out.push(n); }
|
|
136
135
|
}
|
|
137
136
|
return out;
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
|
|
141
|
-
// situations "pool épuisé". Les IDs HTML des placeholders sont rendus uniques
|
|
142
|
-
// (suffixés) afin de ne pas dupliquer d'ID dans le DOM.
|
|
143
|
-
const allowReuse = () => true;
|
|
139
|
+
const normBool = (v) => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
144
140
|
|
|
145
141
|
const isFilled = (n) =>
|
|
146
142
|
!!(n?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'));
|
|
@@ -233,19 +229,13 @@
|
|
|
233
229
|
const i = S.cursors[poolKey] % pool.length;
|
|
234
230
|
S.cursors[poolKey] = (S.cursors[poolKey] + 1) % pool.length;
|
|
235
231
|
const id = pool[i];
|
|
236
|
-
return id;
|
|
232
|
+
if (!S.mountedIds.has(id)) return id;
|
|
237
233
|
}
|
|
238
234
|
return null;
|
|
239
235
|
}
|
|
240
236
|
|
|
241
237
|
// ── Wraps DOM ──────────────────────────────────────────────────────────────
|
|
242
238
|
|
|
243
|
-
function nextDomPlaceholderId(id) {
|
|
244
|
-
const cur = (S.reuseSeq.get(id) || 0) + 1;
|
|
245
|
-
S.reuseSeq.set(id, cur);
|
|
246
|
-
return `${PH_PREFIX}${id}-${cur}`;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
239
|
function makeWrap(id, klass, key) {
|
|
250
240
|
const w = document.createElement('div');
|
|
251
241
|
w.className = `${WRAP_CLASS} ${klass}`;
|
|
@@ -254,7 +244,7 @@
|
|
|
254
244
|
w.setAttribute(A_CREATED, String(ts()));
|
|
255
245
|
w.style.cssText = 'width:100%;display:block;';
|
|
256
246
|
const ph = document.createElement('div');
|
|
257
|
-
ph.id =
|
|
247
|
+
ph.id = `${PH_PREFIX}${id}`;
|
|
258
248
|
ph.setAttribute('data-ezoic-id', String(id));
|
|
259
249
|
w.appendChild(ph);
|
|
260
250
|
return w;
|
|
@@ -263,6 +253,8 @@
|
|
|
263
253
|
function insertAfter(el, id, klass, key) {
|
|
264
254
|
if (!el?.insertAdjacentElement) return null;
|
|
265
255
|
if (findWrap(key)) return null; // ancre déjà présente
|
|
256
|
+
if (S.mountedIds.has(id)) return null; // id déjà monté
|
|
257
|
+
if (document.getElementById(`${PH_PREFIX}${id}`)?.isConnected) return null;
|
|
266
258
|
const w = makeWrap(id, klass, key);
|
|
267
259
|
mutate(() => el.insertAdjacentElement('afterend', w));
|
|
268
260
|
S.mountedIds.add(id);
|
|
@@ -277,7 +269,7 @@
|
|
|
277
269
|
// unobserve(null) corrompt l'état interne de l'IO (pubads lève ensuite
|
|
278
270
|
// "parameter 1 is not of type Element" sur le prochain observe).
|
|
279
271
|
try {
|
|
280
|
-
const ph = w.querySelector(
|
|
272
|
+
const ph = w.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
281
273
|
if (ph instanceof Element) S.io?.unobserve(ph);
|
|
282
274
|
} catch (_) {}
|
|
283
275
|
w.remove();
|
|
@@ -600,10 +592,6 @@
|
|
|
600
592
|
|
|
601
593
|
function cleanup() {
|
|
602
594
|
blockedUntil = ts() + 1500;
|
|
603
|
-
|
|
604
|
-
// Disconnect observers to avoid doing work while ajaxify swaps content
|
|
605
|
-
try { if (S.domObs) { S.domObs.disconnect(); S.domObs = null; } } catch (_) {}
|
|
606
|
-
try { if (window.__nbbTcfObs) { window.__nbbTcfObs.disconnect(); window.__nbbTcfObs = null; } } catch (_) {}
|
|
607
595
|
mutate(() => document.querySelectorAll(`.${WRAP_CLASS}`).forEach(dropWrap));
|
|
608
596
|
S.cfg = null;
|
|
609
597
|
S.pools = { topics: [], posts: [], categories: [] };
|
|
@@ -641,7 +629,6 @@
|
|
|
641
629
|
// ── Utilitaires ────────────────────────────────────────────────────────────
|
|
642
630
|
|
|
643
631
|
function muteConsole() {
|
|
644
|
-
if (!S.cfg || !S.cfg.muteEzoicConsole) return;
|
|
645
632
|
if (window.__nbbEzMuted) return;
|
|
646
633
|
window.__nbbEzMuted = true;
|
|
647
634
|
const MUTED = ['[EzoicAds JS]: Placeholder Id', 'Debugger iframe already exists', `with id ${PH_PREFIX}`];
|
|
@@ -744,11 +731,6 @@
|
|
|
744
731
|
let ticking = false;
|
|
745
732
|
window.addEventListener('scroll', () => {
|
|
746
733
|
if (ticking) return;
|
|
747
|
-
// Scroll bursts only matter on pages where content streams in
|
|
748
|
-
const kind = getKind();
|
|
749
|
-
if (kind !== 'topic' && kind !== 'categoryTopics' && kind !== 'categories') return;
|
|
750
|
-
if (isBlocked()) return;
|
|
751
|
-
|
|
752
734
|
ticking = true;
|
|
753
735
|
requestAnimationFrame(() => { ticking = false; requestBurst(); });
|
|
754
736
|
}, { passive: true });
|
package/public/style.css
CHANGED
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
overflow: hidden !important;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
/* ──
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
/* ── Ezoic global (hors de nos wraps) ────────────────────────────────────── */
|
|
75
|
+
.ezoic-ad {
|
|
76
|
+
margin: 0 !important;
|
|
77
|
+
padding: 0 !important;
|
|
78
|
+
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<input class="form-check-input" type="checkbox" id="enableBetweenAds" name="enableBetweenAds" {enableBetweenAds_checked}>
|
|
9
9
|
<label class="form-check-label" for="enableBetweenAds">Activer les pubs entre les posts</label>
|
|
10
10
|
<div class="form-check mt-2">
|
|
11
|
-
<input class="form-check-input" type="checkbox" name="showFirstTopicAd"
|
|
11
|
+
<input class="form-check-input" type="checkbox" name="showFirstTopicAd" />
|
|
12
12
|
<label class="form-check-label">Afficher une pub après le 1er sujet</label>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<div class="mb-3">
|
|
17
17
|
<label class="form-label" for="placeholderIds">Pool d’IDs Ezoic (entre posts)</label>
|
|
18
18
|
<textarea id="placeholderIds" name="placeholderIds" class="form-control" rows="4">{placeholderIds}</textarea>
|
|
19
|
-
<p class="form-text">Un ID par ligne (ou séparé par virgules/espaces).</p>
|
|
19
|
+
<p class="form-text">Un ID par ligne (ou séparé par virgules/espaces). Le nombre d’IDs = nombre max de pubs simultanées.</p>
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
22
|
<div class="mb-3">
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
<p class="form-text">Insère des pubs entre les catégories sur la page d’accueil (liste des catégories).</p>
|
|
34
34
|
|
|
35
35
|
<div class="form-check mb-3">
|
|
36
|
-
<input class="form-check-input" type="checkbox" id="enableCategoryAds" name="enableCategoryAds"
|
|
36
|
+
<input class="form-check-input" type="checkbox" id="enableCategoryAds" name="enableCategoryAds">
|
|
37
37
|
<label class="form-check-label" for="enableCategoryAds">Activer les pubs entre les catégories</label>
|
|
38
38
|
<div class="form-check mt-2">
|
|
39
|
-
<input class="form-check-input" type="checkbox" name="showFirstCategoryAd"
|
|
39
|
+
<input class="form-check-input" type="checkbox" name="showFirstCategoryAd" />
|
|
40
40
|
<label class="form-check-label">Afficher une pub après la 1ère catégorie</label>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
<div class="mb-3">
|
|
45
45
|
<label class="form-label" for="categoryPlaceholderIds">Pool d’IDs Ezoic (catégories)</label>
|
|
46
46
|
<textarea id="categoryPlaceholderIds" name="categoryPlaceholderIds" class="form-control" rows="4">{categoryPlaceholderIds}</textarea>
|
|
47
|
-
<p class="form-text">IDs numériques, un par ligne
|
|
47
|
+
<p class="form-text">IDs numériques, un par ligne. Utilise un pool dédié (différent des pools topics/messages).</p>
|
|
48
48
|
</div>
|
|
49
49
|
|
|
50
50
|
<div class="mb-3">
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
<input class="form-check-input" type="checkbox" id="enableMessageAds" name="enableMessageAds" {enableMessageAds_checked}>
|
|
60
60
|
<label class="form-check-label" for="enableMessageAds">Activer les pubs “message”</label>
|
|
61
61
|
<div class="form-check mt-2">
|
|
62
|
-
<input class="form-check-input" type="checkbox" name="showFirstMessageAd"
|
|
62
|
+
<input class="form-check-input" type="checkbox" name="showFirstMessageAd" />
|
|
63
63
|
<label class="form-check-label">Afficher une pub après le 1er message</label>
|
|
64
64
|
</div>
|
|
65
65
|
</div>
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
<div class="mb-3">
|
|
68
68
|
<label class="form-label" for="messagePlaceholderIds">Pool d’IDs Ezoic (message)</label>
|
|
69
69
|
<textarea id="messagePlaceholderIds" name="messagePlaceholderIds" class="form-control" rows="4">{messagePlaceholderIds}</textarea>
|
|
70
|
-
<p class="form-text">Pool séparé recommandé pour
|
|
70
|
+
<p class="form-text">Pool séparé recommandé pour éviter la réutilisation d’IDs. IMPORTANT : ne réutilise pas les mêmes IDs dans les deux pools.</p>
|
|
71
71
|
</div>
|
|
72
72
|
|
|
73
73
|
<div class="mb-3">
|
|
@@ -82,22 +82,12 @@
|
|
|
82
82
|
<label class="form-label" for="excludedGroups">Groupes exclus</label>
|
|
83
83
|
<select id="excludedGroups" name="excludedGroups" class="form-select" multiple>
|
|
84
84
|
<!-- BEGIN allGroups -->
|
|
85
|
-
<option value="{allGroups.name}"
|
|
85
|
+
<option value="{allGroups.name}">{allGroups.name}</option>
|
|
86
86
|
<!-- END allGroups -->
|
|
87
87
|
</select>
|
|
88
88
|
<p class="form-text">Si l’utilisateur appartient à un de ces groupes, aucune pub n’est injectée.</p>
|
|
89
89
|
</div>
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
<hr/>
|
|
93
|
-
|
|
94
|
-
<h4 class="mt-3">Options avancées</h4>
|
|
95
|
-
|
|
96
|
-
<div class="form-check mb-3">
|
|
97
|
-
<input class="form-check-input" type="checkbox" id="muteEzoicConsole" name="muteEzoicConsole" {muteEzoicConsole_checked}>
|
|
98
|
-
<label class="form-check-label" for="muteEzoicConsole">Filtrer certains logs Ezoic dans la console (évite le bruit)</label>
|
|
99
|
-
<p class="form-text">Désactivé par défaut pour ne pas impacter les logs d’autres plugins.</p>
|
|
100
|
-
</div>
|
|
101
91
|
<button id="save" class="btn btn-primary">Enregistrer</button>
|
|
102
92
|
</form>
|
|
103
93
|
</div>
|