Horlonche

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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;'):''; }

})();