nodebb-plugin-ezoic-infinite 0.1.0

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/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # nodebb-plugin-ezoic-infinite
2
+
3
+ Plugin NodeBB 4.x pour intégrer Ezoic lors des chargements en infinite scroll.
4
+
5
+ ## Fonctionnalités
6
+ - Insertion d'une pub tous les N posts (configurable)
7
+ - Pool d'IDs Ezoic (un ID par placeholder)
8
+ - Fenêtre glissante: le nombre de pubs simultanées est limité à la taille du pool
9
+ - Exclusion par groupes (configurable)
10
+
11
+ ## Configuration
12
+ ACP -> Plugins -> Ezoic Infinite Ads
13
+ - **Pool d’IDs**: un par ligne (ou séparé par virgules)
14
+ - **Intervalle**: ex 6 (pub après #6, #12, #18, ...)
15
+ - **Groupes exclus**: multi-sélection
16
+
17
+ ## Notes Ezoic
18
+ Le plugin supprime les placeholders existants du DOM avant de les recréer, puis appelle
19
+ `ezstandalone.destroyPlaceholders()` avant `ezstandalone.showAds(id)` afin d'éviter les doublons d'IDs.
package/library.js ADDED
@@ -0,0 +1,51 @@
1
+ 'use strict';
2
+
3
+ const meta = require.main.require('./src/meta');
4
+ const groups = require.main.require('./src/groups');
5
+ const db = require.main.require('./src/database');
6
+
7
+ const SETTINGS_KEY = 'ezoic-infinite';
8
+ const plugin = {};
9
+
10
+ function normalizeExcludedGroups(value) {
11
+ if (!value) return [];
12
+ if (Array.isArray(value)) return value;
13
+ return value.split(',').map(s => s.trim()).filter(Boolean);
14
+ }
15
+
16
+ async function getAllGroups() {
17
+ const names = await db.getSortedSetRange('groups:createtime', 0, -1);
18
+ const filtered = names.filter(name => !groups.isPrivilegeGroup(name));
19
+ return groups.getGroupsData(filtered);
20
+ }
21
+
22
+ async function getSettings() {
23
+ const s = await meta.settings.get(SETTINGS_KEY);
24
+ return {
25
+ placeholderIds: (s.placeholderIds || '').trim(),
26
+ intervalPosts: Math.max(1, parseInt(s.intervalPosts, 10) || 6),
27
+ excludedGroups: normalizeExcludedGroups(s.excludedGroups),
28
+ };
29
+ }
30
+
31
+ async function isUserExcluded(uid, excludedGroups) {
32
+ if (!uid || !excludedGroups.length) return false;
33
+ const userGroups = await groups.getUserGroups([uid]);
34
+ return userGroups[0].some(g => excludedGroups.includes(g.name));
35
+ }
36
+
37
+ plugin.init = async ({ router, middleware }) => {
38
+ router.get('/admin/plugins/ezoic-infinite', middleware.admin.buildHeader, async (req, res) => {
39
+ const settings = await getSettings();
40
+ const allGroups = await getAllGroups();
41
+ res.render('admin/plugins/ezoic-infinite', { title: 'Ezoic Infinite Ads', ...settings, allGroups });
42
+ });
43
+
44
+ router.get('/api/plugins/ezoic-infinite/config', middleware.buildHeader, async (req, res) => {
45
+ const settings = await getSettings();
46
+ const excluded = await isUserExcluded(req.uid, settings.excludedGroups);
47
+ res.json({ placeholderIds: settings.placeholderIds, intervalPosts: settings.intervalPosts, excluded });
48
+ });
49
+ };
50
+
51
+ module.exports = plugin;
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "nodebb-plugin-ezoic-infinite",
3
+ "version": "0.1.0",
4
+ "description": "Ezoic ads with infinite scroll using a pool of placeholder IDs",
5
+ "main": "library.js",
6
+ "keywords": [
7
+ "nodebb",
8
+ "nodebb-plugin",
9
+ "ezoic",
10
+ "ads",
11
+ "infinite-scroll"
12
+ ],
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "https://example.com/nodebb-plugin-ezoic-infinite.git"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "peerDependencies": {
22
+ "nodebb": ">=4.0.0"
23
+ }
24
+ }
package/plugin.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "nodebb-plugin-ezoic-infinite",
3
+ "name": "NodeBB Ezoic Infinite Ads",
4
+ "description": "Ezoic ads with infinite scroll using a pool of placeholder IDs",
5
+ "library": "./library.js",
6
+ "hooks": [
7
+ { "hook": "static:app.load", "method": "init" }
8
+ ],
9
+ "staticDirs": { "public": "public" },
10
+ "acpScripts": ["public/admin.js"],
11
+ "scripts": ["public/client.js"],
12
+ "templates": "public/templates"
13
+ }
@@ -0,0 +1,20 @@
1
+ /* globals app */
2
+ 'use strict';
3
+
4
+ $(document).ready(() => {
5
+ $('#save').on('click', function (e) {
6
+ e.preventDefault();
7
+
8
+ $.ajax({
9
+ method: 'PUT',
10
+ url: '/api/admin/settings/ezoic-infinite',
11
+ data: {
12
+ placeholderIds: $('#placeholderIds').val(),
13
+ intervalPosts: $('#intervalPosts').val(),
14
+ excludedGroups: ($('#excludedGroups').val() || []).join(',')
15
+ }
16
+ })
17
+ .done(() => app.alertSuccess('Paramètres enregistrés'))
18
+ .fail(() => app.alertError('Erreur lors de la sauvegarde'));
19
+ });
20
+ });
@@ -0,0 +1,72 @@
1
+ /* globals ajaxify */
2
+ 'use strict';
3
+
4
+ let cachedConfig;
5
+ let lastFetch = 0;
6
+ let debounceTimer;
7
+
8
+ async function fetchConfig() {
9
+ if (cachedConfig && Date.now() - lastFetch < 10000) return cachedConfig;
10
+ const res = await fetch('/api/plugins/ezoic-infinite/config', { credentials: 'same-origin' });
11
+ cachedConfig = await res.json();
12
+ lastFetch = Date.now();
13
+ return cachedConfig;
14
+ }
15
+
16
+ function parsePool(raw) {
17
+ return Array.from(new Set(
18
+ raw.split(/[\n,;\s]+/)
19
+ .map(x => parseInt(x, 10))
20
+ .filter(n => n > 0)
21
+ ));
22
+ }
23
+
24
+ function destroyEzoic() {
25
+ if (window.ezstandalone?.destroyPlaceholders) {
26
+ window.ezstandalone.destroyPlaceholders();
27
+ }
28
+ }
29
+
30
+ function showAd(id) {
31
+ if (window.ezstandalone?.showAds) {
32
+ window.ezstandalone.showAds(id);
33
+ }
34
+ }
35
+
36
+ async function refreshAds() {
37
+ const cfg = await fetchConfig();
38
+ if (!cfg || cfg.excluded) return;
39
+
40
+ const pool = parsePool(cfg.placeholderIds);
41
+ const interval = cfg.intervalPosts;
42
+ const $posts = $('.posts .post');
43
+
44
+ if (!pool.length || !$posts.length) return;
45
+
46
+ pool.forEach(id => $(`#ezoic-pub-ad-placeholder-${id}`).remove());
47
+
48
+ const slots = Math.floor($posts.length / interval);
49
+ const start = Math.max(1, slots - pool.length + 1);
50
+
51
+ const activeIds = [];
52
+
53
+ for (let slot = start, i = 0; slot <= slots; slot++, i++) {
54
+ const id = pool[i];
55
+ const index = slot * interval - 1;
56
+ const $target = $posts.eq(index);
57
+ if (!$target.length) continue;
58
+
59
+ $target.after(`<div id="ezoic-pub-ad-placeholder-${id}"></div>`);
60
+ activeIds.push(id);
61
+ }
62
+
63
+ destroyEzoic();
64
+ activeIds.forEach(showAd);
65
+ }
66
+
67
+ function debounceRefresh() {
68
+ clearTimeout(debounceTimer);
69
+ debounceTimer = setTimeout(refreshAds, 100);
70
+ }
71
+
72
+ $(window).on('action:ajaxify.end action:posts.loaded action:topic.loaded', debounceRefresh);
@@ -0,0 +1,24 @@
1
+ <div class="acp-page-container">
2
+ <h2>Ezoic Infinite Ads</h2>
3
+
4
+ <div class="mb-3">
5
+ <label class="form-label">Pool d’IDs Ezoic</label>
6
+ <textarea id="placeholderIds" class="form-control" rows="4">{placeholderIds}</textarea>
7
+ </div>
8
+
9
+ <div class="mb-3">
10
+ <label class="form-label">Afficher une pub tous les N posts</label>
11
+ <input type="number" id="intervalPosts" class="form-control" value="{intervalPosts}" min="1">
12
+ </div>
13
+
14
+ <div class="mb-3">
15
+ <label class="form-label">Groupes exclus</label>
16
+ <select id="excludedGroups" class="form-select" multiple>
17
+ <!-- BEGIN allGroups -->
18
+ <option value="{allGroups.name}">{allGroups.name}</option>
19
+ <!-- END allGroups -->
20
+ </select>
21
+ </div>
22
+
23
+ <button id="save" class="btn btn-primary">Enregistrer</button>
24
+ </div>