nodebb-plugin-facebook-post 1.0.15 → 1.0.17
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/CLAUDE.md +69 -0
- package/library.js +20 -133
- package/package.json +1 -1
- package/static/lib/composer.js +2 -2
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
NodeBB 4.x plugin that automatically publishes new forum topics to a fixed Facebook Page (association page). Only the first post of a topic is ever published, and only when the user opts in via a composer checkbox.
|
|
8
|
+
|
|
9
|
+
## Installation & Deployment
|
|
10
|
+
|
|
11
|
+
There is no build step for this plugin. NodeBB handles asset bundling.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# After modifying the plugin, copy it into the NodeBB instance and rebuild:
|
|
15
|
+
./nodebb build
|
|
16
|
+
./nodebb restart
|
|
17
|
+
|
|
18
|
+
# Activate via: ACP → Plugins → Facebook Post
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Environment variables (required at NodeBB startup):**
|
|
22
|
+
- `NODEBB_FB_PAGE_ID` – Facebook Page ID
|
|
23
|
+
- `NODEBB_FB_PAGE_ACCESS_TOKEN` – Page access token
|
|
24
|
+
- `NODEBB_FB_GRAPH_VERSION` – optional, defaults to `v25.0`
|
|
25
|
+
- `NODEBB_FB_ALLOWED_GROUPS` – comma-separated NodeBB group names allowed to use the feature (e.g. `Staff,Communication`); empty = nobody
|
|
26
|
+
|
|
27
|
+
## Architecture
|
|
28
|
+
|
|
29
|
+
### File Map
|
|
30
|
+
|
|
31
|
+
| File | Role |
|
|
32
|
+
|------|------|
|
|
33
|
+
| `library.js` | Server-side plugin entry point (Node.js) |
|
|
34
|
+
| `plugin.json` | NodeBB manifest: hooks, static dirs, client scripts |
|
|
35
|
+
| `static/lib/composer.js` | Client JS injected into the NodeBB post composer |
|
|
36
|
+
| `static/lib/admin.js` | Client JS for the ACP settings page |
|
|
37
|
+
| `static/templates/admin/facebook-post.tpl` | NodeBB Benchpress template for ACP page |
|
|
38
|
+
|
|
39
|
+
### Hook Flow
|
|
40
|
+
|
|
41
|
+
1. **`static:app.load` → `Plugin.init`**
|
|
42
|
+
Loads settings, registers Express routes on the NodeBB router:
|
|
43
|
+
- `GET /admin/plugins/facebook-post` – ACP page (HTML)
|
|
44
|
+
- `GET /api/admin/plugins/facebook-post` – ACP page (JSON, for ajaxify)
|
|
45
|
+
- `GET /api/facebook-post/can-post` – called by composer.js to check if the logged-in user may see the Facebook UI
|
|
46
|
+
|
|
47
|
+
2. **`filter:post.create` → `Plugin.onPostCreate`**
|
|
48
|
+
Copies `fbPostEnabled` and `fbPlaceId` from the composer submit payload into the post object so they survive into the next hook.
|
|
49
|
+
|
|
50
|
+
3. **`action:post.save` → `Plugin.onPostSave`**
|
|
51
|
+
Main publication logic: validates guards, calls Facebook Graph API via `postToFacebook()`. Stores `fbPostedId` and `fbPostedAt` on the post to prevent double-posting.
|
|
52
|
+
|
|
53
|
+
### Settings Split
|
|
54
|
+
|
|
55
|
+
- **Sensitive** (env vars, never stored in DB): `fbPageId`, `fbPageAccessToken`, `fbGraphVersion`, `allowedGroups`
|
|
56
|
+
- **Non-sensitive** (stored in NodeBB DB via `meta.settings` with key `facebook-post`): `enabled`, `includeExcerpt`, `excerptMaxLen`, `categoriesWhitelist`, `minimumReputation`, `maxImages`, `enablePlaceTagging`
|
|
57
|
+
|
|
58
|
+
Both are merged in `loadSettings()`, which is called on each relevant request to pick up live DB changes without restart.
|
|
59
|
+
|
|
60
|
+
### Facebook Publication Flow (`postToFacebook`)
|
|
61
|
+
|
|
62
|
+
1. Extract image URLs from post content (Markdown `![]()`, HTML `<img>`, bare URLs) — only forum-hosted images pass the `isForumHosted()` filter.
|
|
63
|
+
2. Upload each image to `/{pageId}/photos` with `published=false` to get photo IDs.
|
|
64
|
+
3. POST to `/{pageId}/feed` with `attached_media[]`, optional `place`, and `link`.
|
|
65
|
+
|
|
66
|
+
### Client-Side (composer.js)
|
|
67
|
+
|
|
68
|
+
Listens to `action:composer.loaded`. Calls `/api/facebook-post/can-post`; if allowed, injects a checkbox and optional Place ID field into the composer. Hooks into `filter:composer.submit.fbpost` to append `fbPostEnabled` and `fbPlaceId` to the submit payload.
|
|
69
|
+
|
package/library.js
CHANGED
|
@@ -4,26 +4,16 @@ const axios = require('axios');
|
|
|
4
4
|
|
|
5
5
|
let meta = {};
|
|
6
6
|
let settings = {};
|
|
7
|
-
let appRef;
|
|
8
7
|
|
|
9
8
|
const SETTINGS_KEY = 'facebook-post';
|
|
10
9
|
|
|
11
|
-
// Non-sensitive settings kept in ACP DB
|
|
12
10
|
const DEFAULT_SETTINGS = {
|
|
13
11
|
enabled: false,
|
|
14
|
-
|
|
15
|
-
// Behaviour
|
|
16
12
|
includeExcerpt: true,
|
|
17
13
|
excerptMaxLen: 220,
|
|
18
|
-
|
|
19
|
-
// Filters
|
|
20
|
-
categoriesWhitelist: '', // comma-separated cids, empty = all
|
|
14
|
+
categoriesWhitelist: '',
|
|
21
15
|
minimumReputation: 0,
|
|
22
|
-
|
|
23
|
-
// Image handling
|
|
24
16
|
maxImages: 4,
|
|
25
|
-
|
|
26
|
-
// Location
|
|
27
17
|
enablePlaceTagging: true,
|
|
28
18
|
};
|
|
29
19
|
|
|
@@ -44,15 +34,11 @@ function parseCsvInts(csv) {
|
|
|
44
34
|
}
|
|
45
35
|
|
|
46
36
|
function readEnv() {
|
|
47
|
-
const env = process.env
|
|
48
|
-
// Fixed Facebook page target (association page)
|
|
37
|
+
const env = process.env;
|
|
49
38
|
return {
|
|
50
39
|
fbGraphVersion: trimStr(env.NODEBB_FB_GRAPH_VERSION) || 'v25.0',
|
|
51
40
|
fbPageId: trimStr(env.NODEBB_FB_PAGE_ID),
|
|
52
41
|
fbPageAccessToken: trimStr(env.NODEBB_FB_PAGE_ACCESS_TOKEN),
|
|
53
|
-
|
|
54
|
-
// Restrict availability to NodeBB groups (comma-separated)
|
|
55
|
-
// If empty => nobody can use Facebook posting
|
|
56
42
|
allowedGroups: trimStr(env.NODEBB_FB_ALLOWED_GROUPS || ''),
|
|
57
43
|
};
|
|
58
44
|
}
|
|
@@ -62,17 +48,13 @@ async function loadSettings() {
|
|
|
62
48
|
settings = Object.assign({}, DEFAULT_SETTINGS, raw || {});
|
|
63
49
|
|
|
64
50
|
settings.enabled = bool(settings.enabled);
|
|
65
|
-
|
|
66
51
|
settings.includeExcerpt = bool(settings.includeExcerpt);
|
|
67
52
|
settings.excerptMaxLen = int(settings.excerptMaxLen, DEFAULT_SETTINGS.excerptMaxLen);
|
|
68
|
-
|
|
69
53
|
settings.minimumReputation = int(settings.minimumReputation, 0);
|
|
70
54
|
settings.maxImages = int(settings.maxImages, DEFAULT_SETTINGS.maxImages);
|
|
71
55
|
settings.enablePlaceTagging = bool(settings.enablePlaceTagging);
|
|
72
|
-
|
|
73
56
|
settings.categoriesWhitelist = trimStr(settings.categoriesWhitelist);
|
|
74
57
|
|
|
75
|
-
// ENV overrides / secrets
|
|
76
58
|
const env = readEnv();
|
|
77
59
|
settings.fbGraphVersion = env.fbGraphVersion;
|
|
78
60
|
settings.fbPageId = env.fbPageId;
|
|
@@ -84,6 +66,7 @@ function getForumBaseUrl() {
|
|
|
84
66
|
const nconf = require.main.require('nconf');
|
|
85
67
|
return trimStr(nconf.get('url')) || '';
|
|
86
68
|
}
|
|
69
|
+
|
|
87
70
|
function absolutizeUrl(url) {
|
|
88
71
|
const base = getForumBaseUrl();
|
|
89
72
|
if (!url) return '';
|
|
@@ -91,8 +74,9 @@ function absolutizeUrl(url) {
|
|
|
91
74
|
if (url.startsWith('//')) return `https:${url}`;
|
|
92
75
|
if (!base) return url;
|
|
93
76
|
if (url.startsWith('/')) return base + url;
|
|
94
|
-
return base
|
|
77
|
+
return `${base}/${url}`;
|
|
95
78
|
}
|
|
79
|
+
|
|
96
80
|
function isForumHosted(urlAbs) {
|
|
97
81
|
const base = getForumBaseUrl();
|
|
98
82
|
if (!base) return false;
|
|
@@ -107,12 +91,10 @@ function extractImageUrlsFromContent(rawContent) {
|
|
|
107
91
|
const urls = new Set();
|
|
108
92
|
if (!rawContent) return [];
|
|
109
93
|
|
|
110
|
-
// Markdown images
|
|
111
94
|
const mdImg = /!\[[^\]]*]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
112
95
|
let m;
|
|
113
96
|
while ((m = mdImg.exec(rawContent)) !== null) urls.add(m[1]);
|
|
114
97
|
|
|
115
|
-
// HTML images
|
|
116
98
|
const htmlImg = /<img[^>]+src=["']([^"']+)["'][^>]*>/gi;
|
|
117
99
|
while ((m = htmlImg.exec(rawContent)) !== null) urls.add(m[1]);
|
|
118
100
|
|
|
@@ -132,25 +114,7 @@ function sanitizeExcerpt(text, maxLen) {
|
|
|
132
114
|
.replace(/<[^>]*>/g, '');
|
|
133
115
|
if (!s) return '';
|
|
134
116
|
if (s.length <= maxLen) return s;
|
|
135
|
-
return s.slice(0,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function normalizeAllowedGroupsValue(value) {
|
|
139
|
-
if (!value) return '';
|
|
140
|
-
if (Array.isArray(value)) {
|
|
141
|
-
return value.map(String).map(v => v.trim()).filter(Boolean).join(',');
|
|
142
|
-
}
|
|
143
|
-
const s = String(value).trim();
|
|
144
|
-
if (!s) return '';
|
|
145
|
-
if (s.startsWith('[')) {
|
|
146
|
-
try {
|
|
147
|
-
const parsed = JSON.parse(s);
|
|
148
|
-
if (Array.isArray(parsed)) {
|
|
149
|
-
return parsed.map(String).map(v => v.trim()).filter(Boolean).join(',');
|
|
150
|
-
}
|
|
151
|
-
} catch (_) {}
|
|
152
|
-
}
|
|
153
|
-
return s;
|
|
117
|
+
return s.slice(0, maxLen - 1).trimEnd() + '\u2026';
|
|
154
118
|
}
|
|
155
119
|
|
|
156
120
|
function parseAllowedGroupsList() {
|
|
@@ -159,7 +123,6 @@ function parseAllowedGroupsList() {
|
|
|
159
123
|
return s.split(',').map(v => v.trim()).filter(Boolean);
|
|
160
124
|
}
|
|
161
125
|
|
|
162
|
-
|
|
163
126
|
function getUidFromReq(req) {
|
|
164
127
|
return req && (req.uid || (req.user && req.user.uid) || (req.session && req.session.uid));
|
|
165
128
|
}
|
|
@@ -170,9 +133,6 @@ async function userIsAllowed(uid) {
|
|
|
170
133
|
|
|
171
134
|
const Groups = require.main.require('./src/groups');
|
|
172
135
|
for (const groupName of allowed) {
|
|
173
|
-
// Group names are case-sensitive in NodeBB
|
|
174
|
-
// If any match => allowed
|
|
175
|
-
// groups.isMember(uid, groupName) returns boolean
|
|
176
136
|
try {
|
|
177
137
|
// eslint-disable-next-line no-await-in-loop
|
|
178
138
|
const ok = await Groups.isMember(uid, groupName);
|
|
@@ -207,36 +167,23 @@ async function getPostContext(postData) {
|
|
|
207
167
|
|
|
208
168
|
function shouldProcessPost(ctx) {
|
|
209
169
|
if (!ctx || !ctx.post || !ctx.topic || !ctx.user) return false;
|
|
210
|
-
|
|
211
|
-
// Plugin enabled
|
|
212
170
|
if (!settings.enabled) return false;
|
|
213
|
-
|
|
214
|
-
// Secrets set
|
|
215
171
|
if (!settings.fbPageId || !settings.fbPageAccessToken) return false;
|
|
216
172
|
|
|
217
|
-
// Only first post of topic
|
|
218
173
|
const isFirstPost = (ctx.post.isMainPost === true) || (ctx.post.index === 0);
|
|
219
174
|
if (!isFirstPost) return false;
|
|
220
175
|
|
|
221
|
-
// User opted-in on composer checkbox
|
|
222
176
|
if (!bool(ctx.post.fbPostEnabled)) return false;
|
|
223
|
-
|
|
224
|
-
// reputation filter
|
|
225
177
|
if ((ctx.user.reputation || 0) < settings.minimumReputation) return false;
|
|
226
178
|
|
|
227
|
-
// category whitelist
|
|
228
179
|
const whitelist = parseCsvInts(settings.categoriesWhitelist);
|
|
229
|
-
if (whitelist.length > 0)
|
|
230
|
-
const cid = parseInt(ctx.topic.cid, 10);
|
|
231
|
-
if (!whitelist.includes(cid)) return false;
|
|
232
|
-
}
|
|
180
|
+
if (whitelist.length > 0 && !whitelist.includes(parseInt(ctx.topic.cid, 10))) return false;
|
|
233
181
|
|
|
234
182
|
return true;
|
|
235
183
|
}
|
|
236
184
|
|
|
237
185
|
async function uploadPhotoToFacebook(urlAbs) {
|
|
238
186
|
const endpoint = `https://graph.facebook.com/${settings.fbGraphVersion}/${settings.fbPageId}/photos`;
|
|
239
|
-
|
|
240
187
|
const resp = await axios.post(endpoint, null, {
|
|
241
188
|
params: {
|
|
242
189
|
url: urlAbs,
|
|
@@ -245,7 +192,6 @@ async function uploadPhotoToFacebook(urlAbs) {
|
|
|
245
192
|
},
|
|
246
193
|
timeout: 20000,
|
|
247
194
|
});
|
|
248
|
-
|
|
249
195
|
return resp.data && resp.data.id;
|
|
250
196
|
}
|
|
251
197
|
|
|
@@ -255,10 +201,8 @@ async function publishFeedPost({ message, link, photoIds, placeId }) {
|
|
|
255
201
|
const form = new URLSearchParams();
|
|
256
202
|
form.append('message', String(message || ''));
|
|
257
203
|
form.append('access_token', String(settings.fbPageAccessToken));
|
|
258
|
-
|
|
259
204
|
if (link) form.append('link', String(link));
|
|
260
205
|
if (placeId) form.append('place', String(placeId));
|
|
261
|
-
|
|
262
206
|
if (Array.isArray(photoIds) && photoIds.length) {
|
|
263
207
|
photoIds.forEach((id, idx) => {
|
|
264
208
|
form.append(`attached_media[${idx}]`, JSON.stringify({ media_fbid: id }));
|
|
@@ -269,42 +213,28 @@ async function publishFeedPost({ message, link, photoIds, placeId }) {
|
|
|
269
213
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
270
214
|
timeout: 20000,
|
|
271
215
|
});
|
|
272
|
-
|
|
273
216
|
return resp.data && resp.data.id;
|
|
274
217
|
}
|
|
275
218
|
|
|
276
219
|
async function postToFacebook(ctx) {
|
|
277
220
|
const rawContent = ctx.post.content || '';
|
|
278
221
|
const topicTitle = ctx.topic.title || 'Nouveau sujet';
|
|
279
|
-
const author = ctx.user.username || 'Quelqu
|
|
222
|
+
const author = ctx.user.username || 'Quelqu\u2019un';
|
|
280
223
|
const link = ctx.topicUrlAbs;
|
|
281
224
|
|
|
282
225
|
const excerpt = settings.includeExcerpt ? sanitizeExcerpt(rawContent, settings.excerptMaxLen) : '';
|
|
283
226
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
.
|
|
287
|
-
.filter(isForumHosted);
|
|
288
|
-
|
|
289
|
-
imageUrls = imageUrls.slice(0, Math.max(0, settings.maxImages));
|
|
227
|
+
const imageUrls = extractImageUrlsFromContent(rawContent)
|
|
228
|
+
.filter(isForumHosted)
|
|
229
|
+
.slice(0, Math.max(0, settings.maxImages));
|
|
290
230
|
|
|
291
|
-
|
|
292
|
-
let placeId = null;
|
|
293
|
-
if (settings.enablePlaceTagging) {
|
|
294
|
-
const pid = trimStr(ctx.post.fbPlaceId);
|
|
295
|
-
if (pid) placeId = pid;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const lines = [];
|
|
299
|
-
lines.push(`📝 ${topicTitle}`);
|
|
300
|
-
if (excerpt) lines.push(`\n${excerpt}`);
|
|
301
|
-
lines.push(`\n🔗 ${link}`);
|
|
302
|
-
lines.push(`\n— ${author}`);
|
|
303
|
-
const message = lines.join('\n');
|
|
231
|
+
const placeId = (settings.enablePlaceTagging && trimStr(ctx.post.fbPlaceId)) || null;
|
|
304
232
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
233
|
+
const lines = [`\uD83D\uDCDD ${topicTitle}`];
|
|
234
|
+
if (excerpt) lines.push(excerpt);
|
|
235
|
+
lines.push(`\uD83D\uDD17 ${link}`);
|
|
236
|
+
lines.push(`\u2014 ${author}`);
|
|
237
|
+
const message = lines.join('\n\n');
|
|
308
238
|
|
|
309
239
|
const photoIds = [];
|
|
310
240
|
for (const urlAbs of imageUrls) {
|
|
@@ -313,18 +243,16 @@ async function postToFacebook(ctx) {
|
|
|
313
243
|
if (id) photoIds.push(id);
|
|
314
244
|
}
|
|
315
245
|
|
|
316
|
-
return
|
|
246
|
+
return publishFeedPost({ message, link, photoIds, placeId });
|
|
317
247
|
}
|
|
318
248
|
|
|
319
249
|
const Plugin = {};
|
|
320
250
|
|
|
321
251
|
Plugin.init = async function (params) {
|
|
322
|
-
// NodeBB v4.9.x: params provides { router, middleware, meta, ... }
|
|
323
252
|
const { router, middleware } = params;
|
|
324
253
|
|
|
325
254
|
meta = (params && params.meta) ? params.meta : require.main.require('./src/meta');
|
|
326
255
|
|
|
327
|
-
// Used to render group list in ACP
|
|
328
256
|
const groups = require.main.require('./src/groups');
|
|
329
257
|
const db = require.main.require('./src/database');
|
|
330
258
|
|
|
@@ -335,11 +263,9 @@ Plugin.init = async function (params) {
|
|
|
335
263
|
if (!names?.length) {
|
|
336
264
|
names = await db.getSortedSetRange('groups:visible:createtime', 0, -1);
|
|
337
265
|
}
|
|
338
|
-
|
|
339
266
|
const data = await groups.getGroupsData(
|
|
340
267
|
(names || []).filter(name => !groups.isPrivilegeGroup(name))
|
|
341
268
|
);
|
|
342
|
-
|
|
343
269
|
return (data || [])
|
|
344
270
|
.filter(g => g?.name && g.name.toLowerCase() !== 'guests')
|
|
345
271
|
.sort((a, b) => String(a.name).localeCompare(String(b.name), undefined, { sensitivity: 'base' }));
|
|
@@ -370,38 +296,6 @@ Plugin.init = async function (params) {
|
|
|
370
296
|
return res.json({ allowed: false, reason: 'error' });
|
|
371
297
|
}
|
|
372
298
|
});
|
|
373
|
-
} catch {
|
|
374
|
-
return res.json({ allowed: false });
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
router.post('/api/admin/plugins/facebook-post/mark-posted', middleware.ensureLoggedIn, async (req, res) => {
|
|
379
|
-
try {
|
|
380
|
-
await loadSettings();
|
|
381
|
-
|
|
382
|
-
const secret = trimStr(req.body && req.body.secret);
|
|
383
|
-
if (!secret || secret !== settings.workerSecret) {
|
|
384
|
-
return res.status(403).json({ error: 'bad_secret' });
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const ok = await ensureAdmin(req);
|
|
388
|
-
if (!ok) return res.status(403).json({ error: 'forbidden' });
|
|
389
|
-
|
|
390
|
-
const pid = parseInt(req.body && req.body.pid, 10);
|
|
391
|
-
const fbPostedId = trimStr(req.body && req.body.fbPostedId);
|
|
392
|
-
const fbPostedAt = parseInt(req.body && req.body.fbPostedAt, 10) || Date.now();
|
|
393
|
-
if (!Number.isFinite(pid) || !fbPostedId) return res.status(400).json({ error: 'bad_payload' });
|
|
394
|
-
|
|
395
|
-
const Posts = require.main.require('./src/posts');
|
|
396
|
-
await Posts.setPostField(pid, 'fbPostedId', fbPostedId);
|
|
397
|
-
await Posts.setPostField(pid, 'fbPostedAt', fbPostedAt);
|
|
398
|
-
await Posts.deletePostField(pid, 'fbQueueJobId');
|
|
399
|
-
await Posts.deletePostField(pid, 'fbQueuedAt');
|
|
400
|
-
return res.json({ ok: true });
|
|
401
|
-
} catch {
|
|
402
|
-
return res.status(500).json({ error: 'failed' });
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
299
|
};
|
|
406
300
|
|
|
407
301
|
Plugin.addAdminNavigation = async function (header) {
|
|
@@ -414,17 +308,13 @@ Plugin.addAdminNavigation = async function (header) {
|
|
|
414
308
|
return header;
|
|
415
309
|
};
|
|
416
310
|
|
|
417
|
-
|
|
418
|
-
// Save custom composer data into post fields
|
|
419
311
|
Plugin.onPostCreate = async function (hookData) {
|
|
420
312
|
try {
|
|
421
313
|
if (!hookData?.post || !hookData?.data) return hookData;
|
|
422
|
-
|
|
423
314
|
hookData.post.fbPostEnabled = bool(hookData.data.fbPostEnabled);
|
|
424
|
-
|
|
425
315
|
const fbPlaceId = trimStr(hookData.data.fbPlaceId);
|
|
426
316
|
if (fbPlaceId) hookData.post.fbPlaceId = fbPlaceId;
|
|
427
|
-
} catch
|
|
317
|
+
} catch {
|
|
428
318
|
// swallow
|
|
429
319
|
}
|
|
430
320
|
return hookData;
|
|
@@ -439,7 +329,6 @@ Plugin.onPostSave = async function (hookData) {
|
|
|
439
329
|
const ctx = await getPostContext(hookData && hookData.post ? hookData.post : hookData);
|
|
440
330
|
if (!ctx) return;
|
|
441
331
|
|
|
442
|
-
// Allowed group check (server-side enforcement)
|
|
443
332
|
const allowed = await userIsAllowed(ctx.post.uid);
|
|
444
333
|
if (!allowed) return;
|
|
445
334
|
|
|
@@ -447,7 +336,6 @@ Plugin.onPostSave = async function (hookData) {
|
|
|
447
336
|
|
|
448
337
|
const Posts = require.main.require('./src/posts');
|
|
449
338
|
|
|
450
|
-
// Avoid double-post
|
|
451
339
|
const already = await Posts.getPostField(ctx.post.pid, 'fbPostedId');
|
|
452
340
|
if (already) return;
|
|
453
341
|
|
|
@@ -457,10 +345,9 @@ Plugin.onPostSave = async function (hookData) {
|
|
|
457
345
|
await Posts.setPostField(ctx.post.pid, 'fbPostedAt', Date.now());
|
|
458
346
|
}
|
|
459
347
|
} catch (e) {
|
|
460
|
-
const detail = e
|
|
348
|
+
const detail = e?.response ? JSON.stringify(e.response.data) : (e?.message || e);
|
|
461
349
|
winston.error(`[facebook-post] Failed: ${detail}`);
|
|
462
350
|
}
|
|
463
351
|
};
|
|
464
352
|
|
|
465
353
|
module.exports = Plugin;
|
|
466
|
-
|
package/package.json
CHANGED
package/static/lib/composer.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
});
|
|
11
11
|
if (!res.ok) return { allowed: false };
|
|
12
12
|
return await res.json();
|
|
13
|
-
} catch {
|
|
13
|
+
} catch (err) {
|
|
14
14
|
return { allowed: false };
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
if (!perm.allowed) return;
|
|
72
72
|
|
|
73
73
|
injectUI($composer);
|
|
74
|
-
} catch {
|
|
74
|
+
} catch (err) {
|
|
75
75
|
// ignore
|
|
76
76
|
}
|
|
77
77
|
});
|