nodebb-plugin-ezoic-infinite 1.8.94 → 1.8.96
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 +23 -5
- package/package.json +1 -1
- package/public/client.js +38 -40
package/library.js
CHANGED
|
@@ -29,17 +29,25 @@ function parseBool(v, def = false) {
|
|
|
29
29
|
return s === '1' || s === 'true' || s === 'on' || s === 'yes';
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
let _allGroupsCache = null;
|
|
33
|
+
let _allGroupsCacheAt = 0;
|
|
34
|
+
const ALL_GROUPS_TTL = 300_000; // 5 min — admin-only route
|
|
35
|
+
|
|
32
36
|
async function getAllGroups() {
|
|
37
|
+
const t = Date.now();
|
|
38
|
+
if (_allGroupsCache && (t - _allGroupsCacheAt) < ALL_GROUPS_TTL) return _allGroupsCache;
|
|
33
39
|
try {
|
|
34
40
|
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
35
41
|
if (!names?.length) {
|
|
36
42
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
37
43
|
}
|
|
38
|
-
|
|
44
|
+
_allGroupsCache = (await groups.getGroupsData(
|
|
39
45
|
(names || []).filter(name => !groups.isPrivilegeGroup(name))
|
|
40
46
|
))
|
|
41
47
|
.filter(g => g?.name)
|
|
42
48
|
.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
49
|
+
_allGroupsCacheAt = Date.now();
|
|
50
|
+
return _allGroupsCache;
|
|
43
51
|
} catch (err) {
|
|
44
52
|
console.error('[ezoic-infinite] getAllGroups error:', err.message);
|
|
45
53
|
return [];
|
|
@@ -108,10 +116,17 @@ async function isUserExcluded(uid, excludedGroups) {
|
|
|
108
116
|
return value;
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
let _inlineCfgNormal = null;
|
|
120
|
+
let _inlineCfgExcluded = null;
|
|
121
|
+
|
|
111
122
|
function clearCaches() {
|
|
112
123
|
_settingsCache = null;
|
|
113
124
|
_settingsCacheAt = 0;
|
|
114
125
|
_excludeCache.clear();
|
|
126
|
+
_inlineCfgNormal = null;
|
|
127
|
+
_inlineCfgExcluded = null;
|
|
128
|
+
_allGroupsCache = null;
|
|
129
|
+
_allGroupsCacheAt = 0;
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
// ── Client config ────────────────────────────────────────────────────────────
|
|
@@ -176,8 +191,12 @@ plugin.injectEzoicHead = async (data) => {
|
|
|
176
191
|
const uid = data.req?.uid ?? 0;
|
|
177
192
|
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
178
193
|
|
|
194
|
+
if (!_inlineCfgNormal) {
|
|
195
|
+
_inlineCfgNormal = serializeInlineConfig(buildClientConfig(settings, false));
|
|
196
|
+
_inlineCfgExcluded = serializeInlineConfig(buildClientConfig(settings, true));
|
|
197
|
+
}
|
|
198
|
+
|
|
179
199
|
if (excluded) {
|
|
180
|
-
const cfg = buildClientConfig(settings, true);
|
|
181
200
|
const stub = '<script data-cfasync="false">'
|
|
182
201
|
+ 'window._ezaq=window._ezaq||{};'
|
|
183
202
|
+ 'window.ezstandalone=window.ezstandalone||{};'
|
|
@@ -185,14 +204,13 @@ plugin.injectEzoicHead = async (data) => {
|
|
|
185
204
|
+ '</script>';
|
|
186
205
|
data.templateData.customHTML =
|
|
187
206
|
stub + '\n' +
|
|
188
|
-
|
|
207
|
+
_inlineCfgExcluded +
|
|
189
208
|
(data.templateData.customHTML || '');
|
|
190
209
|
} else {
|
|
191
|
-
const cfg = buildClientConfig(settings, false);
|
|
192
210
|
data.templateData.customHTML =
|
|
193
211
|
HEAD_PRECONNECTS + '\n' +
|
|
194
212
|
EZOIC_SCRIPTS + '\n' +
|
|
195
|
-
|
|
213
|
+
_inlineCfgNormal +
|
|
196
214
|
(data.templateData.customHTML || '');
|
|
197
215
|
}
|
|
198
216
|
} catch (err) {
|
package/package.json
CHANGED
package/public/client.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* NodeBB Ezoic Infinite Ads — client.js v2.5.
|
|
2
|
+
* NodeBB Ezoic Infinite Ads — client.js v2.5.2
|
|
3
3
|
*
|
|
4
4
|
* Architecture: proven v50 core + targeted improvements.
|
|
5
5
|
* Ezoic API: showAds() + destroyPlaceholders() per official docs.
|
|
6
6
|
* Key features: O(1) recycle via wrapsByClass, MutationObserver fill detect,
|
|
7
7
|
* conservative empty check, aria-hidden + TCF protection, retry boot for
|
|
8
8
|
* Cloudflare/async timing.
|
|
9
|
+
* v2.5.2: removed redundant warmNetwork() — preconnects are server-injected.
|
|
9
10
|
*/
|
|
10
11
|
(function nbbEzoicInfinite() {
|
|
11
12
|
'use strict';
|
|
@@ -80,6 +81,7 @@
|
|
|
80
81
|
pageKey: null,
|
|
81
82
|
kind: null,
|
|
82
83
|
cfg: null,
|
|
84
|
+
opts: null,
|
|
83
85
|
poolsReady: false,
|
|
84
86
|
pools: { topics: [], posts: [], categories: [] },
|
|
85
87
|
cursors: { topics: 0, posts: 0, categories: 0 },
|
|
@@ -131,6 +133,17 @@
|
|
|
131
133
|
S.pools.topics = parseIds(cfg.placeholderIds);
|
|
132
134
|
S.pools.posts = parseIds(cfg.messagePlaceholderIds);
|
|
133
135
|
S.pools.categories = parseIds(cfg.categoryPlaceholderIds);
|
|
136
|
+
S.opts = {
|
|
137
|
+
enableBetweenAds: normBool(cfg.enableBetweenAds),
|
|
138
|
+
showFirstTopicAd: normBool(cfg.showFirstTopicAd),
|
|
139
|
+
intervalTopics: Math.max(1, parseInt(cfg.intervalPosts, 10) || 3),
|
|
140
|
+
enableCategoryAds: normBool(cfg.enableCategoryAds),
|
|
141
|
+
showFirstCategoryAd: normBool(cfg.showFirstCategoryAd),
|
|
142
|
+
intervalCategories: Math.max(1, parseInt(cfg.intervalCategories, 10) || 3),
|
|
143
|
+
enableMessageAds: normBool(cfg.enableMessageAds),
|
|
144
|
+
showFirstMessageAd: normBool(cfg.showFirstMessageAd),
|
|
145
|
+
messageInterval: Math.max(1, parseInt(cfg.messageIntervalPosts, 10) || 3),
|
|
146
|
+
};
|
|
134
147
|
S.poolsReady = true;
|
|
135
148
|
}
|
|
136
149
|
|
|
@@ -168,6 +181,7 @@
|
|
|
168
181
|
for (let i = 0; i < all.length; i++) {
|
|
169
182
|
const el = all[i];
|
|
170
183
|
if (!el.isConnected) continue;
|
|
184
|
+
if (el.childElementCount === 0) continue;
|
|
171
185
|
if (!el.querySelector('[component="post/content"]')) continue;
|
|
172
186
|
const parent = el.parentElement?.closest(SEL.post);
|
|
173
187
|
if (parent && parent !== el) continue;
|
|
@@ -620,18 +634,19 @@
|
|
|
620
634
|
const kind = getKind();
|
|
621
635
|
if (kind === 'other') return 0;
|
|
622
636
|
|
|
623
|
-
const exec = (klass, getItems,
|
|
624
|
-
if (!
|
|
625
|
-
return injectBetween(klass, getItems(),
|
|
637
|
+
const exec = (klass, getItems, enable, interval, showFirst, poolKey) => {
|
|
638
|
+
if (!enable) return 0;
|
|
639
|
+
return injectBetween(klass, getItems(), interval, showFirst, poolKey);
|
|
626
640
|
};
|
|
627
641
|
|
|
642
|
+
const o = S.opts;
|
|
628
643
|
if (kind === 'topic')
|
|
629
|
-
return exec('ezoic-ad-message', getPosts,
|
|
644
|
+
return exec('ezoic-ad-message', getPosts, o.enableMessageAds, o.messageInterval, o.showFirstMessageAd, 'posts');
|
|
630
645
|
if (kind === 'categoryTopics') {
|
|
631
646
|
pruneOrphansBetween();
|
|
632
|
-
return exec('ezoic-ad-between', getTopics,
|
|
647
|
+
return exec('ezoic-ad-between', getTopics, o.enableBetweenAds, o.intervalTopics, o.showFirstTopicAd, 'topics');
|
|
633
648
|
}
|
|
634
|
-
return exec('ezoic-ad-categories', getCategories,
|
|
649
|
+
return exec('ezoic-ad-categories', getCategories, o.enableCategoryAds, o.intervalCategories, o.showFirstCategoryAd, 'categories');
|
|
635
650
|
}
|
|
636
651
|
|
|
637
652
|
// ── Scheduler ──────────────────────────────────────────────────────────────
|
|
@@ -687,6 +702,7 @@
|
|
|
687
702
|
S.domObs?.disconnect(); S.domObs = null;
|
|
688
703
|
S.io?.disconnect(); S.io = null;
|
|
689
704
|
S.cfg = null;
|
|
705
|
+
S.opts = null;
|
|
690
706
|
S.poolsReady = false;
|
|
691
707
|
S.pools = { topics: [], posts: [], categories: [] };
|
|
692
708
|
S.cursors = { topics: 0, posts: 0, categories: 0 };
|
|
@@ -727,16 +743,21 @@
|
|
|
727
743
|
for (const node of m.addedNodes) {
|
|
728
744
|
if (!(node instanceof Element)) continue;
|
|
729
745
|
try {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
746
|
+
// Check closest first (cheap) before doing FILL_SEL querySelector on every added node
|
|
747
|
+
const emptyWrap = node.closest?.(`.${WRAP_CLASS}.is-empty`) ?? m.target?.closest?.(`.${WRAP_CLASS}.is-empty`);
|
|
748
|
+
if (emptyWrap && (node.matches?.(FILL_SEL) || node.querySelector?.(FILL_SEL))) {
|
|
749
|
+
clearEmptyIfFilled(emptyWrap);
|
|
733
750
|
}
|
|
734
751
|
} catch (_) {}
|
|
735
752
|
try {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
753
|
+
if (node.classList?.contains(WRAP_CLASS)) {
|
|
754
|
+
const id = parseInt(node.getAttribute(ATTR.WRAPID), 10);
|
|
755
|
+
if (id > 0) { const ph = node.querySelector(`[id^="${PH_PREFIX}"]`); if (ph) try { getIO()?.observe(ph); } catch (_) {} }
|
|
756
|
+
} else if (node.childElementCount > 0) {
|
|
757
|
+
for (const wrap of node.querySelectorAll(`.${WRAP_CLASS}`)) {
|
|
758
|
+
const id = parseInt(wrap.getAttribute(ATTR.WRAPID), 10);
|
|
759
|
+
if (id > 0) { const ph = wrap.querySelector(`[id^="${PH_PREFIX}"]`); if (ph) try { getIO()?.observe(ph); } catch (_) {} }
|
|
760
|
+
}
|
|
740
761
|
}
|
|
741
762
|
} catch (_) {}
|
|
742
763
|
if (!needsBurst) {
|
|
@@ -798,30 +819,6 @@
|
|
|
798
819
|
try { window.__nbbAriaObs.observe(document.body, { attributes: true, attributeFilter: ['aria-hidden'] }); } catch (_) {}
|
|
799
820
|
}
|
|
800
821
|
|
|
801
|
-
// ── Network warmup ─────────────────────────────────────────────────────────
|
|
802
|
-
|
|
803
|
-
let _warmed = false;
|
|
804
|
-
function warmNetwork() {
|
|
805
|
-
if (_warmed) return;
|
|
806
|
-
_warmed = true;
|
|
807
|
-
const head = document.head;
|
|
808
|
-
if (!head) return;
|
|
809
|
-
for (const [rel, href, cors] of [
|
|
810
|
-
['preconnect', 'https://g.ezoic.net', true ],
|
|
811
|
-
['preconnect', 'https://go.ezoic.net', true ],
|
|
812
|
-
['preconnect', 'https://securepubads.g.doubleclick.net', true ],
|
|
813
|
-
['preconnect', 'https://pagead2.googlesyndication.com', true ],
|
|
814
|
-
['dns-prefetch', 'https://g.ezoic.net', false],
|
|
815
|
-
['dns-prefetch', 'https://securepubads.g.doubleclick.net', false],
|
|
816
|
-
]) {
|
|
817
|
-
if (head.querySelector(`link[rel="${rel}"][href="${href}"]`)) continue;
|
|
818
|
-
const l = document.createElement('link');
|
|
819
|
-
l.rel = rel; l.href = href;
|
|
820
|
-
if (cors) l.crossOrigin = 'anonymous';
|
|
821
|
-
head.appendChild(l);
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
|
|
825
822
|
// ── Bindings ───────────────────────────────────────────────────────────────
|
|
826
823
|
|
|
827
824
|
function bindNodeBB() {
|
|
@@ -832,7 +829,7 @@
|
|
|
832
829
|
$(window).on('action:ajaxify.end.nbbEzoic', () => {
|
|
833
830
|
S.pageKey = pageKey(); S.kind = null; S.blockedUntil = 0;
|
|
834
831
|
ensureTcfLocator(); protectAriaHidden();
|
|
835
|
-
|
|
832
|
+
patchShowAds(); getIO(); ensureDomObserver();
|
|
836
833
|
requestBurst();
|
|
837
834
|
});
|
|
838
835
|
// action:ajaxify.contentLoaded et action:category.loaded ne passent pas par hooks → jQuery uniquement
|
|
@@ -863,7 +860,6 @@
|
|
|
863
860
|
S.pageKey = pageKey();
|
|
864
861
|
ensureTcfLocator();
|
|
865
862
|
protectAriaHidden();
|
|
866
|
-
warmNetwork();
|
|
867
863
|
patchShowAds();
|
|
868
864
|
getIO();
|
|
869
865
|
ensureDomObserver();
|
|
@@ -890,6 +886,8 @@
|
|
|
890
886
|
function retryBoot() {
|
|
891
887
|
if (RETRY.count >= 12) return;
|
|
892
888
|
RETRY.count++;
|
|
889
|
+
// On pages that never show ads, no need to retry (unless mid-reload recovery)
|
|
890
|
+
if (!RETRY.scriptReloaded && getKind() === 'other') return;
|
|
893
891
|
patchShowAds();
|
|
894
892
|
|
|
895
893
|
const ez = window.ezstandalone;
|