Horlonche

Planificateur de posts, sniper de topics et historique pour Onche.org

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Advertisement:

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

Advertisement:

// ==UserScript==
// @name         Horlonche
// @namespace    http://tampermonkey.net/
// @version      14.0
// @description  Planificateur de posts, sniper de topics et historique pour Onche.org
// @author       Musclor1000
// @match        https://onche.org/topic/*
// @match        https://onche.org/forum/*
// @match        https://onche.org/
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    var estForum = window.location.pathname.indexOf('/forum/') === 0;

    var DELAI_MIN_POSTS = 8000;
    var dernierPost     = 0;
    var postEnCours     = false;
    var fileAttente = [];
    var idCounter   = 0;

    function sauvegarderFile() {
        var data = fileAttente.map(function(item) {
            return { id: item.id, mode: item.mode, titre: item.titre, message: item.message, tsEcheance: item.tsEcheance, label: item.label, cibleInfo: item.cibleInfo || null, pageUrl: item.pageUrl || null };
        }).filter(function(item) { return item.tsEcheance > Date.now(); });
        sessionStorage.setItem('horlonche_file', JSON.stringify(data));
    }

    function restaurerFile() {
        var raw = sessionStorage.getItem('horlonche_file');
        if (!raw) return;
        try {
            var data = JSON.parse(raw);
            for (var i = 0; i < data.length; i++) {
                var item = data[i];
                if (item.tsEcheance > Date.now()) { if (item.id >= idCounter) idCounter = item.id; ajouterFile(item); }
            }
        } catch(e) {}
    }

    var vueActive  = 'liste';
    var modeChoisi = null;

    var panel = document.createElement('div');
    panel.style.cssText = 'position:fixed;bottom:20px;right:20px;background:#1e2a3a;border:1px solid #4a90d9;border-radius:8px;padding:14px;z-index:99999;width:270px;font-family:sans-serif;font-size:13px;color:#eee;box-shadow:0 4px 15px rgba(0,0,0,0.5)';

    panel.innerHTML = ''
        + '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">'
        + '<span style="font-weight:bold;font-size:14px;color:#4a90d9">Horlonche</span>'
        + '<div style="display:flex;gap:4px">'
        + '<button type="button" id="os-btn-sniper" title="Sniper de topic" style="background:none;border:1px solid #e74c3c;color:#e74c3c;border-radius:4px;width:24px;height:24px;cursor:pointer;font-size:13px;line-height:1;padding:0">🎯</button>'
        + '<button type="button" id="os-btn-spear" title="Spear" style="background:none;border:1px solid #7f8c8d;color:#7f8c8d;border-radius:4px;width:24px;height:24px;cursor:pointer;font-size:11px;line-height:1;padding:0;font-weight:bold">🗡</button>'
        + '<button type="button" id="os-btn-plus" title="Ajouter un post" style="background:none;border:1px solid #4a90d9;color:#4a90d9;border-radius:4px;width:24px;height:24px;cursor:pointer;font-size:16px;line-height:1;padding:0">+</button>'
        + '</div>'
        + '</div>'
        + '<div id="os-vue-liste">'
        + '<div id="os-file-vide" style="color:#aaa;font-size:12px;text-align:center;padding:10px 0">Aucun post programme.<br>Clique sur <b>+</b> pour en ajouter un.</div>'
        + '<div id="os-file-liste"></div>'
        + '<div id="os-cooldown" style="margin-top:6px;padding:5px 8px;background:#1a0000;border-left:3px solid #e74c3c;border-radius:3px;font-size:11px;color:#e74c3c;font-family:monospace">&#9888; Delai min entre posts : <b>8s</b> — Cloudflare</div>'
        + '</div>'
        + '<div id="os-vue-form" style="display:none">'
        + '<div id="os-etape1">'
        + '<div style="margin-bottom:8px;color:#aaa;font-size:12px">Que veux-tu faire ?</div>'
        + (estForum
            ? '<button type="button" class="os-mode-btn" data-mode="nouveau" style="width:100%;padding:7px;margin-bottom:6px;background:#2c3e50;color:#eee;border:1px solid #4a90d9;border-radius:4px;cursor:pointer;text-align:left">📝 Creer un nouveau topic</button>'
            : '<button type="button" class="os-mode-btn" data-mode="topic" style="width:100%;padding:7px;margin-bottom:6px;background:#2c3e50;color:#eee;border:1px solid #4a90d9;border-radius:4px;cursor:pointer;text-align:left">💬 Repondre dans le topic</button>'
              + '<button type="button" class="os-mode-btn" data-mode="citation" style="width:100%;padding:7px;margin-bottom:6px;background:#2c3e50;color:#eee;border:1px solid #4a90d9;border-radius:4px;cursor:pointer;text-align:left">↩ Citer / repondre a un message</button>'
          )
        + '<button type="button" id="os-btn-annuler-form" style="width:100%;padding:5px;margin-top:4px;background:#2c3e50;color:#aaa;border:1px solid #555;border-radius:4px;cursor:pointer;font-size:12px">← Retour</button>'
        + '</div>'
        + '<div id="os-etape2" style="display:none">'
        + '<div id="os-label-mode" style="margin-bottom:10px;padding:5px 8px;background:#0d1b2a;border-radius:4px;font-size:12px;color:#4a90d9"></div>'
        + '<div id="os-instruction-citation" style="display:none;margin-bottom:10px;padding:6px;background:#2c1a00;border:1px solid #f39c12;border-radius:4px;font-size:11px;color:#f39c12">⚠ Clique sur la fleche du message AVANT de cliquer "Ajouter"</div>'
        + '<div id="os-champ-titre" style="display:none">'
        + '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">'
        + '<label>Titre du topic</label>'
        + '<label style="font-size:10px;color:#f39c12;cursor:pointer;display:flex;align-items:center;gap:4px"><input type="checkbox" id="os-chk-troll" style="cursor:pointer"> Fantome</label>'
        + '</div>'
        + '<input id="os-titre" type="text" placeholder="Titre..." style="width:100%;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #4a90d9;border-radius:4px;margin-bottom:4px;box-sizing:border-box">'
        + '<div id="os-titre-troll-info" style="display:none;padding:5px 7px;background:#0d1b2a;border-left:3px solid #f39c12;border-radius:3px;font-size:10px;color:#f39c12;margin-bottom:6px">Onche recevra ton emoji seul — slug vide garanti.<br>Laisse vide pour un topic sans titre visible.<br>Ton titre sera poste en 1er message automatiquement.</div>'
        + '</div>'
        + '<div id="os-champ-url-cible" style="margin-bottom:8px">'
        + '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:3px">'
        + '<label style="font-size:12px;color:#aaa">URL cible (optionnel)</label>'
        + '<button type="button" id="os-btn-detecter-url" style="padding:2px 6px;background:none;border:1px solid #4a90d9;color:#4a90d9;border-radius:3px;cursor:pointer;font-size:10px">Auto</button>'
        + '</div>'
        + '<input id="os-url-cible" type="text" placeholder="Laisser vide = page courante" style="width:100%;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #2c3e50;border-radius:4px;font-size:11px;box-sizing:border-box;margin-bottom:4px">'
        + '<div id="os-url-info" style="display:none;padding:5px 7px;background:#0d1b2a;border-left:3px solid #f39c12;border-radius:3px;font-size:10px;color:#f39c12"></div>'
        + '</div>'
        + '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">'
        + '<label>Heure (HH:MM:SS)</label>'
        + '<label style="font-size:11px;color:#aaa;cursor:pointer;display:flex;align-items:center;gap:4px"><input type="checkbox" id="os-chk-jour" style="cursor:pointer"> Jour</label>'
        + '</div>'
        + '<input id="os-heure" type="time" step="1" style="width:100%;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #4a90d9;border-radius:4px;margin-bottom:6px;box-sizing:border-box">'
        + '<div id="os-champ-jour" style="display:none;margin-bottom:6px"><input id="os-jour" type="date" style="width:100%;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #4a90d9;border-radius:4px;box-sizing:border-box"></div>'
        + '<label style="display:block;margin-bottom:4px">Message</label>'
        + '<textarea id="os-message" rows="3" style="width:100%;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #4a90d9;border-radius:4px;margin-bottom:6px;box-sizing:border-box;resize:vertical"></textarea>'
        + '<button type="button" id="os-chk-spear" data-active="0" style="width:100%;padding:4px 8px;margin-bottom:8px;background:#1a0a2e;color:#9b59b6;border:1px solid #9b59b6;border-radius:4px;cursor:pointer;font-size:11px;text-align:left">Spear — OFF</button>'
        + '<button type="button" id="os-btn-preview" style="width:100%;padding:5px;background:#2c3e50;color:#aaa;border:1px solid #4a90d9;border-radius:4px;cursor:pointer;font-size:12px;margin-bottom:8px">👁 Previsualiser</button>'
        + '<button type="button" id="os-btn-start" style="width:100%;padding:7px;background:#4a90d9;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;margin-bottom:6px">Ajouter a la file</button>'
        + '<button type="button" id="os-btn-back" style="width:100%;padding:5px;background:#2c3e50;color:#aaa;border:1px solid #4a90d9;border-radius:4px;cursor:pointer;font-size:12px">← Retour</button>'
        + '</div>'
        + '</div>'
        + '<div id="os-vue-sniper" style="display:none">'
        + '<div style="margin-bottom:8px;color:#e74c3c;font-weight:bold;font-size:13px">🎯 Sniper de topic</div>'
        + '<label style="display:block;margin-bottom:4px;font-size:12px">Forums a scanner</label>'
        + '<div id="os-sniper-forums-liste" style="margin-bottom:4px"></div>'
        + '<div style="display:flex;gap:4px;margin-bottom:8px">'
        + '<input id="os-sniper-url-input" type="text" placeholder="https://onche.org/forum/..." style="flex:1;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #e74c3c;border-radius:4px;font-size:11px;box-sizing:border-box">'
        + '<button type="button" id="os-sniper-url-add" style="padding:5px 8px;background:#e74c3c;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px">+</button>'
        + '</div>'
        + '<label style="display:block;margin-bottom:4px;font-size:12px">Mots cles (separes par virgules)</label>'
        + '<input id="os-sniper-mot" type="text" placeholder="ex: crypto, bonjour..." style="width:100%;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #e74c3c;border-radius:4px;margin-bottom:8px;box-sizing:border-box">'
        + '<label style="display:block;margin-bottom:4px;font-size:12px">Message a poster</label>'
        + '<textarea id="os-sniper-message" rows="3" style="width:100%;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #e74c3c;border-radius:4px;margin-bottom:8px;box-sizing:border-box;resize:vertical"></textarea>'
        + '<label style="display:block;margin-bottom:4px;font-size:12px">Intervalle de scan (secondes)</label>'
        + '<input id="os-sniper-intervalle" type="number" value="10" min="5" max="60" style="width:100%;padding:5px;background:#0d1b2a;color:#eee;border:1px solid #e74c3c;border-radius:4px;margin-bottom:8px;box-sizing:border-box">'
        + '<button type="button" id="os-sniper-start" style="width:100%;padding:7px;background:#e74c3c;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;margin-bottom:6px">Activer le sniper</button>'
        + '<button type="button" id="os-sniper-stop" style="display:none;width:100%;padding:7px;background:#c0392b;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;margin-bottom:6px">Desactiver</button>'
        + '<div style="display:flex;gap:4px">'
        + '<button type="button" id="os-sniper-retour" style="flex:1;padding:5px;background:#2c3e50;color:#aaa;border:1px solid #555;border-radius:4px;cursor:pointer;font-size:12px">← Retour</button>'
        + '<button type="button" id="os-sniper-historique-btn" style="padding:5px 8px;background:#2c3e50;color:#f39c12;border:1px solid #f39c12;border-radius:4px;cursor:pointer;font-size:12px">📋</button>'
        + '</div>'
        + '<div id="os-sniper-status" style="margin-top:8px;font-size:11px;color:#aaa;text-align:center"></div>'
        + '<div id="os-sniper-log" style="margin-top:6px;max-height:80px;overflow-y:auto;font-size:10px;color:#666"></div>'
        + '</div>'
        + '<div id="os-vue-spear" style="display:none">'
        + '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">'
        + '<span style="font-weight:bold;font-size:13px;color:#9b59b6">Spear</span>'
        + '<button type="button" id="os-spear-clear" style="padding:3px 8px;background:none;border:1px solid #c0392b;color:#c0392b;border-radius:3px;cursor:pointer;font-size:11px">Vider</button>'
        + '</div>'
        + '<div style="padding:6px 8px;background:#1a0a2e;border:1px solid #9b59b6;border-radius:4px;margin-bottom:10px;font-size:11px;color:#d7bde2">'
        + '<div style="margin-bottom:4px;font-size:10px;color:#9b59b6;font-weight:bold">Protocole actif</div>'
        + 'Les messages chiffres Spear sont decodes automatiquement. Les autres voient le Braille.'
        + '</div>'
        + '<div style="margin-bottom:6px;font-size:11px;color:#aaa">Messages Spear detectes :</div>'
        + '<div id="os-spear-historique" style="max-height:280px;overflow-y:auto"></div>'
        + '<button type="button" id="os-spear-retour" style="width:100%;padding:5px;margin-top:8px;background:#2c3e50;color:#aaa;border:1px solid #555;border-radius:4px;cursor:pointer;font-size:12px">&#8592; Retour</button>'
        + '</div>'
        + '<div id="os-vue-historique" style="display:none">'
        + '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">'
        + '<span style="font-weight:bold;font-size:13px;color:#f39c12">📋 Historique</span>'
        + '<button type="button" id="os-historique-clear" style="padding:3px 8px;background:none;border:1px solid #c0392b;color:#c0392b;border-radius:3px;cursor:pointer;font-size:11px">Vider</button>'
        + '</div>'
        + '<div style="display:flex;gap:4px;margin-bottom:8px">'
        + '<button type="button" id="os-hist-tab-tracking" style="flex:1;padding:5px;background:#4a90d9;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px">Tracking (0)</button>'
        + '<button type="button" id="os-hist-tab-posting" style="flex:1;padding:5px;background:#2c3e50;color:#aaa;border:1px solid #2ecc71;border-radius:4px;cursor:pointer;font-size:11px">Posting (0)</button>'
        + '</div>'
        + '<div id="os-historique-liste" style="max-height:260px;overflow-y:auto"></div>'
        + '<button type="button" id="os-historique-retour" style="width:100%;padding:5px;margin-top:8px;background:#2c3e50;color:#aaa;border:1px solid #555;border-radius:4px;cursor:pointer;font-size:12px">← Retour</button>'
        + '</div>'
        + '</div>';

    document.body.appendChild(panel);
    restaurerFile();

    function normaliserUrlTopic(url) {
        if (!url) return window.location.href.split('#')[0];
        if (url.indexOf('http') !== 0) url = 'https://onche.org' + url;
        var ancre = '';
        var urlSansAncre = url;
        var hashIdx = url.indexOf('#');
        if (hashIdx !== -1) {
            var ancreVal = url.substring(hashIdx + 1);
            urlSansAncre = url.substring(0, hashIdx);
            if (/^message_\d+$/.test(ancreVal)) ancre = '#' + ancreVal;
            else if (ancreVal.indexOf('http') !== -1 || ancreVal.length > 50) ancre = '';
            else ancre = '#' + ancreVal;
        }
        var mId = urlSansAncre.match(/onche\.org\/topic\/(\d+)/);
        if (!mId) return url;
        var topicId = mId[1];
        var reste   = urlSansAncre.split('/topic/' + topicId)[1] || '';
        var parties = reste.split('/');
        var slug    = parties.length > 1 ? parties[1] : '';
        var page    = parties.length > 2 ? parties[2] : '';
        var slugVisible = slug;
        try { slugVisible = decodeURIComponent(slug); } catch(e) { return 'https://onche.org/topic/' + topicId + '/' + ancre; }
        slugVisible = slugVisible.replace(/[\u0000-\u001F\u007F-\u009F\u200B-\u200D\uFEFF\u00AD\u00A0\u2028\u2029]/g, '').trim();
        if (!slugVisible) return 'https://onche.org/topic/' + topicId + '/' + ancre;
        if (slugVisible.length > 120) slugVisible = slugVisible.substring(0, 120);
        var pageNum = 1;
        if (page) { pageNum = parseInt(page) || 1; if (pageNum < 1) pageNum = 1; if (pageNum > 9999) pageNum = 1; }
        return 'https://onche.org/topic/' + topicId + '/' + slugVisible + '/' + pageNum + ancre;
    }

    setTimeout(function() {
        if (window.location.href.indexOf('/topic/') === -1) return;
        try {
            var urlCorrigee = normaliserUrlTopic(window.location.href);
            if (urlCorrigee !== window.location.href && urlCorrigee !== window.location.href.split('#')[0]) window.location.replace(urlCorrigee);
        } catch(e) {}
        function corrigerLiens() {
            document.querySelectorAll('a[href*="/topic/"]').forEach(function(a) {
                var href = a.getAttribute('href');
                if (!href) return;
                var abs  = href.indexOf('http') === 0 ? href : 'https://onche.org' + href;
                var norm = normaliserUrlTopic(abs);
                if (norm !== abs) { a.setAttribute('href', norm); a.title = 'Lien corrige : ' + norm; }
            });
        }
        corrigerLiens();
        var debounce = null;
        new MutationObserver(function() { if (debounce) clearTimeout(debounce); debounce = setTimeout(corrigerLiens, 300); }).observe(document.body, { childList: true, subtree: false });
    }, 100);

    function detecterInfosTopic() {
        var url  = window.location.href;
        var info = document.getElementById('os-url-info');
        var inp  = document.getElementById('os-url-cible');
        if (!info || !inp) return;
        var m = url.match(/onche\.org\/topic\/(\d+)\/([^\/]*)\/?(\d*)/);
        if (!m) { info.style.display = 'none'; return; }
        var topicId = m[1], slug = m[2] || '', page = m[3] || '1';
        var messages = [];
        if (!slug || slug.length === 0) {
            messages.push('⚠ Troll #1 : slug vide !');
            inp.value = 'https://onche.org/topic/' + topicId + '/';
            var pc = parseInt(page) || 1;
            messages.push('Page ' + pc + ' — suivante : https://onche.org/topic/' + topicId + '//' + (pc + 1));
        }
        if (messages.length > 0) {
            info.style.display = 'block'; info.style.borderLeftColor = '#f39c12'; info.style.color = '#f39c12';
            info.innerHTML = messages.join('<br>');
        } else {
            var pn = parseInt(page) || 1;
            info.style.display = 'block'; info.style.borderLeftColor = '#2ecc71'; info.style.color = '#2ecc71';
            info.innerHTML = '✓ Normal — page ' + pn + ' — suivante : https://onche.org/topic/' + topicId + '/' + slug + '/' + (pn+1);
        }
    }

    function getUrlCible() {
        var inp = document.getElementById('os-url-cible');
        if (inp && inp.value.trim()) {
            var url = inp.value.trim();
            if (url.indexOf('https://onche.org/') !== 0) { inp.style.borderColor = '#e74c3c'; return window.location.href.split('#')[0]; }
            inp.style.borderColor = '#2ecc71';
            return url;
        }
        return window.location.href.split('#')[0];
    }

    setTimeout(function() { var btnAuto = document.getElementById('os-btn-detecter-url'); if (btnAuto) btnAuto.addEventListener('click', detecterInfosTopic); }, 350);

    var sniperTimer = null, sniperActif = false, sniperTopicsVus = {};
    try { var raw = sessionStorage.getItem('horlonche_sniper_vus'); if (raw) sniperTopicsVus = JSON.parse(raw); } catch(e) {}
    function sauvegarderTopicsVus() { try { sessionStorage.setItem('horlonche_sniper_vus', JSON.stringify(sniperTopicsVus)); } catch(e) {} }

    var sniperForums = ['https://onche.org/forum/1/blabla-general'];
    function afficherForums() {
        var liste = document.getElementById('os-sniper-forums-liste');
        if (!liste) return;
        liste.innerHTML = '';
        sniperForums.forEach(function(url, idx) {
            var ligne = document.createElement('div');
            ligne.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:4px;background:#0d1b2a;border:1px solid #3a2000;border-radius:4px;padding:3px 6px';
            var nom = url.replace('https://onche.org/forum/','').replace(/\/.*/,'');
            nom = ({1:'Blabla General',4:'Goulag',7:'Finance',8:'Jeux Video',9:'Autonomie',13:'Videoclub'})[nom] || url.split('/').pop();
            ligne.innerHTML = '<span style="flex:1;color:#eee;font-size:11px">' + nom + '</span><button type="button" data-idx="' + idx + '" style="background:none;border:none;color:#c0392b;cursor:pointer;font-size:13px;padding:0 2px">✕</button>';
            ligne.querySelector('button').addEventListener('click', function() { sniperForums.splice(parseInt(this.getAttribute('data-idx')),1); afficherForums(); });
            liste.appendChild(ligne);
        });
    }
    setTimeout(function() {
        afficherForums();
        document.getElementById('os-sniper-url-add').addEventListener('click', function() {
            var url = document.getElementById('os-sniper-url-input').value.trim();
            if (!url) return;
            if (url.indexOf('onche.org/forum/') === -1) { alert('URL invalide'); return; }
            if (sniperForums.indexOf(url) !== -1) { alert('Forum deja dans la liste'); return; }
            sniperForums.push(url); document.getElementById('os-sniper-url-input').value = ''; afficherForums();
        });
        document.getElementById('os-sniper-url-input').addEventListener('keydown', function(e) { if (e.key==='Enter') document.getElementById('os-sniper-url-add').click(); });
    }, 400);

    function ajouterHistorique(type, data) {
        var today = new Date().toLocaleDateString();
        var raw   = localStorage.getItem('horlonche_historique_' + today);
        var liste = [];
        try { if (raw) liste = JSON.parse(raw); } catch(e) {}
        liste.unshift(Object.assign({ heure: new Date().toLocaleTimeString(), type: type }, data));
        if (liste.length > 500) liste = liste.slice(0, 500);
        localStorage.setItem('horlonche_historique_' + today, JSON.stringify(liste));
    }

    var ongletHistActif = 'tracking';
    function chargerEntrees() {
        var entrees = [];
        for (var d = 0; d < 7; d++) {
            var date = new Date(); date.setDate(date.getDate() - d);
            var raw = localStorage.getItem('horlonche_historique_' + date.toLocaleDateString());
            try { if (raw) { var items = JSON.parse(raw); items.forEach(function(i){i.date=date.toLocaleDateString();}); entrees = entrees.concat(items); } } catch(e) {}
        }
        return entrees;
    }

    function afficherHistorique() {
        var liste = document.getElementById('os-historique-liste');
        if (!liste) return;
        liste.innerHTML = '';
        var entrees  = chargerEntrees();
        var filtrees = entrees.filter(function(e) { return ongletHistActif==='tracking' ? e.type==='trouve' : (e.type==='poste'||e.type==='erreur'); });
        var nbT = entrees.filter(function(e){return e.type==='trouve';}).length;
        var nbP = entrees.filter(function(e){return e.type==='poste'||e.type==='erreur';}).length;
        var btnT = document.getElementById('os-hist-tab-tracking');
        var btnP = document.getElementById('os-hist-tab-posting');
        if (btnT) btnT.textContent = 'Tracking (' + nbT + ')';
        if (btnP) btnP.textContent = 'Posting ('  + nbP + ')';
        if (filtrees.length === 0) { liste.innerHTML = '<div style="color:#aaa;text-align:center;padding:20px;font-size:12px">Aucune entree</div>'; return; }
        filtrees.forEach(function(item) {
            var ligne = document.createElement('div');
            ligne.style.cssText = 'padding:7px 8px;margin-bottom:5px;background:#0d1b2a;border-radius:4px;font-size:11px;' + (item.type==='trouve'?'border-left:3px solid #4a90d9':item.type==='poste'?'border-left:3px solid #2ecc71':'border-left:3px solid #e74c3c');
            var html = '<div style="display:flex;justify-content:space-between;margin-bottom:4px"><span style="color:#888">' + item.date + ' ' + item.heure + '</span></div>';
            if (item.type==='trouve') {
                html += '<div style="color:#4a90d9;font-weight:bold;margin-bottom:3px">' + (item.titre||'(sans titre)') + '</div>';
                if (item.auteur) html += '<div style="color:#aaa">Auteur : <span style="color:#eee">' + item.auteur + '</span></div>';
                if (item.topicUrl) html += '<a href="' + item.topicUrl + '" target="_blank" style="color:#4a90d9;text-decoration:none;font-size:10px">Voir →</a>';
            } else {
                html += '<div style="color:#aaa">Topic : <a href="' + (item.topicUrl||'#') + '" target="_blank" style="color:#2ecc71;text-decoration:none">' + (item.topicTitre||'?') + '</a></div>';
                if (item.message) html += '<div style="color:#eee;background:#111;padding:4px 6px;border-radius:3px;margin-top:3px">' + item.message.substring(0,100) + '</div>';
            }
            ligne.innerHTML = html;
            liste.appendChild(ligne);
        });
    }

    document.getElementById('os-sniper-historique-btn').addEventListener('click', function() { document.getElementById('os-vue-sniper').style.display='none'; document.getElementById('os-vue-historique').style.display='block'; afficherHistorique(); });
    document.getElementById('os-historique-retour').addEventListener('click', function() { document.getElementById('os-vue-historique').style.display='none'; document.getElementById('os-vue-sniper').style.display='block'; });
    document.getElementById('os-hist-tab-tracking').addEventListener('click', function() { ongletHistActif='tracking'; this.style.background='#4a90d9'; this.style.color='#fff'; this.style.border='none'; var p=document.getElementById('os-hist-tab-posting'); p.style.background='#2c3e50'; p.style.color='#aaa'; p.style.border='1px solid #2ecc71'; afficherHistorique(); });
    document.getElementById('os-hist-tab-posting').addEventListener('click', function() { ongletHistActif='posting'; this.style.background='#2ecc71'; this.style.color='#fff'; this.style.border='none'; var t=document.getElementById('os-hist-tab-tracking'); t.style.background='#2c3e50'; t.style.color='#aaa'; t.style.border='1px solid #4a90d9'; afficherHistorique(); });
    document.getElementById('os-historique-clear').addEventListener('click', function() { if (!confirm("Vider l'historique ?")) return; for (var d=0;d<7;d++){var date=new Date();date.setDate(date.getDate()-d);localStorage.removeItem('horlonche_historique_'+date.toLocaleDateString());} afficherHistorique(); });

    function restaurerSniper() {
        var raw = sessionStorage.getItem('horlonche_sniper_config');
        if (!raw) return;
        try {
            var config = JSON.parse(raw);
            if (!config.actif) return;
            document.getElementById('os-sniper-mot').value        = config.mot;
            document.getElementById('os-sniper-message').value    = config.message;
            document.getElementById('os-sniper-intervalle').value = config.intervalle;
            if (config.forums && Array.isArray(config.forums)) { sniperForums = config.forums; afficherForums(); }
            document.getElementById('os-sniper-start').click();
        } catch(e) {}
    }
    setTimeout(restaurerSniper, 300);

    document.getElementById('os-btn-sniper').addEventListener('click', function() { document.getElementById('os-vue-liste').style.display='none'; document.getElementById('os-vue-form').style.display='none'; document.getElementById('os-vue-sniper').style.display='block'; });
    document.getElementById('os-sniper-retour').addEventListener('click', function() { document.getElementById('os-vue-sniper').style.display='none'; document.getElementById('os-vue-liste').style.display='block'; });
    document.getElementById('os-sniper-start').addEventListener('click', function() {
        var mot = document.getElementById('os-sniper-mot').value.trim().toLowerCase().replace(/\s*,\s*/g,',');
        var message = document.getElementById('os-sniper-message').value.trim();
        var intervalle = parseInt(document.getElementById('os-sniper-intervalle').value) || 10;
        var scanUrl = sniperForums.length > 0 ? sniperForums : ['https://onche.org/forum/1/blabla-general'];
        if (!mot) { alert('Ecris un mot cle !'); return; }
        sniperActif = true;
        document.getElementById('os-sniper-start').style.display = 'none';
        document.getElementById('os-sniper-stop').style.display  = 'block';
        setSniperStatus('Actif — scan toutes les ' + intervalle + 's', '#2ecc71');
        sessionStorage.setItem('horlonche_sniper_config', JSON.stringify({actif:true,mot:mot,message:message,intervalle:intervalle,scanUrl:scanUrl,forums:sniperForums}));
        var forumsAScan = Array.isArray(scanUrl) ? scanUrl : [scanUrl];
        forumsAScan.forEach(function(url) { scannerTopics(mot, message, url); });
        sniperTimer = setInterval(function() { forumsAScan.forEach(function(url) { scannerTopics(mot,message,url); }); }, intervalle*1000);
    });
    document.getElementById('os-sniper-stop').addEventListener('click', function() {
        if (sniperTimer) clearInterval(sniperTimer);
        sniperTimer = null; sniperActif = false;
        document.getElementById('os-sniper-start').style.display = 'block';
        document.getElementById('os-sniper-stop').style.display  = 'none';
        setSniperStatus('Inactif', '#aaa');
        sessionStorage.removeItem('horlonche_sniper_config');
    });

    function scannerTopics(mot, message, scanUrl) {
        fetch(scanUrl||'https://onche.org/forum/1/blabla-general', {credentials:'include'}).then(function(r){return r.text();}).then(function(html) {
            var doc = new DOMParser().parseFromString(html,'text/html');
            doc.querySelectorAll('a.topic-subject, a[href*="/topic/"]').forEach(function(lien) {
                var titre = lien.textContent.trim().toLowerCase();
                var href  = lien.getAttribute('href');
                if (!href || href.indexOf('/topic/')===-1) return;
                var matchId = href.match(/\/topic\/(\d+)\//);
                if (!matchId) return;
                var topicId = matchId[1];
                if (sniperTopicsVus[topicId]) return;
                var mots = mot.split(',').map(function(m){return m.trim();}).filter(Boolean);
                if (!mots.some(function(m){return titre.indexOf(m)!==-1;})) return;
                sniperTopicsVus[topicId] = true; sauvegarderTopicsVus();
                ajouterLogSniper('🎯 ' + lien.textContent.trim().substring(0,40), 'trouve');
                ajouterHistorique('trouve',{titre:lien.textContent.trim(),auteur:'',forum:scanUrl,topicUrl:'https://onche.org'+href});
                posterDansTopicSniper(href, message, topicId);
            });
            setSniperStatus('Actif — dernier scan : ' + new Date().toLocaleTimeString(), '#2ecc71');
        }).catch(function() { setSniperStatus('Erreur de scan','#e74c3c'); });
    }

    function posterDansTopicSniper(topicHref, message, topicId) {
        var topicUrl = topicHref.indexOf('https://')===0 ? topicHref : 'https://onche.org' + topicHref.replace(/\/\d+$/,'/1');
        fetch(topicUrl,{credentials:'include'}).then(function(r){return r.text();}).then(function(html) {
            var match = html.match(/name="token"[^>]*value="([^"]+)"/) || html.match(/value="([^"]+)"[^>]*name="token"/) || html.match(/data-token="([^"]+)"/);
            var token = match ? match[1] : '';
            if (!token) { ajouterLogSniper('❌ Token introuvable #'+topicId,'erreur'); return; }
            var body = new URLSearchParams();
            body.append('message',message); body.append('poll_question',''); body.append('poll[]',''); body.append('token',token);
            return fetch(topicUrl.split('#')[0]+'#last',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body.toString(),credentials:'include'});
        }).then(function(r) {
            if (r&&(r.ok||r.redirected)) { ajouterLogSniper('✅ Poste #'+topicId,'poste'); ajouterHistorique('poste',{topicUrl:topicUrl,topicTitre:'Topic #'+topicId,message:message}); }
            else if (r) ajouterLogSniper('❌ Erreur '+r.status+' #'+topicId,'erreur');
        }).catch(function() { ajouterLogSniper('❌ Reseau #'+topicId,'erreur'); });
    }

    function setSniperStatus(t,c) { var el=document.getElementById('os-sniper-status'); if(el){el.textContent=t;el.style.color=c||'#aaa';} }
    function ajouterLogSniper(texte, type) {
        var log = document.getElementById('os-sniper-log');
        if (log) {
            var l=document.createElement('div');
            l.style.color=type==='poste'?'#2ecc71':type==='erreur'?'#e74c3c':type==='trouve'?'#4a90d9':'#888';
            l.textContent=new Date().toLocaleTimeString()+' — '+texte;
            log.insertBefore(l,log.firstChild);
            while(log.children.length>20) log.removeChild(log.lastChild);
        }
    }

    var messageCibleCapture = null;
    document.addEventListener('click', function(e) {
        var fleche = e.target.closest('[data-message-quote]');
        if (!fleche) return;
        var messageEl = fleche.closest('[data-id]');
        if (!messageEl) return;
        messageCibleCapture = { messageId: messageEl.getAttribute('data-id'), username: messageEl.getAttribute('data-username') || '' };
    }, true);

    var previewBox = document.createElement('div');
    previewBox.style.cssText = 'display:none;position:fixed;bottom:20px;right:310px;background:#1e2a3a;border:1px solid #4a90d9;border-radius:8px;padding:14px;z-index:99998;width:320px;max-height:280px;font-family:sans-serif;font-size:14px;color:#eee;box-shadow:0 4px 15px rgba(0,0,0,0.5);overflow-y:auto;word-break:break-word;white-space:pre-wrap;line-height:1.6';
    document.body.appendChild(previewBox);
    var previewOuvert = false;

    document.getElementById('os-btn-preview').addEventListener('click', function() {
        if (!previewOuvert) {
            majPreview();
            previewBox.style.display = 'block';
            this.textContent = '✕ Fermer la previsualisation'; this.style.color = '#4a90d9';
            previewOuvert = true;
            document.getElementById('os-message').addEventListener('input', majPreview);
            var chkSpear = document.getElementById('os-chk-spear');
            if (chkSpear) chkSpear.addEventListener('click', majPreview);
        } else {
            previewBox.style.display = 'none';
            this.textContent = '👁 Previsualiser'; this.style.color = '#aaa';
            previewOuvert = false;
            document.getElementById('os-message').removeEventListener('input', majPreview);
        }
    });

    function majPreview() {
        var msg      = document.getElementById('os-message').value || '';
        var chkSpear = document.getElementById('os-chk-spear');
        var useSpear = chkSpear && chkSpear.getAttribute('data-active') === '1';
        if (useSpear) {
            var braillePreview = msg.trim() ? spearEncodeTexte(msg) : '';
            previewBox.innerHTML = ''
                + '<div style="font-size:9px;color:#9b59b6;margin-bottom:4px;font-weight:bold">Ton message (membres Spear) :</div>'
                + '<div style="color:#d7bde2;background:#1a0a2e;padding:5px;border-radius:3px;margin-bottom:8px;font-size:12px">' + escHtml(msg || '(vide)') + '</div>'
                + '<div style="font-size:9px;color:#888;margin-bottom:4px">Ce que les autres voient sur Onche :</div>'
                + '<div style="color:#f39c12;background:#1a0a00;padding:5px;border-radius:3px;font-size:12px;word-break:break-all">' + escHtml(braillePreview || '(vide)') + '</div>';
        } else {
            previewBox.innerHTML = '<div style="color:#eee;font-size:13px;white-space:pre-wrap">' + escHtml(msg || '(message vide)') + '</div>';
        }
    }

    document.getElementById('os-btn-plus').addEventListener('click', function() { document.getElementById('os-vue-liste').style.display='none'; document.getElementById('os-vue-form').style.display='block'; document.getElementById('os-etape1').style.display='block'; document.getElementById('os-etape2').style.display='none'; vueActive='formulaire'; });
    document.getElementById('os-btn-annuler-form').addEventListener('click', retourListe);
    document.getElementById('os-btn-back').addEventListener('click', function() { document.getElementById('os-etape2').style.display='none'; document.getElementById('os-etape1').style.display='block'; modeChoisi=null; });

    function retourListe() {
        document.getElementById('os-vue-form').style.display  = 'none';
        document.getElementById('os-vue-liste').style.display = 'block';
        vueActive = 'liste'; modeChoisi = null;
        var btnSp = document.getElementById('os-chk-spear'); if(btnSp) btnSp.style.display='';
        if (previewOuvert) document.getElementById('os-btn-preview').click();
    }

    var modeBtns = panel.querySelectorAll('.os-mode-btn');
    for (var i = 0; i < modeBtns.length; i++) {
        modeBtns[i].addEventListener('click', function() {
            modeChoisi = this.getAttribute('data-mode');
            document.getElementById('os-etape1').style.display = 'none';
            document.getElementById('os-etape2').style.display = 'block';
            try { detecterInfosTopic(); } catch(e) {}
            var labels = {'topic':'Repondre dans le topic','citation':'Citer un message','nouveau':'Nouveau topic'};
            document.getElementById('os-label-mode').textContent = '→ ' + labels[modeChoisi];
            document.getElementById('os-instruction-citation').style.display = (modeChoisi==='citation') ? 'block' : 'none';
            document.getElementById('os-champ-titre').style.display           = (modeChoisi==='nouveau')  ? 'block' : 'none';
            var btnSp = document.getElementById('os-chk-spear');
            if(btnSp) btnSp.style.display = (modeChoisi==='citation') ? 'none' : '';
        });
    }

    document.getElementById('os-chk-jour').addEventListener('change', function() {
        var champ = document.getElementById('os-champ-jour');
        if (this.checked) { champ.style.display='block'; var auj=new Date(); document.getElementById('os-jour').value=auj.getFullYear()+'-'+pad(auj.getMonth()+1)+'-'+pad(auj.getDate()); }
        else { champ.style.display='none'; document.getElementById('os-jour').value=''; }
    });

    setTimeout(function() {
        var btnSpear = document.getElementById('os-chk-spear');
        if (btnSpear) {
            btnSpear.addEventListener('click', function() {
                var actif = this.getAttribute('data-active') === '1';
                if (actif) {
                    this.setAttribute('data-active', '0'); this.style.background='#1a0a2e'; this.style.color='#9b59b6'; this.textContent='Spear — OFF';
                    var msgArea = document.getElementById('os-message');
                    if (msgArea) { msgArea.style.borderColor='#4a90d9'; msgArea.placeholder=''; }
                } else {
                    this.setAttribute('data-active', '1'); this.style.background='#9b59b6'; this.style.color='#fff'; this.textContent='Spear — ON';
                    var msgArea = document.getElementById('os-message');
                    if (msgArea) { msgArea.style.borderColor='#9b59b6'; msgArea.placeholder='Message secret (chiffre en Braille)...'; }
                }
                majPreview();
            });
        }
    }, 300);

    setTimeout(function() {
        var chk = document.getElementById('os-chk-troll');
        if (!chk) return;
        chk.addEventListener('change', function() {
            var info=document.getElementById('os-titre-troll-info'), titre=document.getElementById('os-titre');
            if (!info||!titre) return;
            if (this.checked) { info.style.display='block'; titre.style.borderColor='#f39c12'; titre.placeholder='Titre visible dans le 1er message...'; }
            else               { info.style.display='none';  titre.style.borderColor='#4a90d9'; titre.placeholder='Titre...'; }
        });
    }, 400);

    document.getElementById('os-btn-start').addEventListener('click', function() {
        var heureVal    = document.getElementById('os-heure').value;
        var useSpearNow = document.getElementById('os-chk-spear') && document.getElementById('os-chk-spear').getAttribute('data-active') === '1';
        var messageVal;
        if (useSpearNow) {
            var msgSecretVal = document.getElementById('os-message').value || '';
            messageVal = msgSecretVal.trim() ? spearEncodeTexte(msgSecretVal) : '\u200B';
        } else {
            messageVal = document.getElementById('os-message').value;
        }
        if (!messageVal || messageVal.trim() === '') messageVal = '\u200B';
        var titreEl   = document.getElementById('os-titre');
        var titreVal  = titreEl ? titreEl.value.trim() : '';
        var jourVal   = document.getElementById('os-jour').value;
        var modeTroll = document.getElementById('os-chk-troll') && document.getElementById('os-chk-troll').checked;
        var vraiTitre = null;
        if (!heureVal) { alert('Choisis une heure !'); return; }
        if (modeChoisi === 'nouveau' && !titreVal && !modeTroll) { alert('Ecris un titre !'); return; }
        if (modeTroll) { vraiTitre=titreVal; if (!titreVal||titreVal.trim()==='') titreVal='\u200B'; }
        var parts = heureVal.split(':');
        var cible = new Date();
        if (jourVal) { var jp=jourVal.split('-'); cible.setFullYear(parseInt(jp[0]),parseInt(jp[1])-1,parseInt(jp[2])); }
        cible.setHours(parseInt(parts[0]),parseInt(parts[1]),parseInt(parts[2]||0),0);
        if (!jourVal && cible.getTime() <= Date.now()) cible.setDate(cible.getDate()+1);
        var labels = {'topic':'💬','citation':'↩','nouveau':'📝'};
        var labelJour = jourVal ? jourVal.split('-').reverse().join('/')+' ' : '';
        var labelAffiche = labels[modeChoisi]+' '+labelJour+heureVal;
        if (modeChoisi==='nouveau' && titreVal) labelAffiche += ' — '+titreVal.substring(0,20);
        if (modeChoisi==='citation' && messageCibleCapture) labelAffiche += ' (@'+(messageCibleCapture.username||'?')+')';
        var cibleInfoPourCetItem = (modeChoisi==='citation') ? messageCibleCapture : null;
        messageCibleCapture = null;
        if (modeChoisi==='citation' && !cibleInfoPourCetItem) { alert('Aucun message cible !'); return; }
        ajouterFile({ id:++idCounter, mode:modeChoisi, titre:titreVal, vraiTitre:vraiTitre||null, message:messageVal, tsEcheance:cible.getTime(), label:labelAffiche, heure:heureVal, pageUrl:window.location.href.split('#')[0], cibleInfo:cibleInfoPourCetItem });
        document.getElementById('os-heure').value = '';
        document.getElementById('os-message').value = '';
        if (titreEl) titreEl.value = '';
        document.getElementById('os-chk-jour').checked = false;
        document.getElementById('os-champ-jour').style.display = 'none';
        document.getElementById('os-jour').value = '';
        retourListe();
    });

    function ajouterFile(item) {
        fileAttente.push(item);
        afficherFile(); sauvegarderFile();
        item.timer = setInterval(function() {
            var resteMs = item.tsEcheance - Date.now();
            if (resteMs <= 0) { clearInterval(item.timer); executerItem(item); return; }
            var el = document.getElementById('os-countdown-'+item.id);
            if (el) { var s=Math.floor(resteMs/1000); el.textContent=pad(Math.floor(s/3600))+':'+pad(Math.floor((s%3600)/60))+':'+pad(s%60); }
        }, 200);
    }

    function supprimerFile(id) {
        for (var i=0;i<fileAttente.length;i++) { if (fileAttente[i].id===id) { clearInterval(fileAttente[i].timer); fileAttente.splice(i,1); break; } }
        afficherFile(); sauvegarderFile();
    }

    function afficherFile() {
        var liste=document.getElementById('os-file-liste'), vide=document.getElementById('os-file-vide');
        liste.style.cssText='max-height:220px;overflow-y:auto';
        if (fileAttente.length===0) { vide.style.display='block'; liste.innerHTML=''; return; }
        vide.style.display='none'; liste.innerHTML='';
        for (var i=0;i<fileAttente.length;i++) {
            (function(item) {
                var apercu = (item.message||'').replace(/[\u2800-\u28FF\u200B-\u200D\uFEFF\u00AD]/g,'').substring(0,40)+(item.message.length>40?'...':'');
                var ligne=document.createElement('div');
                ligne.style.cssText='padding:6px 8px;margin-bottom:5px;background:#0d1b2a;border:1px solid #2a3f55;border-radius:4px;font-size:12px';
                ligne.innerHTML='<div style="display:flex;align-items:center;justify-content:space-between"><span style="color:#eee;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:160px">'+item.label+'</span><div style="display:flex;align-items:center;gap:6px"><span id="os-countdown-'+item.id+'" style="color:#4a90d9;font-size:11px;font-weight:bold">--:--:--</span><button type="button" data-id="'+item.id+'" style="background:none;border:1px solid #c0392b;color:#c0392b;border-radius:3px;cursor:pointer;font-size:11px;padding:2px 5px">✕</button></div></div><div style="color:#8aa8c8;font-size:11px;margin-top:3px;font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+apercu+'</div>';
                ligne.querySelector('button').addEventListener('click', function() { supprimerFile(parseInt(this.getAttribute('data-id'))); });
                liste.appendChild(ligne);
            })(fileAttente[i]);
        }
    }

    function executerItem(item) {
        var el = document.getElementById('os-countdown-'+item.id);
        if (el) { el.textContent='Envoi...'; el.style.color='#f39c12'; }
        if (item.mode==='citation') posterAvecFetch(item,true);
        else if (item.mode==='nouveau') posterNouveauTopic(item);
        else posterAvecFetch(item,false);
    }

    function terminerItem(id, succes, msg) {
        var el = document.getElementById('os-countdown-'+id);
        if (el) { el.textContent=msg; el.style.color=succes?'#2ecc71':'#e74c3c'; }
        if (succes) demarrerCooldownAffichage();
        setTimeout(function() { for (var i=0;i<fileAttente.length;i++) { if(fileAttente[i].id===id){fileAttente.splice(i,1);break;} } afficherFile(); sauvegarderFile(); }, 3000);
    }

    var cooldownInterval = null;
    function demarrerCooldownAffichage() {
        var cdEl = document.getElementById('os-cooldown');
        if (!cdEl) return;
        if (cooldownInterval) clearInterval(cooldownInterval);
        cooldownInterval = setInterval(function() {
            var reste = Math.ceil((DELAI_MIN_POSTS-(Date.now()-dernierPost))/1000);
            if (reste<=0) { cdEl.innerHTML='&#9888; Delai min entre posts : <b>8s</b> — Cloudflare'; clearInterval(cooldownInterval); cooldownInterval=null; }
            else           cdEl.innerHTML='&#9888; Prochain post dans <b>'+reste+'s</b> — Cloudflare actif';
        }, 250);
    }

    function posterAvecFetch(item, estCitation) {
        if (postEnCours) { terminerItem(item.id,false,'Post en cours, attends !'); return; }
        if ((Date.now()-dernierPost) < DELAI_MIN_POSTS && dernierPost>0) { var att=Math.ceil((DELAI_MIN_POSTS-(Date.now()-dernierPost))/1000); terminerItem(item.id,false,'Trop rapide ! Attends '+att+'s'); return; }
        postEnCours = true;
        var failsafe = setTimeout(function(){if(postEnCours)postEnCours=false;},30000);
        var pageUrl  = normaliserUrlTopic((item.pageUrl||getUrlCible()).split('#')[0]);
        fetch(pageUrl,{credentials:'include'}).then(function(r){return r.text();}).then(function(html) {
            var match = html.match(/name="token"[^>]*value="([^"]+)"/) || html.match(/value="([^"]+)"[^>]*name="token"/) || html.match(/data-token="([^"]+)"/);
            var token = match ? match[1] : '';
            if (!token||token.length<10) { clearTimeout(failsafe); postEnCours=false; terminerItem(item.id,false,'Token invalide !'); return; }
            var body = new URLSearchParams();
            if (estCitation&&item.cibleInfo&&item.cibleInfo.messageId) { var mid=parseInt(item.cibleInfo.messageId); if(mid>0) body.append('answer',mid); }
            body.append('message',item.message); body.append('poll_question',''); body.append('poll[]',''); body.append('token',token);
            return fetch(pageUrl+'#last',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body.toString(),credentials:'include'});
        }).then(function(r) {
            clearTimeout(failsafe); postEnCours=false;
            if (r&&(r.ok||r.redirected)) { dernierPost=Date.now(); terminerItem(item.id,true,estCitation?'Citation postee !':'Poste !'); }
            else if (r) { if(r.status===429){terminerItem(item.id,false,'Rate limit 429 !');DELAI_MIN_POSTS=Math.min(DELAI_MIN_POSTS+5000,30000);}else terminerItem(item.id,false,'Erreur '+r.status); }
        }).catch(function(){clearTimeout(failsafe);postEnCours=false;terminerItem(item.id,false,'Erreur reseau !');});
    }

    function posterNouveauTopic(item) {
        if (postEnCours) { terminerItem(item.id,false,'Post en cours, attends !'); return; }
        if ((Date.now()-dernierPost)<DELAI_MIN_POSTS && dernierPost>0) { var att=Math.ceil((DELAI_MIN_POSTS-(Date.now()-dernierPost))/1000); terminerItem(item.id,false,'Trop rapide ! Attends '+att+'s'); return; }
        postEnCours = true;
        var pageUrl = normaliserUrlTopic((item.pageUrl||window.location.href).split('#')[0]);
        fetch(pageUrl,{credentials:'include'}).then(function(r){return r.text();}).then(function(html) {
            var match = html.match(/name="token"[^>]*value="([^"]+)"/) || html.match(/value="([^"]+)"[^>]*name="token"/) || html.match(/data-token="([^"]+)"/);
            var token = match ? match[1] : '';
            if (!token||token.length<10) { postEnCours=false; terminerItem(item.id,false,'Token introuvable !'); return; }
            var body = new URLSearchParams();
            body.append('title',item.titre); body.append('message',item.message); body.append('poll[]',''); body.append('token',token);
            return fetch(pageUrl+'#post',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body.toString(),credentials:'include'});
        }).then(function(r) {
            postEnCours=false;
            if (r&&(r.ok||r.redirected)) {
                dernierPost=Date.now();
                if (item.vraiTitre) { var mId=(r.url||'').match(/\/topic\/(\d+)\//); if(mId) setTimeout(function(){ posterPremierMessage('https://onche.org/topic/'+mId[1]+'/', item.vraiTitre); }, 2000); terminerItem(item.id,true,'Topic troll cree !'); }
                else terminerItem(item.id,true,'Topic cree !');
            } else if (r) terminerItem(item.id,false,'Erreur '+r.status);
        }).catch(function(){postEnCours=false;terminerItem(item.id,false,'Erreur reseau !');});
    }

    function posterPremierMessage(topicUrl, texte) {
        fetch(topicUrl,{credentials:'include'}).then(function(r){return r.text();}).then(function(html) {
            var match = html.match(/name="token"[^>]*value="([^"]+)"/) || html.match(/value="([^"]+)"[^>]*name="token"/) || html.match(/data-token="([^"]+)"/);
            var token = match ? match[1] : '';
            if (!token||token.length<10) return;
            var body=new URLSearchParams(); body.append('message',texte); body.append('poll_question',''); body.append('poll[]',''); body.append('token',token);
            fetch(topicUrl+'#last',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:body.toString(),credentials:'include'});
        }).catch(function(){});
    }

    // =========================================================
    //  SPEAR V3 — Braille visible
    // =========================================================
    var SPEAR_CLE = 'H0rl0nch3_Sp3ar_V2_X9k#mQ2@zL5';
    var SPEAR_MQ  = '\u2800\u2803\u2805';

    function spearSha256(str){function rr(n,x){return((x>>>n)|(x<<(32-n)));}var h=[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19];var k=[0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2];var bytes=[];for(var i=0;i<str.length;i++){var c=str.charCodeAt(i);if(c<128)bytes.push(c);else if(c<2048){bytes.push(192|(c>>6));bytes.push(128|(c&63));}else{bytes.push(224|(c>>12));bytes.push(128|((c>>6)&63));bytes.push(128|(c&63));}}var blen=bytes.length;bytes.push(0x80);while(bytes.length%64!==56)bytes.push(0);var bits=blen*8;for(var i=7;i>=0;i--){bytes.push(bits&255);bits=Math.floor(bits/256);}var words=[];for(var i=0;i<bytes.length;i+=4)words.push((bytes[i]<<24)|(bytes[i+1]<<16)|(bytes[i+2]<<8)|bytes[i+3]);for(var chunk=0;chunk<words.length;chunk+=16){var w=words.slice(chunk,chunk+16);for(var i=16;i<64;i++){var s0=(rr(7,w[i-15])^rr(18,w[i-15])^(w[i-15]>>>3));var s1=(rr(17,w[i-2])^rr(19,w[i-2])^(w[i-2]>>>10));w[i]=(w[i-16]+s0+w[i-7]+s1)>>>0;}var a=h[0],b=h[1],c=h[2],d=h[3],e=h[4],f=h[5],g=h[6],hh=h[7];for(var i=0;i<64;i++){var S1=(rr(6,e)^rr(11,e)^rr(25,e));var ch=(e&f)^((~e)&g);var t1=(hh+S1+ch+k[i]+w[i])>>>0;var S0=(rr(2,a)^rr(13,a)^rr(22,a));var maj=(a&b)^(a&c)^(b&c);var t2=(S0+maj)>>>0;hh=g;g=f;f=e;e=(d+t1)>>>0;d=c;c=b;b=a;a=(t1+t2)>>>0;}h[0]=(h[0]+a)>>>0;h[1]=(h[1]+b)>>>0;h[2]=(h[2]+c)>>>0;h[3]=(h[3]+d)>>>0;h[4]=(h[4]+e)>>>0;h[5]=(h[5]+f)>>>0;h[6]=(h[6]+g)>>>0;h[7]=(h[7]+hh)>>>0;}return h.map(function(v){return('00000000'+v.toString(16)).slice(-8);}).join('');}
    function spearGenTable(g){var t=[],sb=[];for(var i=0;i<256;i++)t[i]=i;for(var i=0;i<g.length;i++)sb.push(g.charCodeAt(i));var j=0;for(var i=255;i>0;i--){j=(j+t[i]+sb[i%sb.length])%(i+1);var tmp=t[i];t[i]=t[j];t[j]=tmp;}return t;}
    function spearInvTable(t){var inv=new Array(256);for(var i=0;i<256;i++)inv[t[i]]=i;return inv;}
    function spearChecksum(d){var a=0x67452301,b=0xefcdab89,c=0x98badcfe;for(var i=0;i<d.length;i++){a=(a+d[i]*0x9e3779b9)>>>0;b=(b^(a<<5|a>>>27))>>>0;c=(c+b*0x6c62272e)>>>0;}return[(a>>>0)%256,(b>>>0)%256,(c>>>0)%256];}
    function spearEstCode(t){if(!t||t.length<8)return false;return t.charCodeAt(0)===0x2800&&t.charCodeAt(1)===0x2803&&t.charCodeAt(2)===0x2805;}
    function spearEstSteg(t){return !!(t&&t.indexOf(SPEAR_MQ)!==-1);}

    function spearEncodeTexte(texte){
        var cle=spearSha256(SPEAR_CLE),tbl=spearGenTable(cle.substring(0,16)),cleB=[];
        for(var i=0;i<cle.length;i++)cleB.push(cle.charCodeAt(i));
        var data=[];for(var i=0;i<texte.length;i++)data.push(tbl[texte.charCodeAt(i)%256]);
        for(var i=0;i<data.length;i++)data[i]=(data[i]+cleB[i%cleB.length]+i*7)%256;
        var ck=spearChecksum(data);
        var len=data.length;
        var br=SPEAR_MQ+String.fromCharCode(0x2800+(len>>8))+String.fromCharCode(0x2800+(len&0xFF));
        br+=ck.map(function(b){return String.fromCharCode(0x2800+b);}).join('');
        br+=data.map(function(b){return String.fromCharCode(0x2800+b);}).join('');
        return br;
    }

    function spearDecodeTexte(br){
        if(!spearEstCode(br))return null;
        var cle=spearSha256(SPEAR_CLE),tbl=spearGenTable(cle.substring(0,16)),tblI=spearInvTable(tbl),cleB=[];
        for(var i=0;i<cle.length;i++)cleB.push(cle.charCodeAt(i));
        var len=((br.charCodeAt(3)-0x2800)<<8)|(br.charCodeAt(4)-0x2800);
        var ck=[br.charCodeAt(5)-0x2800,br.charCodeAt(6)-0x2800,br.charCodeAt(7)-0x2800];
        var data=[];for(var i=8;i<8+len;i++)data.push(br.charCodeAt(i)-0x2800);
        if(data.length!==len)return null;
        var cke=spearChecksum(data);
        if(ck[0]!==cke[0]||ck[1]!==cke[1]||ck[2]!==cke[2])return null;
        for(var i=0;i<data.length;i++)data[i]=((data[i]-cleB[i%cleB.length]-i*7)%256+256)%256;
        for(var i=0;i<data.length;i++)data[i]=tblI[data[i]];
        return data.map(function(b){return String.fromCharCode(b);}).join('');
    }

    function spearTrouver(texte){
        var idx=texte.indexOf(SPEAR_MQ);
        if(idx===-1)return null;
        var br='';
        for(var i=idx;i<texte.length;i++){
            var c=texte.charCodeAt(i);
            if(c>=0x2800&&c<=0x28FF)br+=texte[i];
            else if(br.length>3)break;
        }
        return br.length>=8?br:null;
    }

    var spearHistorique=[];
    var spearTraductions={};
    try{var rawSH=localStorage.getItem('horlonche_spear_hist');if(rawSH)spearHistorique=JSON.parse(rawSH);}catch(e){}

    var monPseudo='';
    try{var lienProfil=document.querySelector('a[href*="/profil/"]');if(lienProfil)monPseudo=lienProfil.textContent.trim();}catch(e){}

    function sauvegarderSpearHist(){try{if(spearHistorique.length>200)spearHistorique=spearHistorique.slice(0,200);localStorage.setItem('horlonche_spear_hist',JSON.stringify(spearHistorique));}catch(e){}}
    function ajouterSpearHist(auteur,url,msg){
        if(monPseudo&&auteur&&auteur.toLowerCase()===monPseudo.toLowerCase())return;
        var deja=spearHistorique.some(function(item){return item.auteur===auteur&&item.message===msg.substring(0,200);});
        if(deja)return;
        spearHistorique.unshift({date:new Date().toLocaleDateString(),heure:new Date().toLocaleTimeString(),auteur:auteur,url:url,message:msg.substring(0,200)});
        sauvegarderSpearHist();
    }

    function afficherSpearHistorique() {
        var liste=document.getElementById('os-spear-historique');if(!liste)return;liste.innerHTML='';
        if(spearHistorique.length===0){liste.innerHTML='<div style="color:#aaa;text-align:center;padding:20px;font-size:12px">Aucun message Spear detecte</div>';return;}
        spearHistorique.forEach(function(item){
            var l=document.createElement('div');l.style.cssText='padding:7px 8px;margin-bottom:5px;background:#1a0a2e;border-radius:4px;font-size:11px;border-left:3px solid #9b59b6';
            var h='<div style="display:flex;justify-content:space-between;margin-bottom:4px"><span style="color:#888">'+escHtml(item.date)+' '+escHtml(item.heure)+'</span><span style="color:#9b59b6;font-weight:bold">'+escHtml(item.auteur)+'</span></div>';
            if(item.url)h+='<div style="margin-bottom:4px"><a href="'+escHtml(item.url)+'" target="_blank" style="color:#9b59b6;font-size:10px;text-decoration:none">Voir &#8594;</a></div>';
            h+='<div style="color:#d7bde2;background:#0d0015;padding:5px 7px;border-radius:3px;word-break:break-word">'+escHtml(item.message)+'</div>';
            l.innerHTML=h;liste.appendChild(l);
        });
    }

    function decoderMessagesSpear() {
        document.querySelectorAll('.message[data-id][data-username]').forEach(function(msgEl){
            // Citations : afficher traduction depuis cache si disponible
            if(msgEl.classList.contains('answer')&&msgEl.closest('.message')) {
                if(msgEl.getAttribute('data-spear-decoded'))return;
                var ca=msgEl.querySelector('.message-content');
                var cid=msgEl.getAttribute('data-id');
                var te=msgEl.getAttribute('data-text-edit')||'';
                if(!ca||(!spearEstSteg(te)&&!spearTraductions[cid]))return;
                msgEl.setAttribute('data-spear-decoded','1');
                var brC=spearTrouver(te);
                var secC=brC?spearDecodeTexte(brC):(spearTraductions[cid]||null);
                if(secC){spearTraductions[cid]=secC;ca.innerHTML='<span style="color:#9b59b6;font-size:11px;font-style:italic">Spear : </span><span style="color:#d7bde2">'+escHtml(secC)+'</span>';}
                else ca.innerHTML='<span style="color:#9b59b6;font-size:11px;font-style:italic">Spear</span>';
                return;
            }
            if(msgEl.closest('.message-content'))return;
            if(msgEl.getAttribute('data-spear-decoded'))return;
            var c=msgEl.querySelector('.message-content');if(!c)return;
            var texte=msgEl.getAttribute('data-text-edit')||'';
            // Fallback : chercher le braille dans le DOM si data-text-edit est vide
            if(!spearEstSteg(texte)){
                var mc=msgEl.querySelector('.message-content');
                if(mc){var bm=mc.innerHTML.match(/[\u2800-\u28FF]{3,}/g);if(bm)texte=bm.join('');}
            }
            if(!spearEstSteg(texte))return;
            var braille=spearTrouver(texte);if(!braille)return;
            var msgSecret=spearDecodeTexte(braille);if(!msgSecret)return;
            msgEl.setAttribute('data-spear-decoded','1');
            spearTraductions[msgEl.getAttribute('data-id')]=msgSecret;
            var texteVisible=texte.replace(/[\u2800-\u28FF]/g,'').trim();
            c.innerHTML='<div style="padding:6px 10px;background:linear-gradient(135deg,#0d1b2a,#1a0a2e);border-left:3px solid #9b59b6;border-radius:4px;font-size:13px">'
                +(texteVisible?'<div style="color:#eee;margin-bottom:4px">'+escHtml(texteVisible)+'</div>':'')
                +'<div style="color:#d7bde2;background:#0d0015;padding:4px 6px;border-radius:3px">'+escHtml(msgSecret)+'</div>'
                +'</div>';
            ajouterSpearHist(msgEl.getAttribute('data-username')||'?',window.location.href.split('#')[0]+'#message_'+msgEl.getAttribute('data-id'),msgSecret);
        });
    }
    new MutationObserver(function(){decoderMessagesSpear();}).observe(document.body,{childList:true,subtree:true});
    decoderMessagesSpear();

    setTimeout(function() {
        var btnSpearOnglet=document.getElementById('os-btn-spear');
        if(btnSpearOnglet)btnSpearOnglet.addEventListener('click',function(){['os-vue-liste','os-vue-form','os-vue-sniper','os-vue-historique'].forEach(function(id){var el=document.getElementById(id);if(el)el.style.display='none';});var vs=document.getElementById('os-vue-spear');if(vs)vs.style.display='block';afficherSpearHistorique();});
        var btnSpearRetour=document.getElementById('os-spear-retour');
        if(btnSpearRetour)btnSpearRetour.addEventListener('click',function(){var vs=document.getElementById('os-vue-spear');if(vs)vs.style.display='none';var vl=document.getElementById('os-vue-liste');if(vl)vl.style.display='block';});
        var btnSpearClear=document.getElementById('os-spear-clear');
        if(btnSpearClear)btnSpearClear.addEventListener('click',function(){if(!confirm('Vider tout l historique Spear ?'))return;spearHistorique=[];sauvegarderSpearHist();afficherSpearHistorique();});
    }, 200);

    function pad(n)    { return n < 10 ? '0'+n : ''+n; }
    function escHtml(s){ return s?String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'):''; }

})();