nodebb-plugin-ezoic-infinite 1.8.21 → 1.8.22
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/package.json +1 -1
- package/public/client.js +590 -908
package/public/client.js
CHANGED
|
@@ -1,328 +1,114 @@
|
|
|
1
|
-
(function
|
|
1
|
+
(function nbbEzoicInfiniteV22() {
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const A_SHOWN = 'data-ezoic-shown'; // timestamp dernier showAds ms
|
|
12
|
-
|
|
13
|
-
// Tunables (stables en prod)
|
|
14
|
-
const MIN_PRUNE_AGE_MS = 8_000; // délai de grâce avant pruning (stabilisation DOM)
|
|
15
|
-
const MAX_INSERTS_RUN = 10; // plus réactif si NodeBB injecte en rafale
|
|
16
|
-
const MAX_INFLIGHT = 2; // ids max simultanés en vol (garde-fou)
|
|
17
|
-
const MAX_SHOW_BATCH = 4; // ids max par appel showAds(...ids)
|
|
18
|
-
const SHOW_THROTTLE_MS = 500; // anti-spam showAds() par id (plus réactif)
|
|
19
|
-
const SHOW_RELEASE_MS = 300; // relâche inflight après showAds() batché
|
|
20
|
-
const SHOW_FAILSAFE_MS = 7000; // relâche forcée si stack pub lente
|
|
21
|
-
const BATCH_FLUSH_MS = 30; // micro-buffer pour regrouper les ids proches
|
|
22
|
-
const MAX_DESTROY_BATCH = 4; // ids max par destroyPlaceholders(...ids)
|
|
23
|
-
const DESTROY_FLUSH_MS = 30; // micro-buffer destroy pour lisser les rafales
|
|
24
|
-
const BURST_COOLDOWN_MS = 100; // délai min entre deux déclenchements de burst
|
|
25
|
-
const CLEANUP_GRACE_MS = 3_500; // délai mini avant cleanup d'un wrap candidat
|
|
26
|
-
const SPECIAL_GRACE_MS = 30_000; // délai allongé pour sticky/fixed/adhesion
|
|
27
|
-
const RECENT_WRAP_ACTIVITY_MS = 5_000; // protège un wrap récemment muté/rafraîchi
|
|
28
|
-
const VIEWPORT_BUFFER_DESKTOP = 500;
|
|
29
|
-
const VIEWPORT_BUFFER_MOBILE = 250;
|
|
30
|
-
|
|
31
|
-
// Marges IO larges et fixes — observer créé une seule fois au boot
|
|
32
|
-
const IO_MARGIN_DESKTOP = '2500px 0px 2500px 0px';
|
|
33
|
-
const IO_MARGIN_MOBILE = '3500px 0px 3500px 0px';
|
|
4
|
+
const WRAP_CLASS = 'nodebb-ezoic-wrap';
|
|
5
|
+
const PH_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
6
|
+
const A_ANCHOR = 'data-ezoic-anchor';
|
|
7
|
+
const A_WRAPID = 'data-ezoic-wrapid';
|
|
8
|
+
const A_CREATED = 'data-ezoic-created';
|
|
9
|
+
const A_SHOWN = 'data-ezoic-shown';
|
|
10
|
+
const A_STATE = 'data-ezoic-state';
|
|
34
11
|
|
|
35
12
|
const SEL = {
|
|
36
|
-
post:
|
|
37
|
-
topic:
|
|
13
|
+
post: '[component="post"][data-pid]',
|
|
14
|
+
topic: 'li[component="category/topic"]',
|
|
38
15
|
category: 'li[component="categories/category"]',
|
|
39
16
|
};
|
|
40
|
-
const WRAP_SEL = `.${WRAP_CLASS}`;
|
|
41
|
-
const FILL_SEL = 'iframe, ins, img, video, [data-google-container-id]';
|
|
42
|
-
const CONTENT_SEL_LIST = [SEL.post, SEL.topic, SEL.category];
|
|
43
17
|
|
|
44
|
-
/**
|
|
45
|
-
* Table KIND — source de vérité par kindClass.
|
|
46
|
-
*
|
|
47
|
-
* sel sélecteur CSS complet des éléments cibles
|
|
48
|
-
* baseTag préfixe tag pour querySelector d'ancre
|
|
49
|
-
* (vide pour posts : le sélecteur commence par '[')
|
|
50
|
-
* anchorAttr attribut DOM stable → clé unique du wrap
|
|
51
|
-
* ordinalAttr attribut 0-based pour le calcul de l'intervalle
|
|
52
|
-
* null → fallback positionnel (catégories)
|
|
53
|
-
*/
|
|
54
18
|
const KIND = {
|
|
55
|
-
'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index' },
|
|
56
|
-
'ezoic-ad-between': { sel: SEL.topic, baseTag: 'li', anchorAttr: 'data-index', ordinalAttr: 'data-index' },
|
|
57
|
-
'ezoic-ad-categories': { sel: SEL.category, baseTag: 'li', anchorAttr: 'data-cid', ordinalAttr: null },
|
|
19
|
+
'ezoic-ad-message': { sel: SEL.post, baseTag: '', anchorAttr: 'data-pid', ordinalAttr: 'data-index', pool: 'posts' },
|
|
20
|
+
'ezoic-ad-between': { sel: SEL.topic, baseTag: 'li', anchorAttr: 'data-index', ordinalAttr: 'data-index', pool: 'topics' },
|
|
21
|
+
'ezoic-ad-categories': { sel: SEL.category, baseTag: 'li', anchorAttr: 'data-cid', ordinalAttr: null, pool: 'categories' },
|
|
58
22
|
};
|
|
59
23
|
|
|
60
|
-
|
|
24
|
+
const CFG = {
|
|
25
|
+
maxInsertsPerRun: 8,
|
|
26
|
+
maxShowInflight: 1,
|
|
27
|
+
maxShowBatch: 1,
|
|
28
|
+
showThrottleMs: 1500,
|
|
29
|
+
showCooldownMs: 15000,
|
|
30
|
+
showReleaseMs: 350,
|
|
31
|
+
showFailsafeMs: 8000,
|
|
32
|
+
batchFlushMs: 80,
|
|
33
|
+
runDebounceMs: 80,
|
|
34
|
+
orphanGraceMs: 12000,
|
|
35
|
+
retireGraceMs: 25000,
|
|
36
|
+
recentActivityMs: 6000,
|
|
37
|
+
viewportBufferDesktop: 700,
|
|
38
|
+
viewportBufferMobile: 350,
|
|
39
|
+
ioMarginDesktop: '1800px 0px 2200px 0px',
|
|
40
|
+
ioMarginMobile: '2400px 0px 2800px 0px',
|
|
41
|
+
maintenanceEveryMs: 1200,
|
|
42
|
+
minHeightRememberMin: 40,
|
|
43
|
+
};
|
|
61
44
|
|
|
62
45
|
const S = {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
destroyPending: [],
|
|
79
|
-
destroyPendingSet: new Set(),
|
|
80
|
-
sweepQueued: false,
|
|
81
|
-
wrapByKey: new Map(), // anchorKey → wrap DOM node
|
|
82
|
-
ezActiveIds: new Set(), // ids actifs côté plugin (wrap présent / récemment show)
|
|
83
|
-
ezShownSinceDestroy: new Set(), // ids déjà show depuis le dernier destroy Ezoic
|
|
84
|
-
wrapActivityAt: new Map(), // key/id -> ts activité DOM récente
|
|
46
|
+
cfg: null,
|
|
47
|
+
poolsReady: false,
|
|
48
|
+
pools: { topics: [], posts: [], categories: [] },
|
|
49
|
+
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
50
|
+
pageKey: null,
|
|
51
|
+
blockedUntil: 0,
|
|
52
|
+
runTimer: 0,
|
|
53
|
+
maintenanceTimer: 0,
|
|
54
|
+
io: null,
|
|
55
|
+
domObs: null,
|
|
56
|
+
muting: 0,
|
|
57
|
+
mountedIds: new Set(),
|
|
58
|
+
wrapByKey: new Map(),
|
|
59
|
+
regById: new Map(),
|
|
60
|
+
lastShowById: new Map(),
|
|
85
61
|
lastWrapHeightByClass: new Map(),
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
runQueued: false,
|
|
91
|
-
burstActive: false,
|
|
92
|
-
burstDeadline: 0,
|
|
93
|
-
burstCount: 0,
|
|
94
|
-
lastBurstTs: 0,
|
|
62
|
+
pendingShow: [],
|
|
63
|
+
pendingSet: new Set(),
|
|
64
|
+
showTimer: 0,
|
|
65
|
+
inflightShow: 0,
|
|
95
66
|
};
|
|
96
67
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
function viewportBufferPx() {
|
|
106
|
-
return isMobile() ? VIEWPORT_BUFFER_MOBILE : VIEWPORT_BUFFER_DESKTOP;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function textSig(el) {
|
|
110
|
-
if (!(el instanceof Element)) return '';
|
|
111
|
-
const parts = [el.id || '', String(el.className || ''), el.getAttribute?.('name') || ''];
|
|
112
|
-
try {
|
|
113
|
-
for (const a of ['data-google-query-id', 'data-google-container-id', 'data-slot', 'data-ad-slot']) {
|
|
114
|
-
const v = el.getAttribute?.(a);
|
|
115
|
-
if (v) parts.push(v);
|
|
116
|
-
}
|
|
117
|
-
} catch (_) {}
|
|
118
|
-
return parts.join(' ').toLowerCase();
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function isSpecialSlotLike(el) {
|
|
122
|
-
const sig = textSig(el);
|
|
123
|
-
return /adhesion|interstitial|anchor|sticky|outofpage/.test(sig);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function hasSpecialSlotMarkers(root) {
|
|
127
|
-
if (!(root instanceof Element)) return false;
|
|
128
|
-
if (isSpecialSlotLike(root)) return true;
|
|
129
|
-
try {
|
|
130
|
-
const nodes = root.querySelectorAll('[id],[class],[name],[data-slot],[data-ad-slot]');
|
|
131
|
-
for (const n of nodes) if (isSpecialSlotLike(n)) return true;
|
|
132
|
-
} catch (_) {}
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function hasFixedLikeNode(root, maxScan = 24) {
|
|
137
|
-
if (!(root instanceof Element)) return false;
|
|
138
|
-
const q = [root];
|
|
139
|
-
let seen = 0;
|
|
140
|
-
while (q.length && seen < maxScan) {
|
|
141
|
-
const n = q.shift();
|
|
142
|
-
seen++;
|
|
143
|
-
try {
|
|
144
|
-
const cs = window.getComputedStyle(n);
|
|
145
|
-
if (cs.position === 'fixed' || cs.position === 'sticky') return true;
|
|
146
|
-
} catch (_) {}
|
|
147
|
-
for (const c of n.children || []) q.push(c);
|
|
148
|
-
}
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function markWrapActivity(wrapOrId) {
|
|
153
|
-
const t = ts();
|
|
154
|
-
try {
|
|
155
|
-
if (wrapOrId instanceof Element) {
|
|
156
|
-
const key = wrapOrId.getAttribute(A_ANCHOR);
|
|
157
|
-
const id = parseInt(wrapOrId.getAttribute(A_WRAPID), 10);
|
|
158
|
-
if (key) S.wrapActivityAt.set(`k:${key}`, t);
|
|
159
|
-
if (Number.isFinite(id)) S.wrapActivityAt.set(`i:${id}`, t);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const id = parseInt(wrapOrId, 10);
|
|
163
|
-
if (Number.isFinite(id)) S.wrapActivityAt.set(`i:${id}`, t);
|
|
164
|
-
} catch (_) {}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function wrapRecentActivity(w) {
|
|
168
|
-
try {
|
|
169
|
-
const key = w?.getAttribute?.(A_ANCHOR);
|
|
170
|
-
const id = parseInt(w?.getAttribute?.(A_WRAPID), 10);
|
|
171
|
-
const t1 = key ? (S.wrapActivityAt.get(`k:${key}`) || 0) : 0;
|
|
172
|
-
const t2 = Number.isFinite(id) ? (S.wrapActivityAt.get(`i:${id}`) || 0) : 0;
|
|
173
|
-
const t3 = parseInt(w?.getAttribute?.(A_SHOWN) || '0', 10) || 0;
|
|
174
|
-
return (ts() - Math.max(t1, t2, t3)) < RECENT_WRAP_ACTIVITY_MS;
|
|
175
|
-
} catch (_) { return false; }
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function wrapCleanupGraceMs(w) {
|
|
179
|
-
return (hasSpecialSlotMarkers(w) || hasFixedLikeNode(w)) ? SPECIAL_GRACE_MS : CLEANUP_GRACE_MS;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function wrapNearViewport(w) {
|
|
183
|
-
try {
|
|
184
|
-
const r = w.getBoundingClientRect();
|
|
185
|
-
const b = viewportBufferPx();
|
|
186
|
-
const vh = window.innerHeight || 800;
|
|
187
|
-
return r.bottom > -b && r.top < vh + b;
|
|
188
|
-
} catch (_) { return true; }
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function rememberWrapHeight(w) {
|
|
192
|
-
try {
|
|
193
|
-
if (!(w instanceof Element)) return;
|
|
194
|
-
const klass = [...(w.classList || [])].find(c => c.startsWith('ezoic-ad-'));
|
|
195
|
-
if (!klass) return;
|
|
196
|
-
const h = Math.round(w.getBoundingClientRect().height || 0);
|
|
197
|
-
if (h >= 40) S.lastWrapHeightByClass.set(klass, h);
|
|
198
|
-
} catch (_) {}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function healFalseEmpty(root = document) {
|
|
202
|
-
try {
|
|
203
|
-
const list = [];
|
|
204
|
-
if (root instanceof Element && root.classList?.contains(WRAP_CLASS)) list.push(root);
|
|
205
|
-
const found = root.querySelectorAll ? root.querySelectorAll(`.${WRAP_CLASS}.is-empty`) : [];
|
|
206
|
-
for (const w of found) list.push(w);
|
|
207
|
-
for (const w of list) {
|
|
208
|
-
if (!w?.classList?.contains('is-empty')) continue;
|
|
209
|
-
if (isFilled(w)) w.classList.remove('is-empty');
|
|
210
|
-
}
|
|
211
|
-
} catch (_) {}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function phEl(id) {
|
|
215
|
-
return document.getElementById(`${PH_PREFIX}${id}`);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function hasSinglePlaceholder(id) {
|
|
219
|
-
try { return document.querySelectorAll(`#${PH_PREFIX}${id}`).length === 1; } catch (_) { return false; }
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function canShowPlaceholderId(id, now = ts()) {
|
|
223
|
-
const n = parseInt(id, 10);
|
|
224
|
-
if (!Number.isFinite(n) || n <= 0) return false;
|
|
225
|
-
if (now - (S.lastShow.get(n) ?? 0) < SHOW_THROTTLE_MS) return false;
|
|
226
|
-
const ph = phEl(n);
|
|
227
|
-
if (!ph?.isConnected || isFilled(ph)) return false;
|
|
228
|
-
if (!hasSinglePlaceholder(n)) return false;
|
|
229
|
-
return true;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function queueSweepDeadWraps() {
|
|
233
|
-
if (S.sweepQueued) return;
|
|
234
|
-
S.sweepQueued = true;
|
|
235
|
-
requestAnimationFrame(() => {
|
|
236
|
-
S.sweepQueued = false;
|
|
237
|
-
sweepDeadWraps();
|
|
238
|
-
healFalseEmpty();
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
function getDynamicShowBatchMax() {
|
|
243
|
-
const speed = S.scrollSpeed || 0;
|
|
244
|
-
const pend = S.pending.length;
|
|
245
|
-
// Scroll très rapide => petits batches (réduit le churn/unused)
|
|
246
|
-
if (speed > 2600) return 2;
|
|
247
|
-
if (speed > 1400) return 3;
|
|
248
|
-
// Peu de candidats => flush plus vite, inutile d'attendre 4
|
|
249
|
-
if (pend <= 1) return 1;
|
|
250
|
-
if (pend <= 3) return 2;
|
|
251
|
-
// Par défaut compromis dynamique
|
|
252
|
-
return 3;
|
|
253
|
-
}
|
|
68
|
+
const now = () => Date.now();
|
|
69
|
+
const isBlocked = () => now() < S.blockedUntil;
|
|
70
|
+
const isMobile = () => {
|
|
71
|
+
try { return window.innerWidth < 768; } catch (_) { return false; }
|
|
72
|
+
};
|
|
73
|
+
const viewportBuffer = () => (isMobile() ? CFG.viewportBufferMobile : CFG.viewportBufferDesktop);
|
|
74
|
+
const normBool = v => v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
254
75
|
|
|
255
76
|
function mutate(fn) {
|
|
256
|
-
S.
|
|
257
|
-
try { fn(); } finally { S.
|
|
77
|
+
S.muting++;
|
|
78
|
+
try { return fn(); } finally { S.muting = Math.max(0, S.muting - 1); }
|
|
258
79
|
}
|
|
259
|
-
function scheduleDestroyFlush() {
|
|
260
|
-
if (S.destroyBatchTimer) return;
|
|
261
|
-
S.destroyBatchTimer = setTimeout(() => {
|
|
262
|
-
S.destroyBatchTimer = 0;
|
|
263
|
-
flushDestroyBatch();
|
|
264
|
-
}, DESTROY_FLUSH_MS);
|
|
265
|
-
}
|
|
266
80
|
|
|
267
|
-
function
|
|
268
|
-
if (!S.destroyPending.length) return;
|
|
269
|
-
const ids = [];
|
|
270
|
-
while (S.destroyPending.length && ids.length < MAX_DESTROY_BATCH) {
|
|
271
|
-
const id = S.destroyPending.shift();
|
|
272
|
-
S.destroyPendingSet.delete(id);
|
|
273
|
-
if (!Number.isFinite(id) || id <= 0) continue;
|
|
274
|
-
ids.push(id);
|
|
275
|
-
}
|
|
276
|
-
if (ids.length) {
|
|
81
|
+
function pageKey() {
|
|
277
82
|
try {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
83
|
+
const d = window.ajaxify?.data;
|
|
84
|
+
if (d?.tid) return `t:${d.tid}`;
|
|
85
|
+
if (d?.cid) return `c:${d.cid}`;
|
|
281
86
|
} catch (_) {}
|
|
87
|
+
return location.pathname;
|
|
282
88
|
}
|
|
283
|
-
if (S.destroyPending.length) scheduleDestroyFlush();
|
|
284
|
-
}
|
|
285
89
|
|
|
286
|
-
function
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
90
|
+
function getKind() {
|
|
91
|
+
const p = location.pathname;
|
|
92
|
+
if (/^\/topic\//.test(p)) return 'topic';
|
|
93
|
+
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
94
|
+
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
95
|
+
if (document.querySelector(SEL.post)) return 'topic';
|
|
96
|
+
if (document.querySelector(SEL.topic)) return 'categoryTopics';
|
|
97
|
+
if (document.querySelector(SEL.category)) return 'categories';
|
|
98
|
+
return 'other';
|
|
293
99
|
}
|
|
294
|
-
scheduleDestroyFlush();
|
|
295
|
-
}
|
|
296
100
|
|
|
297
|
-
function
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
out.push(id);
|
|
306
|
-
try {
|
|
307
|
-
const wrap = phEl(id)?.closest?.(WRAP_SEL) || null;
|
|
308
|
-
if (wrap && (wrapRecentActivity(wrap) || hasSpecialSlotMarkers(wrap) || hasFixedLikeNode(wrap))) {
|
|
309
|
-
continue;
|
|
310
|
-
}
|
|
311
|
-
} catch (_) {}
|
|
312
|
-
if (S.ezShownSinceDestroy.has(id)) toDestroy.push(id);
|
|
313
|
-
}
|
|
314
|
-
if (toDestroy.length) {
|
|
315
|
-
try {
|
|
316
|
-
const ez = window.ezstandalone;
|
|
317
|
-
const run = () => { try { ez?.destroyPlaceholders?.(toDestroy); } catch (_) {} };
|
|
318
|
-
try { (typeof ez?.cmd?.push === 'function') ? ez.cmd.push(run) : run(); } catch (_) {}
|
|
319
|
-
} catch (_) {}
|
|
320
|
-
for (const id of toDestroy) S.ezShownSinceDestroy.delete(id);
|
|
101
|
+
function getPosts() {
|
|
102
|
+
return Array.from(document.querySelectorAll(SEL.post)).filter(el => {
|
|
103
|
+
if (!el.isConnected) return false;
|
|
104
|
+
if (!el.querySelector('[component="post/content"]')) return false;
|
|
105
|
+
const p = el.parentElement?.closest?.(SEL.post);
|
|
106
|
+
if (p && p !== el) return false;
|
|
107
|
+
return el.getAttribute('component') !== 'post/parent';
|
|
108
|
+
});
|
|
321
109
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
// ── Config ─────────────────────────────────────────────────────────────────
|
|
110
|
+
const getTopics = () => Array.from(document.querySelectorAll(SEL.topic));
|
|
111
|
+
const getCategories = () => Array.from(document.querySelectorAll(SEL.category));
|
|
326
112
|
|
|
327
113
|
async function fetchConfig() {
|
|
328
114
|
if (S.cfg) return S.cfg;
|
|
@@ -334,9 +120,10 @@ function destroyBeforeReuse(ids) {
|
|
|
334
120
|
}
|
|
335
121
|
|
|
336
122
|
function parseIds(raw) {
|
|
337
|
-
const out = []
|
|
338
|
-
|
|
339
|
-
|
|
123
|
+
const out = [];
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
for (const line of String(raw || '').split(/\r?\n/)) {
|
|
126
|
+
const n = parseInt(line.trim(), 10);
|
|
340
127
|
if (n > 0 && Number.isFinite(n) && !seen.has(n)) { seen.add(n); out.push(n); }
|
|
341
128
|
}
|
|
342
129
|
return out;
|
|
@@ -344,258 +131,147 @@ function destroyBeforeReuse(ids) {
|
|
|
344
131
|
|
|
345
132
|
function initPools(cfg) {
|
|
346
133
|
if (S.poolsReady) return;
|
|
347
|
-
S.pools
|
|
348
|
-
|
|
349
|
-
|
|
134
|
+
S.pools = {
|
|
135
|
+
topics: parseIds(cfg.placeholderIds),
|
|
136
|
+
posts: parseIds(cfg.messagePlaceholderIds),
|
|
137
|
+
categories: parseIds(cfg.categoryPlaceholderIds),
|
|
138
|
+
};
|
|
350
139
|
S.poolsReady = true;
|
|
351
140
|
}
|
|
352
141
|
|
|
353
|
-
|
|
142
|
+
function textSig(el) {
|
|
143
|
+
if (!(el instanceof Element)) return '';
|
|
144
|
+
const parts = [el.id || '', String(el.className || ''), el.getAttribute('name') || ''];
|
|
145
|
+
for (const a of ['data-slot', 'data-ad-slot', 'data-google-container-id']) {
|
|
146
|
+
try { const v = el.getAttribute(a); if (v) parts.push(v); } catch (_) {}
|
|
147
|
+
}
|
|
148
|
+
return parts.join(' ').toLowerCase();
|
|
149
|
+
}
|
|
354
150
|
|
|
355
|
-
function
|
|
151
|
+
function isSpecialLike(root) {
|
|
152
|
+
if (!(root instanceof Element)) return false;
|
|
153
|
+
const test = el => /adhesion|interstitial|anchor|sticky|outofpage/.test(textSig(el));
|
|
154
|
+
if (test(root)) return true;
|
|
356
155
|
try {
|
|
357
|
-
const
|
|
358
|
-
if (
|
|
359
|
-
if (d?.cid) return `c:${d.cid}`;
|
|
156
|
+
const nodes = root.querySelectorAll('[id],[class],[name],[data-slot],[data-ad-slot]');
|
|
157
|
+
for (const n of nodes) if (test(n)) return true;
|
|
360
158
|
} catch (_) {}
|
|
361
|
-
return
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function getKind() {
|
|
365
|
-
const p = location.pathname;
|
|
366
|
-
if (/^\/topic\//.test(p)) return 'topic';
|
|
367
|
-
if (/^\/category\//.test(p)) return 'categoryTopics';
|
|
368
|
-
if (p === '/' || /^\/categories/.test(p)) return 'categories';
|
|
369
|
-
if (document.querySelector(SEL.category)) return 'categories';
|
|
370
|
-
if (document.querySelector(SEL.post)) return 'topic';
|
|
371
|
-
if (document.querySelector(SEL.topic)) return 'categoryTopics';
|
|
372
|
-
return 'other';
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ── Items DOM ──────────────────────────────────────────────────────────────
|
|
376
|
-
|
|
377
|
-
function getPosts() {
|
|
378
|
-
return Array.from(document.querySelectorAll(SEL.post)).filter(el => {
|
|
379
|
-
if (!el.isConnected) return false;
|
|
380
|
-
if (!el.querySelector('[component="post/content"]')) return false;
|
|
381
|
-
const p = el.parentElement?.closest(SEL.post);
|
|
382
|
-
if (p && p !== el) return false;
|
|
383
|
-
return el.getAttribute('component') !== 'post/parent';
|
|
384
|
-
});
|
|
159
|
+
return false;
|
|
385
160
|
}
|
|
386
161
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (!key) return false;
|
|
400
|
-
// Lookup O(1) dans le registre — vrai si le wrap EST encore dans le registre
|
|
401
|
-
// et connecté au DOM (le registre est tenu à jour par insertAfter/dropWrap).
|
|
402
|
-
if (S.wrapByKey.get(key) === wrap) return wrap.isConnected;
|
|
403
|
-
// Fallback : registre pas encore à jour ou wrap non enregistré.
|
|
404
|
-
const colonIdx = key.indexOf(':');
|
|
405
|
-
const klass = key.slice(0, colonIdx);
|
|
406
|
-
const anchorId = key.slice(colonIdx + 1);
|
|
407
|
-
const cfg = KIND[klass];
|
|
408
|
-
if (!cfg) return false;
|
|
409
|
-
// Optimisation : si l'ancre est un frère direct du wrap, pas besoin
|
|
410
|
-
// de querySelector global — on cherche parmi les voisins immédiats.
|
|
411
|
-
const parent = wrap.parentElement;
|
|
412
|
-
if (parent) {
|
|
413
|
-
for (const sib of parent.children) {
|
|
414
|
-
if (sib === wrap) continue;
|
|
415
|
-
try {
|
|
416
|
-
if (sib.matches(`${cfg.baseTag || ''}[${cfg.anchorAttr}="${anchorId}"]`)) {
|
|
417
|
-
return sib.isConnected;
|
|
418
|
-
}
|
|
419
|
-
} catch (_) {}
|
|
420
|
-
}
|
|
162
|
+
function hasFixedLike(root, max = 16) {
|
|
163
|
+
if (!(root instanceof Element)) return false;
|
|
164
|
+
const q = [root];
|
|
165
|
+
let seen = 0;
|
|
166
|
+
while (q.length && seen < max) {
|
|
167
|
+
const n = q.shift();
|
|
168
|
+
seen++;
|
|
169
|
+
try {
|
|
170
|
+
const cs = window.getComputedStyle(n);
|
|
171
|
+
if (cs.position === 'fixed' || cs.position === 'sticky') return true;
|
|
172
|
+
} catch (_) {}
|
|
173
|
+
for (const c of n.children || []) q.push(c);
|
|
421
174
|
}
|
|
422
|
-
|
|
423
|
-
try {
|
|
424
|
-
const found = document.querySelector(`${cfg.sel}[${cfg.anchorAttr}="${anchorId}"]`);
|
|
425
|
-
return !!(found?.isConnected);
|
|
426
|
-
} catch (_) { return false; }
|
|
175
|
+
return false;
|
|
427
176
|
}
|
|
428
177
|
|
|
429
|
-
function
|
|
430
|
-
return
|
|
178
|
+
function isFilled(wrap) {
|
|
179
|
+
try { return !!wrap?.querySelector?.('iframe, ins, img, video, [data-google-container-id]'); } catch (_) { return false; }
|
|
431
180
|
}
|
|
432
181
|
|
|
433
|
-
|
|
182
|
+
function phEl(id) { return document.getElementById(`${PH_PREFIX}${id}`); }
|
|
434
183
|
|
|
435
|
-
|
|
436
|
-
* Retourne la valeur de l'attribut stable pour cet élément,
|
|
437
|
-
* ou un fallback positionnel si l'attribut est absent.
|
|
438
|
-
*/
|
|
439
|
-
function stableId(klass, el) {
|
|
184
|
+
function anchorStableId(klass, el) {
|
|
440
185
|
const attr = KIND[klass]?.anchorAttr;
|
|
441
186
|
if (attr) {
|
|
442
187
|
const v = el.getAttribute(attr);
|
|
443
188
|
if (v !== null && v !== '') return v;
|
|
444
189
|
}
|
|
445
190
|
let i = 0;
|
|
446
|
-
for (const s of el.parentElement?.children
|
|
447
|
-
if (s === el) return `i${i}`;
|
|
448
|
-
i++;
|
|
449
|
-
}
|
|
191
|
+
for (const s of el.parentElement?.children || []) { if (s === el) return `i${i}`; i++; }
|
|
450
192
|
return 'i0';
|
|
451
193
|
}
|
|
194
|
+
const anchorKey = (klass, el) => `${klass}:${anchorStableId(klass, el)}`;
|
|
452
195
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
function findWrap(key) {
|
|
196
|
+
function findWrapByKey(key) {
|
|
456
197
|
const w = S.wrapByKey.get(key);
|
|
457
198
|
return (w?.isConnected) ? w : null;
|
|
458
199
|
}
|
|
459
200
|
|
|
460
|
-
|
|
201
|
+
function rememberWrapHeight(wrap) {
|
|
202
|
+
try {
|
|
203
|
+
if (!(wrap instanceof Element)) return;
|
|
204
|
+
const klass = [...(wrap.classList || [])].find(c => c.startsWith('ezoic-ad-'));
|
|
205
|
+
if (!klass) return;
|
|
206
|
+
const h = Math.round(wrap.getBoundingClientRect().height || 0);
|
|
207
|
+
if (h >= CFG.minHeightRememberMin) S.lastWrapHeightByClass.set(klass, h);
|
|
208
|
+
} catch (_) {}
|
|
209
|
+
}
|
|
461
210
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
for (let t = 0; t < pool.length; t++) {
|
|
470
|
-
const i = S.cursors[poolKey] % pool.length;
|
|
471
|
-
S.cursors[poolKey] = (S.cursors[poolKey] + 1) % pool.length;
|
|
472
|
-
const id = pool[i];
|
|
473
|
-
if (!S.mountedIds.has(id)) return id;
|
|
474
|
-
}
|
|
475
|
-
return null;
|
|
211
|
+
function wrapNearViewport(wrap) {
|
|
212
|
+
try {
|
|
213
|
+
const r = wrap.getBoundingClientRect();
|
|
214
|
+
const b = viewportBuffer();
|
|
215
|
+
const vh = window.innerHeight || 800;
|
|
216
|
+
return r.bottom > -b && r.top < vh + b;
|
|
217
|
+
} catch (_) { return true; }
|
|
476
218
|
}
|
|
477
219
|
|
|
478
|
-
function
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
220
|
+
function ensureRegistry(id, wrap) {
|
|
221
|
+
let rec = S.regById.get(id);
|
|
222
|
+
if (!rec) {
|
|
223
|
+
rec = {
|
|
224
|
+
id,
|
|
225
|
+
wrap,
|
|
226
|
+
key: wrap?.getAttribute(A_ANCHOR) || '',
|
|
227
|
+
state: 'idle',
|
|
228
|
+
typeClass: [...(wrap?.classList || [])].find(c => c.startsWith('ezoic-ad-')) || '',
|
|
229
|
+
isSpecial: false,
|
|
230
|
+
isFixedLike: false,
|
|
231
|
+
createdAt: now(),
|
|
232
|
+
shownAt: 0,
|
|
233
|
+
lastSeenAt: 0,
|
|
234
|
+
lastMutationAt: now(),
|
|
235
|
+
cooldownUntil: 0,
|
|
236
|
+
};
|
|
237
|
+
S.regById.set(id, rec);
|
|
493
238
|
}
|
|
494
|
-
if (
|
|
495
|
-
|
|
239
|
+
if (wrap && rec.wrap !== wrap) rec.wrap = wrap;
|
|
240
|
+
if (wrap) {
|
|
241
|
+
rec.key = wrap.getAttribute(A_ANCHOR) || rec.key;
|
|
242
|
+
rec.typeClass = [...(wrap.classList || [])].find(c => c.startsWith('ezoic-ad-')) || rec.typeClass;
|
|
496
243
|
}
|
|
244
|
+
return rec;
|
|
497
245
|
}
|
|
498
246
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
* destroy([id]) → 300ms → define([id]) → 300ms → displayMore([id])
|
|
503
|
-
* displayMore = API Ezoic prévue pour l'infinite scroll.
|
|
504
|
-
* Priorité : wraps vides d'abord, remplis si nécessaire.
|
|
505
|
-
*/
|
|
506
|
-
function recycleAndMove(klass, targetEl, newKey) {
|
|
507
|
-
const ez = window.ezstandalone;
|
|
508
|
-
if (typeof ez?.destroyPlaceholders !== 'function' ||
|
|
509
|
-
typeof ez?.define !== 'function' ||
|
|
510
|
-
typeof ez?.displayMore !== 'function') return null;
|
|
511
|
-
|
|
512
|
-
const vh = window.innerHeight || 800;
|
|
513
|
-
const preferAbove = S.scrollDir >= 0; // scroll bas => recycle en haut
|
|
514
|
-
const farAbove = -vh;
|
|
515
|
-
const farBelow = vh * 2;
|
|
516
|
-
|
|
517
|
-
let bestPrefEmpty = null, bestPrefMetric = Infinity;
|
|
518
|
-
let bestPrefFilled = null, bestPrefFilledMetric = Infinity;
|
|
519
|
-
let bestAnyEmpty = null, bestAnyMetric = Infinity;
|
|
520
|
-
let bestAnyFilled = null, bestAnyFilledMetric = Infinity;
|
|
521
|
-
|
|
522
|
-
for (const wrap of S.wrapByKey.values()) {
|
|
523
|
-
if (!wrap?.isConnected || !wrap.classList?.contains(WRAP_CLASS) || !wrap.classList.contains(klass)) continue;
|
|
524
|
-
if (wrapRecentActivity(wrap)) continue;
|
|
525
|
-
if (hasSpecialSlotMarkers(wrap) || hasFixedLikeNode(wrap)) continue;
|
|
526
|
-
try {
|
|
527
|
-
const rect = wrap.getBoundingClientRect();
|
|
528
|
-
const isAbove = rect.bottom <= farAbove;
|
|
529
|
-
const isBelow = rect.top >= farBelow;
|
|
530
|
-
const anyFar = isAbove || isBelow;
|
|
531
|
-
if (!anyFar) continue;
|
|
532
|
-
|
|
533
|
-
const qualifies = preferAbove ? isAbove : isBelow;
|
|
534
|
-
const metric = preferAbove ? Math.abs(rect.bottom) : Math.abs(rect.top - vh);
|
|
535
|
-
const filled = isFilled(wrap);
|
|
536
|
-
|
|
537
|
-
if (qualifies) {
|
|
538
|
-
if (!filled) {
|
|
539
|
-
if (metric < bestPrefMetric) { bestPrefMetric = metric; bestPrefEmpty = wrap; }
|
|
540
|
-
} else {
|
|
541
|
-
if (metric < bestPrefFilledMetric) { bestPrefFilledMetric = metric; bestPrefFilled = wrap; }
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
if (!filled) {
|
|
545
|
-
if (metric < bestAnyMetric) { bestAnyMetric = metric; bestAnyEmpty = wrap; }
|
|
546
|
-
} else {
|
|
547
|
-
if (metric < bestAnyFilledMetric) { bestAnyFilledMetric = metric; bestAnyFilled = wrap; }
|
|
548
|
-
}
|
|
549
|
-
} catch (_) {}
|
|
247
|
+
function updateWrapState(rec, state) {
|
|
248
|
+
rec.state = state;
|
|
249
|
+
try { rec.wrap?.setAttribute?.(A_STATE, state); } catch (_) {}
|
|
550
250
|
}
|
|
551
251
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
rememberWrapHeight(best);
|
|
561
|
-
best.setAttribute(A_ANCHOR, newKey);
|
|
562
|
-
best.setAttribute(A_CREATED, String(ts()));
|
|
563
|
-
best.setAttribute(A_SHOWN, '0');
|
|
564
|
-
best.classList.remove('is-empty');
|
|
565
|
-
const ph = best.querySelector(`#${PH_PREFIX}${id}`);
|
|
566
|
-
if (ph) ph.innerHTML = '';
|
|
567
|
-
targetEl.insertAdjacentElement('afterend', best);
|
|
568
|
-
});
|
|
569
|
-
if (oldKey && S.wrapByKey.get(oldKey) === best) S.wrapByKey.delete(oldKey);
|
|
570
|
-
S.wrapByKey.set(newKey, best);
|
|
571
|
-
|
|
572
|
-
const doDestroy = () => {
|
|
573
|
-
if (S.ezShownSinceDestroy.has(id)) {
|
|
574
|
-
try { ez.destroyPlaceholders([id]); } catch (_) {}
|
|
575
|
-
S.ezShownSinceDestroy.delete(id);
|
|
252
|
+
function pickId(poolKey) {
|
|
253
|
+
const pool = S.pools[poolKey] || [];
|
|
254
|
+
if (!pool.length) return null;
|
|
255
|
+
for (let t = 0; t < pool.length; t++) {
|
|
256
|
+
const idx = S.cursors[poolKey] % pool.length;
|
|
257
|
+
S.cursors[poolKey] = (S.cursors[poolKey] + 1) % pool.length;
|
|
258
|
+
const id = pool[idx];
|
|
259
|
+
if (!S.mountedIds.has(id)) return id;
|
|
576
260
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
};
|
|
580
|
-
const doDefine = () => { try { ez.define([id]); } catch (_) {} setTimeout(doDisplay, 300); };
|
|
581
|
-
const doDisplay = () => { try { ez.displayMore([id]); S.ezActiveIds.add(id); S.ezShownSinceDestroy.add(id); } catch (_) {} };
|
|
582
|
-
try { (typeof ez.cmd?.push === 'function') ? ez.cmd.push(doDestroy) : doDestroy(); } catch (_) {}
|
|
583
|
-
|
|
584
|
-
return { id, wrap: best };
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// ── Wraps DOM — création / suppression ────────────────────────────────────
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
588
263
|
|
|
589
264
|
function makeWrap(id, klass, key) {
|
|
590
265
|
const w = document.createElement('div');
|
|
591
266
|
w.className = `${WRAP_CLASS} ${klass}`;
|
|
592
|
-
w.setAttribute(A_ANCHOR,
|
|
593
|
-
w.setAttribute(A_WRAPID,
|
|
594
|
-
w.setAttribute(A_CREATED, String(
|
|
595
|
-
w.setAttribute(A_SHOWN,
|
|
596
|
-
w.
|
|
597
|
-
|
|
598
|
-
|
|
267
|
+
w.setAttribute(A_ANCHOR, key);
|
|
268
|
+
w.setAttribute(A_WRAPID, String(id));
|
|
269
|
+
w.setAttribute(A_CREATED, String(now()));
|
|
270
|
+
w.setAttribute(A_SHOWN, '0');
|
|
271
|
+
w.setAttribute(A_STATE, 'idle');
|
|
272
|
+
w.style.cssText = 'display:block;width:100%;';
|
|
273
|
+
const h = S.lastWrapHeightByClass.get(klass);
|
|
274
|
+
if (Number.isFinite(h) && h > 0) w.style.minHeight = `${h}px`;
|
|
599
275
|
const ph = document.createElement('div');
|
|
600
276
|
ph.id = `${PH_PREFIX}${id}`;
|
|
601
277
|
ph.setAttribute('data-ezoic-id', String(id));
|
|
@@ -603,473 +279,431 @@ function recycleAndMove(klass, targetEl, newKey) {
|
|
|
603
279
|
return w;
|
|
604
280
|
}
|
|
605
281
|
|
|
606
|
-
function
|
|
607
|
-
if (!
|
|
608
|
-
if (
|
|
609
|
-
if (S.mountedIds.has(id))
|
|
610
|
-
if (
|
|
282
|
+
function insertWrapAfter(anchorEl, id, klass, key) {
|
|
283
|
+
if (!anchorEl?.insertAdjacentElement) return null;
|
|
284
|
+
if (findWrapByKey(key)) return null;
|
|
285
|
+
if (S.mountedIds.has(id)) return null;
|
|
286
|
+
if (phEl(id)?.isConnected) return null;
|
|
611
287
|
const w = makeWrap(id, klass, key);
|
|
612
|
-
mutate(() =>
|
|
288
|
+
mutate(() => anchorEl.insertAdjacentElement('afterend', w));
|
|
613
289
|
S.mountedIds.add(id);
|
|
614
290
|
S.wrapByKey.set(key, w);
|
|
291
|
+
const rec = ensureRegistry(id, w);
|
|
292
|
+
rec.key = key;
|
|
293
|
+
rec.createdAt = now();
|
|
294
|
+
rec.lastMutationAt = now();
|
|
295
|
+
rec.isSpecial = false;
|
|
296
|
+
rec.isFixedLike = false;
|
|
297
|
+
updateWrapState(rec, 'idle');
|
|
298
|
+
observePlaceholder(id);
|
|
615
299
|
return w;
|
|
616
300
|
}
|
|
617
301
|
|
|
618
|
-
function
|
|
302
|
+
function releaseWrap(wrap) {
|
|
619
303
|
try {
|
|
620
|
-
rememberWrapHeight(
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
const
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
if (
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
304
|
+
rememberWrapHeight(wrap);
|
|
305
|
+
const id = parseInt(wrap.getAttribute(A_WRAPID), 10);
|
|
306
|
+
const key = wrap.getAttribute(A_ANCHOR) || '';
|
|
307
|
+
const ph = wrap.querySelector(`[id^="${PH_PREFIX}"]`);
|
|
308
|
+
if (ph) try { S.io?.unobserve(ph); } catch (_) {}
|
|
309
|
+
if (key && S.wrapByKey.get(key) === wrap) S.wrapByKey.delete(key);
|
|
310
|
+
if (Number.isFinite(id)) {
|
|
311
|
+
S.mountedIds.delete(id);
|
|
312
|
+
S.pendingSet.delete(id);
|
|
313
|
+
S.lastShowById.delete(id);
|
|
314
|
+
const rec = S.regById.get(id);
|
|
315
|
+
if (rec) {
|
|
316
|
+
rec.wrap = null;
|
|
317
|
+
rec.state = 'retired';
|
|
318
|
+
rec.cooldownUntil = Math.max(rec.cooldownUntil || 0, now() + 3000);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
wrap.remove();
|
|
630
322
|
} catch (_) {}
|
|
631
323
|
}
|
|
632
324
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
// supprimerait les wraps, et provoquerait une réinjection en haut.
|
|
647
|
-
|
|
648
|
-
function pruneOrphansBetween() {
|
|
649
|
-
const klass = 'ezoic-ad-between';
|
|
650
|
-
const cfg = KIND[klass];
|
|
325
|
+
function wrapHasAnchor(wrap) {
|
|
326
|
+
const key = wrap?.getAttribute?.(A_ANCHOR);
|
|
327
|
+
if (!key) return false;
|
|
328
|
+
const i = key.indexOf(':');
|
|
329
|
+
const klass = key.slice(0, i);
|
|
330
|
+
const sid = key.slice(i + 1);
|
|
331
|
+
const def = KIND[klass];
|
|
332
|
+
if (!def) return false;
|
|
333
|
+
try {
|
|
334
|
+
const sel = `${def.sel}[${def.anchorAttr}="${sid}"]`;
|
|
335
|
+
return !!document.querySelector(sel);
|
|
336
|
+
} catch (_) { return false; }
|
|
337
|
+
}
|
|
651
338
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (
|
|
655
|
-
|
|
339
|
+
function sweepDisconnected() {
|
|
340
|
+
for (const [key, wrap] of [...S.wrapByKey.entries()]) {
|
|
341
|
+
if (wrap?.isConnected) continue;
|
|
342
|
+
S.wrapByKey.delete(key);
|
|
343
|
+
const id = parseInt(wrap?.getAttribute?.(A_WRAPID), 10);
|
|
344
|
+
if (Number.isFinite(id)) {
|
|
345
|
+
S.mountedIds.delete(id);
|
|
346
|
+
S.pendingSet.delete(id);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
656
350
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
351
|
+
function classifyWrap(rec) {
|
|
352
|
+
const w = rec.wrap;
|
|
353
|
+
if (!w?.isConnected) return;
|
|
354
|
+
if (!rec.isSpecial) rec.isSpecial = isSpecialLike(w);
|
|
355
|
+
if (!rec.isFixedLike) rec.isFixedLike = hasFixedLike(w);
|
|
356
|
+
}
|
|
660
357
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
358
|
+
function maintenance() {
|
|
359
|
+
if (isBlocked()) return;
|
|
360
|
+
sweepDisconnected();
|
|
361
|
+
const t = now();
|
|
362
|
+
for (const [id, rec] of S.regById) {
|
|
363
|
+
const w = rec.wrap;
|
|
364
|
+
if (!w?.isConnected) continue;
|
|
365
|
+
classifyWrap(rec);
|
|
366
|
+
if (wrapNearViewport(w)) {
|
|
367
|
+
rec.lastSeenAt = t;
|
|
368
|
+
if (rec.state === 'retiring') updateWrapState(rec, 'live');
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const created = parseInt(w.getAttribute(A_CREATED) || '0', 10) || rec.createdAt || 0;
|
|
372
|
+
const shown = parseInt(w.getAttribute(A_SHOWN) || '0', 10) || rec.shownAt || 0;
|
|
373
|
+
const age = t - Math.max(created, shown || 0, rec.lastMutationAt || 0);
|
|
374
|
+
const activeRecently = (t - (rec.lastMutationAt || 0)) < CFG.recentActivityMs;
|
|
375
|
+
const filled = isFilled(w);
|
|
376
|
+
if (filled) { rec.lastMutationAt = t; rec.shownAt = Math.max(rec.shownAt || 0, shown || 0); }
|
|
377
|
+
if (rec.isSpecial || rec.isFixedLike) continue; // never retire plugin-side
|
|
378
|
+
if (activeRecently) continue;
|
|
379
|
+
if (filled) continue; // keep rendered slots stable; no aggressive destroy/recycle
|
|
380
|
+
if (age < CFG.retireGraceMs) continue;
|
|
381
|
+
if (rec.state !== 'retiring') {
|
|
382
|
+
updateWrapState(rec, 'retiring');
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
// second pass confirmation
|
|
386
|
+
if (wrapNearViewport(w) || isFilled(w)) { updateWrapState(rec, 'live'); continue; }
|
|
387
|
+
releaseWrap(w);
|
|
388
|
+
}
|
|
664
389
|
}
|
|
665
390
|
|
|
666
|
-
|
|
391
|
+
function scheduleMaintenance() {
|
|
392
|
+
if (S.maintenanceTimer) return;
|
|
393
|
+
S.maintenanceTimer = setTimeout(() => {
|
|
394
|
+
S.maintenanceTimer = 0;
|
|
395
|
+
maintenance();
|
|
396
|
+
}, CFG.maintenanceEveryMs);
|
|
397
|
+
}
|
|
667
398
|
|
|
668
|
-
/**
|
|
669
|
-
* Ordinal 0-based pour le calcul de l'intervalle d'injection.
|
|
670
|
-
* Utilise ordinalAttr si défini, sinon compte les frères dans le parent.
|
|
671
|
-
*/
|
|
672
399
|
function ordinal(klass, el) {
|
|
673
400
|
const attr = KIND[klass]?.ordinalAttr;
|
|
674
401
|
if (attr) {
|
|
675
402
|
const v = el.getAttribute(attr);
|
|
676
403
|
if (v !== null && v !== '' && !isNaN(v)) return parseInt(v, 10);
|
|
677
404
|
}
|
|
678
|
-
const fullSel = KIND[klass]?.sel ?? '';
|
|
679
405
|
let i = 0;
|
|
680
|
-
|
|
406
|
+
const fullSel = KIND[klass]?.sel || '';
|
|
407
|
+
for (const s of el.parentElement?.children || []) {
|
|
681
408
|
if (s === el) return i;
|
|
682
409
|
if (!fullSel || s.matches?.(fullSel)) i++;
|
|
683
410
|
}
|
|
684
411
|
return 0;
|
|
685
412
|
}
|
|
686
413
|
|
|
687
|
-
function
|
|
688
|
-
|
|
689
|
-
|
|
414
|
+
function adjacentManagedWrap(el) {
|
|
415
|
+
const isManaged = n => !!(n?.classList?.contains(WRAP_CLASS) && n.isConnected);
|
|
416
|
+
return isManaged(el.nextElementSibling) || isManaged(el.previousElementSibling);
|
|
417
|
+
}
|
|
690
418
|
|
|
419
|
+
function injectFor(klass, items, interval, showFirst) {
|
|
420
|
+
let inserted = 0;
|
|
421
|
+
const poolKey = KIND[klass].pool;
|
|
691
422
|
for (const el of items) {
|
|
692
|
-
if (inserted >=
|
|
423
|
+
if (inserted >= CFG.maxInsertsPerRun) break;
|
|
693
424
|
if (!el?.isConnected) continue;
|
|
694
|
-
|
|
695
425
|
const ord = ordinal(klass, el);
|
|
696
426
|
if (!(showFirst && ord === 0) && (ord + 1) % interval !== 0) continue;
|
|
697
|
-
if (
|
|
698
|
-
|
|
427
|
+
if (adjacentManagedWrap(el)) continue;
|
|
699
428
|
const key = anchorKey(klass, el);
|
|
700
|
-
if (
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
if (
|
|
704
|
-
if (id) {
|
|
705
|
-
const w = insertAfter(el, id, klass, key);
|
|
706
|
-
if (w) { observePh(id); inserted++; }
|
|
707
|
-
} else {
|
|
708
|
-
const recycled = recycleAndMove(klass, el, key);
|
|
709
|
-
if (!recycled) break;
|
|
710
|
-
inserted++;
|
|
711
|
-
}
|
|
429
|
+
if (findWrapByKey(key)) continue;
|
|
430
|
+
const id = pickId(poolKey);
|
|
431
|
+
if (!id) break; // no recycle = anti-churn by design
|
|
432
|
+
if (insertWrapAfter(el, id, klass, key)) inserted++;
|
|
712
433
|
}
|
|
713
434
|
return inserted;
|
|
714
435
|
}
|
|
715
436
|
|
|
716
|
-
|
|
437
|
+
function canShowId(id) {
|
|
438
|
+
const t = now();
|
|
439
|
+
const last = S.lastShowById.get(id) || 0;
|
|
440
|
+
if (t - last < CFG.showThrottleMs) return false;
|
|
441
|
+
const rec = S.regById.get(id);
|
|
442
|
+
if (rec && rec.cooldownUntil && t < rec.cooldownUntil) return false;
|
|
443
|
+
const ph = phEl(id);
|
|
444
|
+
if (!ph?.isConnected) return false;
|
|
445
|
+
const wrap = ph.closest?.(`.${WRAP_CLASS}`);
|
|
446
|
+
if (!wrap?.isConnected) return false;
|
|
447
|
+
if (!wrapNearViewport(wrap)) {
|
|
448
|
+
// allow preloading within IO margin only if reasonably close
|
|
449
|
+
try {
|
|
450
|
+
const r = ph.getBoundingClientRect();
|
|
451
|
+
const vh = window.innerHeight || 800;
|
|
452
|
+
const preload = isMobile() ? 1800 : 1400;
|
|
453
|
+
if (r.top > vh + preload || r.bottom < -preload) return false;
|
|
454
|
+
} catch (_) {}
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
717
458
|
|
|
718
|
-
function
|
|
719
|
-
if (
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
} catch (_) { S.io = null; }
|
|
730
|
-
return S.io;
|
|
459
|
+
function enqueueShow(id) {
|
|
460
|
+
if (isBlocked()) return;
|
|
461
|
+
const n = parseInt(id, 10);
|
|
462
|
+
if (!Number.isFinite(n) || n <= 0) return;
|
|
463
|
+
if (!S.pendingSet.has(n)) { S.pendingSet.add(n); S.pendingShow.push(n); }
|
|
464
|
+
if (!S.showTimer) {
|
|
465
|
+
S.showTimer = setTimeout(() => {
|
|
466
|
+
S.showTimer = 0;
|
|
467
|
+
drainShowQueue();
|
|
468
|
+
}, CFG.batchFlushMs);
|
|
469
|
+
}
|
|
731
470
|
}
|
|
732
471
|
|
|
733
|
-
function
|
|
472
|
+
function observePlaceholder(id) {
|
|
734
473
|
const ph = phEl(id);
|
|
735
|
-
if (ph?.isConnected)
|
|
736
|
-
|
|
474
|
+
if (!ph?.isConnected) return;
|
|
475
|
+
try { getIO()?.observe(ph); } catch (_) {}
|
|
737
476
|
try {
|
|
738
|
-
|
|
739
|
-
const rect = ph.getBoundingClientRect();
|
|
477
|
+
const r = ph.getBoundingClientRect();
|
|
740
478
|
const vh = window.innerHeight || 800;
|
|
741
|
-
const preload = isMobile() ?
|
|
742
|
-
if (
|
|
479
|
+
const preload = isMobile() ? 1600 : 1200;
|
|
480
|
+
if (r.top <= vh + preload && r.bottom >= -preload) enqueueShow(id);
|
|
743
481
|
} catch (_) {}
|
|
744
482
|
}
|
|
745
483
|
|
|
746
|
-
function
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
if (S.showBatchTimer) return;
|
|
758
|
-
S.showBatchTimer = setTimeout(() => {
|
|
759
|
-
S.showBatchTimer = 0;
|
|
760
|
-
drainQueue();
|
|
761
|
-
}, BATCH_FLUSH_MS);
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
function drainQueue() {
|
|
765
|
-
if (isBlocked()) return;
|
|
766
|
-
const free = Math.max(0, MAX_INFLIGHT - S.inflight);
|
|
767
|
-
if (!free || !S.pending.length) return;
|
|
484
|
+
function startShow(ids) {
|
|
485
|
+
if (!ids.length || isBlocked()) return;
|
|
486
|
+
S.inflightShow += ids.length;
|
|
487
|
+
let released = false;
|
|
488
|
+
const release = () => {
|
|
489
|
+
if (released) return;
|
|
490
|
+
released = true;
|
|
491
|
+
S.inflightShow = Math.max(0, S.inflightShow - ids.length);
|
|
492
|
+
drainShowQueue();
|
|
493
|
+
};
|
|
494
|
+
const guard = setTimeout(release, CFG.showFailsafeMs);
|
|
768
495
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
496
|
+
requestAnimationFrame(() => {
|
|
497
|
+
try {
|
|
498
|
+
if (isBlocked()) { clearTimeout(guard); return release(); }
|
|
499
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
500
|
+
const ez = window.ezstandalone;
|
|
501
|
+
const valid = [];
|
|
502
|
+
const t = now();
|
|
503
|
+
for (const id of ids) {
|
|
504
|
+
if (!canShowId(id)) continue;
|
|
505
|
+
const ph = phEl(id);
|
|
506
|
+
const wrap = ph?.closest?.(`.${WRAP_CLASS}`);
|
|
507
|
+
if (!wrap) continue;
|
|
508
|
+
wrap.setAttribute(A_SHOWN, String(t));
|
|
509
|
+
const rec = ensureRegistry(id, wrap);
|
|
510
|
+
rec.wrap = wrap;
|
|
511
|
+
rec.shownAt = t;
|
|
512
|
+
rec.lastMutationAt = t;
|
|
513
|
+
rec.cooldownUntil = t + CFG.showCooldownMs;
|
|
514
|
+
classifyWrap(rec);
|
|
515
|
+
updateWrapState(rec, 'showing');
|
|
516
|
+
S.lastShowById.set(id, t);
|
|
517
|
+
valid.push(id);
|
|
518
|
+
}
|
|
519
|
+
if (!valid.length) { clearTimeout(guard); return release(); }
|
|
520
|
+
const run = () => {
|
|
521
|
+
try { ez.showAds(...valid); } catch (_) {
|
|
522
|
+
try { ez.showAds(valid); } catch (_) {}
|
|
523
|
+
}
|
|
524
|
+
setTimeout(() => {
|
|
525
|
+
for (const id of valid) {
|
|
526
|
+
const rec = S.regById.get(id);
|
|
527
|
+
if (!rec) continue;
|
|
528
|
+
const w = rec.wrap;
|
|
529
|
+
if (w?.isConnected) {
|
|
530
|
+
if (isFilled(w)) rec.lastMutationAt = now();
|
|
531
|
+
updateWrapState(rec, 'live');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
clearTimeout(guard);
|
|
535
|
+
release();
|
|
536
|
+
}, CFG.showReleaseMs);
|
|
537
|
+
};
|
|
538
|
+
Array.isArray(ez.cmd) ? ez.cmd.push(run) : run();
|
|
539
|
+
} catch (_) {
|
|
540
|
+
clearTimeout(guard);
|
|
541
|
+
release();
|
|
542
|
+
}
|
|
543
|
+
});
|
|
779
544
|
}
|
|
780
|
-
if (picked.length) startShowBatch(picked);
|
|
781
|
-
if (S.pending.length && (MAX_INFLIGHT - S.inflight) > 0) scheduleDrainQueue();
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
function startShowBatch(ids) {
|
|
785
|
-
if (!ids?.length || isBlocked()) return;
|
|
786
|
-
const reserve = ids.length;
|
|
787
|
-
S.inflight += reserve;
|
|
788
545
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
if (
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
546
|
+
function drainShowQueue() {
|
|
547
|
+
if (isBlocked()) return;
|
|
548
|
+
if (S.inflightShow >= CFG.maxShowInflight) return;
|
|
549
|
+
if (!S.pendingShow.length) return;
|
|
550
|
+
const cap = Math.min(CFG.maxShowBatch, CFG.maxShowInflight - S.inflightShow);
|
|
551
|
+
const picked = [];
|
|
552
|
+
while (S.pendingShow.length && picked.length < cap) {
|
|
553
|
+
const id = S.pendingShow.shift();
|
|
554
|
+
S.pendingSet.delete(id);
|
|
555
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
556
|
+
if (!phEl(id)?.isConnected) continue;
|
|
557
|
+
picked.push(id);
|
|
558
|
+
}
|
|
559
|
+
if (picked.length) startShow(picked);
|
|
560
|
+
if (S.pendingShow.length) scheduleMaintenance();
|
|
561
|
+
}
|
|
797
562
|
|
|
798
|
-
|
|
563
|
+
function getIO() {
|
|
564
|
+
if (S.io) return S.io;
|
|
799
565
|
try {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
if (!canShowPlaceholderId(id, t)) continue;
|
|
810
|
-
|
|
811
|
-
S.lastShow.set(id, t);
|
|
812
|
-
try { const wrap = ph.closest?.(WRAP_SEL); wrap?.setAttribute(A_SHOWN, String(t)); if (wrap) markWrapActivity(wrap); } catch (_) {}
|
|
813
|
-
valid.push(id);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
if (!valid.length) { clearTimeout(timer); return release(); }
|
|
817
|
-
|
|
818
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
819
|
-
const ez = window.ezstandalone;
|
|
820
|
-
const doShow = () => {
|
|
821
|
-
const prepared = destroyBeforeReuse(valid);
|
|
822
|
-
if (!prepared.length) { setTimeout(() => { clearTimeout(timer); release(); }, SHOW_RELEASE_MS); return; }
|
|
823
|
-
try { ez.showAds(...prepared); } catch (_) {}
|
|
824
|
-
for (const id of prepared) {
|
|
825
|
-
S.ezActiveIds.add(id);
|
|
826
|
-
S.ezShownSinceDestroy.add(id);
|
|
566
|
+
S.io = new IntersectionObserver(entries => {
|
|
567
|
+
for (const e of entries) {
|
|
568
|
+
const t = e.target;
|
|
569
|
+
if (!(t instanceof Element)) continue;
|
|
570
|
+
const id = parseInt(t.getAttribute('data-ezoic-id'), 10);
|
|
571
|
+
if (!Number.isFinite(id) || id <= 0) continue;
|
|
572
|
+
const rec = S.regById.get(id);
|
|
573
|
+
if (rec) rec.lastSeenAt = now();
|
|
574
|
+
if (e.isIntersecting) enqueueShow(id);
|
|
827
575
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
//
|
|
837
|
-
// Intercepte ez.showAds() pour :
|
|
838
|
-
// – ignorer les appels pendant blockedUntil
|
|
839
|
-
// – filtrer les ids dont le placeholder n'est pas en DOM
|
|
576
|
+
}, {
|
|
577
|
+
root: null,
|
|
578
|
+
rootMargin: isMobile() ? CFG.ioMarginMobile : CFG.ioMarginDesktop,
|
|
579
|
+
threshold: 0,
|
|
580
|
+
});
|
|
581
|
+
} catch (_) { S.io = null; }
|
|
582
|
+
return S.io;
|
|
583
|
+
}
|
|
840
584
|
|
|
841
585
|
function patchShowAds() {
|
|
842
|
-
const
|
|
586
|
+
const install = () => {
|
|
843
587
|
try {
|
|
844
588
|
window.ezstandalone = window.ezstandalone || {};
|
|
845
589
|
const ez = window.ezstandalone;
|
|
846
|
-
if (window.
|
|
847
|
-
window.
|
|
590
|
+
if (window.__nbbEzPatchedV22 || typeof ez.showAds !== 'function') return;
|
|
591
|
+
window.__nbbEzPatchedV22 = true;
|
|
848
592
|
const orig = ez.showAds.bind(ez);
|
|
849
593
|
ez.showAds = function (...args) {
|
|
850
594
|
if (isBlocked()) return;
|
|
851
|
-
const ids = args.length === 1 && Array.isArray(args[0]) ? args[0] : args;
|
|
595
|
+
const ids = (args.length === 1 && Array.isArray(args[0])) ? args[0] : args;
|
|
852
596
|
const valid = [];
|
|
853
597
|
const seen = new Set();
|
|
854
598
|
for (const v of ids) {
|
|
855
599
|
const id = parseInt(v, 10);
|
|
856
600
|
if (!Number.isFinite(id) || id <= 0 || seen.has(id)) continue;
|
|
857
|
-
|
|
601
|
+
const ph = phEl(id);
|
|
602
|
+
if (!ph?.isConnected) continue;
|
|
603
|
+
const wrap = ph.closest?.(`.${WRAP_CLASS}`);
|
|
604
|
+
if (!wrap?.isConnected) continue;
|
|
605
|
+
const rec = ensureRegistry(id, wrap);
|
|
606
|
+
if (rec.cooldownUntil && now() < rec.cooldownUntil && (now() - (rec.shownAt || 0)) < 500) continue;
|
|
858
607
|
seen.add(id);
|
|
859
608
|
valid.push(id);
|
|
860
609
|
}
|
|
861
610
|
if (!valid.length) return;
|
|
862
|
-
try { orig(...valid); } catch (_) {
|
|
863
|
-
for (const id of valid) {
|
|
864
|
-
try { orig(id); } catch (_) {}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
611
|
+
try { return orig(...valid); } catch (_) { try { return orig(valid); } catch (_) {} }
|
|
867
612
|
};
|
|
868
613
|
} catch (_) {}
|
|
869
614
|
};
|
|
870
|
-
|
|
871
|
-
if (!window.
|
|
615
|
+
install();
|
|
616
|
+
if (!window.__nbbEzPatchedV22) {
|
|
872
617
|
window.ezstandalone = window.ezstandalone || {};
|
|
873
|
-
(window.ezstandalone.cmd = window.ezstandalone.cmd || []).push(
|
|
618
|
+
(window.ezstandalone.cmd = window.ezstandalone.cmd || []).push(install);
|
|
874
619
|
}
|
|
875
620
|
}
|
|
876
621
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
const cfg = await fetchConfig();
|
|
885
|
-
if (!cfg || cfg.excluded) return 0;
|
|
886
|
-
initPools(cfg);
|
|
887
|
-
|
|
888
|
-
const kind = getKind();
|
|
889
|
-
if (kind === 'other') return 0;
|
|
890
|
-
|
|
891
|
-
const exec = (klass, getItems, cfgEnable, cfgInterval, cfgShowFirst, poolKey) => {
|
|
892
|
-
if (!normBool(cfgEnable)) return 0;
|
|
893
|
-
const interval = Math.max(1, parseInt(cfgInterval, 10) || 3);
|
|
894
|
-
return injectBetween(klass, getItems(), interval, normBool(cfgShowFirst), poolKey);
|
|
895
|
-
};
|
|
896
|
-
|
|
897
|
-
if (kind === 'topic') return exec(
|
|
898
|
-
'ezoic-ad-message', getPosts,
|
|
899
|
-
cfg.enableMessageAds, cfg.messageIntervalPosts, cfg.showFirstMessageAd, 'posts'
|
|
900
|
-
);
|
|
901
|
-
|
|
902
|
-
if (kind === 'categoryTopics') {
|
|
903
|
-
pruneOrphansBetween();
|
|
904
|
-
return exec(
|
|
905
|
-
'ezoic-ad-between', getTopics,
|
|
906
|
-
cfg.enableBetweenAds, cfg.intervalPosts, cfg.showFirstTopicAd, 'topics'
|
|
907
|
-
);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
return exec(
|
|
911
|
-
'ezoic-ad-categories', getCategories,
|
|
912
|
-
cfg.enableCategoryAds, cfg.intervalCategories, cfg.showFirstCategoryAd, 'categories'
|
|
913
|
-
);
|
|
914
|
-
}
|
|
915
|
-
|
|
916
|
-
// ── Scheduler ──────────────────────────────────────────────────────────────
|
|
917
|
-
|
|
918
|
-
function scheduleRun(cb) {
|
|
919
|
-
if (S.runQueued) return;
|
|
920
|
-
S.runQueued = true;
|
|
921
|
-
requestAnimationFrame(async () => {
|
|
922
|
-
S.runQueued = false;
|
|
923
|
-
if (S.pageKey && pageKey() !== S.pageKey) return;
|
|
924
|
-
let n = 0;
|
|
925
|
-
try { n = await runCore(); } catch (_) {}
|
|
926
|
-
try { cb?.(n); } catch (_) {}
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
function requestBurst() {
|
|
931
|
-
if (isBlocked()) return;
|
|
932
|
-
const t = ts();
|
|
933
|
-
if (t - S.lastBurstTs < BURST_COOLDOWN_MS) return;
|
|
934
|
-
S.lastBurstTs = t;
|
|
935
|
-
S.pageKey = pageKey();
|
|
936
|
-
S.burstDeadline = t + 2000;
|
|
937
|
-
|
|
938
|
-
if (S.burstActive) return;
|
|
939
|
-
S.burstActive = true;
|
|
940
|
-
S.burstCount = 0;
|
|
941
|
-
|
|
942
|
-
const step = () => {
|
|
943
|
-
if (pageKey() !== S.pageKey || isBlocked() || ts() > S.burstDeadline || S.burstCount >= 8) {
|
|
944
|
-
S.burstActive = false; return;
|
|
622
|
+
function healFalseEmpty(root) {
|
|
623
|
+
try {
|
|
624
|
+
const list = [];
|
|
625
|
+
if (root instanceof Element && root.classList.contains(WRAP_CLASS)) list.push(root);
|
|
626
|
+
if (root?.querySelectorAll) {
|
|
627
|
+
for (const w of root.querySelectorAll(`.${WRAP_CLASS}.is-empty`)) list.push(w);
|
|
945
628
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
});
|
|
951
|
-
};
|
|
952
|
-
step();
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// ── Cleanup navigation ─────────────────────────────────────────────────────
|
|
956
|
-
|
|
957
|
-
function cleanup() {
|
|
958
|
-
blockedUntil = ts() + 1500;
|
|
959
|
-
mutate(() => document.querySelectorAll(WRAP_SEL).forEach(dropWrap));
|
|
960
|
-
S.cfg = null;
|
|
961
|
-
S.poolsReady = false;
|
|
962
|
-
S.pools = { topics: [], posts: [], categories: [] };
|
|
963
|
-
S.cursors = { topics: 0, posts: 0, categories: 0 };
|
|
964
|
-
S.mountedIds.clear();
|
|
965
|
-
S.lastShow.clear();
|
|
966
|
-
S.wrapByKey.clear();
|
|
967
|
-
S.ezActiveIds.clear();
|
|
968
|
-
S.ezShownSinceDestroy.clear();
|
|
969
|
-
S.wrapActivityAt.clear();
|
|
970
|
-
S.inflight = 0;
|
|
971
|
-
S.pending = [];
|
|
972
|
-
S.pendingSet.clear();
|
|
973
|
-
if (S.showBatchTimer) { clearTimeout(S.showBatchTimer); S.showBatchTimer = 0; }
|
|
974
|
-
if (S.destroyBatchTimer) { clearTimeout(S.destroyBatchTimer); S.destroyBatchTimer = 0; }
|
|
975
|
-
S.destroyPending = [];
|
|
976
|
-
S.destroyPendingSet.clear();
|
|
977
|
-
S.burstActive = false;
|
|
978
|
-
S.runQueued = false;
|
|
979
|
-
S.sweepQueued = false;
|
|
980
|
-
S.scrollSpeed = 0;
|
|
981
|
-
S.lastScrollY = 0;
|
|
982
|
-
S.lastScrollTs = 0;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
function nodeMatchesAny(node, selectors) {
|
|
986
|
-
if (!(node instanceof Element)) return false;
|
|
987
|
-
for (const sel of selectors) {
|
|
988
|
-
try { if (node.matches(sel)) return true; } catch (_) {}
|
|
989
|
-
}
|
|
990
|
-
return false;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
function nodeContainsAny(node, selectors) {
|
|
994
|
-
if (!(node instanceof Element)) return false;
|
|
995
|
-
for (const sel of selectors) {
|
|
996
|
-
try { if (node.querySelector(sel)) return true; } catch (_) {}
|
|
997
|
-
}
|
|
998
|
-
return false;
|
|
629
|
+
for (const w of list) {
|
|
630
|
+
if (w.classList.contains('is-empty') && isFilled(w)) w.classList.remove('is-empty');
|
|
631
|
+
}
|
|
632
|
+
} catch (_) {}
|
|
999
633
|
}
|
|
1000
634
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
function ensureDomObserver() {
|
|
635
|
+
function ensureObserver() {
|
|
1004
636
|
if (S.domObs) return;
|
|
1005
637
|
S.domObs = new MutationObserver(muts => {
|
|
1006
|
-
if (S.
|
|
638
|
+
if (S.muting > 0 || isBlocked()) return;
|
|
639
|
+
let shouldRun = false;
|
|
1007
640
|
for (const m of muts) {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
641
|
+
for (const n of m.addedNodes) {
|
|
642
|
+
if (!(n instanceof Element)) continue;
|
|
643
|
+
healFalseEmpty(n);
|
|
644
|
+
const wrap = n.classList?.contains(WRAP_CLASS) ? n : n.querySelector?.(`.${WRAP_CLASS}`);
|
|
645
|
+
if (wrap) {
|
|
646
|
+
const id = parseInt(wrap.getAttribute(A_WRAPID), 10);
|
|
647
|
+
if (Number.isFinite(id)) {
|
|
648
|
+
const rec = ensureRegistry(id, wrap);
|
|
649
|
+
rec.lastMutationAt = now();
|
|
650
|
+
classifyWrap(rec);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (n.matches?.(SEL.post) || n.matches?.(SEL.topic) || n.matches?.(SEL.category) ||
|
|
654
|
+
n.querySelector?.(SEL.post) || n.querySelector?.(SEL.topic) || n.querySelector?.(SEL.category)) {
|
|
655
|
+
shouldRun = true;
|
|
1017
656
|
}
|
|
1018
657
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
if (n.
|
|
1022
|
-
|
|
1023
|
-
try {
|
|
1024
|
-
const w = (n instanceof Element && (n.classList?.contains(WRAP_CLASS) ? n : n.closest?.(WRAP_SEL))) || null;
|
|
1025
|
-
if (w) markWrapActivity(w);
|
|
1026
|
-
else if (n instanceof Element) { const inner = n.querySelector?.(WRAP_SEL); if (inner) markWrapActivity(inner); }
|
|
1027
|
-
} catch (_) {}
|
|
1028
|
-
// matches() d'abord (O(1)), querySelector() seulement si nécessaire
|
|
1029
|
-
if (nodeMatchesAny(n, CONTENT_SEL_LIST) || nodeContainsAny(n, CONTENT_SEL_LIST)) {
|
|
1030
|
-
requestBurst(); return;
|
|
658
|
+
for (const n of m.removedNodes) {
|
|
659
|
+
if (!(n instanceof Element)) continue;
|
|
660
|
+
if (n.classList?.contains(WRAP_CLASS) || n.querySelector?.(`.${WRAP_CLASS}`)) {
|
|
661
|
+
shouldRun = true;
|
|
1031
662
|
}
|
|
1032
663
|
}
|
|
1033
664
|
}
|
|
665
|
+
if (shouldRun) {
|
|
666
|
+
scheduleRun();
|
|
667
|
+
scheduleMaintenance();
|
|
668
|
+
}
|
|
1034
669
|
});
|
|
1035
670
|
try { S.domObs.observe(document.body, { childList: true, subtree: true }); } catch (_) {}
|
|
1036
671
|
}
|
|
1037
672
|
|
|
1038
|
-
// ── Utilitaires ────────────────────────────────────────────────────────────
|
|
1039
|
-
|
|
1040
673
|
function ensureTcfLocator() {
|
|
1041
674
|
try {
|
|
1042
675
|
if (!window.__tcfapi && !window.__cmp) return;
|
|
1043
676
|
const inject = () => {
|
|
1044
677
|
if (document.getElementById('__tcfapiLocator')) return;
|
|
1045
678
|
const f = document.createElement('iframe');
|
|
1046
|
-
f.style.display = 'none';
|
|
679
|
+
f.style.display = 'none';
|
|
680
|
+
f.id = f.name = '__tcfapiLocator';
|
|
1047
681
|
(document.body || document.documentElement).appendChild(f);
|
|
1048
682
|
};
|
|
1049
683
|
inject();
|
|
1050
|
-
if (!window.
|
|
1051
|
-
window.
|
|
1052
|
-
window.
|
|
684
|
+
if (!window.__nbbTcfObsV22) {
|
|
685
|
+
window.__nbbTcfObsV22 = new MutationObserver(inject);
|
|
686
|
+
window.__nbbTcfObsV22.observe(document.documentElement, { childList: true, subtree: true });
|
|
1053
687
|
}
|
|
1054
688
|
} catch (_) {}
|
|
1055
689
|
}
|
|
1056
690
|
|
|
1057
|
-
const
|
|
691
|
+
const warmed = new Set();
|
|
1058
692
|
function warmNetwork() {
|
|
1059
693
|
const head = document.head;
|
|
1060
694
|
if (!head) return;
|
|
1061
695
|
const frag = document.createDocumentFragment();
|
|
1062
|
-
|
|
1063
|
-
['preconnect',
|
|
1064
|
-
['preconnect',
|
|
1065
|
-
['preconnect',
|
|
1066
|
-
['
|
|
1067
|
-
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
696
|
+
const defs = [
|
|
697
|
+
['preconnect', 'https://g.ezoic.net', true],
|
|
698
|
+
['preconnect', 'https://go.ezoic.net', true],
|
|
699
|
+
['preconnect', 'https://securepubads.g.doubleclick.net', true],
|
|
700
|
+
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
1068
701
|
['dns-prefetch', 'https://securepubads.g.doubleclick.net', false],
|
|
1069
|
-
]
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
702
|
+
];
|
|
703
|
+
for (const [rel, href, cors] of defs) {
|
|
704
|
+
const key = `${rel}|${href}`;
|
|
705
|
+
if (warmed.has(key)) continue;
|
|
706
|
+
warmed.add(key);
|
|
1073
707
|
const l = document.createElement('link');
|
|
1074
708
|
l.rel = rel; l.href = href;
|
|
1075
709
|
if (cors) l.crossOrigin = 'anonymous';
|
|
@@ -1078,33 +712,93 @@ function startShowBatch(ids) {
|
|
|
1078
712
|
head.appendChild(frag);
|
|
1079
713
|
}
|
|
1080
714
|
|
|
1081
|
-
|
|
715
|
+
async function runCore() {
|
|
716
|
+
if (isBlocked()) return 0;
|
|
717
|
+
patchShowAds();
|
|
718
|
+
const cfg = await fetchConfig();
|
|
719
|
+
if (!cfg || cfg.excluded) return 0;
|
|
720
|
+
initPools(cfg);
|
|
721
|
+
|
|
722
|
+
const kind = getKind();
|
|
723
|
+
if (kind === 'other') return 0;
|
|
724
|
+
|
|
725
|
+
if (kind === 'topic') {
|
|
726
|
+
if (!normBool(cfg.enableMessageAds)) return 0;
|
|
727
|
+
const interval = Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3);
|
|
728
|
+
return injectFor('ezoic-ad-message', getPosts(), interval, normBool(cfg.showFirstMessageAd));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
if (kind === 'categoryTopics') {
|
|
732
|
+
if (!normBool(cfg.enableBetweenAds)) return 0;
|
|
733
|
+
const interval = Math.max(1, parseInt(cfg.intervalPosts, 10) || 3);
|
|
734
|
+
return injectFor('ezoic-ad-between', getTopics(), interval, normBool(cfg.showFirstTopicAd));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (!normBool(cfg.enableCategoryAds)) return 0;
|
|
738
|
+
const interval = Math.max(1, parseInt(cfg.intervalCategories, 10) || 3);
|
|
739
|
+
return injectFor('ezoic-ad-categories', getCategories(), interval, normBool(cfg.showFirstCategoryAd));
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
function scheduleRun() {
|
|
743
|
+
if (isBlocked()) return;
|
|
744
|
+
if (S.runTimer) return;
|
|
745
|
+
S.runTimer = setTimeout(async () => {
|
|
746
|
+
S.runTimer = 0;
|
|
747
|
+
const pk = pageKey();
|
|
748
|
+
if (S.pageKey && S.pageKey !== pk) return;
|
|
749
|
+
try { await runCore(); } catch (_) {}
|
|
750
|
+
scheduleMaintenance();
|
|
751
|
+
}, CFG.runDebounceMs);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function cleanup() {
|
|
755
|
+
S.blockedUntil = now() + 1500;
|
|
756
|
+
if (S.runTimer) { clearTimeout(S.runTimer); S.runTimer = 0; }
|
|
757
|
+
if (S.showTimer) { clearTimeout(S.showTimer); S.showTimer = 0; }
|
|
758
|
+
if (S.maintenanceTimer) { clearTimeout(S.maintenanceTimer); S.maintenanceTimer = 0; }
|
|
759
|
+
mutate(() => {
|
|
760
|
+
for (const w of document.querySelectorAll(`.${WRAP_CLASS}`)) releaseWrap(w);
|
|
761
|
+
});
|
|
762
|
+
S.cfg = null;
|
|
763
|
+
S.poolsReady = false;
|
|
764
|
+
S.pools = { topics: [], posts: [], categories: [] };
|
|
765
|
+
S.cursors = { topics: 0, posts: 0, categories: 0 };
|
|
766
|
+
S.mountedIds.clear();
|
|
767
|
+
S.wrapByKey.clear();
|
|
768
|
+
S.regById.clear();
|
|
769
|
+
S.pendingShow = [];
|
|
770
|
+
S.pendingSet.clear();
|
|
771
|
+
S.lastShowById.clear();
|
|
772
|
+
S.inflightShow = 0;
|
|
773
|
+
}
|
|
1082
774
|
|
|
1083
775
|
function bindNodeBB() {
|
|
1084
776
|
const $ = window.jQuery;
|
|
1085
777
|
if (!$) return;
|
|
1086
|
-
|
|
1087
|
-
$(window).
|
|
1088
|
-
$(window).on('action:ajaxify.
|
|
1089
|
-
|
|
1090
|
-
S.
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
patchShowAds();
|
|
778
|
+
$(window).off('.nbbEzoicV22');
|
|
779
|
+
$(window).on('action:ajaxify.start.nbbEzoicV22', cleanup);
|
|
780
|
+
$(window).on('action:ajaxify.end.nbbEzoicV22', () => {
|
|
781
|
+
S.pageKey = pageKey();
|
|
782
|
+
S.blockedUntil = 0;
|
|
783
|
+
ensureTcfLocator();
|
|
784
|
+
warmNetwork();
|
|
785
|
+
patchShowAds();
|
|
786
|
+
getIO();
|
|
787
|
+
ensureObserver();
|
|
788
|
+
scheduleRun();
|
|
1094
789
|
});
|
|
1095
|
-
|
|
1096
|
-
const burstEvts = [
|
|
790
|
+
const events = [
|
|
1097
791
|
'action:ajaxify.contentLoaded', 'action:posts.loaded', 'action:topics.loaded',
|
|
1098
792
|
'action:categories.loaded', 'action:category.loaded', 'action:topic.loaded',
|
|
1099
|
-
]
|
|
1100
|
-
|
|
1101
|
-
|
|
793
|
+
];
|
|
794
|
+
for (const ev of events) {
|
|
795
|
+
$(window).on(`${ev}.nbbEzoicV22`, () => { if (!isBlocked()) scheduleRun(); });
|
|
796
|
+
}
|
|
1102
797
|
try {
|
|
1103
798
|
require(['hooks'], hooks => {
|
|
1104
799
|
if (typeof hooks?.on !== 'function') return;
|
|
1105
|
-
for (const ev of ['action:ajaxify.end', 'action:posts.loaded', 'action:topics.loaded',
|
|
1106
|
-
|
|
1107
|
-
try { hooks.on(ev, () => { if (!isBlocked()) requestBurst(); }); } catch (_) {}
|
|
800
|
+
for (const ev of ['action:ajaxify.end', 'action:posts.loaded', 'action:topics.loaded', 'action:categories.loaded', 'action:topic.loaded']) {
|
|
801
|
+
try { hooks.on(ev, () => { if (!isBlocked()) scheduleRun(); }); } catch (_) {}
|
|
1108
802
|
}
|
|
1109
803
|
});
|
|
1110
804
|
} catch (_) {}
|
|
@@ -1112,39 +806,27 @@ function startShowBatch(ids) {
|
|
|
1112
806
|
|
|
1113
807
|
function bindScroll() {
|
|
1114
808
|
let ticking = false;
|
|
1115
|
-
try {
|
|
1116
|
-
S.lastScrollY = window.scrollY || window.pageYOffset || 0;
|
|
1117
|
-
S.lastScrollTs = ts();
|
|
1118
|
-
} catch (_) {}
|
|
1119
809
|
window.addEventListener('scroll', () => {
|
|
1120
|
-
try {
|
|
1121
|
-
const y = window.scrollY || window.pageYOffset || 0;
|
|
1122
|
-
const t = ts();
|
|
1123
|
-
const dy = y - (S.lastScrollY || 0);
|
|
1124
|
-
const dt = Math.max(1, t - (S.lastScrollTs || t));
|
|
1125
|
-
if (Math.abs(dy) > 1) S.scrollDir = dy >= 0 ? 1 : -1;
|
|
1126
|
-
const inst = Math.abs(dy) * 1000 / dt;
|
|
1127
|
-
S.scrollSpeed = S.scrollSpeed ? (S.scrollSpeed * 0.7 + inst * 0.3) : inst;
|
|
1128
|
-
S.lastScrollY = y;
|
|
1129
|
-
S.lastScrollTs = t;
|
|
1130
|
-
} catch (_) {}
|
|
1131
810
|
if (ticking) return;
|
|
1132
811
|
ticking = true;
|
|
1133
|
-
requestAnimationFrame(() => {
|
|
812
|
+
requestAnimationFrame(() => {
|
|
813
|
+
ticking = false;
|
|
814
|
+
if (!isBlocked()) {
|
|
815
|
+
scheduleMaintenance();
|
|
816
|
+
scheduleRun();
|
|
817
|
+
}
|
|
818
|
+
});
|
|
1134
819
|
}, { passive: true });
|
|
1135
820
|
}
|
|
1136
821
|
|
|
1137
|
-
// ── Boot ───────────────────────────────────────────────────────────────────
|
|
1138
|
-
|
|
1139
822
|
S.pageKey = pageKey();
|
|
1140
823
|
ensureTcfLocator();
|
|
1141
824
|
warmNetwork();
|
|
1142
825
|
patchShowAds();
|
|
1143
826
|
getIO();
|
|
1144
|
-
|
|
827
|
+
ensureObserver();
|
|
1145
828
|
bindNodeBB();
|
|
1146
829
|
bindScroll();
|
|
1147
|
-
blockedUntil = 0;
|
|
1148
|
-
|
|
1149
|
-
|
|
830
|
+
S.blockedUntil = 0;
|
|
831
|
+
scheduleRun();
|
|
1150
832
|
})();
|