The current code of the bot

image.png

image.png

Last issue to fix:

At startup, the robot’s VP (voting power) isn’t loading correctly. It keeps the VP value it had in memory when it last shut down, but it doesn’t account for the VP that recharged during its pause or downtime.
We’ll need to force it to update this value—hopefully, that won’t be too hard to implement! ^^

Second minor issue to resolve:

We set a minimum VP threshold below which the robot shouldn’t vote anymore. Right now, it acknowledges the limit but keeps voting anyway without stopping. Another puzzle to solve, but overall, the project is coming along nicely!
As I’ve mentioned in previous posts, I eventually plan to release this publicly—but first, we’ll need to iron out all these little details and run thorough tests.

I’m sure these are just silly little fixes, but as I’ve said before, I’m not a pro coder. I’m learning as I go, with help from some AI tutors, and I only modify the code if I fully understand what I’m doing. ^^



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

// ----------- AUTO-VOTE CONFIGURATION -----------
define('AUTO_VOTE_ENABLED', true);  // 
define('AUTO_VOTE_DELAY', 4000);    //Delay between each vote (ms)
define('AUTO_REFRESH_AFTER_VOTE', true); // Refresh after voting all

// ========== 🎯 CHANGE YOUR VOTING THRESHOLDS HERE ==========
// Format: words => %%%% (example, 700 = 7%)
$VOTE_RULES = [
    1500 => 1500,  // 15% for 1500+ words
    1000 => 1400,  // 14% words1000-1499 words
    500 => 1300,  // 13 %words500-999 words
    350  => 1200   // 12% for 350-499 words
];
// ========================================================

// ----------- WEIGHT CALCULATION FUNCTION -----------
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;
}

// ----------- WORDS  CALCULATED -----------
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);
}

// ----------- 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'] ?? [];
}

// ----------- ACCOMPT -----------
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;
}

// ----------- 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;
}

// ----------- MAIN LOGIC -----------
$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;
    }
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>🤖 Hive Auto Words</title>
    <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;600&family=Rajdhani:wght@500;600&display=swap" rel="stylesheet">
    <style>
        :root {
            --neon-blue: #00d4ff;
            --neon-purple: #b200ff;
            --neon-green: #00ff88;
            --neon-red: #ff2e63;
            --neon-yellow: #ffc700;
            --bg-dark: #0a0a1a;
            --bg-card: #121230;
            --text-primary: #e0e0ff;
            --text-secondary: #a0a0c0;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Rajdhani', sans-serif;
            background: linear-gradient(135deg, #0a0a1a 0%, #1a0a2e 100%);
            color: var(--text-primary);
            padding: 40px 20px;
            min-height: 100vh;
        }

        h1 {
            font-family: 'Orbitron', sans-serif;
            text-align: center;
            font-size: 2.5rem;
            margin-bottom: 40px;
            background: linear-gradient(90deg, var(--neon-blue), var(--neon-purple));
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            text-shadow: 0 0 20px rgba(0, 212, 255, 0.3);
        }

        .card {
            background: rgba(18, 18, 48, 0.9);
            border: 1px solid rgba(178, 0, 255, 0.2);
            border-radius: 15px;
            padding: 25px;
            margin-bottom: 30px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
        }

        .card h2 {
            font-family: 'Orbitron', sans-serif;
            color: var(--neon-purple);
            margin-bottom: 20px;
            font-size: 1.5rem;
        }

        .card p {
            font-size: 1.1rem;
            margin-bottom: 12px;
            color: var(--text-secondary);
        }

        .card strong {
            color: var(--text-primary);
        }

        .vp-container {
            background: rgba(10, 10, 30, 0.5);
            padding: 15px;
            border-radius: 10px;
            margin: 15px 0;
        }

        .vp-bar {
            width: 100%;
            height: 20px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 10px;
            overflow: hidden;
            margin-top: 10px;
        }

        .vp-fill {
            height: 100%;
            width: <?php echo $current_vp; ?>%;
            background: linear-gradient(90deg, var(--neon-green), var(--neon-yellow));
            transition: width 0.5s ease;
        }

        table {
            width: 100%;
            border-collapse: collapse;
            background: rgba(18, 18, 48, 0.6);
            border-radius: 10px;
            overflow: hidden;
        }

        th, td {
            padding: 15px;
            text-align: left;
            border-bottom: 1px solid rgba(178, 0, 255, 0.1);
        }

        th {
            background: rgba(178, 0, 255, 0.1);
            color: var(--neon-purple);
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 1px;
            font-size: 0.9rem;
        }

        tr:hover {
            background: rgba(0, 212, 255, 0.05);
        }

        button {
            padding: 8px 16px;
            border: none;
            border-radius: 6px;
            font-family: 'Rajdhani', sans-serif;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.2s;
            background: linear-gradient(90deg, var(--neon-blue), var(--neon-purple));
            color: white;
            box-shadow: 0 4px 6px rgba(0, 212, 255, 0.2);
            position: relative;
            overflow: hidden;
        }

        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(0, 212, 255, 0.3);
        }

        button:active {
            transform: translateY(0);
        }

        button:disabled {
            background: #3a3a5a;
            color: #666;
            cursor: not-allowed;
            box-shadow: none;
        }

        button.voted {
            background: linear-gradient(90deg, var(--neon-green), var(--neon-yellow));
            animation: pulse 1.5s infinite;
        }

        @keyframes pulse {
            0% { box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.4); }
            70% { box-shadow: 0 0 0 10px rgba(0, 255, 136, 0); }
            100% { box-shadow: 0 0 0 0 rgba(0, 255, 136, 0); }
        }

        .debug-table {
            margin-top: 40px;
            border-top: 1px solid rgba(178, 0, 255, 0.2);
            padding-top: 30px;
        }

        .debug-table h2 {
            color: var(--neon-purple);
            text-shadow: 0 0 8px rgba(178, 0, 255, 0.3);
        }

        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(10px); }
            to { opacity: 1; transform: translateY(0); }
        }

        .card, table, h1, h2 {
            animation: fadeIn 0.5s ease-out forwards;
        }

        .history-item {
            background: rgba(10, 10, 30, 0.7);
            border: 1px solid rgba(178, 0, 255, 0.3);
            border-radius: 10px;
            padding: 15px;
            display: grid;
            grid-template-columns: 80px 1fr auto;
            gap: 15px;
            align-items: center;
            transition: all 0.3s ease;
        }

        .history-item:hover {
            background: rgba(0, 212, 255, 0.1);
            border-color: var(--neon-blue);
            transform: translateX(5px);
            box-shadow: 0 4px 15px rgba(0, 212, 255, 0.2);
        }

        .history-avatar {
            width: 60px;
            height: 60px;
            border-radius: 50%;
            border: 2px solid var(--neon-purple);
            object-fit: cover;
        }

        .history-content {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }

        .history-author {
            font-weight: bold;
            color: var(--neon-blue);
            text-decoration: none;
            font-size: 1.1rem;
        }

        .history-author:hover {
            color: var(--neon-purple);
        }

        .history-post-link {
            color: var(--text-secondary);
            text-decoration: none;
            font-size: 0.9rem;
        }

        .history-post-link:hover {
            color: var(--neon-green);
        }

        .history-meta {
            display: flex;
            flex-direction: column;
            align-items: flex-end;
            gap: 8px;
        }

        .history-weight {
            background: linear-gradient(90deg, var(--neon-purple), var(--neon-blue));
            padding: 6px 12px;
            border-radius: 6px;
            font-weight: bold;
            font-size: 0.95rem;
        }

        .history-time {
            color: var(--text-secondary);
            font-size: 0.85rem;
        }

        .status-badge {
            padding: 6px 12px;
            border-radius: 6px;
            font-size: 0.85rem;
            font-weight: 600;
            text-align: center;
        }

        .status-success {
            background: rgba(0, 255, 136, 0.2);
            color: var(--neon-green);
            border: 1px solid rgba(0, 255, 136, 0.3);
        }

        .status-failed {
            background: rgba(255, 46, 99, 0.2);
            color: var(--neon-red);
            border: 1px solid rgba(255, 46, 99, 0.3);
        }

        .status-loading {
            background: rgba(0, 212, 255, 0.2);
            color: var(--neon-blue);
            border: 1px solid rgba(0, 212, 255, 0.3);
        }

        @keyframes pulse-glow {
            0%, 100% {
                box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
                border-color: rgba(0, 212, 255, 0.4);
            }
            50% {
                box-shadow: 0 6px 20px rgba(0, 212, 255, 0.6);
                border-color: rgba(0, 212, 255, 0.8);
            }
        }

        @media (max-width: 768px) {
            body { padding: 15px; }
            .card { padding: 15px; }
            th, td { padding: 8px 10px; }
            h1 { font-size: 1.8rem; }
        }
    </style>
</head>
<body>
    <h1>🤖 HIVE AUTO WORDS <span style="font-size: 1.2rem; font-weight: normal; color: var(--text-secondary);">Edition</span></h1>

    <!-- ========== PATCH 2: Account Switcher ========== -->
    <div class="card" style="position: sticky; top: 20px; z-index: 1000; margin-bottom: 30px;">
        <h2 style="margin-bottom: 15px;">🔐 Voting Account</h2>
        
        <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>

    <div class="card">
        <h2>📊 Current Status</h2>
        <p><strong>Account:</strong> <span style="color: var(--neon-purple);" id="current-account-display">@<?php echo VOTER_ACCOUNT; ?></span></p>
        
        <div class="vp-container">
            <div>
                <strong>VP:</strong> <span style="color: <?php echo $current_vp >= 80 ? 'var(--neon-green)' : ($current_vp >= 50 ? 'var(--neon-yellow)' : 'var(--neon-red)'); ?>;">
                    <?php echo $current_vp; ?>%
                </span>
            </div>
            <div class="vp-bar">
                <div class="vp-fill"></div>
            </div>
        </div>

        <p><strong>Eligible Posts:</strong> <span style="color: var(--neon-green);"><?php echo count($eligible_posts); ?></span>/<?php echo count($posts); ?></p>
        
        <!-- ========== DISPLAY OF VOTING RULES ========== -->
        <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 ========== -->
    <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>

    <?php if (DEBUG_MODE): ?>
    <div class="debug-table">
        <h2>🐞 Debug Log</h2>
        <table>
            <thead>
                <tr>
                    <th>Author</th>
                    <th>Permlink</th>
                    <th>Words</th>
                    <th>Status</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($debug_logs as $log): ?>
                <tr>
                    <td>@<?php echo htmlspecialchars($log['author']); ?></td>
                    <td><?php echo htmlspecialchars($log['permlink']); ?></td>
                    <td><?php echo $log['words']; ?></td>
                    <td><?php echo $log['reason']; ?></td>
                </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
    </div>
    <?php endif; ?>

    <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 START BOT
    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;
    }

    // ✅ YOUR CODING AUTO-VOTE 
    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();
        }

        // ========== 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('@', ''))
    )];

    --------------  // ✅ HEADER OPTIONNEL ----------
    let csvContent = "";

    uniqueAccounts.forEach(username => {
        csvContent += `${username},0.001,GLYPH,You are eligible to vote, GLYPH is currently under construction.\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 ${uniqueAccounts.length} accounts in GLYPH format`);
}

        // ========== 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 if enabled AND posts available
            if (AUTO_VOTE_ENABLED && eligibleCount > 0) {
                console.log(`🤖 AUTO-VOTE enabled: ${eligibleCount} posts detected`);
                setTimeout(() => {
                    startAutoVote();
                }, 2000); //  2s 
            } else if (eligibleCount === 0) {
                console.log("⚠️ No eligible posts - scheduling auto-refresh");
                scheduleAutoRefresh();
            }

            // Button 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 ========== -->
<div style="
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 9999;
    background: rgba(18, 18, 48, 0.95);
    border-radius: 50px;
    padding: 15px 20px;
    display: flex;
    align-items: center;
    gap: 15px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
    border: 2px solid var(--neon-blue);
    backdrop-filter: blur(10px);
">
    <button
        id="autoVoteToggle"
        onclick="toggleAutoVote()"
        style="
            background: linear-gradient(90deg, var(--neon-blue), var(--neon-purple));
            color: white;
            border: none;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            font-size: 20px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3);
            transition: all 0.3s;
        "
        onmouseover="this.style.transform='scale(1.1)'"
        onmouseout="this.style.transform='scale(1)'"
    >
        ⏸
    </button>
    <div style="color: var(--neon-green); font-family: 'Orbitron', sans-serif;">
        AUTO-VOTE: <span id="autoVoteStatus">ACTIVE</span>
    </div>
</div>

<script>
// ========== 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)";

        // Restart the auto-vote if any posts are eligible.
        const eligibleCount = <?php echo count($eligible_posts); ?>;
        if (eligibleCount > 0) {
            setTimeout(() => {
                startAutoVote();
            }, 1000);
        }
    }
}

// Modified startAutoVote to handle the 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 (!<?php echo AUTO_VOTE_ENABLED ? 'true' : 'false'; ?>) {
        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.");
        };
    }
});
</script>
    
</body>
</html>

In the OPTIONAL HEADER
You can change the CSV as you wish, or leave only the :`${username}, which takes the voted account names

image.png

image.png

0.02088112 BEE
0 comments