Update Bot auto curation

<!-- ===== 🔥 HIVEHUB DASHBOARD FINAL FIX ===== -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<style>
.hivehub-grid{
    margin-top:50px;
    display:grid;
    grid-template-columns: repeat(auto-fit,minmax(420px,1fr));
    gap:25px;
}
.hivehub-card{
    background:#1e1e2f;
    border-radius:12px;
    padding:20px;
    border:1px solid rgba(255,255,255,0.05);
}
.hivehub-title{
    font-size:14px;
    color:#aaa;
}
.hivehub-value{
    font-size:26px;
    font-weight:bold;
    color:#fff;
    margin-bottom:10px;
}
</style>

<div class="hivehub-grid">

    <div class="hivehub-card">
        <div class="hivehub-title">Hive Power</div>
        <div class="hivehub-value" id="hpValue">...</div>
        <canvas id="hpChart"></canvas>
    </div>

    <div class="hivehub-card">
        <div class="hivehub-title">HP Delta</div>
        <div class="hivehub-value" id="hpDeltaValue">...</div>
        <canvas id="hpDeltaChart"></canvas>
    </div>

    <div class="hivehub-card">
        <div class="hivehub-title">Author Rewards</div>
        <div class="hivehub-value" id="authorValue">...</div>
        <canvas id="authorChart"></canvas>
    </div>

    <div class="hivehub-card">
        <div class="hivehub-title">Curation Rewards</div>
        <div class="hivehub-value" id="curationValue">...</div>
        <canvas id="curationChart"></canvas>
    </div>

</div>

<script>
async function loadHiveHubExact() {

    try {

        const username = localStorage.getItem('hive_account') || '<?php echo VOTER_ACCOUNT; ?>';

        // ===== GLOBAL PROPS =====
        const propsRes = await fetch('https://api.hive.blog', {
            method:'POST',
            headers:{'Content-Type':'application/json'},
            body: JSON.stringify({
                jsonrpc:"2.0",
                method:"condenser_api.get_dynamic_global_properties",
                params:[],
                id:1
            })
        });

        const props = (await propsRes.json()).result;

        const total_vesting_fund = parseFloat(props.total_vesting_fund_hive);
        const total_vesting_shares = parseFloat(props.total_vesting_shares);

        function vestsToHP(vests){
            return (vests * total_vesting_fund) / total_vesting_shares;
        }

        // ===== ACCOUNT =====
        const accRes = await fetch('https://api.hive.blog', {
            method:'POST',
            headers:{'Content-Type':'application/json'},
            body: JSON.stringify({
                jsonrpc:"2.0",
                method:"condenser_api.get_accounts",
                params:[[username]],
                id:1
            })
        });

        const accData = await accRes.json();
        if (!accData.result || !accData.result[0]) return;

        const acc = accData.result[0];

        const totalVests =
            parseFloat(acc.vesting_shares) +
            parseFloat(acc.received_vesting_shares) -
            parseFloat(acc.delegated_vesting_shares);

        const currentHP = vestsToHP(totalVests);

        document.getElementById('hpValue').innerText = currentHP.toFixed(0) + " HP";

        // ===== HISTORY =====
        const histRes = await fetch('https://api.hive.blog', {
            method:'POST',
            headers:{'Content-Type':'application/json'},
            body: JSON.stringify({
                jsonrpc:"2.0",
                method:"account_history_api.get_account_history",
                params:{ account: username, start:-1, limit:1000 },
                id:1
            })
        });

        const histData = await histRes.json();
        if (!histData.result) return;

        const history = histData.result.history;

        let days = 30;
        let now = Date.now();

        let hpDelta = {};
        let author = {};
        let curation = {};

        for(let i=0;i<days;i++){
            let d = new Date(now - i*86400000).toISOString().slice(0,10);
            hpDelta[d] = 0;
            author[d] = 0;
            curation[d] = 0;
        }

        history.forEach(e=>{
            const op = e[1].op;
            const ts = new Date(e[1].timestamp).getTime();
            const day = new Date(ts).toISOString().slice(0,10);

            if(!(day in hpDelta)) return;

            // ===== CURATION =====
            if(op[0]==="curation_reward"){
                let vests = parseFloat(op[1].reward);
                let hp = vestsToHP(vests);
                if (!isNaN(hp)) {
                    curation[day]+=hp;
                    hpDelta[day]+=hp;
                }
            }

            // ===== AUTHOR =====
            if(op[0]==="author_reward"){
                let total = 0;
                if(op[1].hbd_payout) total += parseFloat(op[1].hbd_payout);
                if(op[1].hive_payout) total += parseFloat(op[1].hive_payout);
                if (!isNaN(total)) author[day]+=total;
            }

            // ===== POWER UP =====
            if(op[0]==="transfer_to_vesting"){
                let amount = parseFloat(op[1].amount);
                if (!isNaN(amount)) hpDelta[day]+=amount;
            }

            // ===== POWER DOWN =====
            if(op[0]==="fill_vesting_withdraw"){
                let amount = parseFloat(op[1].withdrawn);
                if (!isNaN(amount)) hpDelta[day]-=amount;
            }

        });

        let labels = Object.keys(hpDelta).reverse();

        let cumulative = currentHP;
        let hpData = [];

        labels.forEach(d=>{
            cumulative -= hpDelta[d];
            hpData.push(cumulative);
        });

        hpData.reverse();

        // ===== VALUES =====
        document.getElementById('hpDeltaValue').innerText =
            Object.values(hpDelta).reduce((a,b)=>a+b,0).toFixed(0)+" HP";

        document.getElementById('authorValue').innerText =
            Object.values(author).reduce((a,b)=>a+b,0).toFixed(2)+" HBD";

        document.getElementById('curationValue').innerText =
            Object.values(curation).reduce((a,b)=>a+b,0).toFixed(2)+" HP";

        const baseOptions = {
            responsive:true,
            plugins:{legend:{display:false}},
            scales:{
                x:{ticks:{color:'#777'}},
                y:{ticks:{color:'#777'}}
            }
        };

        new Chart(hpChart,{
            type:'line',
            data:{labels,datasets:[{data:hpData,borderColor:'#00ff88',tension:0.3}]},
            options:baseOptions
        });

        new Chart(hpDeltaChart,{
            type:'bar',
            data:{labels,datasets:[{data:Object.values(hpDelta).reverse(),backgroundColor:'#00ff8855'}]},
            options:baseOptions
        });

        new Chart(authorChart,{
            type:'bar',
            data:{labels,datasets:[{data:Object.values(author).reverse(),backgroundColor:'#00d4ff88'}]},
            options:baseOptions
        });

        new Chart(curationChart,{
            type:'bar',
            data:{labels,datasets:[{data:Object.values(curation).reverse(),backgroundColor:'#00ff8888'}]},
            options:baseOptions
        });

    } catch (e) {
        console.error("🔥 HiveHub error:", e);
    }
}

document.addEventListener('DOMContentLoaded', loadHiveHubExact);
</script>


image.png

I managed to add a table at the bottom of my code to display a copy-paste of:
https://hivehub.dev/stats/account?username=we-are-ai&category=financial&days=30

My HP
My HP delta
My author reward
My curation reward

I improve it whenever I have time to build a fully automated curation bot .

And I’ll keep you posted if I have other ideas for things to add to the screen, or if you have any.

full code in comment

0.01341132 BEE
8 comments

!HBIT

0.00001392 BEE
<?php
// ----------- CONFIGURATION -----------
define('VOTER_ACCOUNT', 'we-are-ai');
define('MIN_VP_THRESHOLD', 78);
define('MIN_WORD_COUNT', 300);
define('POSTS_PER_PAGE', 20);
define('DEBUG_MODE', true);

// ----------- AUTO-VOTE CONFIGURATION -----------
define('AUTO_VOTE_ENABLED', true);  // ⚠️ Mettre false pour désactiver
define('AUTO_VOTE_DELAY', 5000);    // Délai entre chaque vote (ms) 5 secondes
define('AUTO_REFRESH_AFTER_VOTE', true); // Refresh après avoir tout voté

// ========== 🎯 MODIFIE ICI TES SEUILS DE VOTE ==========
// Format: nombre_de_mots => poids_de_vote (en centièmes, 500 = 5%)
$VOTE_RULES = [
   
    
    300  => 350   // 2.5% pour 300-100000 mots
];
// ========================================================

// ----------- FONCTION DE CALCUL DE POIDS -----------
function get_vote_weight($word_count) {
    global $VOTE_RULES;
    foreach ($VOTE_RULES as $min_words => $weight) {
        if ($word_count >= $min_words) return $weight;
    }
    return 0;
}

// ----------- COMPTEUR DE MOTS -----------
function count_words($text) {
    $text = preg_replace('/^#+\s*.+/m', '', $text);
    $text = preg_replace('/\[[^\]]*\]\([^)]*\)/', '', $text);
    $text = preg_replace('/!\[[^\]]*\]\([^)]*\)/', '', $text);
    $text = preg_replace('/`{1,3}[^`]+`{1,3}/', '', $text);
    $text = strip_tags($text);
    $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
    $text = preg_replace('/[^\p{L}\p{N}\s\']/u', ' ', $text);
    $text = preg_replace('/\s+/', ' ', trim($text));
    $words = explode(' ', $text);
    return count($words);
}

// ----------- RÉCUPÉRATION DES POSTS -----------
function get_hive_posts() {
    $url = "https://api.hive.blog";
    $payload = json_encode([
        "jsonrpc" => "2.0",
        "method" => "bridge.get_ranked_posts",
        "params" => [
            "sort" => "created",
            "tag" => "",
            "observer" => VOTER_ACCOUNT,
            "limit" => POSTS_PER_PAGE
        ],
        "id" => 1
    ]);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
        CURLOPT_SSL_VERIFYPEER => false,
    ]);

    $res = curl_exec($ch);
    curl_close($ch);

    $json = json_decode($res, true);
    return $json['result'] ?? [];
}

// ----------- RÉCUPÉRATION COMPTE -----------
function get_hive_account($account) {
    $url = "https://api.hive.blog";
    $payload = json_encode([
        "jsonrpc" => "2.0",
        "method" => "condenser_api.get_accounts",
        "params" => [[$account]],
        "id" => 1
    ]);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
        CURLOPT_SSL_VERIFYPEER => false,
    ]);

    $res = curl_exec($ch);
    curl_close($ch);

    $json = json_decode($res, true);
    return $json['result'][0] ?? null;
}

// ----------- RÉCUPÉRATION DU VP -----------
function get_current_vp($account) {
    $url = "https://api.hive.blog";
    $payload = json_encode([
        "jsonrpc" => "2.0",
        "method" => "condenser_api.get_accounts",
        "params" => [[$account]],
        "id" => 1
    ]);
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS => $payload,
        CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
        CURLOPT_SSL_VERIFYPEER => false,
    ]);
    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);
    return isset($response['result'][0]['voting_power'])
        ? round($response['result'][0]['voting_power'] / 100, 1)
        : 0;
}

...
0.00001390 BEE

part 2

...
// ----------- LOGIQUE PRINCIPALE -----------
$account = get_hive_account(VOTER_ACCOUNT);
$current_vp = get_current_vp(VOTER_ACCOUNT);
$posts = get_hive_posts();

$eligible_posts = [];
$debug_logs = [];

foreach ($posts as $p) {
$words = count_words($p['body']);
$vote_weight = get_vote_weight($words);
$user_voted = false;
$reason = "";

foreach ($p['active_votes'] ?? [] as $vote) {
    if ($vote['voter'] === VOTER_ACCOUNT) {
        $user_voted = true;
        $reason = "Already voted";
        break;
    }
}

if ($words < MIN_WORD_COUNT) {
    $reason = "Too short ($words words)";
} elseif ($user_voted) {
    $reason = "Already voted";
} else {
    $reason = "✅ Eligible";
}

$debug_logs[] = [
    'author' => $p['author'],
    'permlink' => $p['permlink'],
    'words' => $words,
    'reason' => $reason
];

if ($vote_weight > 0 && !$user_voted && $current_vp >= MIN_VP_THRESHOLD) {
    $p['is_eligible'] = true;
    $eligible_posts[] = $p;
}

}
?>

🤖 Hive Auto Words
0.00001389 BEE

part 4

...
// ========== AUTO-VOTE INDICATOR ==========
function showAutoVoteIndicator(totalPosts) {
let indicator = document.getElementById('auto-vote-indicator');
if (!indicator) {
indicator = document.createElement('div');
indicator.id = 'auto-vote-indicator';
indicator.style.cssText = position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(18, 18, 48, 0.98); border: 2px solid var(--neon-blue); border-radius: 15px; padding: 30px 40px; z-index: 10000; text-align: center; box-shadow: 0 8px 32px rgba(0, 212, 255, 0.5); animation: pulse-glow 2s infinite;;
document.body.appendChild(indicator);
}

        indicator.innerHTML = `
            <div style="font-size: 3rem; margin-bottom: 15px;">🤖</div>
            <div style="font-family: 'Orbitron', sans-serif; font-size: 1.5rem; color: var(--neon-blue); margin-bottom: 10px;">AUTO-VOTE ACTIVE</div>
            <div id="auto-vote-progress" style="font-size: 1.2rem; color: var(--neon-green);">0 / ${totalPosts}</div>
        `;
        indicator.style.display = 'block';
    }

    function updateAutoVoteIndicator(current, total) {
        const progress = document.getElementById('auto-vote-progress');
        if (progress) {
            progress.textContent = `${current} / ${total}`;
        }
    }

    function hideAutoVoteIndicator() {
        const indicator = document.getElementById('auto-vote-indicator');
        if (indicator) {
            indicator.style.animation = 'fadeOut 0.5s forwards';
            setTimeout(() => {
                indicator.remove();
            }, 500);
        }
    }

    // ========== AUTO-REFRESH FUNCTIONS ==========
    function clearAutoRefresh() {
        if (autoRefreshTimer) {
            clearInterval(autoRefreshTimer);
            autoRefreshTimer = null;
            const timerDisplay = document.getElementById('refresh-timer');
            if (timerDisplay) {
                timerDisplay.remove();
            }
        }
    }

    function scheduleAutoRefresh(delay = AUTO_REFRESH_DELAY) {
        clearAutoRefresh();
        
        let remainingTime = delay / 1000;
        
        let timerDisplay = document.getElementById('refresh-timer');
        if (!timerDisplay) {
            timerDisplay = document.createElement('div');
            timerDisplay.id = 'refresh-timer';
            timerDisplay.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                background: rgba(18, 18, 48, 0.95);
                border: 1px solid rgba(0, 212, 255, 0.4);
                border-radius: 8px;
                padding: 12px 20px;
                color: var(--neon-blue);
                font-family: 'Orbitron', sans-serif;
                font-size: 0.9rem;
                z-index: 9999;
                box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
                animation: pulse-glow 2s infinite;
            `;
            document.body.appendChild(timerDisplay);
        }

        timerDisplay.textContent = `🔄 Refresh in ${remainingTime}s`;

        autoRefreshTimer = setInterval(() => {
            remainingTime--;
            timerDisplay.textContent = `🔄 Refresh in ${remainingTime}s`;
            
            if (remainingTime <= 0) {
                clearAutoRefresh();
                location.reload();
            }
        }, 1000);
    }

    // ========== EXPORT CSV ==========
    function exportVotedAccountsCSV() {
if (voteSession.history.length === 0) {
    alert('⚠️ No votes to export!');
    return;
}

const uniqueAccounts = [...new Set(
    voteSession.history.map(vote => vote.author.replace('@', ''))
)];

let csvContent = "username,post_url\n"; // header CSV

voteSession.history.forEach(vote => {
const username = vote.author.replace('@', '');
const postUrl = https://peakd.com/@${vote.author}/${vote.permlink};

csvContent += `${username},${postUrl}\n`;

});

const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);

const now = new Date();
const timestamp = now.toISOString().slice(0, 19).replace(/:/g, '-');
const filename = glyph_voters_${timestamp}.csv;

link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';

document.body.appendChild(link);
link.click();
document.body.removeChild(link);

console.log(✅ Exported ${voteSession.history.length} votes with links);
}

    // ========== AUTO-START ON PAGE LOAD ==========
    window.addEventListener('DOMContentLoaded', () => {
        loadVoteSession();
        
        const savedAccount = localStorage.getItem(ACCOUNT_STORAGE_KEY);
        if (savedAccount) {
            document.getElementById('current-account-display').textContent = `@${savedAccount}`;
        }

        const eligibleCount = <?php echo count($eligible_posts); ?>;
        
        // Auto-vote si activé ET posts disponibles
        if (AUTO_VOTE_ENABLED && eligibleCount > 0) {
            console.log(`🤖 AUTO-VOTE enabled: ${eligibleCount} posts detected`);
            setTimeout(() => {
                startAutoVote();
            }, 2000); // Délai de 2s pour laisser charger la page
        } else if (eligibleCount === 0) {
            console.log("⚠️ No eligible posts - scheduling auto-refresh");
            scheduleAutoRefresh();
        }

        // Ajoute le bouton d'export CSV
        const historySection = document.getElementById('vote-history');
        if (historySection) {
            const clearButton = historySection.querySelector('button[onclick="clearVoteHistory()"]');
            if (clearButton) {
                const exportButton = document.createElement('button');
                exportButton.textContent = '📊 Export CSV';
                exportButton.onclick = exportVotedAccountsCSV;
                exportButton.style.cssText = `
                    background: linear-gradient(45deg, #00d4ff, #b200ff);
                    color: white;
                    border: none;
                    padding: 10px 20px;
                    border-radius: 8px;
                    cursor: pointer;
                    font-weight: bold;
                    margin-right: 10px;
                    transition: all 0.3s;
                    font-family: 'Orbitron', sans-serif;
                `;
                exportButton.onmouseover = () => exportButton.style.transform = 'scale(1.05)';
                exportButton.onmouseout = () => exportButton.style.transform = 'scale(1)';
                
                clearButton.parentElement.insertBefore(exportButton, clearButton);
            }
        }
    });

    window.addEventListener('beforeunload', () => {
        clearAutoRefresh();
    });

    // Ajoute l'animation fadeOut pour l'indicateur
    const style = document.createElement('style');
    style.textContent = `
        @keyframes fadeOut {
            from { opacity: 1; transform: translate(-50%, -50%) scale(1); }
            to { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
        }
    `;
    document.head.appendChild(style);
</script>

<!-- ========== PATCH: Auto-Vote Play/Pause Control ========== -->
AUTO-VOTE: ACTIVE
0.00001388 BEE

part 5

...
========== AUTO-VOTE TOGGLE FUNCTION ==========
let autoVotePaused = false;
let autoVoteInterval = null;

function toggleAutoVote() {
autoVotePaused = !autoVotePaused;
const toggleBtn = document.getElementById('autoVoteToggle');
const statusText = document.getElementById('autoVoteStatus');

if (autoVotePaused) {
    toggleBtn.textContent = "▶";
    statusText.textContent = "PAUSED";
    statusText.style.color = "var(--neon-red)";
    clearAutoRefresh();
    clearInterval(autoVoteInterval);

    // Arrêter le processus auto-vote en cours
    if (window.voteNextTimeout) {
        clearTimeout(window.voteNextTimeout);
    }
} else {
    toggleBtn.textContent = "⏸";
    statusText.textContent = "ACTIVE";
    statusText.style.color = "var(--neon-green)";

    // Relancer l'auto-vote si des posts sont éligibles
    const eligibleCount = <?php echo count($eligible_posts); ?>;
    if (eligibleCount > 0) {
        setTimeout(() => {
            startAutoVote();
        }, 1000);
    }
}

}

// Modification de startAutoVote pour gérer la pause
window.startAutoVoteOriginal = window.startAutoVote;
window.startAutoVote = async function() {
if (autoVotePaused) return;
await window.startAutoVoteOriginal();
}

// Initialisation
document.addEventListener('DOMContentLoaded', function() {
// Vérifier si l'auto-vote est activé dans la config
if (!) {
const toggleBtn = document.getElementById('autoVoteToggle');
const statusText = document.getElementById('autoVoteStatus');
toggleBtn.textContent = "▶";
statusText.textContent = "DISABLED";
statusText.style.color = "var(--text-secondary)";
toggleBtn.onclick = function() {
alert("Auto-vote is disabled in configuration. Set AUTO_VOTE_ENABLED to true in PHP config.");
};
}
});

0.00001386 BEE

part 6

...
// ✅ 1. Neutraliser TOUS les alert() (anti blocage total)
window.alert = function(message) {
console.warn("🚫 Alert bloquée :", message);
};

// ✅ 2. Optionnel : petit toast visuel (non bloquant)
function showToast(msg) {
const div = document.createElement('div');
div.textContent = msg;
div.style = position: fixed; bottom: 20px; left: 20px; background: #ff2e63; color: white; padding: 10px 15px; border-radius: 8px; z-index: 9999; font-size: 0.8rem; opacity: 0.9; font-family: sans-serif;;
document.body.appendChild(div);
setTimeout(() => div.remove(), 3000);
}

// ✅ 3. Hook automatique sur les boutons de vote
document.addEventListener('click', function(e) {
const btn = e.target.closest('button');
if (!btn) return;

// détecte les boutons de vote (tu peux ajuster si besoin)
if (btn.textContent.includes('%') || btn.textContent.includes('Vote')) {

    // sécurité anti freeze visuel
    setTimeout(() => {
        if (btn.textContent.includes('❌')) {
            console.log("⚠️ Vote failed auto-skip");
            showToast("Vote failed → skipped");
        }
    }, 1500);
}

});

// ✅ 4. Sécurité globale JS (aucune erreur ne bloque)
window.onerror = function(msg, url, line, col, error) {
console.error("💥 JS Error caught:", msg);
return true; // empêche blocage
};

window.onunhandledrejection = function(event) {
console.error("💥 Promise Error:", event.reason);
};

<button onclick="openDonationModal()" style="
    background: linear-gradient(135deg, #ff6b9d, #ffa800);
    color: white;
    border: none;
    border-radius: 16px;
    padding: 18px 28px;
    font-size: 16px;
    font-weight: bold;
    cursor: pointer;
    box-shadow: 0 8px 30px rgba(255, 107, 157, 0.6);
    transition: all 0.25s ease;
    max-width: 280px;
    text-align: left;
    line-height: 1.4;
    animation: pulse 2s infinite;
"
onmouseover="this.style.transform='scale(1.08)'"
onmouseout="this.style.transform='scale(1)'">

    💝 If you want to support the project,<br>
    I accept donations.

</button>
<div style="
    background:#111;
    padding:25px;
    border-radius:12px;
    width:320px;
    color:white;
    text-align:center;
    box-shadow: 0 0 25px rgba(255,107,157,0.4);
">

    <h2>💝 Support the project</h2>

    <input id="donationUser" placeholder="Your Hive username" style="width:100%; padding:10px; margin:10px 0; border-radius:8px; border:none;">
    
    <input id="donationAmount" placeholder="Amount" type="number" style="width:100%; padding:10px; margin:10px 0; border-radius:8px; border:none;">

    <select id="donationToken" style="width:100%; padding:10px; margin:10px 0; border-radius:8px;">




































































    <button onclick="sendDonation()" style="
        width:100%;
        padding:12px;
        margin-top:10px;
        background:#ff6b9d;
        border:none;
        border-radius:8px;
        color:white;
        font-weight:bold;
        cursor:pointer;
    ">
        Send Donation
    </button>

    <button onclick="closeDonationModal()" style="
        width:100%;
        padding:10px;
        margin-top:10px;
        background:#333;
        border:none;
        border-radius:8px;
        color:white;
        cursor:pointer;
    ">
        Cancel
    </button>

</div>
...
0.00001385 BEE

part 7

...

🔄 Refresh in 900s
<div class="hivehub-card">
    <div class="hivehub-title">Hive Power</div>
    <div class="hivehub-value" id="hpValue">...</div>
    <canvas id="hpChart"></canvas>
</div>

<div class="hivehub-card">
    <div class="hivehub-title">HP Delta</div>
    <div class="hivehub-value" id="hpDeltaValue">...</div>
    <canvas id="hpDeltaChart"></canvas>
</div>

<div class="hivehub-card">
    <div class="hivehub-title">Author Rewards</div>
    <div class="hivehub-value" id="authorValue">...</div>
    <canvas id="authorChart"></canvas>
</div>

<div class="hivehub-card">
    <div class="hivehub-title">Curation Rewards</div>
    <div class="hivehub-value" id="curationValue">...</div>
    <canvas id="curationChart"></canvas>
</div>
...
0.00001383 BEE

part 3

...

🔐 Voting Account

    <div style="display: flex; gap: 10px; align-items: center; margin-bottom: 10px;">
        <input 
            type="text" 
            id="account-input" 
            placeholder="Enter Hive username"
            style="flex: 1; padding: 10px; background: rgba(18, 18, 48, 0.8); border: 1px solid rgba(178, 0, 255, 0.3); border-radius: 8px; color: var(--text-primary); font-size: 1rem;"
        />
        <button 
            id="connect-btn" 
            onclick="connectAccount()"
            style="padding: 10px 20px; white-space: nowrap;">
            🔗 Connect & Validate
        </button>
    </div>
    
    <div id="account-status" style="display: none; padding: 10px; border-radius: 6px; margin-top: 10px; font-size: 0.9rem;"></div>
</div>

    <p><strong>Eligible Posts:</strong> <span style="color: var(--neon-green);"><?php echo count($eligible_posts); ?></span>/<?php echo count($posts); ?></p>
    
    <!-- ========== AFFICHAGE DES RÈGLES DE VOTE ========== -->
    <div style="margin-top: 20px; padding: 15px; background: rgba(0, 212, 255, 0.05); border-radius: 8px; border: 1px solid rgba(0, 212, 255, 0.2);">
        <h3 style="color: var(--neon-blue); margin-bottom: 10px; font-size: 1.1rem;">⚙️ Vote Rules</h3>
        <?php foreach ($VOTE_RULES as $min_words => $weight): ?>
            <p style="margin: 5px 0; font-size: 0.95rem;">
                <span style="color: var(--neon-yellow);">≥ <?php echo $min_words; ?> words</span> 
                → 
                <span style="color: var(--neon-green); font-weight: bold;"><?php echo $weight / 100; ?>%</span>
            </p>
        <?php endforeach; ?>
    </div>
</div>

<?php if (count($eligible_posts) > 0): ?>
<div class="card">
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
        <h2 style="margin: 0;">✅ Eligible Posts (<?php echo count($eligible_posts); ?>)</h2>
        <button onclick="voteAll()" style="padding: 10px 20px;">🚀 Vote All</button>
    </div>

    <table>
        <thead>
            <tr>
                <th>Author</th>
                <th>Title</th>
                <th>Words</th>
                <th>Weight</th>
                <th>Action</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($eligible_posts as $p): 
                $words = count_words($p['body']);
                $vote_weight = get_vote_weight($words);
            ?>
            <tr>
                <td>
                    <a href="https://peakd.com/@<?php echo $p['author']; ?>" 
                       target="_blank" 
                       style="color: var(--neon-blue); text-decoration: none;">
                        @<?php echo $p['author']; ?>
                    </a>
                </td>
                <td>
                    <a href="https://peakd.com/@<?php echo $p['author']; ?>/<?php echo $p['permlink']; ?>" 
                       target="_blank"
                       style="color: var(--text-primary); text-decoration: none;">
                        <?php echo htmlspecialchars(mb_substr($p['title'], 0, 60)) . (mb_strlen($p['title']) > 60 ? '...' : ''); ?>
                    </a>
                </td>
                <td><?php echo $words; ?></td>
                <td>
                    <span style="
                        color: <?php 
                            echo $vote_weight >= 600 ? 'var(--neon-green)' : 
                                 ($vote_weight >= 500 ? 'var(--neon-yellow)' : 'var(--neon-blue)'); 
                        ?>;
                        font-weight: bold;
                    ">
                        <?php echo $vote_weight / 100; ?>%
                    </span>
                </td>
                <td>
                    <button
                        class="vote-btn"
                        onclick="votePost(
                            '<?php echo addslashes($p['author']); ?>',
                            '<?php echo addslashes($p['permlink']); ?>',
                            this,
                            <?php echo $words; ?>
                        )">
                        Vote <?php echo $vote_weight / 100; ?>%
                    </button>
                </td>
            </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
</div>
<?php else: ?>
<div class="card">
    <h2>⚠️ No Eligible Posts</h2>
    <p style="color: var(--text-secondary);">All posts have already been voted on or don't meet criteria.</p>
</div>
<?php endif; ?>

<!-- ========== PATCH 1: Vote History (AMÉLIORÉ) ========== -->
<div id="vote-history" style="
    margin-top: 40px;
    padding: 30px;
    background: linear-gradient(135deg, rgba(18, 18, 48, 0.95), rgba(30, 30, 70, 0.85));
    border-radius: 15px;
    border: 2px solid var(--neon-purple);
    box-shadow: 0 8px 32px rgba(178, 0, 255, 0.3);
">
    <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
        <h2 style="
            font-family: 'Orbitron', sans-serif;
            color: var(--neon-purple);
            text-shadow: 0 0 10px var(--neon-purple);
            margin: 0;
        ">📊 Vote Session History</h2>
        <button onclick="clearVoteHistory()" style="
            background: linear-gradient(45deg, #ff0040, #ff6b9d);
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 8px;
            cursor: pointer;
            font-weight: bold;
            transition: all 0.3s;
            font-family: 'Orbitron', sans-serif;
        " onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
            🗑️ Clear History
        </button>
    </div>
    <div id="history-list" style="
        display: grid;
        gap: 15px;
        max-height: 500px;
        overflow-y: auto;
    "></div>
</div>



<script>
    // ========== CONFIGURATION ==========
    const VOTE_STORAGE_KEY = 'hiveVoteHistory';
    const ACCOUNT_STORAGE_KEY = 'hiveVoterAccount';
    const VOTE_DELAY = 5000;
    const AUTO_REFRESH_DELAY = 900000;
    
    // ========== AUTO-VOTE CONFIG (FROM PHP) ==========
    const AUTO_VOTE_ENABLED = <?php echo AUTO_VOTE_ENABLED ? 'true' : 'false'; ?>;
    const AUTO_VOTE_DELAY = <?php echo AUTO_VOTE_DELAY; ?>;
    const AUTO_REFRESH_AFTER_VOTE = <?php echo AUTO_REFRESH_AFTER_VOTE ? 'true' : 'false'; ?>;

    // ========== VOTE RULES (SYNCHRONIZED WITH PHP) ==========
    const VOTE_RULES = <?php echo json_encode($VOTE_RULES); ?>;

    function calculateVoteWeight(wordCount) {
        // Trie les seuils par ordre décroissant
        const sortedThresholds = Object.keys(VOTE_RULES)
            .map(Number)
            .sort((a, b) => b - a);
        
        for (const minWords of sortedThresholds) {
            if (wordCount >= minWords) {
                return VOTE_RULES[minWords];
            }
        }
        return 0;
    }

    // ========== VOTE SESSION ==========
    let voteSession = {
        history: [],
        votedPosts: []
    };

    let autoRefreshTimer = null;

    // ========== CONNECT ACCOUNT ==========
    function connectAccount() {
        const accountInput = document.getElementById('account-input');
        const statusDiv = document.getElementById('account-status');
        const username = accountInput.value.trim().replace('@', '');
        
        if (!username) {
            alert('⚠️ Please enter a username');
            return;
        }

        statusDiv.style.display = 'block';
        statusDiv.className = 'status-badge status-loading';
        statusDiv.textContent = '⏳ Validating account...';

        fetch('https://api.hive.blog', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
                jsonrpc: '2.0',
                method: 'condenser_api.get_accounts',
                params: [[username]],
                id: 1
            })
        })
        .then(res => res.json())
        .then(data => {
            if (data.result && data.result.length > 0) {
                localStorage.setItem(ACCOUNT_STORAGE_KEY, username);
                document.getElementById('current-account-display').textContent = `@${username}`;
                statusDiv.className = 'status-badge status-success';
                statusDiv.textContent = `✅ Connected as @${username}`;
                console.log(`✅ Account validated: @${username}`);
            } else {
                statusDiv.className = 'status-badge status-failed';
                statusDiv.textContent = '❌ Account not found';
            }
        })
        .catch(err => {
            statusDiv.className = 'status-badge status-failed';
            statusDiv.textContent = '❌ Connection error';
            console.error(err);
        });
    }

    // ========== VOTE FUNCTION ==========
    function votePost(author, permlink, button, wordCount) {
        const savedAccount = localStorage.getItem(ACCOUNT_STORAGE_KEY) || '<?php echo VOTER_ACCOUNT; ?>';
        
        if (button.disabled) return;
        
        button.disabled = true;
        button.textContent = "⏳ Voting...";

        // Calculate weight using centralized function
        let weight = calculateVoteWeight(wordCount);

        console.log(`🗳️ Voting for @${author}/${permlink} with ${weight/100}% (${wordCount} words)`);

        hive_keychain.requestVote(
            savedAccount,
            permlink,
            author,
            weight,
            response => {
                if (response.success) {
                    button.textContent = "✅ Voted";
                    button.classList.add("voted");
                    addVoteToSession(author, permlink, weight, true);
                    updateHistoryDisplay();
                    console.log(`✅ Successfully voted for @${author}/${permlink}`);
                } else {
                    button.textContent = "❌ Failed";
                    button.style.background = "linear-gradient(90deg, var(--neon-red), #ff7a18)";
                    console.error("Vote error:", response.message);
                    alert(`❌ Vote failed: ${response.message}`);
                }
            }
        );
    }

    // ========== ADD VOTE TO SESSION ==========
    function addVoteToSession(author, permlink, weight, success) {
        const voteData = {
            author: author,
            permlink: permlink,
            weight: weight,
            success: success,
            timestamp: Date.now()
        };
        
        voteSession.history.unshift(voteData);
        voteSession.votedPosts.push(`${author}/${permlink}`);
        
        localStorage.setItem(VOTE_STORAGE_KEY, JSON.stringify(voteSession));
    }

    // ========== LOAD VOTE SESSION ==========
    function loadVoteSession() {
        const stored = localStorage.getItem(VOTE_STORAGE_KEY);
        if (stored) {
            voteSession = JSON.parse(stored);
            updateHistoryDisplay();
        }
    }

    // ========== CLEAR VOTE HISTORY ==========
    function clearVoteHistory() {
        if (confirm('🗑️ Clear all vote history?')) {
            voteSession = { history: [], votedPosts: [] };
            localStorage.removeItem(VOTE_STORAGE_KEY);
            updateHistoryDisplay();
            console.log('✅ Vote history cleared');
        }
    }

    // ========== UPDATE HISTORY DISPLAY ==========
    function updateHistoryDisplay() {
        const historyList = document.getElementById('history-list');
        
        if (voteSession.history.length === 0) {
            historyList.innerHTML = '<p style="text-align: center; color: var(--text-secondary); padding: 20px;">No votes yet in this session</p>';
            return;
        }

        historyList.innerHTML = voteSession.history.map(vote => {
            const authorName = `@${vote.author}`;
            const postUrl = `https://peakd.com/@${vote.author}/${vote.permlink}`;
            const profileUrl = `https://peakd.com/@${vote.author}`;
            const avatarUrl = `https://images.hive.blog/u/${vote.author}/avatar`;
            const timeAgo = getTimeAgo(vote.timestamp);
            
            return `
                <div class="history-item">
                    <a href="${profileUrl}" target="_blank">
                        <img src="${avatarUrl}" alt="${authorName}" class="history-avatar" 
                             onerror="this.src='https://images.hive.blog/u/hive.blog/avatar'">
                    </a>
                    
                    <div class="history-content">
                        <a href="${profileUrl}" target="_blank" class="history-author">
                            ${authorName}
                        </a>
                        <a href="${postUrl}" target="_blank" class="history-post-link">
                            🔗 View post on PeakD
                        </a>
                    </div>
                    
                    <div class="history-meta">
                        <span class="history-weight">💎 ${vote.weight / 100}%</span>
                        <span class="history-time">${timeAgo}</span>
                    </div>
                </div>
            `;
        }).join('');
    }

    function getTimeAgo(timestamp) {
        const now = Date.now();
        const diffMs = now - timestamp;
        const diffMins = Math.floor(diffMs / 60000);
        
        if (diffMins < 1) return 'Just now';
        if (diffMins < 60) return `${diffMins}m ago`;
        
        const diffHours = Math.floor(diffMins / 60);
        if (diffHours < 24) return `${diffHours}h ago`;
        
        const diffDays = Math.floor(diffHours / 24);
        return `${diffDays}d ago`;
    }

// ========== AUTO-VOTE FUNCTION ==========
async function startAutoVote() {

const username = localStorage.getItem(ACCOUNT_STORAGE_KEY)
    || '<?php echo VOTER_ACCOUNT; ?>';

// ✅ CHECK VP LIVE AU DÉMARRAGE
const res = await fetch('https://api.hive.blog', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        jsonrpc: '2.0',
        method: 'condenser_api.get_accounts',
        params: [[username]],
        id: 1
    })
});

const data = await res.json();
const liveVP = data?.result?.[0]
    ? Math.round(data.result[0].voting_power / 100)
    : 0;

console.log(`🔋 VP au démarrage : ${liveVP}%`);

if (liveVP < <?php echo MIN_VP_THRESHOLD; ?>) {
    console.log("⛔ VP insuffisant, auto-vote annulé");
    return;
}

// ✅ TON CODE AUTO-VOTE EXISTANT
const buttons = document.querySelectorAll("button.vote-btn:not([disabled])");

if (buttons.length === 0) {
    console.log("✅ No posts to vote");
    if (AUTO_REFRESH_AFTER_VOTE) scheduleAutoRefresh();
    return;
}

console.log(`🤖 AUTO-VOTE: Starting for ${buttons.length} posts`);
showAutoVoteIndicator(buttons.length);

let index = 0;
function voteNext() {
    if (index >= buttons.length) {
        console.log("✅ AUTO-VOTE: Complete");
        hideAutoVoteIndicator();
        if (AUTO_REFRESH_AFTER_VOTE) scheduleAutoRefresh();
        return;
    }

    updateAutoVoteIndicator(index + 1, buttons.length);
    buttons[index].click();
    index++;
    setTimeout(voteNext, AUTO_VOTE_DELAY);
}

voteNext();

}

    // ========== VOTE ALL FUNCTION (MANUAL) ==========
    function voteAll() {
        const buttons = document.querySelectorAll("button.vote-btn:not([disabled])");
        
        if (buttons.length === 0) {
            console.log("✅ No eligible posts - refreshing in 5 seconds");
            scheduleAutoRefresh();
            return;
        }

        let index = 0;
        function voteNext() {
            if (index >= buttons.length) {
                console.log("✅ Voting complete - refreshing in 5 seconds");
                scheduleAutoRefresh();
                return;
            }
            buttons[index].click();
            index++;
            setTimeout(voteNext, 3000);
        }

        voteNext();
    }

...

0.00000000 BEE