// app-main.jsx — Générateur de posts NOMADE const { useState, useEffect, useRef, useMemo, useCallback } = React; // ── Field ────────────────────────────────────────────────────────────────── function Field({ label, hint, children }) { return (
{children} {hint &&
{hint}
}
); } // ── URL Importer ─────────────────────────────────────────────────────────── async function fetchViaProxy(url) { const enc = encodeURIComponent(url); const proxies = [ { url: 'https://api.codetabs.com/v1/proxy/?quest=' + enc, json: false }, { url: 'https://api.allorigins.win/get?url=' + enc, json: true }, { url: 'https://api.allorigins.win/raw?url=' + enc, json: false }, ]; for (const p of proxies) { try { const res = await fetch(p.url); if (!res.ok) continue; if (p.json) { const data = await res.json(); if (data?.contents && data.contents.length > 200) return data.contents; } else { const text = await res.text(); if (text && text.length > 200) return text; } } catch (e) { /* try next */ } } throw new Error('Toutes les méthodes de récupération ont échoué'); } async function fetchImageBlob(url) { const enc = encodeURIComponent(url); const proxies = [ 'https://api.codetabs.com/v1/proxy/?quest=' + enc, 'https://api.allorigins.win/raw?url=' + enc, url, ]; for (const p of proxies) { try { const res = await fetch(p); if (!res.ok) continue; const blob = await res.blob(); if (blob.type && (blob.type.startsWith('image/') || blob.size > 1000)) return blob; } catch (e) { /* try next */ } } throw new Error('Image inaccessible'); } function ImportTab({ onPickImage }) { const [url, setUrl] = useState(''); const [loading, setLoading] = useState(false); const [picking, setPicking] = useState(null); const [error, setError] = useState(''); const [images, setImages] = useState([]); const isDirectImageURL = (u) => /\.(jpg|jpeg|png|webp|gif|avif)(\?|#|$)/i.test(u); const runFetch = async () => { const u = url.trim(); if (!u) { setError('Colle une URL.'); return; } setLoading(true); setError(''); setImages([]); try { if (isDirectImageURL(u)) { setImages([{ src: u, label: 'Image directe' }]); setLoading(false); return; } const html = await fetchViaProxy(u); const doc = new DOMParser().parseFromString(html, 'text/html'); const base = doc.querySelector('base')?.href || u; const resolve = (s) => { try { return new URL(s, base).href; } catch { return s; } }; const found = new Map(); const add = (src, label, priority) => { if (!src) return; const r = resolve(src); if (!r.startsWith('http')) return; if (!found.has(r) || found.get(r).priority < priority) { found.set(r, { src: r, label, priority }); } }; doc.querySelectorAll('meta[property^="og:image"], meta[name^="twitter:image"], meta[property^="al:"]').forEach(m => { add(m.getAttribute('content'), 'OG image', 10); }); doc.querySelectorAll('link[rel="image_src"]').forEach(l => add(l.getAttribute('href'), 'link image_src', 9)); doc.querySelectorAll('script[type="application/ld+json"]').forEach(s => { try { const j = JSON.parse(s.textContent); const items = Array.isArray(j) ? j : [j]; items.forEach(it => { const im = it.image; if (typeof im === 'string') add(im, 'JSON-LD', 8); else if (Array.isArray(im)) im.forEach(i => add(typeof i === 'string' ? i : i.url, 'JSON-LD', 8)); else if (im?.url) add(im.url, 'JSON-LD', 8); if (it.thumbnailUrl) add(it.thumbnailUrl, 'thumbnail', 7); }); } catch (e) {} }); doc.querySelectorAll('img').forEach(img => { const s = img.getAttribute('src') || img.getAttribute('data-src') || img.getAttribute('data-original'); add(s, '', 1); const srcset = img.getAttribute('srcset'); if (srcset) { const candidates = srcset.split(',').map(c => c.trim().split(/\s+/)); if (candidates.length) { const largest = candidates.reduce((a, b) => { const aw = parseFloat(a[1]) || 0; const bw = parseFloat(b[1]) || 0; return bw > aw ? b : a; }); add(largest[0], '', 2); } } }); const sorted = [...found.values()] .sort((a, b) => b.priority - a.priority) .filter(i => !/favicon|sprite|emoji|tracker|pixel/i.test(i.src)); if (!sorted.length) { setError('Aucune image trouvée sur cette page.'); } else { setImages(sorted.slice(0, 24)); } } catch (e) { console.error(e); setError('Erreur — l\'URL n\'est peut-être pas accessible. Instagram bloque souvent le scraping; essaie un lien d\'image directe ou un autre site.'); } setLoading(false); }; const pickImage = async (imgUrl) => { setPicking(imgUrl); setError(''); try { const blob = await fetchImageBlob(imgUrl); const reader = new FileReader(); reader.onload = () => { onPickImage(reader.result); setPicking(null); }; reader.onerror = () => { setError('Impossible de lire l\'image.'); setPicking(null); }; reader.readAsDataURL(blob); } catch (e) { setError('Impossible de télécharger cette image — CORS bloque.'); setPicking(null); } }; return (
🔗 Import depuis une URL