Programmed an auto-vote that is smarter than a simple live vote and infinitely configurable ? already functional
I'm posting this code because my @we-are-ai account has a negative reputation

For those who can and have an internal server set up at home:
The complete code will be at the bottom of the page.
Here is a complete code that I improve every day when I have time. The program is functional for those who would like it. Okay, right now it's made up of patch blocks one after the other, while I clean it all up, but the important thing is that it works very well and is very efficient.
The program presented does the following, which you can fully modify on screen while it's running, all in real time:
Choose the voting account with Hive keychain verification. The only drawback so far is that you have to leave the keychain open while it's running and check the "always allowed" box (I'm looking for a different way to do this, but I don't want to hardcode the keys).

Choose the voting rules, minimum word count, and vote percentage (if you don't specify, the vote will be passed). The vote percentage is displayed at the bottom, and you can also choose the minimum value.
The program scans the blockchain every 5 seconds and identifies the last 20 eligible posts. It cannot go back further; it uses the Peakd API.

Despite my attempts, I cannot get it to modify the text at the top live.

You can then choose the minimum VP so that he no longer votes.

The program counts the votes for you until you press "clear" to reset everything.
There's a play/pause button. If you've never created an account, it's supposed to start in pause mode. However, it keeps the last settings and votes in memory until you press "clear" and start immediately upon boot, if your keychain and account have already been used for the program.
If I've made a mistake, just pause it when you start it.


You may have noticed there's a CSV button; I use that one myself in my projects. You can modify it further down in the code.

You will find the voting details below.
If you click on the logo, it will take you to the account; if you click on "view post in peakd", you will see the post in question.

If anyone is interested, I am currently doing the same for tag detectors.
:peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive::peakd/hive:
<?php
// ----------- CONFIGURATION -----------
define('VOTER_ACCOUNT', 'YOUR ACCOUNT');
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); // ⚠️ Set false to disable
define('AUTO_VOTE_DELAY', 5000); // Delay between each vote (ms): 5 seconds
define('AUTO_REFRESH_AFTER_VOTE', true); // Refresh after voting all
// ========== 🎯 CHANGE YOUR VOTING THRESHOLDS HERE ==========
// Format: number_of_words => vote_weight (in hundredths, 700 = 7%)
$VOTE_RULES = [
300 => 500 // 5% 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;
}
// ----------- WORD COUNTER -----------
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);
}
// ----------- POS RECOVERY -----------
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'] ?? [];
}
// ----------- ACCOUNT RECOVERY -----------
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 RECOVERY -----------
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 = 5000;
// ========== AUTO-VOTE CONFIG ==========
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 ==========
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 at startup: ${liveVP}%`);
if (liveVP < <?php echo MIN_VP_THRESHOLD; ?>) {
console.log("⛔ Insufficient VP, self-vote cancelled");
return;
}
// ✅ YOUR EXISTING AUTO-VOTE CODE
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 (You can delete it if you want.)
let csvContent = "";
uniqueAccounts.forEach(username => {
csvContent += `${username},YOUR CSV,YOUR CSV,YOUR CSV\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); // 2-second delay to allow page to load
} else if (eligibleCount === 0) {
console.log("⚠️ No eligible posts - scheduling auto-refresh");
scheduleAutoRefresh();
}
// Adds the CSV export button
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)";
// 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 (!<?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>
<!-- ========== PATCH: Global Vote Counter ========== -->
<div id="global-vote-counter" style="
position: fixed;
bottom: 90px;
right: 20px;
z-index: 9999;
background: rgba(18, 18, 48, 0.95);
border-radius: 12px;
padding: 12px 18px;
border: 2px solid var(--neon-green);
color: var(--neon-green);
font-family: 'Orbitron', sans-serif;
font-size: 1rem;
box-shadow: 0 4px 15px rgba(0, 255, 136, 0.3);
">
🗳️ Votes: <span id="vote-count">0</span>
</div>
<script>
const VOTE_COUNT_KEY = 'global_vote_count';
// Charge the counter at startup
function loadVoteCounter() {
const count = parseInt(localStorage.getItem(VOTE_COUNT_KEY)) || 0;
document.getElementById('vote-count').textContent = count;
}
// Increment the counter
function incrementVoteCounter() {
let count = parseInt(localStorage.getItem(VOTE_COUNT_KEY)) || 0;
count++;
localStorage.setItem(VOTE_COUNT_KEY, count);
document.getElementById('vote-count').textContent = count;
}
// Counter reset
function resetVoteCounter() {
localStorage.removeItem(VOTE_COUNT_KEY);
document.getElementById('vote-count').textContent = 0;
}
// Hook in the existing vote
const originalVotePost = votePost;
votePost = function(author, permlink, button, wordCount) {
originalVotePost(author, permlink, button, wordCount);
incrementVoteCounter();
};
// Hook in the clear history
const originalClearHistory = clearVoteHistory;
clearVoteHistory = function() {
originalClearHistory();
resetVoteCounter();
};
// Init on loading
document.addEventListener('DOMContentLoaded', loadVoteCounter);
</script>
<!-- ========== PATCH: Live Vote Weight Control ========== -->
<div id="vote-weight-control" style="
position: fixed;
bottom: 150px;
right: 20px;
z-index: 9999;
background: rgba(18, 18, 48, 0.95);
border-radius: 12px;
padding: 12px 18px;
border: 2px solid var(--neon-blue);
color: var(--neon-blue);
font-family: 'Orbitron', sans-serif;
font-size: 0.9rem;
box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3);
">
⚙️ Vote % :
<input id="vote-weight-input" type="number" min="1" max="100" step="0.1" style="
width: 60px;
margin-left: 8px;
background: #111;
color: white;
border: 1px solid var(--neon-blue);
border-radius: 6px;
padding: 4px;
"> %
</div>
<script>
const VOTE_WEIGHT_KEY = 'custom_vote_weight';
// Charger la valeur
function loadVoteWeight() {
const saved = localStorage.getItem(VOTE_WEIGHT_KEY);
const input = document.getElementById('vote-weight-input');
if (saved) {
input.value = saved;
} else {
input.value = 5; // valeur par défaut
}
}
// Live backup
document.addEventListener('input', function(e) {
if (e.target.id === 'vote-weight-input') {
localStorage.setItem(VOTE_WEIGHT_KEY, e.target.value);
}
});
// Calculation override
const originalCalculateVoteWeight = calculateVoteWeight;
calculateVoteWeight = function(wordCount) {
const custom = localStorage.getItem(VOTE_WEIGHT_KEY);
if (custom) {
return Math.round(parseFloat(custom) * 100); // ex: 5 → 500
}
return originalCalculateVoteWeight(wordCount);
};
// Init
document.addEventListener('DOMContentLoaded', loadVoteWeight);
</script>
<!-- ========== PATCH: Live Vote Rules Editor ========== -->
<div id="vote-rules-editor" style="
position: fixed;
bottom: 220px;
right: 20px;
z-index: 9999;
background: rgba(18, 18, 48, 0.95);
border-radius: 12px;
padding: 15px;
border: 2px solid var(--neon-purple);
color: var(--neon-purple);
font-family: 'Orbitron', sans-serif;
font-size: 0.85rem;
box-shadow: 0 4px 15px rgba(178, 0, 255, 0.3);
width: 220px;
">
<div style="margin-bottom:10px;">⚙️ Rules Editor</div>
<div id="rules-container"></div>
<button onclick="addRule()" style="margin-top:10px;">➕ Add</button>
</div>
<script>
const RULES_KEY = 'custom_vote_rules';
// Charger règles
function loadRules() {
let rules = JSON.parse(localStorage.getItem(RULES_KEY));
if (!rules) {
rules = [{words:300, weight:5}];
localStorage.setItem(RULES_KEY, JSON.stringify(rules));
}
renderRules(rules);
}
// Affichage
function renderRules(rules) {
const container = document.getElementById('rules-container');
container.innerHTML = '';
rules.forEach((rule, index) => {
container.innerHTML += `
<div style="margin-bottom:6px;">
≥ <input type="number" value="${rule.words}" style="width:50px;" onchange="updateRule(${index}, 'words', this.value)">
→ <input type="number" value="${rule.weight}" style="width:40px;" onchange="updateRule(${index}, 'weight', this.value)"> %
<span onclick="deleteRule(${index})" style="cursor:pointer;color:red;">✖</span>
</div>
`;
});
}
// Update règle
function updateRule(index, key, value) {
let rules = JSON.parse(localStorage.getItem(RULES_KEY));
rules[index][key] = parseFloat(value);
localStorage.setItem(RULES_KEY, JSON.stringify(rules));
}
// Ajouter
function addRule() {
let rules = JSON.parse(localStorage.getItem(RULES_KEY));
rules.push({words:500, weight:10});
localStorage.setItem(RULES_KEY, JSON.stringify(rules));
renderRules(rules);
}
// Supprimer
function deleteRule(index) {
let rules = JSON.parse(localStorage.getItem(RULES_KEY));
rules.splice(index, 1);
localStorage.setItem(RULES_KEY, JSON.stringify(rules));
renderRules(rules);
}
// Calculation override
const originalCalculateVoteWeightRules = calculateVoteWeight;
calculateVoteWeight = function(wordCount) {
const rules = JSON.parse(localStorage.getItem(RULES_KEY));
if (!rules) return originalCalculateVoteWeightRules(wordCount);
// tri décroissant
rules.sort((a,b) => b.words - a.words);
for (let rule of rules) {
if (wordCount >= rule.words) {
return Math.round(rule.weight * 100);
}
}
return 0;
};
// Update display (Rules + painting)
function refreshUIRules() {
const rules = JSON.parse(localStorage.getItem(RULES_KEY));
if (!rules) return;
// bloc rules
const rulesDiv = document.querySelector('.card div[style*="Vote Rules"]');
if (rulesDiv) {
rulesDiv.innerHTML = `<h3 style="color: var(--neon-blue);">⚙️ Vote Rules (Live)</h3>` +
rules.map(r => `
<p style="margin:5px 0;">
<span style="color: var(--neon-yellow);">≥ ${r.words} words</span>
→
<span style="color: var(--neon-green); font-weight:bold;">${r.weight}%</span>
</p>
`).join('');
}
// painting
document.querySelectorAll('td span').forEach(el => {
if (el.textContent.includes('%')) {
const rulesSorted = [...rules].sort((a,b)=>b.words-a.words);
const row = el.closest('tr');
const words = parseInt(row.children[2]?.textContent || 0);
for (let r of rulesSorted) {
if (words >= r.words) {
el.textContent = r.weight + '%';
break;
}
}
}
});
}
// refresh after modification
document.addEventListener('input', function(e){
if (e.target.closest('#rules-container')) {
refreshUIRules();
}
});
// init
document.addEventListener('DOMContentLoaded', () => {
loadRules();
refreshUIRules();
});
</script>
<script>
// Control key
const AUTO_VOTE_STATE_KEY = 'auto_vote_state';
// Forced state PAUSE on first load
document.addEventListener('DOMContentLoaded', () => {
let state = localStorage.getItem(AUTO_VOTE_STATE_KEY);
// If not yet defined → force PAUSE
if (!state) {
localStorage.setItem(AUTO_VOTE_STATE_KEY, 'paused');
state = 'paused';
}
applyAutoVoteState(state);
});
// Apply UI state + logic
function applyAutoVoteState(state) {
const btn = document.querySelector('#auto-vote-button'); // adapte si ton ID est différent
if (!btn) return;
if (state === 'paused') {
btn.innerHTML = '▶️ START AUTO-VOTE';
btn.style.background = 'linear-gradient(45deg, #00c6ff, #0072ff)';
btn.dataset.state = 'paused';
// stop ton loop si existant
if (window.autoVoteInterval) {
clearInterval(window.autoVoteInterval);
window.autoVoteInterval = null;
}
console.log('⏸️ Auto-vote paused');
} else {
btn.innerHTML = '⏸️ PAUSE AUTO-VOTE';
btn.style.background = 'linear-gradient(45deg, #ff4d4d, #ff0000)';
btn.dataset.state = 'running';
startAutoVote(); // utilise ta fonction existante
console.log('▶️ Auto-vote running');
}
}
// Toggle bouton
document.addEventListener('click', function(e) {
if (e.target.closest('#auto-vote-button')) {
let current = localStorage.getItem(AUTO_VOTE_STATE_KEY) || 'paused';
let next = current === 'running' ? 'paused' : 'running';
localStorage.setItem(AUTO_VOTE_STATE_KEY, next);
applyAutoVoteState(next);
}
});
</script>
<!-- ========== PATCH: VP LIMIT CONTROL ========== -->
<div id="vp-control" style="
position: fixed;
bottom: 350px;
right: 20px;
z-index: 9999;
background: rgba(18, 18, 48, 0.95);
border-radius: 12px;
padding: 12px 15px;
border: 2px solid var(--neon-yellow);
color: var(--neon-yellow);
font-family: 'Orbitron', sans-serif;
font-size: 0.85rem;
box-shadow: 0 4px 15px rgba(255, 255, 0, 0.3);
width: 200px;
">
<div>⚡ Min VP</div>
<input id="vp-threshold" type="number" min="1" max="100" style="width:60px; margin-top:8px;">
<span>%</span>
</div>
<script>
const VP_THRESHOLD_KEY = 'vp_threshold';
// init
document.addEventListener('DOMContentLoaded', () => {
const input = document.getElementById('vp-threshold');
let saved = localStorage.getItem(VP_THRESHOLD_KEY);
if (!saved) {
saved = 80; // valeur par défaut
localStorage.setItem(VP_THRESHOLD_KEY, saved);
}
input.value = saved;
});
// save live
document.addEventListener('input', function(e){
if (e.target.id === 'vp-threshold') {
localStorage.setItem(VP_THRESHOLD_KEY, e.target.value);
}
});
//check VP before voting
function canVoteWithVP(currentVP) {
const threshold = parseFloat(localStorage.getItem(VP_THRESHOLD_KEY) || 80);
return currentVP >= threshold;
}
</script>
<!-- ========== PATCH: No Alert / Auto-Skip Errors ========== -->
<script>
// ✅ 1. Neutralize ALL alert() (anti-total blocking)
window.alert = function(message) {
console.warn("🚫 Alert bloquée :", message);
};
// ✅ 2. Optional: small visual toast (non-blocking)
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. Automatic hook on voting buttons
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')) {
// visual anti-freeze security
setTimeout(() => {
if (btn.textContent.includes('❌')) {
console.log("⚠️ Vote failed auto-skip");
showToast("Vote failed → skipped");
}
}, 1500);
}
});
// ✅ 4. Overall JS security (no errors block)
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);
};
</script>
</body>
</html>
Very cool my friend well done people that can code can do very well for themselves in this digital world we now live in @benef.alive

Made in Canva
!ALIVE
This post has been curated by the Alive And Thriving Team, we curate good content in the We Are Alive Tribe that is on topic for #aliveandthriving, and it's included in our daily curation report on @aliveandthriving, plus @youarealive is following our Curation Trail.
thx you What I enjoy most is doing it. !ALIVE !HEARTBEAT
I'm in, brother. I will try to use this code. Thanks for sharing.
!ALIVE
It's not perfect, but it works, I'm learning at the same time, and I'm having fun.
That's great, learning and implementing what you are learning is actually a great progress and growth.
Keep learning, growing and have #fun.
!ALIVE
!INDEED
!BBH