SPB Git forge

spb/admin-ka

Public
41commits 1branches 0releases
172.9 MBsize
maindefault branch
19 days agolast push
JavaScript 65.5% Python 17.8% CSS 13% HTML 3.7%

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>
Simon-Pierre Boucher committed 1 mo ago (Aug 23, 2026) parent 0604e62

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