nodebb-plugin-ezoic-infinite 1.7.35 → 1.7.37
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 +73 -43
- package/package.json +1 -1
- package/plugin.json +5 -1
- package/public/client.js +13 -9
package/library.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const meta
|
|
3
|
+
const meta = require.main.require('./src/meta');
|
|
4
4
|
const groups = require.main.require('./src/groups');
|
|
5
|
-
const db
|
|
5
|
+
const db = require.main.require('./src/database');
|
|
6
6
|
|
|
7
7
|
const SETTINGS_KEY = 'ezoic-infinite';
|
|
8
8
|
const plugin = {};
|
|
9
9
|
|
|
10
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
10
12
|
function normalizeExcludedGroups(value) {
|
|
11
13
|
if (!value) return [];
|
|
12
14
|
if (Array.isArray(value)) return value;
|
|
@@ -27,14 +29,16 @@ async function getAllGroups() {
|
|
|
27
29
|
}
|
|
28
30
|
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
29
31
|
const data = await groups.getGroupsData(filtered);
|
|
30
|
-
// Filter out nulls (groups deleted between the sorted-set read and getGroupsData)
|
|
31
32
|
const valid = data.filter(g => g && g.name);
|
|
32
33
|
valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
33
34
|
return valid;
|
|
34
35
|
}
|
|
35
|
-
|
|
36
|
+
|
|
37
|
+
// ── Settings cache (30s TTL) ────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
let _settingsCache = null;
|
|
36
40
|
let _settingsCacheAt = 0;
|
|
37
|
-
const SETTINGS_TTL
|
|
41
|
+
const SETTINGS_TTL = 30_000;
|
|
38
42
|
|
|
39
43
|
async function getSettings() {
|
|
40
44
|
const now = Date.now();
|
|
@@ -42,25 +46,22 @@ async function getSettings() {
|
|
|
42
46
|
const s = await meta.settings.get(SETTINGS_KEY);
|
|
43
47
|
_settingsCacheAt = Date.now();
|
|
44
48
|
_settingsCache = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
enableCategoryAds: parseBool(s.enableCategoryAds, false),
|
|
53
|
-
showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
|
|
49
|
+
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
50
|
+
showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
|
|
51
|
+
placeholderIds: (s.placeholderIds || '').trim(),
|
|
52
|
+
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
53
|
+
|
|
54
|
+
enableCategoryAds: parseBool(s.enableCategoryAds, false),
|
|
55
|
+
showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
|
|
54
56
|
categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
|
|
55
|
-
intervalCategories:
|
|
57
|
+
intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
59
|
+
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
60
|
+
showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
|
|
61
|
+
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
62
|
+
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
62
63
|
|
|
63
|
-
excludedGroups:
|
|
64
|
+
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
64
65
|
};
|
|
65
66
|
return _settingsCache;
|
|
66
67
|
}
|
|
@@ -71,58 +72,87 @@ async function isUserExcluded(uid, excludedGroups) {
|
|
|
71
72
|
return (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// ── Scripts Ezoic à injecter dans le <head> ────────────────────────────────
|
|
76
|
+
//
|
|
77
|
+
// Injectés uniquement pour les utilisateurs non-exclus.
|
|
78
|
+
// Recommandation Ezoic : charger ces scripts en <head> plutôt qu'en widget
|
|
79
|
+
// pour garantir le chargement avant le contenu de la page.
|
|
80
|
+
|
|
81
|
+
const EZOIC_HEAD_HTML = `<script data-cfasync="false" src="https://cmp.gatekeeperconsent.com/min.js"></script>
|
|
82
|
+
<script data-cfasync="false" src="https://the.gatekeeperconsent.com/cmp.min.js"></script>
|
|
83
|
+
<script async src="//www.ezojs.com/ezoic/sa.min.js"></script>
|
|
84
|
+
<script>
|
|
85
|
+
window.ezstandalone = window.ezstandalone || {};
|
|
86
|
+
ezstandalone.cmd = ezstandalone.cmd || [];
|
|
87
|
+
</script>`;
|
|
88
|
+
|
|
89
|
+
// ── Hooks ──────────────────────────────────────────────────────────────────
|
|
90
|
+
|
|
74
91
|
plugin.onSettingsSet = function (data) {
|
|
75
|
-
|
|
76
|
-
if (data && data.hash === SETTINGS_KEY) {
|
|
77
|
-
_settingsCache = null;
|
|
78
|
-
}
|
|
92
|
+
if (data && data.hash === SETTINGS_KEY) _settingsCache = null;
|
|
79
93
|
};
|
|
80
94
|
|
|
81
95
|
plugin.addAdminNavigation = async (header) => {
|
|
82
96
|
header.plugins = header.plugins || [];
|
|
83
97
|
header.plugins.push({
|
|
84
98
|
route: '/plugins/ezoic-infinite',
|
|
85
|
-
icon:
|
|
86
|
-
name:
|
|
99
|
+
icon: 'fa-ad',
|
|
100
|
+
name: 'Ezoic Infinite Ads',
|
|
87
101
|
});
|
|
88
102
|
return header;
|
|
89
103
|
};
|
|
90
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Injecte les scripts Ezoic dans le <head> pour les utilisateurs non-exclus.
|
|
107
|
+
* Hook : filter:middleware.renderHeader
|
|
108
|
+
* NodeBB appelle ce hook avant de rendre le header — templateData.headHTML
|
|
109
|
+
* est concaténé dans le <head> de chaque page.
|
|
110
|
+
*/
|
|
111
|
+
plugin.injectEzoicHead = async (data) => {
|
|
112
|
+
try {
|
|
113
|
+
const settings = await getSettings();
|
|
114
|
+
const uid = data.templateData?.uid ?? data.req?.uid ?? 0;
|
|
115
|
+
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
116
|
+
if (!excluded) {
|
|
117
|
+
data.templateData.headHTML = (data.templateData.headHTML || '') + EZOIC_HEAD_HTML;
|
|
118
|
+
}
|
|
119
|
+
} catch (_) {}
|
|
120
|
+
return data;
|
|
121
|
+
};
|
|
122
|
+
|
|
91
123
|
plugin.init = async ({ router, middleware }) => {
|
|
92
124
|
async function render(req, res) {
|
|
93
|
-
const settings
|
|
125
|
+
const settings = await getSettings();
|
|
94
126
|
const allGroups = await getAllGroups();
|
|
95
|
-
|
|
96
127
|
res.render('admin/plugins/ezoic-infinite', {
|
|
97
128
|
title: 'Ezoic Infinite Ads',
|
|
98
129
|
...settings,
|
|
99
130
|
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
100
|
-
enableMessageAds_checked:
|
|
131
|
+
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
101
132
|
allGroups,
|
|
102
133
|
});
|
|
103
134
|
}
|
|
104
135
|
|
|
105
|
-
router.get('/admin/plugins/ezoic-infinite',
|
|
136
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
106
137
|
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
107
138
|
|
|
108
139
|
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
109
140
|
const settings = await getSettings();
|
|
110
141
|
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
111
|
-
|
|
112
142
|
res.json({
|
|
113
143
|
excluded,
|
|
114
|
-
enableBetweenAds:
|
|
115
|
-
showFirstTopicAd:
|
|
116
|
-
placeholderIds:
|
|
117
|
-
intervalPosts:
|
|
118
|
-
enableCategoryAds:
|
|
119
|
-
showFirstCategoryAd:
|
|
144
|
+
enableBetweenAds: settings.enableBetweenAds,
|
|
145
|
+
showFirstTopicAd: settings.showFirstTopicAd,
|
|
146
|
+
placeholderIds: settings.placeholderIds,
|
|
147
|
+
intervalPosts: settings.intervalPosts,
|
|
148
|
+
enableCategoryAds: settings.enableCategoryAds,
|
|
149
|
+
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
120
150
|
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
121
|
-
intervalCategories:
|
|
122
|
-
enableMessageAds:
|
|
123
|
-
showFirstMessageAd:
|
|
124
|
-
messagePlaceholderIds:
|
|
125
|
-
messageIntervalPosts:
|
|
151
|
+
intervalCategories: settings.intervalCategories,
|
|
152
|
+
enableMessageAds: settings.enableMessageAds,
|
|
153
|
+
showFirstMessageAd: settings.showFirstMessageAd,
|
|
154
|
+
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
155
|
+
messageIntervalPosts: settings.messageIntervalPosts,
|
|
126
156
|
});
|
|
127
157
|
});
|
|
128
158
|
};
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
{
|
|
16
16
|
"hook": "action:settings.set",
|
|
17
17
|
"method": "onSettingsSet"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"hook": "filter:middleware.renderHeader",
|
|
21
|
+
"method": "injectEzoicHead"
|
|
18
22
|
}
|
|
19
23
|
],
|
|
20
24
|
"staticDirs": {
|
|
@@ -30,4 +34,4 @@
|
|
|
30
34
|
"css": [
|
|
31
35
|
"public/style.css"
|
|
32
36
|
]
|
|
33
|
-
}
|
|
37
|
+
}
|
package/public/client.js
CHANGED
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
*
|
|
33
33
|
* v34 moveDistantWrap — voir v38.
|
|
34
34
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* v43 Seuil de recyclage abaissé à -vh (hors viewport visible).
|
|
36
|
+
* recycleAndMove appelle unobserve(ph) avant de déplacer le wrap,
|
|
37
|
+
* ce qui neutralise l'IO sur ce nœud — plus de risque de showAds
|
|
38
|
+
* parasite après recyclage. Plus de wraps éligibles = moins de pool sec.
|
|
39
|
+
*
|
|
40
|
+
* v42 Seuil -(IO_MARGIN + vh) (trop strict, peu de wraps éligibles).
|
|
39
41
|
*
|
|
40
42
|
* v41 Seuil -1vh (trop permissif sur mobile, ignorait IO_MARGIN).
|
|
41
43
|
*
|
|
@@ -310,11 +312,10 @@
|
|
|
310
312
|
typeof ez?.define !== 'function' ||
|
|
311
313
|
typeof ez?.displayMore !== 'function') return null;
|
|
312
314
|
|
|
313
|
-
const vh
|
|
314
|
-
// Seuil :
|
|
315
|
-
//
|
|
316
|
-
const
|
|
317
|
-
const threshold = -(margin + vh);
|
|
315
|
+
const vh = window.innerHeight || 800;
|
|
316
|
+
// Seuil : -1vh (hors viewport visible). On appelle unobserve(ph) juste
|
|
317
|
+
// après pour neutraliser l'IO — plus de showAds parasite possible.
|
|
318
|
+
const threshold = -vh;
|
|
318
319
|
let bestEmpty = null, bestEmptyBottom = Infinity;
|
|
319
320
|
let bestFilled = null, bestFilledBottom = Infinity;
|
|
320
321
|
|
|
@@ -336,6 +337,9 @@
|
|
|
336
337
|
if (!Number.isFinite(id)) return null;
|
|
337
338
|
|
|
338
339
|
const oldKey = best.getAttribute(A_ANCHOR);
|
|
340
|
+
// Neutraliser l'IO sur ce wrap avant déplacement — évite un showAds
|
|
341
|
+
// parasite si le nœud était encore dans la zone IO_MARGIN.
|
|
342
|
+
try { const ph = best.querySelector(`#${PH_PREFIX}${id}`); if (ph) S.io?.unobserve(ph); } catch (_) {}
|
|
339
343
|
mutate(() => {
|
|
340
344
|
best.setAttribute(A_ANCHOR, newKey);
|
|
341
345
|
best.setAttribute(A_CREATED, String(ts()));
|