Social: publication FB fiabilisee sur le noeud
- auto-clic Basculer (agir en tant que Page) si vue Gerer la Page - fermeture auto des boites Plus tard / boost / onboarding - fin de publication detectee sur disparition du composeur (pas un dialogue quelconque) - verifie: post vrai-prix publie en direct depuis le noeud Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 changed file +65 −5
modified
server/social.js
+65 −5
@@ -139,6 +139,23 @@ async function gotoPage() { | ||
| 139 | 139 | `tell application "Safari"\nwith timeout of 20 seconds\nset URL of tab 1 of window id ${fbWinId} to "${PAGE_URL}"\nend timeout\nend tell`]); |
| 140 | 140 | } |
| 141 | 141 | |
| 142 | +// ferme les boîtes FB non bloquantes (« Plus tard », boost, onboarding…) | |
| 143 | +const DISMISS_JS = `(function(){ | |
| 144 | + var labels=['Plus tard','Pas maintenant','Non merci','Ignorer','Peut-être plus tard','Fermer','Terminer','OK','Continuer','Passer']; | |
| 145 | + var clicked=''; | |
| 146 | + document.querySelectorAll('div[role=dialog] [role=button], [aria-label]').forEach(function(e){ | |
| 147 | + if(clicked) return; | |
| 148 | + var t=(e.innerText||'').trim(); var al=e.getAttribute('aria-label')||''; | |
| 149 | + for(var i=0;i<labels.length;i++){ | |
| 150 | + if(t===labels[i] || al===labels[i] || al==='Fermer' || al==='Fermer la boîte de dialogue'){ | |
| 151 | + ['mousedown','mouseup','click'].forEach(function(ev){e.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));}); | |
| 152 | + clicked=labels[i]||al; break; | |
| 153 | + } | |
| 154 | + } | |
| 155 | + }); | |
| 156 | + return clicked||'NONE'; | |
| 157 | +})();`; | |
| 158 | + | |
| 142 | 159 | async function pollJS(jsCode, want, tries = 12, delay = 4000) { |
| 143 | 160 | let last = ''; |
| 144 | 161 | for (let i = 0; i < tries; i++) { |
@@ -149,6 +166,17 @@ async function pollJS(jsCode, want, tries = 12, delay = 4000) { | ||
| 149 | 166 | return last; |
| 150 | 167 | } |
| 151 | 168 | |
| 169 | +async function dismissModals(times) { | |
| 170 | + times = times || 3; | |
| 171 | + let any = false; | |
| 172 | + for (let i = 0; i < times; i++) { | |
| 173 | + let r = 'NONE'; | |
| 174 | + try { r = await osaJS(DISMISS_JS); } catch {} | |
| 175 | + if (r && r !== 'NONE') { any = true; await sleep(1500); } else break; | |
| 176 | + } | |
| 177 | + return any; | |
| 178 | +} | |
| 179 | + | |
| 152 | 180 | // vérifie que Facebook est connecté (sinon, l'auto-post est mis en pause) |
| 153 | 181 | export function fbLoginState() { return withSafari(_fbLoginState); } |
| 154 | 182 | async function _fbLoginState() { |
@@ -184,7 +212,24 @@ async function _publishToPage(caption, imagePath) { | ||
| 184 | 212 | ['mousedown','mouseup','click'].forEach(function(ev){btn.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));}); |
| 185 | 213 | return 'opened'; |
| 186 | 214 | })();`; |
| 187 | − const r1 = await pollJS(openComposer, v => v === 'opened', 10, 5000); | |
| 215 | + // La Page peut s'ouvrir en vue « Gérer la Page » (contexte profil perso) sans | |
| 216 | + // composeur : cliquer « Basculer » pour agir en tant que Page, puis réessayer. | |
| 217 | + const basculer = `(function(){ | |
| 218 | + var btn=null; | |
| 219 | + document.querySelectorAll('[role=button]').forEach(function(e){ | |
| 220 | + var t=(e.innerText||'').trim(); | |
| 221 | + if(!btn && (t==='Basculer' || t.indexOf('Basculer sur la Page')===0)) btn=e; | |
| 222 | + }); | |
| 223 | + if(!btn) return 'NO_BTN'; | |
| 224 | + ['mousedown','mouseup','click'].forEach(function(ev){btn.dispatchEvent(new MouseEvent(ev,{bubbles:true,cancelable:true,view:window}));}); | |
| 225 | + return 'switched'; | |
| 226 | + })();`; | |
| 227 | + let r1 = await pollJS(openComposer, v => v === 'opened', 4, 4000); | |
| 228 | + if (r1 !== 'opened') { | |
| 229 | + const sw = await osaJS(basculer); | |
| 230 | + if (sw === 'switched') { await sleep(9000); await dismissModals(3); } | |
| 231 | + r1 = await pollJS(openComposer, v => v === 'opened', 8, 5000); | |
| 232 | + } | |
| 188 | 233 | if (r1 !== 'opened') throw new Error('composeur introuvable (' + r1 + ')'); |
| 189 | 234 | await sleep(3000); |
| 190 | 235 | |
@@ -263,10 +308,25 @@ async function _publishToPage(caption, imagePath) { | ||
| 263 | 308 | const pub = await pollJS(clickBtn('Publier'), v => v === 'clicked', 6, 4000); |
| 264 | 309 | if (pub !== 'clicked') throw new Error('bouton Publier indisponible (' + pub + ')'); |
| 265 | 310 | |
| 266 | − // 6) attendre la fermeture du dialogue (upload lent) | |
| 267 | − const closedJS = `document.querySelector('div[role=dialog]') ? 'OPEN' : 'CLOSED';`; | |
| 268 | − const closed = await pollJS(closedJS, v => v === 'CLOSED', 18, 8000); | |
| 269 | − if (closed !== 'CLOSED') throw new Error('dialogue non fermé après Publier (doute — ne pas retenter)'); | |
| 311 | + // 6) attendre la disparition du COMPOSEUR (= publication soumise). Une boîte | |
| 312 | + // « Plus tard » / boost peut apparaître ensuite : on la ferme, elle n'indique | |
| 313 | + // PAS un échec. On juge le succès sur l'absence de la zone de texte du composeur. | |
| 314 | + const composerGoneJS = `(function(){ | |
| 315 | + var open=false; | |
| 316 | + document.querySelectorAll('div[role=dialog]').forEach(function(d){ | |
| 317 | + if(d.querySelector('div[contenteditable=true][role=textbox]')) open=true; | |
| 318 | + }); | |
| 319 | + return open ? 'OPEN' : 'GONE'; | |
| 320 | + })();`; | |
| 321 | + let gone = 'OPEN'; | |
| 322 | + for (let i = 0; i < 18; i++) { | |
| 323 | + await sleep(8000); | |
| 324 | + await dismissModals(2); // ferme toute boîte non bloquante en route | |
| 325 | + try { gone = await osaJS(composerGoneJS); } catch (e) { gone = 'ERR'; } | |
| 326 | + if (gone === 'GONE') break; | |
| 327 | + } | |
| 328 | + await dismissModals(3); // ferme la boîte « Plus tard » finale | |
| 329 | + if (gone !== 'GONE') throw new Error('composeur toujours ouvert après Publier (doute — ne pas retenter)'); | |
| 270 | 330 | return true; |
| 271 | 331 | } |
| 272 | 332 | |
| 273 | 333 | |