@mostajs/social-publish 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/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # Changelog — @mostajs/social-publish
2
+ ## [0.1.0] — 2026-06-22
3
+ ### Ajouté
4
+ - Registre de dialectes de diffusion (modèle @mostajs/auth-dialects) : 6 réseaux natifs (planned) + passerelle webhook. webhookDialect/consoleDialect/socialFromEnv. publish() fan-out best-effort. 5 tests.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # @mostajs/social-publish
2
+
3
+ **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
4
+
5
+ Diffusion (syndication) des **vidéos & enregistrements vers les réseaux sociaux**, **zéro-dépendance**, **modelé sur `@mostajs/auth-dialects`** : chaque réseau est un *dialecte de diffusion* (`native` OAuth ou `webhook` générique), dans un **registre extensible** (`register/get/byKind/active/planned`).
6
+
7
+ ```js
8
+ import { createSocialRegistry, socialFromEnv } from '@mostajs/social-publish';
9
+ const social = createSocialRegistry();
10
+ socialFromEnv(process.env).forEach((d) => social.register(d)); // SOCIAL_YOUTUBE_WEBHOOK=… etc.
11
+ const results = await social.publish({ networks: ['youtube', 'facebook'], post: { title: 'Demo Day', mediaUrl: '/media/demoday.mp4', mediaKind: 'video', link: 'https://market.amia.fr/vox' } });
12
+ ```
13
+
14
+ - **webhook** (Zapier/Make/Buffer/n8n) = diffusion **réelle dès aujourd'hui** sans dépendance.
15
+ - **native** (YouTube Data, Graph API, LinkedIn, X, TikTok) = même interface, à câbler (Phase 2).
16
+
17
+ Tests : `npm test`. Licence : AGPL-3.0-or-later.
package/llms.txt ADDED
@@ -0,0 +1,8 @@
1
+ # @mostajs/social-publish — diffusion vidéos/enregistrements vers les réseaux sociaux
2
+ Zéro-dépendance, modelé sur @mostajs/auth-dialects : chaque réseau = un DIALECTE de diffusion { key,label,kind,status,media,publish? }.
3
+ kind : 'native' (OAuth : YouTube/Facebook/Instagram/LinkedIn/X/TikTok — Phase 2) | 'webhook' (passerelle Zapier/Make/Buffer/n8n — RÉEL, zéro-dép).
4
+ status : 'active' (porte publish()) | 'planned' (catalogue, à activer par register()).
5
+ ## API (calquée sur auth-dialects)
6
+ createSocialRegistry({networks=defaultNetworks()}) → list/get/byKind/active/planned/register(dialect) + publish({networks?,post})→[{network,ok,id?,url?,error?}]
7
+ post = { title, description, link, mediaUrl, mediaKind, tags }
8
+ webhookDialect({key,url,map?,parse?}) · consoleDialect({key}) · socialFromEnv(env) (SOCIAL_<NET>_WEBHOOK → dialectes actifs, sinon console)
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@mostajs/social-publish",
3
+ "version": "0.1.0",
4
+ "description": "Diffusion (syndication) des vidéos & enregistrements vers les réseaux sociaux, zéro-dépendance — modelé sur @mostajs/auth-dialects : chaque réseau (YouTube/Facebook/Instagram/LinkedIn/X/TikTok) est un « dialecte » de diffusion (native OAuth ou passerelle webhook), registre extensible (register/select/active/planned).",
5
+ "license": "AGPL-3.0-or-later",
6
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
7
+ "type": "module",
8
+ "main": "src/index.js",
9
+ "files": [
10
+ "src",
11
+ "llms.txt",
12
+ "README.md",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "exports": {
16
+ ".": "./src/index.js"
17
+ },
18
+ "keywords": [
19
+ "mostajs",
20
+ "social",
21
+ "syndication",
22
+ "publish",
23
+ "youtube",
24
+ "facebook",
25
+ "linkedin",
26
+ "webhook",
27
+ "dialects"
28
+ ],
29
+ "devDependencies": {
30
+ "@mostajs/mjs-unit": "^0.3.0"
31
+ },
32
+ "scripts": {
33
+ "test": "bash test-scripts/run-tests.sh"
34
+ }
35
+ }
package/src/index.js ADDED
@@ -0,0 +1,104 @@
1
+ // @mostajs/social-publish — diffusion (syndication) des vidéos & enregistrements vers les réseaux sociaux. Author: Dr Hamid MADANI <drmdh@msn.com>
2
+ // ZÉRO DÉPENDANCE. Modelé sur @mostajs/auth-dialects : un réseau social = un « DIALECTE » de diffusion. Deux familles (kind) :
3
+ // • native : API officielle du réseau (OAuth) — YouTube Data, Facebook/Instagram Graph, LinkedIn, X, TikTok (à câbler, Phase 2).
4
+ // • webhook : passerelle générique (Zapier/Make/Buffer/n8n/IFTTT) qui relaie vers le réseau réel — RÉEL et zéro-dép dès aujourd'hui.
5
+ // Le registre est EXTENSIBLE (register), comme les dialectes de connexion. Un POST = { title, description, link, mediaUrl, mediaKind, tags }.
6
+ // Author: Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
7
+
8
+ export const KINDS = Object.freeze({ NATIVE: 'native', WEBHOOK: 'webhook' });
9
+ export const STATUS = Object.freeze({ ACTIVE: 'active', PLANNED: 'planned' });
10
+
11
+ /**
12
+ * Catalogue par défaut des réseaux. Forme d'un dialecte de diffusion :
13
+ * { key, label, kind, status, media:[types supportés], requires?, publish?(post)->Promise<{ok,id?,url?,error?}> }
14
+ * - status PLANNED : au catalogue (UI/doc) mais pas encore diffusable (pas de publish) → à activer par register().
15
+ * - status ACTIVE : porte un publish() (webhook configuré, ou driver natif câblé).
16
+ */
17
+ export function defaultNetworks() {
18
+ const all = ['video', 'audio', 'image', 'link'];
19
+ return [
20
+ { key: 'youtube', label: 'YouTube', kind: KINDS.NATIVE, status: STATUS.PLANNED, media: ['video'], requires: 'OAuth — YouTube Data API' },
21
+ { key: 'facebook', label: 'Facebook', kind: KINDS.NATIVE, status: STATUS.PLANNED, media: all, requires: 'OAuth — Graph API' },
22
+ { key: 'instagram', label: 'Instagram', kind: KINDS.NATIVE, status: STATUS.PLANNED, media: ['video', 'image'], requires: 'OAuth — Graph API' },
23
+ { key: 'linkedin', label: 'LinkedIn', kind: KINDS.NATIVE, status: STATUS.PLANNED, media: ['video', 'image', 'link'], requires: 'OAuth — LinkedIn API' },
24
+ { key: 'x', label: 'X (Twitter)', kind: KINDS.NATIVE, status: STATUS.PLANNED, media: ['video', 'image', 'link'], requires: 'OAuth — X API' },
25
+ { key: 'tiktok', label: 'TikTok', kind: KINDS.NATIVE, status: STATUS.PLANNED, media: ['video'], requires: 'OAuth — TikTok API' },
26
+ // Dialecte GÉNÉRIQUE : une passerelle webhook relaie vers n'importe quel réseau. À activer par register(webhookDialect(...)).
27
+ { key: 'webhook', label: 'Passerelle webhook (Zapier/Make/Buffer/n8n)', kind: KINDS.WEBHOOK, status: STATUS.PLANNED, media: all, requires: 'URL de webhook' },
28
+ ];
29
+ }
30
+
31
+ /** Dialecte de DEV (simule la diffusion, journalise) — toujours ACTIVE. */
32
+ export function consoleDialect({ key = 'console', label = 'Console (simulation)' } = {}) {
33
+ return { key, label, kind: KINDS.WEBHOOK, status: STATUS.ACTIVE, media: ['video', 'audio', 'image', 'link'], async publish(post = {}) { return { ok: true, id: 'sim-' + key, url: null, simulated: true }; } };
34
+ }
35
+
36
+ /**
37
+ * Dialecte WEBHOOK actif : POST JSON vers une passerelle (Zapier/Make/Buffer/n8n) qui relaie au réseau réel. Zéro-dépendance (fetch global).
38
+ * `map(post)` adapte la charge utile ; `parse(data)` en extrait { id, url }.
39
+ */
40
+ export function webhookDialect({ key, label = null, url, method = 'POST', headers = {}, map = null, parse = null } = {}) {
41
+ if (!key || !url) throw new Error('@mostajs/social-publish: webhookDialect { key, url } requis');
42
+ return {
43
+ key, label: label || `${key} (webhook)`, kind: KINDS.WEBHOOK, status: STATUS.ACTIVE, media: ['video', 'audio', 'image', 'link'], url,
44
+ async publish(post = {}) {
45
+ try {
46
+ const body = JSON.stringify(map ? map(post) : { network: key, ...post });
47
+ const res = await fetch(url, { method, headers: { 'content-type': 'application/json', ...headers }, body });
48
+ if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
49
+ let data = null; try { data = await res.json(); } catch { /* réponse non-JSON tolérée */ }
50
+ const out = parse ? parse(data) : (data || {});
51
+ return { ok: true, id: out.id || null, url: out.url || out.link || null };
52
+ } catch (e) { return { ok: false, error: e.message }; }
53
+ },
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Construit les dialectes ACTIFS depuis l'environnement : pour chaque réseau, `SOCIAL_<NET>_WEBHOOK` → webhookDialect.
59
+ * Réseaux : YOUTUBE/FACEBOOK/INSTAGRAM/LINKEDIN/X/TIKTOK. Aucun configuré → [consoleDialect()] (dev). À passer à register().
60
+ */
61
+ export function socialFromEnv(env = process.env) {
62
+ const out = [];
63
+ for (const net of ['youtube', 'facebook', 'instagram', 'linkedin', 'x', 'tiktok']) {
64
+ const url = env[`SOCIAL_${net.toUpperCase()}_WEBHOOK`];
65
+ if (url) out.push(webhookDialect({ key: net, label: net, url }));
66
+ }
67
+ return out.length ? out : [consoleDialect()];
68
+ }
69
+
70
+ /**
71
+ * Registre de dialectes de diffusion (extensible). API calquée sur @mostajs/auth-dialects :
72
+ * list() · get(key) · byKind(kind) · active() · planned() · register(dialect) (+ publish pour orchestrer la diffusion).
73
+ * publish({ networks?, post }) → [{ network, ok, id?, url?, error? }] ; networks absent → tous les ACTIFS. Best-effort.
74
+ */
75
+ export function createSocialRegistry({ networks = defaultNetworks() } = {}) {
76
+ const list = networks.map((d) => ({ ...d }));
77
+ const api = {
78
+ list: () => list.map((d) => ({ ...d })),
79
+ get: (key) => list.find((d) => d.key === key) || null,
80
+ byKind: (kind) => list.filter((d) => d.kind === kind).map((d) => ({ ...d })),
81
+ active: () => list.filter((d) => d.status === STATUS.ACTIVE).map((d) => ({ ...d })),
82
+ planned: () => list.filter((d) => d.status === STATUS.PLANNED).map((d) => ({ ...d })),
83
+ register(d) {
84
+ if (!d || !d.key) throw new Error('@mostajs/social-publish: dialect.key requis');
85
+ const i = list.findIndex((x) => x.key === d.key);
86
+ if (i >= 0) list[i] = { ...list[i], ...d }; // upsert : active un réseau planifié (ajoute son publish + status active)
87
+ else list.unshift(d);
88
+ return api;
89
+ },
90
+ async publish({ networks: keys = null, post = {} } = {}) {
91
+ const chosen = keys && keys.length ? keys.map((k) => list.find((d) => d.key === k)).filter(Boolean) : list.filter((d) => d.status === STATUS.ACTIVE);
92
+ const results = [];
93
+ for (const d of chosen) {
94
+ if (typeof d.publish !== 'function') { results.push({ network: d.key, ok: false, error: 'réseau non configuré (planned)' }); continue; }
95
+ let r; try { r = await d.publish(post); } catch (e) { r = { ok: false, error: e.message }; }
96
+ results.push({ network: d.key, ...r });
97
+ }
98
+ return results;
99
+ },
100
+ };
101
+ return api;
102
+ }
103
+
104
+ export default { KINDS, STATUS, defaultNetworks, consoleDialect, webhookDialect, socialFromEnv, createSocialRegistry };