nodebb-plugin-ezoic-infinite 1.1.6 → 1.2.3
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 +229 -355
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -2,93 +2,77 @@
|
|
|
2
2
|
(function () {
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
-
//
|
|
6
|
-
const
|
|
5
|
+
// Optional debug switch
|
|
6
|
+
const DEBUG = !!window.__ezoicInfiniteDebug;
|
|
7
|
+
|
|
8
|
+
const $ = (typeof window.jQuery === 'function') ? window.jQuery : null;
|
|
7
9
|
|
|
8
10
|
const SELECTORS = {
|
|
9
|
-
|
|
11
|
+
topicItem: 'li[component="category/topic"]',
|
|
10
12
|
postItem: '[component="post"][data-pid]',
|
|
11
|
-
postContent: '[component="post/content"]',
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
const WRAP_CLASS = 'ezoic-ad';
|
|
15
16
|
const PLACEHOLDER_PREFIX = 'ezoic-pub-ad-placeholder-';
|
|
16
17
|
|
|
18
|
+
// Prevent “burst” injection: at most N inserts per run per kind
|
|
19
|
+
const MAX_INSERTS_PER_RUN = 2;
|
|
20
|
+
|
|
17
21
|
const state = {
|
|
18
22
|
pageKey: null,
|
|
19
23
|
cfg: null,
|
|
20
24
|
cfgPromise: null,
|
|
21
|
-
// pools and used ids
|
|
22
|
-
usedBetween: new Set(),
|
|
23
|
-
usedMessage: new Set(),
|
|
24
|
-
fifoBetween: [],
|
|
25
|
-
fifoMessage: [],
|
|
26
|
-
// showAds anti-double
|
|
27
|
-
lastShowById: {},
|
|
28
|
-
pendingById: {},
|
|
29
|
-
// retry bookkeeping
|
|
30
|
-
retryCount: {},
|
|
31
|
-
observers: {},
|
|
32
|
-
};
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const ez = window.ezstandalone;
|
|
38
|
-
if (ez.__nodebbEzoicPatched) return;
|
|
39
|
-
if (typeof ez.showAds !== 'function') return;
|
|
26
|
+
// Per-page pools (refilled by recycling)
|
|
27
|
+
poolTopics: [],
|
|
28
|
+
poolPosts: [],
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
// Track inserted ads per page
|
|
31
|
+
usedTopics: new Set(),
|
|
32
|
+
usedPosts: new Set(),
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// Split into individual calls to avoid Ezoic loadMore/placeholder issues.
|
|
48
|
-
try {
|
|
49
|
-
if (window.__ezoicInfiniteDebug) {
|
|
50
|
-
console.warn('[ezoic-infinite] showAds(batch) detected. Source stack:', new Error().stack);
|
|
51
|
-
}
|
|
52
|
-
} catch (e) {}
|
|
34
|
+
// Track which anchors we already evaluated to avoid reprocessing everything on each event
|
|
35
|
+
seenTopicAnchors: new WeakSet(),
|
|
36
|
+
seenPostAnchors: new WeakSet(),
|
|
53
37
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
38
|
+
// showAds anti-double
|
|
39
|
+
lastShowById: new Map(),
|
|
40
|
+
pendingById: new Set(),
|
|
41
|
+
|
|
42
|
+
// debounce
|
|
43
|
+
scheduled: false,
|
|
44
|
+
timer: null,
|
|
45
|
+
|
|
46
|
+
// observers
|
|
47
|
+
obs: null,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function log(...args) { if (DEBUG) console.log('[ezoic-infinite]', ...args); }
|
|
51
|
+
function warn(...args) { if (DEBUG) console.warn('[ezoic-infinite]', ...args); }
|
|
67
52
|
|
|
68
53
|
function normalizeBool(v) {
|
|
69
54
|
return v === true || v === 'true' || v === 1 || v === '1' || v === 'on';
|
|
70
55
|
}
|
|
71
56
|
|
|
72
|
-
function
|
|
73
|
-
if (!raw) return [];
|
|
74
|
-
const lines = String(raw)
|
|
75
|
-
.split(/\r?\n/)
|
|
76
|
-
.map(s => s.trim())
|
|
77
|
-
.filter(Boolean);
|
|
78
|
-
|
|
79
|
-
const ids = lines
|
|
80
|
-
.map(s => parseInt(s, 10))
|
|
81
|
-
.filter(n => Number.isFinite(n) && n > 0);
|
|
82
|
-
|
|
83
|
-
// unique preserve order
|
|
57
|
+
function uniqInts(lines) {
|
|
84
58
|
const out = [];
|
|
85
59
|
const seen = new Set();
|
|
86
|
-
for (const
|
|
87
|
-
|
|
60
|
+
for (const v of lines) {
|
|
61
|
+
const n = parseInt(v, 10);
|
|
62
|
+
if (Number.isFinite(n) && n > 0 && !seen.has(n)) {
|
|
63
|
+
seen.add(n);
|
|
64
|
+
out.push(n);
|
|
65
|
+
}
|
|
88
66
|
}
|
|
89
67
|
return out;
|
|
90
68
|
}
|
|
91
69
|
|
|
70
|
+
function parsePool(raw) {
|
|
71
|
+
if (!raw) return [];
|
|
72
|
+
const lines = String(raw).split(/\r?\n/).map(s => s.trim()).filter(Boolean);
|
|
73
|
+
return uniqInts(lines);
|
|
74
|
+
}
|
|
75
|
+
|
|
92
76
|
function getPageKey() {
|
|
93
77
|
try {
|
|
94
78
|
const ax = window.ajaxify;
|
|
@@ -100,65 +84,83 @@
|
|
|
100
84
|
return window.location.pathname;
|
|
101
85
|
}
|
|
102
86
|
|
|
103
|
-
function
|
|
87
|
+
function getKind() {
|
|
104
88
|
const p = window.location.pathname || '';
|
|
105
89
|
if (/^\/topic\//.test(p)) return 'topic';
|
|
106
90
|
if (/^\/category\//.test(p)) return 'category';
|
|
107
|
-
// fallback
|
|
91
|
+
// fallback
|
|
108
92
|
if (document.querySelector(SELECTORS.postItem)) return 'topic';
|
|
109
|
-
if (document.querySelector(SELECTORS.topicListItem)) return 'category';
|
|
110
93
|
return 'category';
|
|
111
94
|
}
|
|
112
95
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
function hasAdImmediatelyAfter(targetEl) {
|
|
118
|
-
if (!targetEl) return false;
|
|
119
|
-
const next = targetEl.nextElementSibling;
|
|
120
|
-
return !!(next && next.classList && next.classList.contains(WRAP_CLASS));
|
|
96
|
+
function hasAdImmediatelyAfter(el) {
|
|
97
|
+
const n = el && el.nextElementSibling;
|
|
98
|
+
return !!(n && n.classList && n.classList.contains(WRAP_CLASS));
|
|
121
99
|
}
|
|
122
100
|
|
|
123
101
|
function buildWrap(id, kind, afterPos) {
|
|
124
102
|
const wrap = document.createElement('div');
|
|
125
103
|
wrap.className = `${WRAP_CLASS} ${kind}`;
|
|
126
104
|
wrap.setAttribute('data-ezoic-after', String(afterPos));
|
|
127
|
-
wrap.setAttribute('data-ezoic-kind', kind);
|
|
128
105
|
wrap.style.width = '100%';
|
|
129
|
-
|
|
130
106
|
const ph = document.createElement('div');
|
|
131
107
|
ph.id = `${PLACEHOLDER_PREFIX}${id}`;
|
|
132
108
|
wrap.appendChild(ph);
|
|
133
|
-
|
|
134
109
|
return wrap;
|
|
135
110
|
}
|
|
136
111
|
|
|
137
|
-
function insertAfter(
|
|
138
|
-
if (!
|
|
112
|
+
function insertAfter(target, id, kind, afterPos) {
|
|
113
|
+
if (!target || !target.insertAdjacentElement) return null;
|
|
139
114
|
const wrap = buildWrap(id, kind, afterPos);
|
|
140
|
-
|
|
115
|
+
target.insertAdjacentElement('afterend', wrap);
|
|
141
116
|
return wrap;
|
|
142
117
|
}
|
|
143
118
|
|
|
144
|
-
function
|
|
119
|
+
function safeRect(el) {
|
|
120
|
+
try { return el.getBoundingClientRect(); } catch (e) { return null; }
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Patch ezstandalone.showAds to split batch calls (we NEVER call batch from this plugin)
|
|
126
|
+
function patchShowAds() {
|
|
145
127
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
128
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
129
|
+
const ez = window.ezstandalone;
|
|
130
|
+
if (ez.__nodebbEzoicPatched) return;
|
|
131
|
+
if (typeof ez.showAds !== 'function') return;
|
|
132
|
+
|
|
133
|
+
ez.__nodebbEzoicPatched = true;
|
|
134
|
+
const orig = ez.showAds;
|
|
135
|
+
|
|
136
|
+
ez.showAds = function patchedShowAds(arg) {
|
|
137
|
+
if (Array.isArray(arg)) {
|
|
138
|
+
try { warn('showAds(batch) detected. Splitting…'); } catch (e) {}
|
|
139
|
+
const ids = uniqInts(arg);
|
|
140
|
+
for (const id of ids) {
|
|
141
|
+
try { orig.call(ez, id); } catch (e) {}
|
|
142
|
+
}
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
return orig.apply(ez, arguments);
|
|
146
|
+
};
|
|
149
147
|
} catch (e) {}
|
|
150
148
|
}
|
|
151
149
|
|
|
152
|
-
function
|
|
150
|
+
function callShowAdsWhenReady(id) {
|
|
153
151
|
if (!id) return;
|
|
154
|
-
|
|
155
|
-
const
|
|
152
|
+
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
const last = state.lastShowById.get(id) || 0;
|
|
155
|
+
if (now - last < 3500) return;
|
|
156
|
+
|
|
157
|
+
const phId = `${PLACEHOLDER_PREFIX}${id}`;
|
|
156
158
|
|
|
157
159
|
const doCall = () => {
|
|
158
160
|
try {
|
|
159
161
|
window.ezstandalone = window.ezstandalone || {};
|
|
160
162
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
161
|
-
state.lastShowById
|
|
163
|
+
state.lastShowById.set(id, Date.now());
|
|
162
164
|
window.ezstandalone.showAds(id);
|
|
163
165
|
return true;
|
|
164
166
|
}
|
|
@@ -166,122 +168,52 @@
|
|
|
166
168
|
return false;
|
|
167
169
|
};
|
|
168
170
|
|
|
169
|
-
// Wait until the placeholder exists in the DOM (critical on ajaxify navigation)
|
|
170
171
|
let attempts = 0;
|
|
171
|
-
(function
|
|
172
|
+
(function waitForPh() {
|
|
172
173
|
attempts += 1;
|
|
173
|
-
|
|
174
|
-
const el = document.getElementById(placeholderId);
|
|
174
|
+
const el = document.getElementById(phId);
|
|
175
175
|
if (el && el.isConnected) {
|
|
176
|
-
const now = Date.now();
|
|
177
|
-
const last = state.lastShowById[key] || 0;
|
|
178
|
-
if (now - last < 4000) return;
|
|
179
|
-
|
|
180
176
|
if (doCall()) return;
|
|
181
177
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
state.pendingById[key] = true;
|
|
178
|
+
if (state.pendingById.has(id)) return;
|
|
179
|
+
state.pendingById.add(id);
|
|
185
180
|
|
|
186
181
|
window.ezstandalone = window.ezstandalone || {};
|
|
187
182
|
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
188
183
|
window.ezstandalone.cmd.push(() => {
|
|
189
184
|
try {
|
|
190
185
|
if (typeof window.ezstandalone.showAds === 'function') {
|
|
191
|
-
|
|
192
|
-
state.lastShowById
|
|
186
|
+
state.pendingById.delete(id);
|
|
187
|
+
state.lastShowById.set(id, Date.now());
|
|
193
188
|
window.ezstandalone.showAds(id);
|
|
194
189
|
}
|
|
195
190
|
} catch (e) {}
|
|
196
191
|
});
|
|
197
192
|
|
|
198
|
-
//
|
|
193
|
+
// short retries
|
|
199
194
|
let tries = 0;
|
|
200
195
|
(function tick() {
|
|
201
196
|
tries += 1;
|
|
202
|
-
if (doCall() || tries >=
|
|
203
|
-
if (tries >=
|
|
197
|
+
if (doCall() || tries >= 5) {
|
|
198
|
+
if (tries >= 5) state.pendingById.delete(id);
|
|
204
199
|
return;
|
|
205
200
|
}
|
|
206
|
-
setTimeout(tick,
|
|
201
|
+
setTimeout(tick, 700);
|
|
207
202
|
})();
|
|
208
|
-
|
|
209
203
|
return;
|
|
210
204
|
}
|
|
211
205
|
|
|
212
|
-
if (attempts < 40)
|
|
213
|
-
setTimeout(waitForDom, 50);
|
|
214
|
-
}
|
|
206
|
+
if (attempts < 40) setTimeout(waitForPh, 50);
|
|
215
207
|
})();
|
|
216
208
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
function recycleIfNeeded(pool, usedSet, fifo, selectorFn) {
|
|
220
|
-
// only recycle ads far above the viewport, so they don't "disappear" while reading
|
|
221
|
-
const margin = 1200;
|
|
222
|
-
const vpTop = -margin;
|
|
223
|
-
|
|
224
|
-
fifo.sort((a, b) => a.after - b.after);
|
|
225
|
-
|
|
226
|
-
for (let i = 0; i < fifo.length; i++) {
|
|
227
|
-
const old = fifo[i];
|
|
228
|
-
const el = document.querySelector(selectorFn(old));
|
|
229
|
-
if (!el) {
|
|
230
|
-
fifo.splice(i, 1); i--;
|
|
231
|
-
continue;
|
|
232
|
-
}
|
|
233
|
-
const r = safeGetRect(el);
|
|
234
|
-
if (r && r.bottom < vpTop) {
|
|
235
|
-
fifo.splice(i, 1);
|
|
236
|
-
el.remove();
|
|
237
|
-
usedSet.delete(old.id);
|
|
238
|
-
destroyPlaceholder(old.id);
|
|
239
|
-
pool.push(old.id);
|
|
240
|
-
return true;
|
|
241
|
-
}
|
|
242
209
|
}
|
|
243
210
|
return false;
|
|
244
211
|
}
|
|
245
212
|
|
|
246
|
-
function nextId(
|
|
213
|
+
function nextId(kind) {
|
|
214
|
+
const pool = (kind === 'between') ? state.poolTopics : state.poolPosts;
|
|
247
215
|
if (pool.length) return pool.shift();
|
|
248
|
-
//
|
|
249
|
-
const recycled = recycleIfNeeded(pool, usedSet, fifo, selectorFn);
|
|
250
|
-
if (recycled && pool.length) return pool.shift();
|
|
251
|
-
return null;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function getTopicItems() {
|
|
255
|
-
return Array.from(document.querySelectorAll(SELECTORS.topicListItem));
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function getPostItems() {
|
|
259
|
-
// NodeBB harmony: component=post with data-pid is stable
|
|
260
|
-
return Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function cleanupForNewPage() {
|
|
264
|
-
state.pageKey = getPageKey();
|
|
265
|
-
state.cfg = null;
|
|
266
|
-
state.cfgPromise = null;
|
|
267
|
-
|
|
268
|
-
state.usedBetween.clear();
|
|
269
|
-
state.usedMessage.clear();
|
|
270
|
-
state.fifoBetween = [];
|
|
271
|
-
state.fifoMessage = [];
|
|
272
|
-
|
|
273
|
-
state.lastShowById = {};
|
|
274
|
-
state.pendingById = {};
|
|
275
|
-
|
|
276
|
-
// Remove injected wrappers
|
|
277
|
-
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
278
|
-
|
|
279
|
-
// Disconnect observers
|
|
280
|
-
Object.values(state.observers).forEach(obs => { try { obs.disconnect(); } catch (e) {} });
|
|
281
|
-
state.observers = {};
|
|
282
|
-
|
|
283
|
-
state.retryCount = {};
|
|
284
|
-
|
|
216
|
+
return null; // stop injecting when pool is empty
|
|
285
217
|
}
|
|
286
218
|
|
|
287
219
|
async function fetchConfig() {
|
|
@@ -292,9 +224,8 @@
|
|
|
292
224
|
try {
|
|
293
225
|
const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
|
|
294
226
|
if (!res.ok) return null;
|
|
295
|
-
|
|
296
|
-
state.cfg
|
|
297
|
-
return cfg;
|
|
227
|
+
state.cfg = await res.json();
|
|
228
|
+
return state.cfg;
|
|
298
229
|
} catch (e) {
|
|
299
230
|
return null;
|
|
300
231
|
} finally {
|
|
@@ -305,259 +236,202 @@
|
|
|
305
236
|
return state.cfgPromise;
|
|
306
237
|
}
|
|
307
238
|
|
|
308
|
-
function
|
|
309
|
-
|
|
310
|
-
if (state.
|
|
311
|
-
|
|
312
|
-
const hasTargets = () => {
|
|
313
|
-
if (kind === 'topic') return document.querySelectorAll(SELECTORS.postItem).length > 0;
|
|
314
|
-
return document.querySelectorAll(SELECTORS.topicListItem).length > 0;
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
if (hasTargets()) {
|
|
318
|
-
cb();
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const obs = new MutationObserver(() => {
|
|
323
|
-
if (hasTargets()) {
|
|
324
|
-
try { obs.disconnect(); } catch (e) {}
|
|
325
|
-
delete state.observers[key];
|
|
326
|
-
cb();
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
state.observers[key] = obs;
|
|
331
|
-
try {
|
|
332
|
-
obs.observe(document.body, { childList: true, subtree: true });
|
|
333
|
-
} catch (e) {}
|
|
334
|
-
|
|
335
|
-
setTimeout(() => {
|
|
336
|
-
try { obs.disconnect(); } catch (e) {}
|
|
337
|
-
delete state.observers[key];
|
|
338
|
-
}, 15000);
|
|
239
|
+
function initPools(cfg) {
|
|
240
|
+
// Re-init pools once per page
|
|
241
|
+
if (state.poolTopics.length === 0) state.poolTopics = parsePool(cfg.placeholderIds);
|
|
242
|
+
if (state.poolPosts.length === 0) state.poolPosts = parsePool(cfg.messagePlaceholderIds);
|
|
339
243
|
}
|
|
340
244
|
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
state.retryCount[key] = state.retryCount[key] || 0;
|
|
344
|
-
if (state.retryCount[key] >= 24) return;
|
|
345
|
-
state.retryCount[key] += 1;
|
|
346
|
-
setTimeout(run, 250);
|
|
347
|
-
}
|
|
348
|
-
} catch (e) {}
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
state.predeclared[kind].add(key);
|
|
352
|
-
|
|
353
|
-
if (window.ezstandalone && typeof window.ezstandalone.showAds === 'function') {
|
|
354
|
-
call();
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
359
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
360
|
-
window.ezstandalone.cmd.push(call);
|
|
361
|
-
} catch (e) {}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
function injectBetweenTopics(cfg) {
|
|
365
|
-
if (!normalizeBool(cfg.enableBetweenAds)) return;
|
|
245
|
+
function injectTopics(cfg) {
|
|
246
|
+
if (!normalizeBool(cfg.enableBetweenAds)) return 0;
|
|
366
247
|
|
|
367
248
|
const interval = Math.max(1, parseInt(cfg.intervalPosts, 10) || 6);
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
const pool = parsePool(cfg.placeholderIds);
|
|
371
|
-
if (!pool.length) return;
|
|
249
|
+
const first = normalizeBool(cfg.showFirstTopicAd);
|
|
372
250
|
|
|
373
|
-
const items =
|
|
374
|
-
if (!items.length) return;
|
|
375
|
-
|
|
376
|
-
for (let idx = 0; idx < items.length; idx++) {
|
|
377
|
-
const li = items[idx];
|
|
378
|
-
const pos = idx + 1;
|
|
251
|
+
const items = Array.from(document.querySelectorAll(SELECTORS.topicItem));
|
|
252
|
+
if (!items.length) return 0;
|
|
379
253
|
|
|
254
|
+
let inserted = 0;
|
|
255
|
+
for (let i = 0; i < items.length; i++) {
|
|
256
|
+
const li = items[i];
|
|
380
257
|
if (!li || !li.isConnected) continue;
|
|
381
258
|
|
|
382
|
-
//
|
|
383
|
-
|
|
259
|
+
// Avoid re-processing anchors already evaluated
|
|
260
|
+
if (state.seenTopicAnchors.has(li)) continue;
|
|
261
|
+
|
|
262
|
+
const pos = i + 1;
|
|
263
|
+
const ok = (first && pos === 1) || (pos % interval === 0);
|
|
264
|
+
state.seenTopicAnchors.add(li);
|
|
384
265
|
if (!ok) continue;
|
|
385
266
|
|
|
386
267
|
if (hasAdImmediatelyAfter(li)) continue;
|
|
387
268
|
|
|
388
|
-
const id = nextId(
|
|
269
|
+
const id = nextId('between');
|
|
389
270
|
if (!id) break;
|
|
390
271
|
|
|
391
|
-
state.
|
|
272
|
+
state.usedTopics.add(id);
|
|
392
273
|
const wrap = insertAfter(li, id, 'ezoic-ad-between', pos);
|
|
393
274
|
if (!wrap) continue;
|
|
394
275
|
|
|
395
|
-
|
|
276
|
+
inserted += 1;
|
|
277
|
+
|
|
278
|
+
callShowAdsWhenReady(id);
|
|
396
279
|
|
|
397
|
-
|
|
280
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
398
281
|
}
|
|
282
|
+
return inserted;
|
|
399
283
|
}
|
|
400
284
|
|
|
401
|
-
function
|
|
402
|
-
if (!normalizeBool(cfg.enableMessageAds)) return;
|
|
285
|
+
function injectPosts(cfg) {
|
|
286
|
+
if (!normalizeBool(cfg.enableMessageAds)) return 0;
|
|
403
287
|
|
|
404
288
|
const interval = Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3);
|
|
405
|
-
const
|
|
289
|
+
const first = normalizeBool(cfg.showFirstMessageAd);
|
|
406
290
|
|
|
407
|
-
const
|
|
408
|
-
if (!
|
|
291
|
+
const posts = Array.from(document.querySelectorAll(SELECTORS.postItem));
|
|
292
|
+
if (!posts.length) return 0;
|
|
409
293
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
for (let idx = 0; idx < posts.length; idx++) {
|
|
414
|
-
const post = posts[idx];
|
|
415
|
-
const no = idx + 1;
|
|
294
|
+
let inserted = 0;
|
|
295
|
+
for (let i = 0; i < posts.length; i++) {
|
|
296
|
+
const post = posts[i];
|
|
416
297
|
if (!post || !post.isConnected) continue;
|
|
417
298
|
|
|
418
|
-
|
|
299
|
+
if (state.seenPostAnchors.has(post)) continue;
|
|
300
|
+
|
|
301
|
+
const no = i + 1;
|
|
302
|
+
const ok = (first && no === 1) || (no % interval === 0);
|
|
303
|
+
state.seenPostAnchors.add(post);
|
|
419
304
|
if (!ok) continue;
|
|
420
305
|
|
|
421
306
|
if (hasAdImmediatelyAfter(post)) continue;
|
|
422
307
|
|
|
423
|
-
const id = nextId(
|
|
308
|
+
const id = nextId('message');
|
|
424
309
|
if (!id) break;
|
|
425
310
|
|
|
426
|
-
state.
|
|
311
|
+
state.usedPosts.add(id);
|
|
427
312
|
const wrap = insertAfter(post, id, 'ezoic-ad-message', no);
|
|
428
313
|
if (!wrap) continue;
|
|
429
314
|
|
|
430
|
-
|
|
315
|
+
inserted += 1;
|
|
431
316
|
|
|
432
|
-
|
|
317
|
+
callShowAdsWhenReady(id);
|
|
318
|
+
|
|
319
|
+
if (inserted >= MAX_INSERTS_PER_RUN) break;
|
|
433
320
|
}
|
|
321
|
+
return inserted;
|
|
434
322
|
}
|
|
435
323
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if (scheduled) return;
|
|
441
|
-
scheduled = true;
|
|
442
|
-
|
|
443
|
-
const startKey = getPageKey();
|
|
444
|
-
|
|
445
|
-
const settleAndRun = () => {
|
|
446
|
-
// If page changed, reschedule for new page
|
|
447
|
-
if (getPageKey() !== startKey) { scheduled = false; scheduleRun('page-changed'); return; }
|
|
448
|
-
|
|
449
|
-
// Wait until the relevant DOM count is stable for a short window
|
|
450
|
-
const kind = getPageKind();
|
|
451
|
-
const sel = (kind === 'topic') ? SELECTORS.postItem : SELECTORS.topicListItem;
|
|
324
|
+
function cleanup() {
|
|
325
|
+
state.pageKey = getPageKey();
|
|
326
|
+
state.cfg = null;
|
|
327
|
+
state.cfgPromise = null;
|
|
452
328
|
|
|
453
|
-
|
|
454
|
-
|
|
329
|
+
state.poolTopics = [];
|
|
330
|
+
state.poolPosts = [];
|
|
331
|
+
state.usedTopics.clear();
|
|
332
|
+
state.usedPosts.clear();
|
|
455
333
|
|
|
456
|
-
|
|
457
|
-
|
|
334
|
+
state.seenTopicAnchors = new WeakSet();
|
|
335
|
+
state.seenPostAnchors = new WeakSet();
|
|
458
336
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
else { stableTicks = 0; lastCount = c; }
|
|
337
|
+
state.lastShowById = new Map();
|
|
338
|
+
state.pendingById = new Set();
|
|
462
339
|
|
|
463
|
-
|
|
464
|
-
scheduled = false;
|
|
465
|
-
run();
|
|
466
|
-
return;
|
|
467
|
-
}
|
|
468
|
-
requestAnimationFrame(step);
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
requestAnimationFrame(step);
|
|
472
|
-
};
|
|
340
|
+
document.querySelectorAll(`.${WRAP_CLASS}`).forEach(el => el.remove());
|
|
473
341
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
342
|
+
if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; }
|
|
343
|
+
state.scheduled = false;
|
|
344
|
+
clearTimeout(state.timer);
|
|
345
|
+
state.timer = null;
|
|
477
346
|
}
|
|
478
347
|
|
|
479
|
-
function
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
});
|
|
485
|
-
});
|
|
348
|
+
function ensureObserver() {
|
|
349
|
+
if (state.obs) return;
|
|
350
|
+
state.obs = new MutationObserver(() => scheduleRun('dom-mutation'));
|
|
351
|
+
try { state.obs.observe(document.body, { childList: true, subtree: true }); } catch (e) {}
|
|
352
|
+
setTimeout(() => { if (state.obs) { try { state.obs.disconnect(); } catch (e) {} state.obs = null; } }, 12000);
|
|
486
353
|
}
|
|
487
354
|
|
|
488
|
-
async function
|
|
489
|
-
|
|
490
|
-
const cfg = await fetchConfig();
|
|
491
|
-
if (!cfg || cfg.excluded) return;
|
|
355
|
+
async function runCore() {
|
|
356
|
+
patchShowAds();
|
|
492
357
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
window.ezstandalone = window.ezstandalone || {};
|
|
496
|
-
window.ezstandalone.cmd = window.ezstandalone.cmd || [];
|
|
497
|
-
if (typeof window.ezstandalone.showAds !== 'function') {
|
|
498
|
-
const rk = 'ezoicReadyRerun:' + getPageKey();
|
|
499
|
-
if (!state.retryCount[rk]) {
|
|
500
|
-
state.retryCount[rk] = 1;
|
|
501
|
-
window.ezstandalone.cmd.push(() => { setTimeout(run, 0); });
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
} catch (e) {}
|
|
358
|
+
const cfg = await fetchConfig();
|
|
359
|
+
if (!cfg || cfg.excluded) return;
|
|
505
360
|
|
|
506
|
-
|
|
361
|
+
initPools(cfg);
|
|
507
362
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
} catch (e) {
|
|
518
|
-
// Never break NodeBB UI
|
|
363
|
+
const kind = getKind();
|
|
364
|
+
let inserted = 0;
|
|
365
|
+
|
|
366
|
+
if (kind === 'topic') inserted = injectPosts(cfg);
|
|
367
|
+
else inserted = injectTopics(cfg);
|
|
368
|
+
|
|
369
|
+
// If we inserted max per run, schedule another pass to gradually fill (avoids “burst”)
|
|
370
|
+
if (inserted >= MAX_INSERTS_PER_RUN) {
|
|
371
|
+
setTimeout(() => scheduleRun('continue-fill'), 120);
|
|
519
372
|
}
|
|
520
373
|
}
|
|
521
374
|
|
|
522
|
-
function
|
|
523
|
-
if (
|
|
375
|
+
function scheduleRun(reason) {
|
|
376
|
+
if (state.scheduled) return;
|
|
377
|
+
state.scheduled = true;
|
|
378
|
+
|
|
379
|
+
clearTimeout(state.timer);
|
|
380
|
+
state.timer = setTimeout(() => {
|
|
381
|
+
state.scheduled = false;
|
|
382
|
+
// Ensure we're still on same page
|
|
383
|
+
const pk = getPageKey();
|
|
384
|
+
if (state.pageKey && pk !== state.pageKey) return;
|
|
385
|
+
runCore().catch(() => {});
|
|
386
|
+
}, 80);
|
|
387
|
+
|
|
388
|
+
function bind() {
|
|
389
|
+
if (!$) return;
|
|
524
390
|
|
|
525
|
-
|
|
526
|
-
$w.off('.ezoicInfinite');
|
|
391
|
+
$(window).off('.ezoicInfinite');
|
|
527
392
|
|
|
528
|
-
$
|
|
529
|
-
|
|
393
|
+
$(window).on('action:ajaxify.start.ezoicInfinite', () => {
|
|
394
|
+
cleanup();
|
|
530
395
|
});
|
|
531
396
|
|
|
532
|
-
$
|
|
533
|
-
|
|
397
|
+
$(window).on('action:ajaxify.end.ezoicInfinite', () => {
|
|
398
|
+
state.pageKey = getPageKey();
|
|
399
|
+
ensureObserver();
|
|
534
400
|
scheduleRun('ajaxify.end');
|
|
535
401
|
setTimeout(() => scheduleRun('ajaxify.end+250'), 250);
|
|
536
|
-
setTimeout(() => scheduleRun('ajaxify.end+
|
|
402
|
+
setTimeout(() => scheduleRun('ajaxify.end+800'), 800);
|
|
537
403
|
});
|
|
538
404
|
|
|
539
|
-
$
|
|
405
|
+
$(window).on('action:category.loaded.ezoicInfinite', () => {
|
|
406
|
+
ensureObserver();
|
|
540
407
|
scheduleRun('category.loaded');
|
|
541
|
-
setTimeout(() => scheduleRun('category.loaded+
|
|
542
|
-
setTimeout(() => scheduleRun('category.loaded+700'), 700);
|
|
408
|
+
setTimeout(() => scheduleRun('category.loaded+250'), 250);
|
|
543
409
|
});
|
|
544
410
|
|
|
545
|
-
$
|
|
411
|
+
$(window).on('action:topics.loaded.ezoicInfinite', () => {
|
|
412
|
+
ensureObserver();
|
|
413
|
+
scheduleRun('topics.loaded');
|
|
414
|
+
setTimeout(() => scheduleRun('topics.loaded+150'), 150);
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
$(window).on('action:topic.loaded.ezoicInfinite', () => {
|
|
418
|
+
ensureObserver();
|
|
546
419
|
scheduleRun('topic.loaded');
|
|
547
|
-
setTimeout(() => scheduleRun('topic.loaded+
|
|
420
|
+
setTimeout(() => scheduleRun('topic.loaded+200'), 200);
|
|
548
421
|
});
|
|
549
422
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
423
|
+
$(window).on('action:posts.loaded.ezoicInfinite', () => {
|
|
424
|
+
ensureObserver();
|
|
425
|
+
scheduleRun('posts.loaded');
|
|
426
|
+
setTimeout(() => scheduleRun('posts.loaded+150'), 150);
|
|
427
|
+
});
|
|
553
428
|
}
|
|
554
429
|
|
|
555
430
|
// Boot
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
scheduleRun('
|
|
562
|
-
setTimeout(() => scheduleRun('first-load+250'), 250);
|
|
431
|
+
cleanup();
|
|
432
|
+
bind();
|
|
433
|
+
ensureObserver();
|
|
434
|
+
state.pageKey = getPageKey();
|
|
435
|
+
scheduleRun('boot');
|
|
436
|
+
setTimeout(() => scheduleRun('boot+250'), 250);
|
|
563
437
|
})();
|