nodebb-plugin-ezoic-infinite 1.8.12 → 1.8.14
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 +79 -90
- package/package.json +1 -1
- package/plugin.json +26 -10
- package/public/client.js +386 -806
- package/public/style.css +23 -14
package/library.js
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
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
|
-
|
|
12
10
|
function normalizeExcludedGroups(value) {
|
|
13
11
|
if (!value) return [];
|
|
14
12
|
if (Array.isArray(value)) return value;
|
|
15
|
-
|
|
16
|
-
const s = String(value).trim();
|
|
17
|
-
if (s.startsWith('[')) {
|
|
18
|
-
try { const parsed = JSON.parse(s); if (Array.isArray(parsed)) return parsed.map(String).filter(Boolean); } catch (_) {}
|
|
19
|
-
}
|
|
20
|
-
// Fallback : séparation par virgule
|
|
21
|
-
return s.split(',').map(v => v.trim()).filter(Boolean);
|
|
13
|
+
return String(value).split(',').map(s => s.trim()).filter(Boolean);
|
|
22
14
|
}
|
|
23
15
|
|
|
24
16
|
function parseBool(v, def = false) {
|
|
@@ -29,22 +21,34 @@ function parseBool(v, def = false) {
|
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
async function getAllGroups() {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
if (_groupsCache && (now - _groupsCacheAt) < GROUPS_TTL) return _groupsCache;
|
|
26
|
+
|
|
32
27
|
let names = await db.getSortedSetRange('groups:createtime', 0, -1);
|
|
33
28
|
if (!names || !names.length) {
|
|
34
29
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
35
30
|
}
|
|
36
31
|
const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
|
|
37
32
|
const data = await groups.getGroupsData(filtered);
|
|
38
|
-
const valid = data.filter(g => g && g.name);
|
|
33
|
+
const valid = (data || []).filter(g => g && g.name);
|
|
39
34
|
valid.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
35
|
+
|
|
36
|
+
_groupsCacheAt = now;
|
|
37
|
+
_groupsCache = valid;
|
|
40
38
|
return valid;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
let _settingsCache = null;
|
|
41
|
+
let _settingsCache = null;
|
|
42
|
+
_excludedCache.clear();
|
|
46
43
|
let _settingsCacheAt = 0;
|
|
47
|
-
const SETTINGS_TTL
|
|
44
|
+
const SETTINGS_TTL = 30000; // 30s
|
|
45
|
+
|
|
46
|
+
let _groupsCache = null;
|
|
47
|
+
let _groupsCacheAt = 0;
|
|
48
|
+
const GROUPS_TTL = 5 * 60 * 1000; // 5min
|
|
49
|
+
|
|
50
|
+
const _excludedCache = new Map(); // uid -> { at, excluded }
|
|
51
|
+
const EXCLUDED_TTL = 30 * 1000; // 30s
|
|
48
52
|
|
|
49
53
|
async function getSettings() {
|
|
50
54
|
const now = Date.now();
|
|
@@ -52,116 +56,101 @@ async function getSettings() {
|
|
|
52
56
|
const s = await meta.settings.get(SETTINGS_KEY);
|
|
53
57
|
_settingsCacheAt = Date.now();
|
|
54
58
|
_settingsCache = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
// Between-post ads (simple blocks) in category topic list
|
|
60
|
+
enableBetweenAds: parseBool(s.enableBetweenAds, true),
|
|
61
|
+
showFirstTopicAd: parseBool(s.showFirstTopicAd, false),
|
|
62
|
+
placeholderIds: (s.placeholderIds || '').trim(),
|
|
63
|
+
intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
|
|
64
|
+
|
|
65
|
+
// Home/categories list ads (between categories on / or /categories)
|
|
66
|
+
enableCategoryAds: parseBool(s.enableCategoryAds, false),
|
|
67
|
+
showFirstCategoryAd: parseBool(s.showFirstCategoryAd, false),
|
|
61
68
|
categoryPlaceholderIds: (s.categoryPlaceholderIds || '').trim(),
|
|
62
|
-
intervalCategories:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
intervalCategories: Math.max(1, parseInt(s.intervalCategories, 10) || 4),
|
|
70
|
+
|
|
71
|
+
// "Ad message" between replies (looks like a post)
|
|
72
|
+
enableMessageAds: parseBool(s.enableMessageAds, false),
|
|
73
|
+
showFirstMessageAd: parseBool(s.showFirstMessageAd, false),
|
|
74
|
+
messagePlaceholderIds: (s.messagePlaceholderIds || '').trim(),
|
|
75
|
+
messageIntervalPosts: Math.max(1, parseInt(s.messageIntervalPosts, 10) || 3),
|
|
76
|
+
|
|
77
|
+
excludedGroups: normalizeExcludedGroups(s.excludedGroups),
|
|
68
78
|
};
|
|
69
79
|
return _settingsCache;
|
|
70
80
|
}
|
|
71
81
|
|
|
72
82
|
async function isUserExcluded(uid, excludedGroups) {
|
|
73
83
|
if (!uid || !excludedGroups.length) return false;
|
|
74
|
-
|
|
84
|
+
|
|
85
|
+
const now = Date.now();
|
|
86
|
+
const cached = _excludedCache.get(uid);
|
|
87
|
+
if (cached && (now - cached.at) < EXCLUDED_TTL) return cached.excluded;
|
|
88
|
+
|
|
75
89
|
const userGroups = await groups.getUserGroups([uid]);
|
|
76
|
-
|
|
77
|
-
}
|
|
90
|
+
const excluded = (userGroups[0] || []).some(g => excludedGroups.includes(g.name));
|
|
78
91
|
|
|
79
|
-
|
|
92
|
+
_excludedCache.set(uid, { at: now, excluded });
|
|
93
|
+
// petite hygiene : éviter une map qui grossit sans limite
|
|
94
|
+
if (_excludedCache.size > 5000) _excludedCache.clear();
|
|
80
95
|
|
|
81
|
-
|
|
82
|
-
|
|
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>`;
|
|
96
|
+
return excluded;
|
|
97
|
+
}
|
|
88
98
|
|
|
89
|
-
// ── Hooks ──────────────────────────────────────────────────────────────────
|
|
90
99
|
|
|
91
100
|
plugin.onSettingsSet = function (data) {
|
|
92
|
-
|
|
101
|
+
// Invalider le cache dès que les settings de ce plugin sont sauvegardés via l'ACP
|
|
102
|
+
if (data && data.hash === SETTINGS_KEY) {
|
|
103
|
+
_settingsCache = null;
|
|
104
|
+
_excludedCache.clear();
|
|
105
|
+
}
|
|
93
106
|
};
|
|
94
107
|
|
|
95
108
|
plugin.addAdminNavigation = async (header) => {
|
|
96
109
|
header.plugins = header.plugins || [];
|
|
97
|
-
header.plugins.push({
|
|
110
|
+
header.plugins.push({
|
|
111
|
+
route: '/plugins/ezoic-infinite',
|
|
112
|
+
icon: 'fa-ad',
|
|
113
|
+
name: 'Ezoic Infinite Ads'
|
|
114
|
+
});
|
|
98
115
|
return header;
|
|
99
116
|
};
|
|
100
117
|
|
|
101
|
-
/**
|
|
102
|
-
* Injecte les scripts Ezoic dans le <head> via templateData.customHTML.
|
|
103
|
-
*
|
|
104
|
-
* NodeBB v4 / thème Harmony : header.tpl contient {{customHTML}} dans le <head>
|
|
105
|
-
* (render.js ligne 232 : templateValues.customHTML = meta.config.customHTML).
|
|
106
|
-
* Le hook filter:middleware.renderHeader reçoit templateData = headerFooterData
|
|
107
|
-
* et est rendu via req.app.renderAsync('header', hookReturn.templateData).
|
|
108
|
-
* On préfixe customHTML pour que nos scripts passent AVANT le customHTML admin,
|
|
109
|
-
* tout en préservant ce dernier.
|
|
110
|
-
*/
|
|
111
|
-
plugin.injectEzoicHead = async (data) => {
|
|
112
|
-
try {
|
|
113
|
-
const settings = await getSettings();
|
|
114
|
-
const uid = data.req?.uid ?? 0;
|
|
115
|
-
const excluded = await isUserExcluded(uid, settings.excludedGroups);
|
|
116
|
-
if (!excluded) {
|
|
117
|
-
const html = data.templateData.customHTML || '';
|
|
118
|
-
if (!html.includes('data-nbb-ezoic-head="1"')) {
|
|
119
|
-
data.templateData.customHTML = `<meta data-nbb-ezoic-head="1">` + EZOIC_SCRIPTS + html;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
} catch (_) {}
|
|
123
|
-
return data;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
function publicConfigPayload(settings, excluded) {
|
|
128
|
-
return {
|
|
129
|
-
excluded,
|
|
130
|
-
enableBetweenAds: settings.enableBetweenAds,
|
|
131
|
-
showFirstTopicAd: settings.showFirstTopicAd,
|
|
132
|
-
placeholderIds: settings.placeholderIds,
|
|
133
|
-
intervalPosts: settings.intervalPosts,
|
|
134
|
-
enableCategoryAds: settings.enableCategoryAds,
|
|
135
|
-
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
136
|
-
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
137
|
-
intervalCategories: settings.intervalCategories,
|
|
138
|
-
enableMessageAds: settings.enableMessageAds,
|
|
139
|
-
showFirstMessageAd: settings.showFirstMessageAd,
|
|
140
|
-
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
141
|
-
messageIntervalPosts: settings.messageIntervalPosts,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
118
|
plugin.init = async ({ router, middleware }) => {
|
|
146
119
|
async function render(req, res) {
|
|
147
|
-
const settings
|
|
120
|
+
const settings = await getSettings();
|
|
148
121
|
const allGroups = await getAllGroups();
|
|
122
|
+
|
|
149
123
|
res.render('admin/plugins/ezoic-infinite', {
|
|
150
124
|
title: 'Ezoic Infinite Ads',
|
|
151
125
|
...settings,
|
|
152
126
|
enableBetweenAds_checked: settings.enableBetweenAds ? 'checked' : '',
|
|
153
|
-
enableMessageAds_checked:
|
|
127
|
+
enableMessageAds_checked: settings.enableMessageAds ? 'checked' : '',
|
|
154
128
|
allGroups,
|
|
155
129
|
});
|
|
156
130
|
}
|
|
157
131
|
|
|
158
|
-
router.get('/admin/plugins/ezoic-infinite',
|
|
132
|
+
router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, render);
|
|
159
133
|
router.get('/api/admin/plugins/ezoic-infinite', render);
|
|
160
134
|
|
|
161
135
|
router.get('/api/plugins/ezoic-infinite/config', async (req, res) => {
|
|
162
136
|
const settings = await getSettings();
|
|
163
137
|
const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
|
|
164
|
-
|
|
138
|
+
|
|
139
|
+
res.json({
|
|
140
|
+
excluded,
|
|
141
|
+
enableBetweenAds: settings.enableBetweenAds,
|
|
142
|
+
showFirstTopicAd: settings.showFirstTopicAd,
|
|
143
|
+
placeholderIds: settings.placeholderIds,
|
|
144
|
+
intervalPosts: settings.intervalPosts,
|
|
145
|
+
enableCategoryAds: settings.enableCategoryAds,
|
|
146
|
+
showFirstCategoryAd: settings.showFirstCategoryAd,
|
|
147
|
+
categoryPlaceholderIds: settings.categoryPlaceholderIds,
|
|
148
|
+
intervalCategories: settings.intervalCategories,
|
|
149
|
+
enableMessageAds: settings.enableMessageAds,
|
|
150
|
+
showFirstMessageAd: settings.showFirstMessageAd,
|
|
151
|
+
messagePlaceholderIds: settings.messagePlaceholderIds,
|
|
152
|
+
messageIntervalPosts: settings.messageIntervalPosts,
|
|
153
|
+
});
|
|
165
154
|
});
|
|
166
155
|
};
|
|
167
156
|
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -4,14 +4,30 @@
|
|
|
4
4
|
"description": "Ezoic ads with infinite scroll using a pool of placeholder IDs",
|
|
5
5
|
"library": "./library.js",
|
|
6
6
|
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
{
|
|
8
|
+
"hook": "static:app.load",
|
|
9
|
+
"method": "init"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"hook": "filter:admin.header.build",
|
|
13
|
+
"method": "addAdminNavigation"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"hook": "action:settings.set",
|
|
17
|
+
"method": "onSettingsSet"
|
|
18
|
+
}
|
|
11
19
|
],
|
|
12
|
-
"staticDirs": {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
"staticDirs": {
|
|
21
|
+
"public": "public"
|
|
22
|
+
},
|
|
23
|
+
"acpScripts": [
|
|
24
|
+
"public/admin.js"
|
|
25
|
+
],
|
|
26
|
+
"scripts": [
|
|
27
|
+
"public/client.js"
|
|
28
|
+
],
|
|
29
|
+
"templates": "public/templates",
|
|
30
|
+
"css": [
|
|
31
|
+
"public/style.css"
|
|
32
|
+
]
|
|
33
|
+
}
|