nodebb-plugin-facebook-post 1.0.20 → 1.0.22

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 CHANGED
@@ -293,6 +293,39 @@ Plugin.init = async function (params) {
293
293
  return res.json({ allowed: false, reason: 'error' });
294
294
  }
295
295
  });
296
+
297
+ router.get('/api/facebook-post/search-place', middleware.ensureLoggedIn, async (req, res) => {
298
+ res.set('Cache-Control', 'no-store');
299
+ try {
300
+ await loadSettings();
301
+ if (!settings.fbPageAccessToken) return res.json({ results: [] });
302
+ const q = trimStr(req.query.q);
303
+ if (q.length < 2) return res.json({ results: [] });
304
+
305
+ const resp = await axios.get(
306
+ `https://graph.facebook.com/${settings.fbGraphVersion}/search`,
307
+ {
308
+ params: {
309
+ type: 'place',
310
+ q,
311
+ fields: 'id,name,location',
312
+ access_token: settings.fbPageAccessToken,
313
+ limit: 7,
314
+ },
315
+ timeout: 8000,
316
+ }
317
+ );
318
+ const data = (resp.data && resp.data.data) || [];
319
+ const results = data.map(p => ({
320
+ id: p.id,
321
+ name: p.name,
322
+ city: (p.location && (p.location.city || p.location.country)) || '',
323
+ }));
324
+ return res.json({ results });
325
+ } catch {
326
+ return res.json({ results: [] });
327
+ }
328
+ });
296
329
  };
297
330
 
298
331
  Plugin.addAdminNavigation = async function (header) {
@@ -306,44 +339,90 @@ Plugin.addAdminNavigation = async function (header) {
306
339
  };
307
340
 
308
341
  Plugin.onPostCreate = async function (hookData) {
342
+ const winston = require.main.require('winston');
309
343
  try {
310
- if (!hookData?.post || !hookData?.data) return hookData;
344
+ if (!hookData?.post || !hookData?.data) {
345
+ winston.verbose('[facebook-post] onPostCreate: hookData manquant (post ou data absent)');
346
+ return hookData;
347
+ }
311
348
  hookData.post.fbPostEnabled = bool(hookData.data.fbPostEnabled);
312
349
  const fbPlaceId = trimStr(hookData.data.fbPlaceId);
313
350
  if (fbPlaceId) hookData.post.fbPlaceId = fbPlaceId;
314
- } catch {
315
- // swallow
351
+ winston.verbose(`[facebook-post] onPostCreate: pid=${hookData.post.pid} fbPostEnabled=${hookData.post.fbPostEnabled} fbPlaceId=${hookData.post.fbPlaceId || '(none)'}`);
352
+ } catch (e) {
353
+ winston.error(`[facebook-post] onPostCreate erreur: ${e?.message || e}`);
316
354
  }
317
355
  return hookData;
318
356
  };
319
357
 
320
358
  Plugin.onPostSave = async function (hookData) {
321
359
  const winston = require.main.require('winston');
360
+ winston.verbose('[facebook-post] onPostSave: hook déclenché');
322
361
  try {
323
362
  await loadSettings();
324
- if (!settings.enabled) return;
363
+ winston.verbose(`[facebook-post] onPostSave: settings.enabled=${settings.enabled} fbPageId=${settings.fbPageId ? 'défini' : 'MANQUANT'} fbPageAccessToken=${settings.fbPageAccessToken ? 'défini' : 'MANQUANT'}`);
325
364
 
326
- const ctx = await getPostContext(hookData && hookData.post ? hookData.post : hookData);
327
- if (!ctx) return;
365
+ if (!settings.enabled) {
366
+ winston.verbose('[facebook-post] onPostSave: abandon — plugin désactivé dans les paramètres');
367
+ return;
368
+ }
328
369
 
329
- const allowed = await userIsAllowed(ctx.post.uid);
330
- if (!allowed) return;
370
+ const rawPost = hookData && hookData.post ? hookData.post : hookData;
371
+ winston.verbose(`[facebook-post] onPostSave: données brutes reçues — pid=${rawPost?.pid} fbPostEnabled=${rawPost?.fbPostEnabled}`);
331
372
 
332
- if (!shouldProcessPost(ctx)) return;
373
+ const ctx = await getPostContext(rawPost);
374
+ if (!ctx) {
375
+ winston.warn('[facebook-post] onPostSave: abandon — impossible de récupérer le contexte du post (pid invalide ?)');
376
+ return;
377
+ }
378
+ winston.verbose(`[facebook-post] onPostSave: contexte récupéré — pid=${ctx.post.pid} tid=${ctx.topic.tid} cid=${ctx.topic.cid} uid=${ctx.user.uid} isMainPost=${ctx.post.isMainPost} index=${ctx.post.index} reputation=${ctx.user.reputation}`);
379
+
380
+ const allowed = await userIsAllowed(ctx.post.uid);
381
+ if (!allowed) {
382
+ const groups = parseAllowedGroupsList();
383
+ winston.verbose(`[facebook-post] onPostSave: abandon — uid=${ctx.post.uid} n'est pas dans les groupes autorisés: [${groups.join(', ')}]`);
384
+ return;
385
+ }
386
+ winston.verbose(`[facebook-post] onPostSave: uid=${ctx.post.uid} autorisé par les groupes`);
387
+
388
+ const process = shouldProcessPost(ctx);
389
+ if (!process) {
390
+ // Log détaillé de la raison du refus
391
+ const isFirstPost = (ctx.post.isMainPost === true) || (ctx.post.index === 0);
392
+ const fbEnabled = bool(ctx.post.fbPostEnabled);
393
+ const repOk = (ctx.user.reputation || 0) >= settings.minimumReputation;
394
+ const whitelist = parseCsvInts(settings.categoriesWhitelist);
395
+ const catOk = whitelist.length === 0 || whitelist.includes(parseInt(ctx.topic.cid, 10));
396
+ winston.verbose(`[facebook-post] onPostSave: abandon — shouldProcessPost=false. Détail: isFirstPost=${isFirstPost} fbPostEnabled=${fbEnabled} reputationOk=${repOk}(${ctx.user.reputation}>=${settings.minimumReputation}) categoryOk=${catOk}(cid=${ctx.topic.cid} whitelist=[${whitelist}])`);
397
+ return;
398
+ }
399
+ winston.verbose('[facebook-post] onPostSave: shouldProcessPost=true, publication en cours…');
333
400
 
334
401
  const Posts = require.main.require('./src/posts');
335
402
 
336
403
  const already = await Posts.getPostField(ctx.post.pid, 'fbPostedId');
337
- if (already) return;
404
+ if (already) {
405
+ winston.verbose(`[facebook-post] onPostSave: abandon — déjà publié (fbPostedId=${already})`);
406
+ return;
407
+ }
408
+
409
+ const imageUrls = extractImageUrlsFromContent(ctx.post.content || '').filter(isForumHosted);
410
+ winston.verbose(`[facebook-post] onPostSave: images hébergées sur le forum trouvées: ${imageUrls.length} [${imageUrls.join(', ')}]`);
338
411
 
339
412
  const fbId = await postToFacebook(ctx);
340
413
  if (fbId) {
341
414
  await Posts.setPostField(ctx.post.pid, 'fbPostedId', fbId);
342
415
  await Posts.setPostField(ctx.post.pid, 'fbPostedAt', Date.now());
416
+ winston.info(`[facebook-post] onPostSave: publication réussie — fbId=${fbId} pid=${ctx.post.pid}`);
417
+ } else {
418
+ winston.warn('[facebook-post] onPostSave: postToFacebook a retourné un fbId vide (pas d\'erreur levée)');
343
419
  }
344
420
  } catch (e) {
345
421
  const detail = e?.response ? JSON.stringify(e.response.data) : (e?.message || e);
346
- winston.error(`[facebook-post] Failed: ${detail}`);
422
+ winston.error(`[facebook-post] onPostSave: ERREUR: ${detail}`);
423
+ if (e?.response?.config?.url) {
424
+ winston.error(`[facebook-post] onPostSave: URL appelée: ${e.response.config.url} — status: ${e.response.status}`);
425
+ }
347
426
  }
348
427
  };
349
428
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-facebook-post",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Auto-post new NodeBB topics to a fixed Facebook Page (text + NodeBB uploads + place id).",
5
5
  "main": "library.js",
6
6
  "dependencies": {
@@ -26,13 +26,18 @@
26
26
  </div>
27
27
 
28
28
  <div class="mb-2" data-fbpost-place-wrap style="display:none;">
29
- <label class="form-label" style="font-size: 12px; opacity: .8;">
30
- Lieu (Facebook Place ID) – optionnel
29
+ <label class="form-label" style="font-size:12px;opacity:.8;">
30
+ Lieu – optionnel
31
31
  </label>
32
- <input type="text" class="form-control" data-fbpost-place-id placeholder="ex: 123456789012345">
33
- <div class="form-text" style="font-size: 11px;">
34
- Utilisé comme <code>place</code> sur Facebook et <code>location_id</code> sur Instagram.
32
+ <div style="position:relative;">
33
+ <input type="text" class="form-control" data-fbpost-place-query
34
+ placeholder="Rechercher un lieu (ville, adresse…)" autocomplete="off">
35
+ <ul data-fbpost-place-results
36
+ style="display:none;position:absolute;z-index:9999;background:#fff;
37
+ border:1px solid #ccc;border-radius:4px;list-style:none;
38
+ margin:0;padding:0;width:100%;max-height:200px;overflow-y:auto;"></ul>
35
39
  </div>
40
+ <input type="hidden" data-fbpost-place-id>
36
41
  </div>
37
42
  </div>
38
43
  `;
@@ -56,6 +61,57 @@
56
61
  $placeWrap.toggle($enabled.is(':checked'));
57
62
  });
58
63
 
64
+ let _placeTimer = null;
65
+ const $query = $composer.find('[data-fbpost-place-query]');
66
+ const $results = $composer.find('[data-fbpost-place-results]');
67
+ const $placeId = $composer.find('[data-fbpost-place-id]');
68
+
69
+ function clearPlace() {
70
+ $placeId.val('');
71
+ }
72
+ function selectPlace(id, name, city) {
73
+ $placeId.val(id);
74
+ $query.val(name + (city ? ' – ' + city : ''));
75
+ $results.hide().empty();
76
+ }
77
+
78
+ $query.on('input', function () {
79
+ clearPlace();
80
+ clearTimeout(_placeTimer);
81
+ const q = $query.val().trim();
82
+ if (q.length < 2) { $results.hide().empty(); return; }
83
+ _placeTimer = setTimeout(async function () {
84
+ try {
85
+ const res = await fetch('/api/facebook-post/search-place?q=' + encodeURIComponent(q), {
86
+ credentials: 'same-origin', cache: 'no-store',
87
+ });
88
+ if (!res.ok) return;
89
+ const json = await res.json();
90
+ $results.empty();
91
+ if (!json.results || !json.results.length) { $results.hide(); return; }
92
+ json.results.forEach(function (p) {
93
+ const label = p.name + (p.city ? ' – ' + p.city : '');
94
+ $('<li>')
95
+ .text(label)
96
+ .css({ padding: '6px 10px', cursor: 'pointer' })
97
+ .on('mousedown', function (e) {
98
+ e.preventDefault();
99
+ selectPlace(p.id, p.name, p.city);
100
+ })
101
+ .appendTo($results);
102
+ });
103
+ $results.show();
104
+ } catch { /* ignore */ }
105
+ }, 350);
106
+ });
107
+
108
+ $query.on('blur', function () {
109
+ setTimeout(function () { $results.hide(); }, 200);
110
+ });
111
+ $query.on('focus', function () {
112
+ if ($results.children().length) $results.show();
113
+ });
114
+
59
115
  $(window).off('filter:composer.submit.fbpost')
60
116
  .on('filter:composer.submit.fbpost', function (ev2, submitData) {
61
117
  const enabled = $enabled.is(':checked');