// ==UserScript==
// @name Stake Wheel Bot
// @namespace http://tampermonkey.net/
// @version 1.1.0
// @description Advanced Wheel Bot for Stake.us and Stake.com with a resizable UI, intelligent triggers, notifications, and enhanced strategy options.
// @author shdw_lol (Adapted by Gemini)
// @license MIT
// @match *://stake.com/casino/games/wheel*
// @match *://stake.com/*/casino/games/wheel*
// @match *://stake.ac/casino/games/wheel*
// @match *://stake.ac/*/casino/games/wheel*
// @match *://stake.games/casino/games/wheel*
// @match *://stake.games/*/casino/games/wheel*
// @match *://stake.bet/casino/games/wheel*
// @match *://stake.bet/*/casino/games/wheel*
// @match *://stake.pink/casino/games/wheel*
// @match *://stake.pink/*/casino/games/wheel*
// @match *://stake.mba/casino/games/wheel*
// @match *://stake.mba/*/casino/games/wheel*
// @match *://stake.jp/casino/games/wheel*
// @match *://stake.jp/*/casino/games/wheel*
// @match *://stake.bz/casino/games/wheel*
// @match *://stake.bz/*/casino/games/wheel*
// @match *://stake.ceo/casino/games/wheel*
// @match *://stake.ceo/*/casino/games/wheel*
// @match *://stake.krd/casino/games/wheel*
// @match *://stake.krd/*/casino/games/wheel*
// @match *://staketr.com/casino/games/wheel*
// @match *://staketr.com/*/casino/games/wheel*
// @match *://stake.us/casino/games/wheel*
// @match *://stake.us/*/casino/games/wheel*
// @grant GM_addStyle
// @grant unsafeWindow
// @run-at document-idle
// ==/UserScript==
// --- DISCLAIMER ---
// This script interacts with the Stake API to perform actions like changing your game seed.
// To do this, it needs to use your account's session token. This token is retrieved from your browser's
// storage (cookies or local storage) and is ONLY sent back to Stake's official servers.
// It is never sent to any third-party server or stored outside of your browser.
// The password feature uses SHA-256 hashing and stores the result in your browser's local storage.
// It is never transmitted over the network.
(function() {
'use strict';
// --- CONFIGURATION & STATE ---
const state = {
running: false,
isLocked: false,
baseBet: 0.01,
currentBet: 0.01,
betCount: 0,
totalProfit: 0,
winStreak: 0,
lossStreak: 0,
lastBetWasWin: null,
strategyFlags: {}, // For stateful conditions like 'firstStreakOf'
persistentTriggerStates: {}, // For tracking profit/time based one-time actions
lastWinTimestamp: 0,
outcomeHistory: '', // String of 'W' and 'L'
originalAutoStrategy: null,
originalBetDelay: null,
apiBlockStartTime: 0,
stackingDelay: 0,
};
const semiAutoState = {
currentBet: 0.01,
isStarted: false,
};
let advancedStrategies = {};
let isPickingElement = false;
let logEntries = [];
let isBotInitialized = false;
let storedUsername = '';
let storedPasswordHash = '';
let pendingAction = null;
const logFilters = {
system: true,
manual: true,
auto: true,
strategy: true,
};
// --- CONFIG & SELECTORS ---
let CONFIG = {
authToken: '',
seedChangeEndpoint: '/_api/graphql',
betButton: '[data-testid="bet-button"]',
amountInput: '[data-testid="input-game-amount"]',
riskSelect: '[data-testid="risk-select"]',
segmentsSelect: '[data-testid="segments-select"]',
betDelay: '100',
maxBetAmount: 0,
lossMultiplier: 1,
pastBetsContainer: '.past-bets',
autoTab: '[data-testid="auto-tab"]',
manualTab: '[data-testid="manual-tab"]',
uiScale: 100,
};
const CONFIG_FRIENDLY_NAMES = {
authToken: "Auth Token",
seedChangeEndpoint: "API Endpoint",
betButton: "Bet Button",
amountInput: "Amount Input",
riskSelect: "Risk/Difficulty Select",
segmentsSelect: "Segments Select",
betDelay: "Bet Delay Range (ms)",
maxBetAmount: "Max Bet Amount (0=off)",
lossMultiplier: "Loss Threshold (> is Win)",
pastBetsContainer: "Past Bets List",
autoTab: "Auto Tab Button",
manualTab: "Manual Tab Button",
uiScale: "UI Scale (%)",
};
// --- UI (HTML & CSS) ---
const botHtml = `
<div id="bot-notification-container"></div>
<div id="wheel-bot-window" class="bot-window">
<div class="resizer resizer-t"></div>
<div class="resizer resizer-r"></div>
<div class="resizer resizer-b"></div>
<div class="resizer resizer-l"></div>
<div class="resizer resizer-tl"></div>
<div class="resizer resizer-tr"></div>
<div class="resizer resizer-br"></div>
<div class="resizer resizer-bl"></div>
<div id="bot-header" class="bot-header">
<span>Stake Wheel Bot <span id="bot-status-indicator"></span> <span id="profit-loss-display"></span></span>
<div class="window-controls">
<button id="minimize-btn" class="window-btn" title="Minimize">—</button>
<button id="close-btn" class="window-btn" title="Close">×</button>
</div>
</div>
<div id="bot-main-content" class="bot-content">
<div class="bot-tabs">
<button class="bot-tab-btn active" data-tab="semi-auto">Manual</button>
<button class="bot-tab-btn" data-tab="main">Auto</button>
<button class="bot-tab-btn" data-tab="advanced">Advanced</button>
<button class="bot-tab-btn" data-tab="log">Log</button>
<button class="bot-tab-btn" data-tab="config">Config</button>
<button class="bot-tab-btn" data-tab="admin">Admin</button>
</div>
<div id="tab-semi-auto" class="bot-tab-content active">
<div class="bot-input-group"><label>Base Amount</label><input type="number" id="semi-auto-base-bet" class="bot-input" value="0.01" step="any"></div>
<div class="bot-input-group"><label>Strategy</label><select id="semi-auto-strategy" class="bot-input"><option value="none" selected>None (Manual Betting)</option></select></div>
<p id="semi-auto-desc" class="strategy-desc">Select a custom strategy or use manual betting.</p>
<button id="semi-auto-step-btn" class="bot-btn bot-btn-primary" title="Run a single bet (D)">Run Step (D)</button>
<button id="semi-auto-reset-btn" class="bot-btn bot-btn-danger" title="Reset bet amount to base (R)">Reset to Base (R)</button>
<button id="manual-change-seed-btn" class="bot-btn bot-btn-secondary" title="Test the API seed change feature">Change Seed (API)</button>
</div>
<div id="tab-main" class="bot-tab-content">
<div class="bot-input-group"><label>Base Amount</label><input type="number" id="base-bet" class="bot-input" value="0.01" step="any"></div>
<div class="bot-input-group"><label>Difficulty</label><select id="auto-risk" class="bot-input"><option value="low">Low</option><option value="medium" selected>Medium</option><option value="high">High</option></select></div>
<div class="bot-input-group"><label>Segments</label><select id="auto-segments" class="bot-input"><option value="10" selected>10</option><option value="20">20</option><option value="30">30</option><option value="40">40</option><option value="50">50</option></select></div>
<div class="bot-input-group"><label>Strategy</label><select id="auto-strategy-select" class="bot-input"><option value="none">None (No auto changes)</option></select></div>
<button id="start-stop-btn" class="bot-btn bot-btn-primary" title="Starts or stops the bot (S)">Start Auto (S)</button>
</div>
<div id="tab-advanced" class="bot-tab-content">
<div class="bot-input-group">
<label>Your Saved Strategies</label>
<select id="advanced-strategy-list" class="bot-input"></select>
</div>
<div class="btn-grid">
<button id="create-strategy-btn" class="bot-btn bot-btn-secondary">Create New</button>
<button id="edit-strategy-btn" class="bot-btn bot-btn-secondary">Edit Selected</button>
<button id="import-strategy-btn" class="bot-btn bot-btn-secondary">Import</button>
<button id="export-strategy-btn" class="bot-btn bot-btn-secondary">Export Selected</button>
</div>
<button id="delete-strategy-btn" class="bot-btn bot-btn-danger">Delete Selected</button>
</div>
<div id="tab-log" class="bot-tab-content">
<div id="log-filters">
<label><input type="checkbox" data-filter="system" checked> System</label>
<label><input type="checkbox" data-filter="manual" checked> Manual</label>
<label><input type="checkbox" data-filter="auto" checked> Auto</label>
<label><input type="checkbox" data-filter="strategy" checked> Strategy</label>
</div>
<div id="log-container"></div>
<div class="btn-grid">
<button id="reset-stats-btn" class="bot-btn bot-btn-danger">Reset Stats</button>
<button id="save-log-btn" class="bot-btn bot-btn-secondary">Save Log to File</button>
</div>
</div>
<div id="tab-config" class="bot-tab-content">
<p class="strategy-desc">Configure API settings and UI element selectors. The bot tries to find the Auth Token automatically.</p>
<div id="config-container"></div>
<button id="save-config-btn" class="bot-btn bot-btn-primary">Save Config</button>
<button id="reset-config-btn" class="bot-btn bot-btn-danger">Reset to Defaults</button>
</div>
<div id="tab-admin" class="bot-tab-content">
<p class="strategy-desc">Set a password to lock bot settings. When locked, only the Manual and Admin tabs are usable.</p>
<div class="bot-input-group">
<label>Username</label>
<input type="text" id="admin-username" class="bot-input" autocomplete="username">
</div>
<div class="bot-input-group">
<label>New Password (leave blank to keep current)</label>
<input type="password" id="admin-password" class="bot-input" autocomplete="new-password">
</div>
<button id="save-credentials-btn" class="bot-btn bot-btn-secondary">Save Credentials</button>
<hr>
<button id="lock-unlock-btn" class="bot-btn bot-btn-primary">Lock</button>
<p id="admin-status" class="strategy-desc" style="margin-top: 15px; text-align: center;"></p>
</div>
</div>
</div>
<div id="wheel-bot-minimized-bar" style="display: none;">
<span>Stake Wheel Bot</span>
<div class="window-controls">
<button id="maximize-btn" class="window-btn" title="Maximize">□</button>
<button id="close-minimized-btn" class="window-btn" title="Close">×</button>
</div>
</div>
<div id="strategy-modal" style="display: none;">
<div id="strategy-modal-content">
<h3 id="strategy-modal-title">Create Strategy</h3>
<div class="bot-input-group"><label>Strategy Name</label><input type="text" id="strategy-name" class="bot-input"></div>
<div id="strategy-rules-container"></div>
<button id="add-rule-btn" class="bot-btn bot-btn-secondary">Add Condition Block</button>
<hr>
<p id="strategy-modal-warning" style="color: var(--accent-red); text-align: center; display: none;"></p>
<button id="save-strategy-btn" class="bot-btn bot-btn-primary">Save Strategy</button>
<button id="cancel-strategy-btn" class="bot-btn bot-btn-danger">Cancel</button>
</div>
</div>
<div id="password-prompt-modal" style="display: none;">
<div id="password-prompt-content">
<h3>Password Required</h3>
<p id="password-prompt-message">Please enter the password to unlock this feature.</p>
<div class="bot-input-group">
<label>Password</label>
<input type="password" id="prompt-password-input" class="bot-input" autocomplete="current-password">
</div>
<p id="password-prompt-error" style="color: var(--accent-red); display: none;"></p>
<button id="password-prompt-submit" class="bot-btn bot-btn-primary">Submit</button>
<button id="password-prompt-cancel" class="bot-btn bot-btn-secondary">Cancel</button>
</div>
</div>
<div id="element-picker-overlay"></div>
<div id="element-picker-tooltip">Click an element to select it. Press ESC to cancel.</div>
`;
const botCss = `
:root { --bg-primary: #0F212E; --bg-secondary: #213743; --bg-tertiary: #1a2c38; --accent-green: #00b373; --accent-red: #e53e3e; --accent-yellow: #FFC107; --text-primary: #fff; --text-secondary: #b1bad3; }
.bot-window { position: fixed; top: 100px; right: 20px; width: 380px; min-width: 350px; max-width: 800px; height: 550px; min-height: 400px; max-height: 90vh; background-color: var(--bg-primary); border-radius: 8px; z-index: 9999; color: var(--text-primary); font-family: 'Inter', sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); display: none; flex-direction: column; overflow: hidden; }
.bot-header { padding: 10px 15px; cursor: move; background-color: var(--bg-secondary); font-weight: 600; user-select: none; display: flex; justify-content: space-between; align-items: center; flex-shrink: 0; }
#bot-status-indicator { font-weight: normal; font-size: 12px; opacity: 0.8; margin-left: 8px; }
#profit-loss-display { font-weight: 600; font-size: 12px; margin-left: 10px; }
#profit-loss-display.profit { color: var(--accent-green); }
#profit-loss-display.loss { color: var(--accent-red); }
.window-controls { display: flex; gap: 8px; align-items: center; }
.window-btn { background: none; border: none; color: var(--text-secondary); font-size: 20px; cursor: pointer; line-height: 1; padding: 0 5px; font-family: sans-serif; display: flex; align-items: center; justify-content: center; }
.window-btn:hover { color: var(--text-primary); }
#maximize-btn { font-size: 16px; }
.bot-content { padding: 15px; flex-grow: 1; display: flex; flex-direction: column; min-height: 0; }
.bot-tabs { display: flex; margin-bottom: 15px; background-color: var(--bg-primary); border-radius: 20px; padding: 4px; border: 1px solid var(--bg-secondary); flex-shrink: 0; }
.bot-tab-btn { flex: 1; padding: 8px; cursor: pointer; background: transparent; border: none; color: var(--text-secondary); border-radius: 20px; font-weight: 500; transition: all 0.2s ease-in-out; font-size: 13px; white-space: nowrap; }
.bot-tab-btn.active { background-color: var(--bg-secondary); color: var(--text-primary); }
.bot-tabs.locked .bot-tab-btn:not([data-tab="semi-auto"]):not([data-tab="admin"]) { pointer-events: none; opacity: 0.5; }
.bot-tab-content { display: none; }
.bot-tab-content.active { display: flex; flex-grow: 1; flex-direction: column; }
#tab-config { overflow-y: auto; }
.bot-input-group { margin-bottom: 12px; display: flex; flex-direction: column; }
.bot-input-group label { margin-bottom: 5px; font-size: 14px; color: #b1bad3; }
.bot-input { width: 100%; padding: 8px; background-color: var(--bg-primary); border: 1px solid var(--bg-secondary); border-radius: 4px; color: #fff; box-sizing: border-box; }
.bot-input:disabled { background-color: #2a414f; opacity: 0.7; }
#on-win-loss-container.disabled { opacity: 0.5; pointer-events: none; }
.bot-btn { width: 100%; padding: 12px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 600; margin-top: 10px; }
.bot-btn:disabled { background-color: #4A5568; cursor: not-allowed; opacity: 0.7; }
.bot-btn-primary { background-color: var(--accent-green); color: #fff; }
.bot-btn-danger { background-color: var(--accent-red); color: #fff; }
.bot-btn-secondary { background-color: var(--bg-secondary); color: #fff; }
.strategy-desc { font-size: 12px; color: #b1bad3; background-color: var(--bg-secondary); padding: 8px; border-radius: 4px; margin: 5px 0 15px 0; }
#config-container { background-color: var(--bg-primary); border-radius: 4px; padding: 8px; font-size: 12px; line-height: 1.5; }
#log-container { flex-grow: 1; overflow-y: auto; padding-right: 5px; background-color: var(--bg-primary); border: 1px solid var(--bg-secondary); border-radius: 4px; padding: 8px; font-size: 12px; line-height: 1.5; }
#log-container p { margin: 0 0 5px 0; padding: 2px 4px; border-radius: 3px; }
#log-container p.log-manual { color: var(--accent-green); }
#log-container p.log-auto { color: var(--accent-red); }
#log-container p.log-strategy { color: var(--accent-yellow); }
#log-container p b { font-weight: 900; }
#log-filters { display: flex; gap: 15px; margin-bottom: 10px; background-color: var(--bg-secondary); padding: 8px; border-radius: 4px; }
#log-filters label { display: flex; align-items: center; gap: 5px; font-size: 12px; cursor: pointer; }
#wheel-bot-minimized-bar { position: fixed; top: 100px; right: 20px; background-color: var(--bg-secondary); border-radius: 8px; z-index: 9999; color: #fff; font-family: 'Inter', sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); display: none; justify-content: space-between; align-items: center; padding: 8px 12px; cursor: move; }
#wheel-bot-minimized-bar span { font-weight: 600; user-select: none; }
.config-group { display: flex; align-items: center; gap: 5px; margin-bottom: 8px; }
.config-group label { font-size: 12px; color: #b1bad3; flex-basis: 140px; flex-shrink: 0; display: flex; align-items: center; }
.config-group .input-wrapper { display: flex; align-items: center; flex-grow: 1; position: relative; }
.config-group input { flex-grow: 1; font-size: 11px; }
.config-group input[type=text] { padding-right: 30px; }
.config-group input.invalid-range { border-color: var(--accent-red) !important; }
.config-range-wrapper { display: flex; align-items: center; gap: 5px; width: 100%; }
.config-range-wrapper input { flex-grow: 1; text-align: center; }
.config-range-wrapper span { color: #b1bad3; font-size: 11px; }
.config-pick-btn, .config-toggle-vis-btn { padding: 6px 8px; font-size: 10px; flex-shrink: 0; background-color: #4A5568; color: white; border: none; border-radius: 4px; cursor: pointer; }
.config-toggle-vis-btn { position: absolute; right: 1px; top: 1px; bottom: 1px; background-color: var(--bg-secondary); padding: 0 8px; }
.config-toggle-vis-btn svg { width: 16px; height: 16px; pointer-events: none; }
.config-tooltip-trigger { display: inline-block; margin-left: 8px; background-color: var(--accent-yellow); color: var(--bg-primary); border-radius: 50%; width: 16px; height: 16px; text-align: center; line-height: 16px; font-weight: bold; cursor: help; position: relative; font-size: 12px; }
.config-tooltip-trigger .tooltip-text { visibility: hidden; width: 220px; background-color: var(--bg-secondary); color: #fff; text-align: center; border-radius: 6px; padding: 8px; position: absolute; z-index: 1; bottom: 125%; left: 50%; margin-left: -110px; opacity: 0; transition: opacity 0.3s; font-weight: normal; font-size: 11px; }
.config-tooltip-trigger:hover .tooltip-text { visibility: visible; opacity: 1; }
#element-picker-overlay { position: fixed; background-color: rgba(0, 179, 115, 0.3); border: 2px solid var(--accent-green); z-index: 10000; pointer-events: none; display: none; transition: all 0.1s linear; }
#element-picker-tooltip { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background-color: black; color: white; padding: 10px 20px; border-radius: 5px; z-index: 10001; display: none; }
#strategy-modal, #password-prompt-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 10002; display: flex; align-items: center; justify-content: center; }
#strategy-modal-content, #password-prompt-content { background: var(--bg-primary); padding: 20px; border-radius: 8px; border: 1px solid var(--bg-secondary); width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; display: flex; flex-direction: column; gap: 15px; }
#strategy-modal-content > .bot-input-group, #strategy-modal-content > .bot-btn, #strategy-modal-content > hr { margin: 0; }
#password-prompt-content { max-width: 380px; text-align: center; }
#strategy-rules-container { overflow-y: auto; flex-grow: 1; min-height: 0; }
.strategy-rule { background: var(--bg-secondary); padding: 8px; border-radius: 4px; margin-bottom: 8px; }
.strategy-rule-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; }
.strategy-rule-summary { display: flex; gap: 5px; align-items: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.strategy-rule-summary span { font-size: 13px; }
.strategy-rule-summary .summary-action { color: var(--accent-yellow); }
.strategy-rule-controls { display: flex; align-items: center; }
.strategy-rule-editor { display: none; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--bg-primary); }
.strategy-rule.expanded .strategy-rule-editor { display: block; }
.strategy-rule.expanded .strategy-rule-summary { display: none; }
.rule-toggle-group { display: flex; justify-content: center; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; }
.rule-toggle-group label { background: var(--bg-primary); padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }
.rule-toggle-group input { display: none; }
.rule-toggle-group input:checked + label { background: var(--accent-green); color: white; }
.rule-sentence { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; margin-bottom: 8px; }
.rule-sentence span { font-size: 13px; }
.rule-sentence .bot-input { width: auto; flex-grow: 1; min-width: 80px; padding: 4px 6px; }
.action-sentence .action-type { width: 180px; flex-grow: 0; flex-shrink: 0; }
.rule-sentence .bot-input[type=number] { max-width: 100px; }
.remove-btn, .move-btn { color: #fff; cursor: pointer; font-weight: bold; background: none; border: none; font-size: 18px; padding: 0 5px; }
.remove-btn { color: var(--accent-red); }
.btn-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
hr { border: none; border-top: 1px solid var(--bg-secondary); margin: 15px 0; }
.resizer { position: absolute; z-index: 10; }
.resizer-r { cursor: e-resize; height: 100%; width: 5px; right: 0; top: 0; }
.resizer-l { cursor: w-resize; height: 100%; width: 5px; left: 0; top: 0; }
.resizer-b { cursor: s-resize; height: 5px; width: 100%; right: 0; bottom: 0; }
.resizer-t { cursor: n-resize; height: 5px; width: 100%; left: 0; top: 0; }
.resizer-br { cursor: se-resize; height: 15px; width: 15px; right: 0; bottom: 0; }
.resizer-bl { cursor: sw-resize; height: 15px; width: 15px; left: 0; bottom: 0; }
.resizer-tr { cursor: ne-resize; height: 15px; width: 15px; right: 0; top: 0; }
.resizer-tl { cursor: nw-resize; height: 15px; width: 15px; left: 0; top: 0; }
.resizer-br::after { content: ''; position: absolute; bottom: 0; right: 0; width: 0; height: 0; border-style: solid; border-width: 0 0 12px 12px; border-color: transparent transparent rgba(177, 186, 211, 0.2) transparent; pointer-events: none; }
#bot-notification-container { position: fixed; top: 20px; right: 20px; z-index: 10005; display: flex; flex-direction: column; gap: 10px; }
.bot-notification { background-color: var(--bg-secondary); color: white; padding: 15px; border-radius: 8px; box-shadow: 0 3px 10px rgba(0,0,0,0.3); width: 300px; opacity: 0; transform: translateX(100%); animation: slideIn 0.5s forwards; }
.bot-notification.fade-out { animation: slideOut 0.5s forwards; }
.bot-notification-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.bot-notification-header span { font-weight: bold; }
.bot-notification-header .close-btn { background: none; border: none; color: #b1bad3; font-size: 20px; cursor: pointer; line-height: 1; }
@keyframes slideIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } }
@keyframes slideOut { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100%); } }
`;
// --- CORE LOGIC ---
function init() {
if (isBotInitialized) return;
isBotInitialized = true;
loadConfig();
loadStrategies();
document.body.insertAdjacentHTML('beforeend', botHtml);
GM_addStyle(botCss);
document.getElementById('wheel-bot-window').style.display = 'flex';
applyUiScale();
loadSecurityConfig();
populateConfigTab();
populateStrategyDropdowns();
setupEventListeners();
makeDraggable(document.getElementById('wheel-bot-window'), document.getElementById('bot-header'));
makeDraggable(document.getElementById('wheel-bot-minimized-bar'), document.getElementById('wheel-bot-minimized-bar'));
makeResizable(document.getElementById('wheel-bot-window'));
log("Bot UI Initialized. Waiting for game interface...", "system");
waitForGameAndEnable();
updateSemiAutoDescription();
updateProfitDisplay();
if (storedPasswordHash) {
lockUI();
} else {
updateAdminStatus("No password set. Bot is unlocked.", "info");
}
}
function setupEventListeners() {
// Window Controls
document.getElementById('minimize-btn').addEventListener('click', minimizeBot);
document.getElementById('close-btn').addEventListener('click', closeBot);
document.getElementById('maximize-btn').addEventListener('click', maximizeBot);
document.getElementById('close-minimized-btn').addEventListener('click', closeBot);
// Tab switching
document.querySelectorAll('.bot-tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
if (state.isLocked && e.currentTarget.dataset.tab !== 'semi-auto' && e.currentTarget.dataset.tab !== 'admin') {
promptForPassword(unlockUI);
return;
}
const tabName = e.currentTarget.dataset.tab;
document.querySelectorAll('.bot-tab-btn').forEach(b => b.classList.remove('active'));
e.currentTarget.classList.add('active');
document.querySelectorAll('.bot-tab-content').forEach(content => {
content.classList.toggle('active', content.id === `tab-${tabName}`);
});
});
});
// Main controls
document.getElementById('start-stop-btn').addEventListener('click', toggleBot);
document.getElementById('semi-auto-step-btn').addEventListener('click', runSemiAutoStep);
document.getElementById('semi-auto-reset-btn').addEventListener('click', resetSemiAutoProgression);
document.getElementById('manual-change-seed-btn').addEventListener('click', handleManualSeedChange);
document.getElementById('semi-auto-strategy').addEventListener('change', updateSemiAutoDescription);
// Config tab
document.getElementById('save-config-btn').addEventListener('click', saveConfig);
document.getElementById('reset-config-btn').addEventListener('click', resetConfigToDefault);
// Log Tab
document.getElementById('save-log-btn').addEventListener('click', saveLogToFile);
document.getElementById('reset-stats-btn').addEventListener('click', resetState);
// Advanced Strategy tab
document.getElementById('create-strategy-btn').addEventListener('click', () => openStrategyModal());
document.getElementById('edit-strategy-btn').addEventListener('click', editSelectedStrategy);
document.getElementById('delete-strategy-btn').addEventListener('click', deleteSelectedStrategy);
document.getElementById('import-strategy-btn').addEventListener('click', importStrategy);
document.getElementById('export-strategy-btn').addEventListener('click', exportSelectedStrategy);
document.getElementById('save-strategy-btn').addEventListener('click', saveStrategy);
document.getElementById('cancel-strategy-btn').addEventListener('click', () => document.getElementById('strategy-modal').style.display = 'none');
document.getElementById('add-rule-btn').addEventListener('click', () => addRuleToModal());
// Admin tab
document.getElementById('save-credentials-btn').addEventListener('click', saveCredentials);
document.getElementById('lock-unlock-btn').addEventListener('click', handleLockUnlock);
// Password Prompt
document.getElementById('password-prompt-submit').addEventListener('click', handleSubmitPassword);
document.getElementById('password-prompt-cancel').addEventListener('click', () => document.getElementById('password-prompt-modal').style.display = 'none');
// Log Filters
document.getElementById('log-filters').addEventListener('change', (e) => {
if (e.target.matches('input[type="checkbox"]')) {
logFilters[e.target.dataset.filter] = e.target.checked;
applyLogFilters();
}
});
document.addEventListener('keydown', handleKeydown);
}
function toggleBot() {
if (state.running) { // Stopping
state.running = false;
const btn = document.getElementById('start-stop-btn');
btn.textContent = 'Start Auto (S)';
btn.classList.replace('bot-btn-danger', 'bot-btn-primary');
updateStatus('Stopped');
log("Auto betting stopped.", "auto");
updateInputDisabledState();
if (state.originalAutoStrategy) {
document.getElementById('auto-strategy-select').value = state.originalAutoStrategy;
document.getElementById('semi-auto-strategy').value = state.originalAutoStrategy;
state.originalAutoStrategy = null;
}
} else { // Starting
state.running = true;
state.originalAutoStrategy = document.getElementById('auto-strategy-select').value;
const btn = document.getElementById('start-stop-btn');
btn.textContent = 'Stop Auto (S)';
btn.classList.replace('bot-btn-primary', 'bot-btn-danger');
updateStatus('Auto Running...');
updateInputDisabledState();
startBot();
}
}
// --- WINDOW CONTROLS ---
function minimizeBot() {
document.getElementById('wheel-bot-window').style.display = 'none';
document.getElementById('wheel-bot-minimized-bar').style.display = 'flex';
}
function maximizeBot() {
document.getElementById('wheel-bot-window').style.display = 'flex';
document.getElementById('wheel-bot-minimized-bar').style.display = 'none';
}
function closeBot() {
document.getElementById('wheel-bot-window').style.display = 'none';
document.getElementById('wheel-bot-minimized-bar').style.display = 'none';
log("Bot window closed. Refresh the page to reopen.", "system");
}
// --- SECURITY & ADMIN ---
async function hashPassword(password) {
const encoder = new TextEncoder();
const data = encoder.encode(password);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
function loadSecurityConfig() {
storedUsername = localStorage.getItem('stakeWheelBotUser') || '';
storedPasswordHash = localStorage.getItem('stakeWheelBotPassHash') || '';
document.getElementById('admin-username').value = storedUsername;
}
async function saveCredentials() {
const usernameInput = document.getElementById('admin-username');
const passwordInput = document.getElementById('admin-password');
const newUsername = usernameInput.value.trim();
const newPassword = passwordInput.value;
const performSave = async () => {
if (newUsername !== storedUsername) {
storedUsername = newUsername;
localStorage.setItem('stakeWheelBotUser', storedUsername);
log("Username updated.", "system");
}
if (newPassword) {
storedPasswordHash = await hashPassword(newPassword);
localStorage.setItem('stakeWheelBotPassHash', storedPasswordHash);
passwordInput.value = '';
log("Password updated.", "system");
updateAdminStatus("Password updated. You can now lock the bot.", "info");
}
showNotification('Credentials saved!');
};
if (newPassword && storedPasswordHash) {
promptForPassword(performSave, "Enter your CURRENT password to save changes.");
} else {
await performSave();
}
}
function handleLockUnlock() {
if (state.isLocked) {
promptForPassword(unlockUI);
} else {
if (!storedPasswordHash) {
showNotification("Please set a password before locking.");
return;
}
lockUI();
}
}
function lockUI() {
state.isLocked = true;
document.querySelector('.bot-tab-btn[data-tab="semi-auto"]').click();
document.querySelector('.bot-tabs').classList.add('locked');
const lockBtn = document.getElementById('lock-unlock-btn');
lockBtn.textContent = "Unlock";
lockBtn.classList.replace('bot-btn-primary', 'bot-btn-danger');
updateAdminStatus(`Locked by ${storedUsername || 'Admin'}. UI is restricted.`, "locked");
log("Bot settings locked.", "system");
}
function unlockUI() {
state.isLocked = false;
document.querySelector('.bot-tabs').classList.remove('locked');
const lockBtn = document.getElementById('lock-unlock-btn');
lockBtn.textContent = "Lock";
lockBtn.classList.replace('bot-btn-danger', 'bot-btn-primary');
updateAdminStatus("Bot is unlocked. Settings are editable.", "unlocked");
log("Bot settings unlocked.", "system");
}
function promptForPassword(onSuccess, message = "Please enter the password to unlock this feature.") {
pendingAction = onSuccess;
const modal = document.getElementById('password-prompt-modal');
modal.querySelector('#password-prompt-message').textContent = message;
modal.querySelector('#prompt-password-input').value = "";
modal.querySelector('#password-prompt-error').style.display = 'none';
modal.style.display = 'flex';
modal.querySelector('#prompt-password-input').focus();
}
async function handleSubmitPassword() {
const password = document.getElementById('prompt-password-input').value;
const hash = await hashPassword(password);
if (hash === storedPasswordHash) {
document.getElementById('password-prompt-modal').style.display = 'none';
if (typeof pendingAction === 'function') {
pendingAction();
}
pendingAction = null;
} else {
const errorEl = document.getElementById('password-prompt-error');
errorEl.textContent = "Incorrect password. Please try again.";
errorEl.style.display = 'block';
}
}
function updateAdminStatus(text, status) {
const statusEl = document.getElementById('admin-status');
statusEl.textContent = text;
statusEl.style.color = status === 'locked' ? 'var(--accent-red)' : status === 'unlocked' ? 'var(--accent-green)' : '#b1bad3';
}
// --- API & NETWORK LOGIC ---
function getAuthToken() {
try {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.includes('x-access-token')) {
const value = localStorage.getItem(key);
if (typeof value === 'string' && value.split('.').length === 3 && value.startsWith('ey')) {
console.log(`Auto-detected auth token in localStorage key: "${key}"`);
return JSON.parse(value);
}
}
}
const cookieToken = document.cookie.split('; ').find(row => row.startsWith('session='))?.split('=')[1];
if (cookieToken) {
console.log("Auto-detected auth token from cookie.");
return cookieToken;
}
} catch (e) {
console.error("Error retrieving auth token", e);
}
console.warn("Could not auto-detect auth token.");
return '';
}
async function changeSeedProgrammatically() {
updateStatus("Changing Seed...");
log("Attempting to change seed via API...", "strategy");
const token = CONFIG.authToken;
if (!token) {
updateStatus("Error: Auth Token not found!");
log("Seed change failed: Auth Token missing.", "error");
return false;
}
const newClientSeed = (Math.random() + 1).toString(36).substring(2);
try {
const response = await fetch(CONFIG.seedChangeEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-access-token': token
},
body: JSON.stringify({
query: `mutation changeClientSeed($seed: String!) { changeClientSeed(seed: $seed) { id, seed, __typename } }`,
variables: {
seed: newClientSeed
},
operationName: "changeClientSeed"
}),
});
if (!response.ok) throw new Error(`API Error: ${response.statusText}`);
const responseData = await response.json();
if (responseData.errors) throw new Error(responseData.errors.map(e => e.message).join(', '));
log(`Seed changed successfully. New seed: ${newClientSeed}`, "strategy");
return true;
} catch (error) {
log(`Seed change failed: ${error.message}`, "error");
return false;
}
}
// --- CONFIGURATION MANAGEMENT ---
function populateConfigTab() {
const container = document.getElementById('config-container');
container.innerHTML = '';
for (const key in CONFIG) {
const friendlyName = CONFIG_FRIENDLY_NAMES[key] || key;
const group = document.createElement('div');
group.className = 'config-group';
let inputWrapperHtml = '';
let labelHtml = `<label for="config-${key}">${friendlyName}</label>`;
const isSelector = !["authToken", "seedChangeEndpoint", "betDelay", "maxBetAmount", "uiScale", "lossMultiplier"].includes(key);
if (key === 'betDelay') {
labelHtml = `<label>${friendlyName} <span class="config-tooltip-trigger" style="display: none;">?<span class="tooltip-text">A low delay (< 20ms) can cause the bot to miss bet results. Use with caution.</span></span></label>`;
const parts = String(CONFIG.betDelay).split('-').map(p => p.trim());
inputWrapperHtml = `<div class="input-wrapper config-range-wrapper"><input type="number" id="config-betDelay-min" class="bot-input" value="${parts[0] || ''}" placeholder="Min"><span>to</span><input type="number" id="config-betDelay-max" class="bot-input" value="${parts[1] || ''}" placeholder="Max"></div>`;
} else if (key === 'uiScale') {
inputWrapperHtml = `<div class="input-wrapper" style="display: flex; align-items: center; gap: 10px;"><input type="range" id="config-uiScale" class="bot-input" min="50" max="150" value="${CONFIG[key]}" style="flex-grow: 1; padding: 0;"><span id="config-uiScale-value" style="flex-basis: 40px; text-align: right;">${CONFIG[key]}%</span></div>`;
} else if (key === 'authToken') {
inputWrapperHtml = `<div class="input-wrapper"><input type="password" id="config-authToken" class="bot-input" value='${CONFIG[key]}'><button class="config-toggle-vis-btn" data-target="config-authToken"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg></button></div>`;
} else {
inputWrapperHtml = `<div class="input-wrapper"><input type="${typeof CONFIG[key] === 'number' ? 'number' : 'text'}" id="config-${key}" class="bot-input" value='${CONFIG[key]}'>${isSelector ? `<button class="config-pick-btn" data-key="${key}">Pick</button>` : ''}</div>`;
}
group.innerHTML = `${labelHtml}${inputWrapperHtml}`;
container.appendChild(group);
}
container.querySelectorAll('.config-pick-btn').forEach(btn => btn.addEventListener('click', (e) => initSelectorPicker(e.target.dataset.key)));
container.querySelectorAll('.config-toggle-vis-btn').forEach(btn => btn.addEventListener('click', (e) => {
const targetInput = document.getElementById(e.currentTarget.dataset.target);
if (targetInput) targetInput.type = targetInput.type === 'password' ? 'text' : 'password';
}));
const minDelayInput = document.getElementById('config-betDelay-min'),
maxDelayInput = document.getElementById('config-betDelay-max');
if (minDelayInput && maxDelayInput) {
const validate = () => {
const showWarning = parseInt(minDelayInput.value, 10) < 20;
const tooltip = minDelayInput.closest('.config-group').querySelector('.config-tooltip-trigger');
if (tooltip) tooltip.style.display = showWarning ? 'inline-flex' : 'none';
};
minDelayInput.addEventListener('input', validate);
maxDelayInput.addEventListener('input', validate);
validate();
}
document.getElementById('config-uiScale')?.addEventListener('input', (e) => {
document.getElementById('config-uiScale-value').textContent = `${e.target.value}%`;
applyUiScale(e.target.value);
});
}
function initSelectorPicker(configKey) {
if (isPickingElement) return;
isPickingElement = true;
const overlay = document.getElementById('element-picker-overlay');
const tooltip = document.getElementById('element-picker-tooltip');
overlay.style.display = 'block';
tooltip.style.display = 'block';
const mouseMoveHandler = (e) => {
const target = e.target;
if (target.id === 'wheel-bot-window' || target.closest('#wheel-bot-window')) {
overlay.style.display = 'none';
return;
}
overlay.style.display = 'block';
const rect = target.getBoundingClientRect();
Object.assign(overlay.style, {
width: `${rect.width}px`,
height: `${rect.height}px`,
top: `${rect.top}px`,
left: `${rect.left}px`
});
};
const clickHandler = (e) => {
e.preventDefault();
e.stopPropagation();
const target = e.target;
if (target.id === 'wheel-bot-window' || target.closest('#wheel-bot-window')) return;
document.getElementById(`config-${configKey}`).value = generateSelector(target);
cleanup();
};
const escHandler = (e) => e.key === "Escape" && cleanup();
const cleanup = () => {
document.removeEventListener('mousemove', mouseMoveHandler);
document.removeEventListener('click', clickHandler, true);
document.removeEventListener('keydown', escHandler, true);
overlay.style.display = 'none';
tooltip.style.display = 'none';
isPickingElement = false;
};
document.addEventListener('mousemove', mouseMoveHandler);
document.addEventListener('click', clickHandler, true);
document.addEventListener('keydown', escHandler, true);
}
function generateSelector(el) {
if (el.getAttribute('data-testid')) return `[data-testid="${el.getAttribute('data-testid')}"]`;
if (el.id) return `#${el.id}`;
if (el.className) {
const classNames = typeof el.className === 'string' ? el.className.split(' ').filter(c => c && !c.includes('svelte-')) : [];
if (classNames.length > 0) return `.${classNames.join('.')}`;
}
return el.tagName.toLowerCase();
}
function saveConfig() {
updateConfigFromUI(); // Read current values from UI
localStorage.setItem('stakeWheelBotConfig', JSON.stringify(CONFIG));
showNotification('Configuration saved!');
log("Configuration saved.", "system");
}
function updateConfigFromUI() {
const newConfig = { ...CONFIG
};
// This function only reads values for the current session, it doesn't save them to localStorage
for (const key in CONFIG) {
if (key === 'betDelay') {
const minEl = document.getElementById('config-betDelay-min');
const maxEl = document.getElementById('config-betDelay-max');
if (minEl && maxEl) {
const min = minEl.value.trim();
const max = maxEl.value.trim();
newConfig[key] = (min && max) ? `${min}-${max}` : min || '100';
}
} else {
const el = document.getElementById(`config-${key}`);
if (el) {
const value = el.value;
if (typeof CONFIG[key] === 'number' && key !== 'uiScale') {
newConfig[key] = parseFloat(value) || 0;
} else if (key === 'uiScale') {
newConfig[key] = parseInt(value, 10) || 100;
} else {
newConfig[key] = value;
}
}
}
}
CONFIG = newConfig;
}
function loadConfig() {
const saved = localStorage.getItem('stakeWheelBotConfig');
if (saved) {
try {
CONFIG = { ...CONFIG,
...JSON.parse(saved)
};
} catch (e) {
console.error("Failed to load config", e);
}
}
if (!CONFIG.authToken) CONFIG.authToken = getAuthToken();
}
function resetConfigToDefault() {
if (confirm("Are you sure you want to reset all configuration to defaults?")) {
localStorage.removeItem('stakeWheelBotConfig');
CONFIG = {
authToken: '',
seedChangeEndpoint: '/_api/graphql',
betButton: '[data-testid="bet-button"]',
amountInput: '[data-testid="input-game-amount"]',
riskSelect: '[data-testid="risk-select"]',
segmentsSelect: '[data-testid="segments-select"]',
betDelay: '100',
maxBetAmount: 0,
lossMultiplier: 1,
pastBetsContainer: '.past-bets',
autoTab: '[data-testid="auto-tab"]',
manualTab: '[data-testid="manual-tab"]',
uiScale: 100,
};
loadConfig();
populateConfigTab();
applyUiScale();
showNotification("Configuration has been reset to default.");
}
}
// --- ADVANCED STRATEGY LOGIC ---
function openStrategyModal(strategyName = '') {
const modal = document.getElementById('strategy-modal');
const nameInput = document.getElementById('strategy-name');
nameInput.value = strategyName;
nameInput.dataset.originalName = strategyName;
modal.querySelector('#strategy-modal-title').textContent = strategyName ? 'Edit Strategy' : 'Create Strategy';
modal.querySelector('#strategy-rules-container').innerHTML = '';
if (strategyName && advancedStrategies[strategyName]) {
advancedStrategies[strategyName].forEach(rule => addRuleToModal(rule));
} else {
addRuleToModal();
}
modal.style.display = 'flex';
}
function addRuleToModal(rule = {}) {
const rulesContainer = document.getElementById('strategy-rules-container');
const ruleDiv = document.createElement('div');
ruleDiv.className = 'strategy-rule';
const ruleId = `rule-${Date.now()}-${Math.random()}`;
ruleDiv.id = ruleId;
const defaults = {
conditionType: 'bets',
playConditionTerm: 'every',
playConditionValue: 1,
playConditionBetType: 'lose',
netGainCondition: 'greater',
netGainValue: 0,
action: 'increaseByPercentage',
actionValue: 100,
timeSinceWinCondition: 'greater',
timeSinceWinValue: 10,
patternMatch: 'LWL'
};
const currentRule = { ...defaults,
...rule
};
ruleDiv.innerHTML = `
<div class="strategy-rule-header">
<div class="strategy-rule-summary"></div>
<div class="strategy-rule-controls">
<button class="move-btn" data-direction="up" title="Move Up">↑</button>
<button class="move-btn" data-direction="down" title="Move Down">↓</button>
<button class="remove-btn" title="Remove Rule">×</button>
</div>
</div>
<div class="strategy-rule-editor">
<div class="rule-toggle-group">
<input type="radio" id="${ruleId}-type-bets" name="${ruleId}-type" value="bets" ${currentRule.conditionType === 'bets' ? 'checked' : ''}><label for="${ruleId}-type-bets">Play</label>
<input type="radio" id="${ruleId}-type-profit" name="${ruleId}-type" value="profit" ${currentRule.conditionType === 'profit' ? 'checked' : ''}><label for="${ruleId}-type-profit">Net Gain</label>
<input type="radio" id="${ruleId}-type-time" name="${ruleId}-type" value="time" ${currentRule.conditionType === 'time' ? 'checked' : ''}><label for="${ruleId}-type-time">Time</label>
<input type="radio" id="${ruleId}-type-pattern" name="${ruleId}-type" value="pattern" ${currentRule.conditionType === 'pattern' ? 'checked' : ''}><label for="${ruleId}-type-pattern">Pattern</label>
</div>
<div class="play-condition-sentence rule-sentence">
<span>On</span>
<select class="bot-input play-term"><option value="every">Every</option><option value="everyStreakOf">Every streak of</option><option value="firstStreakOf">First streak of</option><option value="streakGreaterThan">Streak greater than</option><option value="streakLowerThan">Streak lower than</option></select>
<input type="number" class="bot-input play-value" value="${currentRule.playConditionValue}">
<select class="bot-input play-bet-type"><option value="win">Wins</option><option value="lose">Losses</option><option value="bet">Games</option></select>
</div>
<div class="net-gain-condition-sentence rule-sentence"><span>If Net Gain is</span><select class="bot-input net-gain-condition"><option value="greater">></option><option value="less"><</option><option value="=">=</option></select><input type="number" step="any" class="bot-input net-gain-value" value="${currentRule.netGainValue}"></div>
<div class="time-condition-sentence rule-sentence"><span>If time since last win is</span><select class="bot-input time-condition"><option value="greater">></option><option value="less"><</option></select><input type="number" class="bot-input time-value" value="${currentRule.timeSinceWinValue}"><span>seconds</span></div>
<div class="pattern-condition-sentence rule-sentence"><span>If last bets match</span><input type="text" class="bot-input pattern-value" placeholder="e.g. LWL" value="${currentRule.patternMatch}"></div>
<div class="action-sentence rule-sentence">
<span>Do</span>
<select class="bot-input action-type"><option value="increaseByPercentage">Increase Amount by %</option><option value="decreaseByPercentage">Decrease Amount by %</option><option value="setAmount">Set Amount</option><option value="resetAmount">Reset Amount</option><option value="setBaseBet">Set Base Bet</option><option value="increaseBaseBetByPercentage">Increase Base Bet by %</option><option value="decreaseBaseBetByPercentage">Decrease Base Bet by %</option><option value="setRisk">Set Risk/Difficulty</option><option value="setSegments">Set Segments</option><option value="changeSeed">Change Seed (API)</option><option value="switchStrategy">Switch Strategy</option><option value="stop">Stop Autoplay</option><option value="notify">Show Notification</option></select>
<input type="number" step="any" class="bot-input action-value" value="${currentRule.actionValue}">
<input type="text" class="bot-input action-value-text" style="display: none;" placeholder="Notification message...">
<select class="bot-input action-strategy-select" style="display: none;"></select>
<select class="bot-input action-risk-select" style="display: none;"><option value="low">Low</option><option value="medium">Medium</option><option value="high">High</option></select>
<select class="bot-input action-segments-select" style="display: none;"><option value="10">10</option><option value="20">20</option><option value="30">30</option><option value="40">40</option><option value="50">50</option></select>
</div>
</div>`;
rulesContainer.appendChild(ruleDiv);
const setVal = (sel, val) => {
if (val !== undefined) ruleDiv.querySelector(sel).value = val;
};
setVal('.play-term', currentRule.playConditionTerm);
setVal('.play-bet-type', currentRule.playConditionBetType);
setVal('.net-gain-condition', currentRule.netGainCondition);
setVal('.time-condition', currentRule.timeSinceWinCondition);
setVal('.action-type', currentRule.action);
const actionStrategySelect = ruleDiv.querySelector('.action-strategy-select');
Object.keys(advancedStrategies).forEach(name => actionStrategySelect.add(new Option(name, name)));
if (currentRule.action === 'switchStrategy') actionStrategySelect.value = currentRule.actionValue;
if (currentRule.action === 'setRisk') ruleDiv.querySelector('.action-risk-select').value = currentRule.actionValue;
if (currentRule.action === 'setSegments') ruleDiv.querySelector('.action-segments-select').value = currentRule.actionValue;
if (currentRule.action === 'notify') ruleDiv.querySelector('.action-value-text').value = currentRule.actionValue;
const updateVisibility = () => {
const type = ruleDiv.querySelector(`input[name="${ruleId}-type"]:checked`).value;
ruleDiv.querySelectorAll('.play-condition-sentence, .net-gain-condition-sentence, .time-condition-sentence, .pattern-condition-sentence').forEach(el => {
el.style.display = 'none';
});
ruleDiv.querySelector(`.${type === 'bets' ? 'play' : type === 'profit' ? 'net-gain' : type}-condition-sentence`).style.display = 'flex';
const actionType = ruleDiv.querySelector('.action-type').value;
const noValueActions = ['resetAmount', 'changeSeed', 'stop'];
const selectActions = ['setRisk', 'setSegments', 'switchStrategy', 'notify'];
ruleDiv.querySelector('.action-value').style.display = noValueActions.includes(actionType) || selectActions.includes(actionType) ? 'none' : 'block';
ruleDiv.querySelector('.action-value-text').style.display = actionType === 'notify' ? 'block' : 'none';
ruleDiv.querySelector('.action-strategy-select').style.display = actionType === 'switchStrategy' ? 'block' : 'none';
ruleDiv.querySelector('.action-risk-select').style.display = actionType === 'setRisk' ? 'block' : 'none';
ruleDiv.querySelector('.action-segments-select').style.display = actionType === 'setSegments' ? 'block' : 'none';
updateSummary();
};
const updateSummary = () => {
let conditionText = '',
actionText = '',
actionValueText = '';
const type = ruleDiv.querySelector(`input[name="${ruleId}-type"]:checked`).value;
if (type === 'bets') conditionText = `On ${ruleDiv.querySelector('.play-term option:checked').textContent} ${ruleDiv.querySelector('.play-value').value} ${ruleDiv.querySelector('.play-bet-type option:checked').textContent}`;
else if (type === 'profit') conditionText = `If Net Gain ${ruleDiv.querySelector('.net-gain-condition option:checked').textContent} ${ruleDiv.querySelector('.net-gain-value').value}`;
else if (type === 'time') conditionText = `If time since win ${ruleDiv.querySelector('.time-condition option:checked').textContent} ${ruleDiv.querySelector('.time-value').value}s`;
else if (type === 'pattern') conditionText = `If pattern is ${ruleDiv.querySelector('.pattern-value').value}`;
const actionTypeSelect = ruleDiv.querySelector('.action-type');
actionText = actionTypeSelect.options[actionTypeSelect.selectedIndex].textContent;
if (actionTypeSelect.value === 'notify') actionValueText = ` "${ruleDiv.querySelector('.action-value-text').value}"`;
else if (actionTypeSelect.value === 'switchStrategy') actionValueText = ` to ${ruleDiv.querySelector('.action-strategy-select').value}`;
else if (actionTypeSelect.value === 'setRisk') actionValueText = ` to ${ruleDiv.querySelector('.action-risk-select').value}`;
else if (actionTypeSelect.value === 'setSegments') actionValueText = ` to ${ruleDiv.querySelector('.action-segments-select').value}`;
else if (!['resetAmount', 'changeSeed', 'stop'].includes(actionTypeSelect.value)) actionValueText = ` ${ruleDiv.querySelector('.action-value').value}`;
ruleDiv.querySelector('.strategy-rule-summary').innerHTML = `<span>${conditionText} →</span><span class="summary-action">${actionText}${actionValueText}</span>`;
};
ruleDiv.querySelector('.strategy-rule-header').addEventListener('click', (e) => {
if (!e.target.closest('button')) ruleDiv.classList.toggle('expanded');
updateSummary();
});
ruleDiv.querySelectorAll('input, select').forEach(el => el.addEventListener('input', updateVisibility));
ruleDiv.querySelector('.remove-btn').addEventListener('click', () => ruleDiv.remove());
ruleDiv.querySelectorAll('.move-btn').forEach(btn => btn.addEventListener('click', (e) => {
const dir = e.target.dataset.direction;
if (dir === 'up' && ruleDiv.previousElementSibling) ruleDiv.parentNode.insertBefore(ruleDiv, ruleDiv.previousElementSibling);
else if (dir === 'down' && ruleDiv.nextElementSibling) ruleDiv.parentNode.insertBefore(ruleDiv.nextElementSibling, ruleDiv);
}));
updateVisibility();
}
function saveStrategy() {
const nameInput = document.getElementById('strategy-name');
const originalName = nameInput.dataset.originalName;
const newName = nameInput.value.trim();
if (!newName) return showNotification('Strategy name cannot be empty.', "error");
if (newName !== originalName && advancedStrategies[newName]) return showNotification('A strategy with this name already exists.', "error");
const rules = [];
document.querySelectorAll('.strategy-rule').forEach(ruleDiv => {
const action = ruleDiv.querySelector('.action-type').value;
let actionValue;
if (action === 'switchStrategy') actionValue = ruleDiv.querySelector('.action-strategy-select').value;
else if (action === 'notify') actionValue = ruleDiv.querySelector('.action-value-text').value;
else if (action === 'setRisk') actionValue = ruleDiv.querySelector('.action-risk-select').value;
else if (action === 'setSegments') actionValue = ruleDiv.querySelector('.action-segments-select').value;
else actionValue = parseFloat(ruleDiv.querySelector('.action-value').value);
rules.push({
conditionType: ruleDiv.querySelector('input[name*="-type"]:checked').value,
playConditionTerm: ruleDiv.querySelector('.play-term').value,
playConditionValue: parseFloat(ruleDiv.querySelector('.play-value').value),
playConditionBetType: ruleDiv.querySelector('.play-bet-type').value,
netGainCondition: ruleDiv.querySelector('.net-gain-condition').value,
netGainValue: parseFloat(ruleDiv.querySelector('.net-gain-value').value),
timeSinceWinCondition: ruleDiv.querySelector('.time-condition').value,
timeSinceWinValue: parseFloat(ruleDiv.querySelector('.time-value').value),
patternMatch: ruleDiv.querySelector('.pattern-value').value,
action: action,
actionValue: actionValue,
});
});
if (originalName && originalName !== newName) delete advancedStrategies[originalName];
advancedStrategies[newName] = rules;
saveStrategies();
document.getElementById('strategy-modal').style.display = 'none';
log(`Strategy '${newName}' saved.`, "system");
}
function validateStrategyRules() {
const warningEl = document.getElementById('strategy-modal-warning');
const saveBtn = document.getElementById('save-strategy-btn');
// Future wheel-specific validation can go here. For now, just ensure UI is correct.
warningEl.style.display = 'none';
saveBtn.disabled = false;
}
function editSelectedStrategy() {
const selectedName = document.getElementById('advanced-strategy-list').value;
if (selectedName) openStrategyModal(selectedName);
else showNotification('Please select a strategy to edit.', "error");
}
function deleteSelectedStrategy() {
const selectedName = document.getElementById('advanced-strategy-list').value;
if (selectedName && confirm(`Are you sure you want to delete the strategy "${selectedName}"?`)) {
delete advancedStrategies[selectedName];
saveStrategies();
log(`Strategy '${selectedName}' deleted.`, "system");
}
}
async function executeAdvancedStrategy(rules, targetState, baseBet) {
let betAmountModified = false;
for (const [index, rule] of rules.entries()) {
const conditionMet = checkCondition(rule);
if (conditionMet) {
log(`<b>Strategy Trigger:</b> Rule #${index + 1}`, "strategy");
const action = rule.action;
if (['increaseByPercentage', 'decreaseByPercentage', 'setAmount', 'resetAmount'].includes(action) && betAmountModified) {
log(` ↳ Action skipped: Bet amount already modified.`, "strategy");
continue;
}
switch (action) {
case 'increaseByPercentage':
targetState.currentBet *= (1 + rule.actionValue / 100);
break;
case 'decreaseByPercentage':
targetState.currentBet *= (1 - rule.actionValue / 100);
break;
case 'setAmount':
targetState.currentBet = rule.actionValue;
break;
case 'resetAmount':
targetState.currentBet = baseBet;
break;
case 'setBaseBet':
state.baseBet = rule.actionValue;
document.getElementById('base-bet').value = rule.actionValue;
break;
case 'increaseBaseBetByPercentage':
state.baseBet *= (1 + rule.actionValue / 100);
document.getElementById('base-bet').value = state.baseBet;
break;
case 'decreaseBaseBetByPercentage':
state.baseBet *= (1 - rule.actionValue / 100);
document.getElementById('base-bet').value = state.baseBet;
break;
case 'setRisk':
setUIValue(CONFIG.riskSelect, rule.actionValue);
break;
case 'setSegments':
setUIValue(CONFIG.segmentsSelect, rule.actionValue);
break;
case 'changeSeed':
await changeSeedProgrammatically();
break;
case 'switchStrategy':
document.getElementById('auto-strategy-select').value = rule.actionValue;
break;
case 'stop':
if (state.running) toggleBot();
break;
case 'notify':
showNotification(rule.actionValue);
break;
}
log(` ↳ Action: ${action.replace(/([A-Z])/g, ' $1')} ${rule.actionValue || ''}`, "strategy");
if (['increaseByPercentage', 'decreaseByPercentage', 'setAmount', 'resetAmount'].includes(action)) betAmountModified = true;
}
}
}
function checkCondition(rule) {
switch (rule.conditionType) {
case 'bets':
const value = rule.playConditionValue;
const streak = rule.playConditionBetType === 'win' ? state.winStreak : state.lossStreak;
switch (rule.playConditionTerm) {
case 'every':
return (rule.playConditionBetType === 'bet' && state.betCount > 0 && state.betCount % value === 0) || (streak > 0 && streak % value === 0);
case 'everyStreakOf':
return streak > 0 && streak % value === 0;
case 'firstStreakOf':
const flag = `firstStreak-${rule.playConditionBetType}-${value}`;
if (streak === value && !state.strategyFlags[flag]) return (state.strategyFlags[flag] = true);
if (streak < value) state.strategyFlags[flag] = false;
break;
case 'streakGreaterThan':
return streak > value;
case 'streakLowerThan':
return streak < value;
}
break;
case 'profit':
if (rule.netGainCondition === 'greater' && state.totalProfit > rule.netGainValue) return true;
if (rule.netGainCondition === 'less' && state.totalProfit < rule.netGainValue) return true;
if (rule.netGainCondition === '=' && state.totalProfit.toFixed(8) == rule.netGainValue.toFixed(8)) return true;
break;
case 'time':
const timeSinceWin = (Date.now() - state.lastWinTimestamp) / 1000;
if (rule.timeSinceWinCondition === 'greater' && timeSinceWin > rule.timeSinceWinValue) return true;
if (rule.timeSinceWinCondition === 'less' && timeSinceWin < rule.timeSinceWinValue) return true;
break;
case 'pattern':
return state.outcomeHistory.endsWith(rule.patternMatch.toUpperCase());
}
return false;
}
// --- HELPER & STATE FUNCTIONS ---
function getBetDelay() {
const parts = String(CONFIG.betDelay).split('-').map(p => parseInt(p.trim(), 10));
if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1]) && parts[0] <= parts[1]) {
return Math.floor(Math.random() * (parts[1] - parts[0] + 1)) + parts[0];
}
if (parts.length === 1 && !isNaN(parts[0])) return parts[0];
return 100;
}
function showNotification(message, type = 'info') {
const container = document.getElementById('bot-notification-container');
const notification = document.createElement('div');
notification.className = 'bot-notification';
notification.innerHTML = `<div class="bot-notification-header"><span>${type === 'error' ? 'Error' : 'Bot Notification'}</span><button class="close-btn">×</button></div><p>${message}</p>`;
if (type === 'error') notification.style.backgroundColor = 'var(--accent-red)';
container.appendChild(notification);
const close = () => {
notification.classList.add('fade-out');
setTimeout(() => notification.remove(), 500);
};
notification.querySelector('.close-btn').addEventListener('click', close);
setTimeout(close, 10000);
}
function simulateClick(element) {
if (!element) return;
const eventOptions = {
bubbles: true,
cancelable: true,
view: unsafeWindow
};
element.dispatchEvent(new MouseEvent('mousedown', eventOptions));
element.dispatchEvent(new MouseEvent('mouseup', eventOptions));
element.dispatchEvent(new MouseEvent('click', eventOptions));
}
function setUIValue(selector, value) {
const input = document.querySelector(selector);
if (input) {
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value")?.set;
const nativeSelectValueSetter = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, "value")?.set;
if (nativeInputValueSetter && input.tagName === 'INPUT') nativeInputValueSetter.call(input, value.toString());
else if (nativeSelectValueSetter && input.tagName === 'SELECT') nativeSelectValueSetter.call(input, value.toString());
else input.value = value;
input.dispatchEvent(new Event('input', {
bubbles: true
}));
input.dispatchEvent(new Event('change', {
bubbles: true
}));
return true;
}
return false;
}
function getStakeBetAmount() {
const el = document.querySelector(CONFIG.amountInput);
return el ? parseFloat(el.value) : 0;
}
function updateStatus(text) {
const el = document.getElementById('bot-status-indicator');
if (el) el.textContent = `- ${text}`;
}
function updateProfitDisplay() {
const el = document.getElementById('profit-loss-display');
if (!el) return;
el.textContent = `${state.totalProfit >= 0 ? '+' : ''}${state.totalProfit.toFixed(8)}`;
el.className = state.totalProfit > 0 ? 'profit' : state.totalProfit < 0 ? 'loss' : '';
}
function resetState() {
state.betCount = 0;
state.totalProfit = 0;
state.winStreak = 0;
state.lossStreak = 0;
state.lastBetWasWin = null;
state.strategyFlags = {};
state.persistentTriggerStates = {};
state.lastWinTimestamp = Date.now();
state.outcomeHistory = '';
state.stackingDelay = 0;
updateProfitDisplay();
log("State reset for new session.", "system");
}
function updateStateFromResult(result) {
state.betCount++;
state.totalProfit += result.profit;
state.lastBetWasWin = result.win;
if (result.win) {
state.winStreak++;
state.lossStreak = 0;
state.lastWinTimestamp = Date.now();
state.outcomeHistory += 'W';
} else {
state.lossStreak++;
state.winStreak = 0;
state.outcomeHistory += 'L';
}
state.outcomeHistory = state.outcomeHistory.slice(-50);
updateProfitDisplay();
}
function updateInputDisabledState() {
const isRunning = state.running;
document.querySelectorAll('#wheel-bot-window .bot-input, #wheel-bot-window .bot-btn, #wheel-bot-window .config-pick-btn, #wheel-bot-window select').forEach(el => {
el.disabled = isRunning && el.id !== 'start-stop-btn';
});
}
function updateSettings() {
state.baseBet = parseFloat(document.getElementById('base-bet').value) || 0.01;
state.currentBet = state.baseBet;
}
async function startBot() {
updateConfigFromUI(); // Ensure latest config from UI is used for the session
updateSettings();
resetState();
setUIValue(CONFIG.riskSelect, document.getElementById('auto-risk').value);
setUIValue(CONFIG.segmentsSelect, document.getElementById('auto-segments').value);
while (state.running) {
if (CONFIG.maxBetAmount > 0 && state.currentBet > CONFIG.maxBetAmount) {
log(`SAFETY STOP: Bet amount (${state.currentBet.toFixed(8)}) exceeded max bet (${CONFIG.maxBetAmount}).`, 'error');
toggleBot();
break;
}
setBetAmount(state.currentBet);
const result = await placeBetAndGetResult('auto');
if (result === null) {
if (state.running) toggleBot();
break;
}
updateStateFromResult(result);
const strategyName = document.getElementById('auto-strategy-select').value;
if (strategyName !== 'none' && advancedStrategies[strategyName]) {
await executeAdvancedStrategy(advancedStrategies[strategyName], state, state.baseBet);
}
await sleep(getBetDelay());
}
}
function setBetAmount(amount) {
setUIValue(CONFIG.amountInput, amount.toFixed(8));
}
async function placeBetAndGetResult(betType = 'manual') {
const amountForProfit = betType === 'auto' ? state.currentBet : semiAutoState.currentBet;
const betButton = document.querySelector(CONFIG.betButton);
if (!betButton || betButton.disabled) {
updateStatus("ERROR: Bet button not found or disabled!");
return null;
}
const pastBetsContainer = document.querySelector(CONFIG.pastBetsContainer);
if (!pastBetsContainer) {
updateStatus("ERROR: Past bets list not found!");
return null;
}
const lastBetId = pastBetsContainer.querySelector('[data-past-bet-id]')?.getAttribute('data-past-bet-id');
await sleep(50); // Give UI time to update from setBetAmount
simulateClick(betButton);
return new Promise(resolve => {
let attempts = 0;
const pollInterval = setInterval(() => {
const newBetElement = pastBetsContainer.querySelector('[data-past-bet-id]');
const newBetId = newBetElement?.getAttribute('data-past-bet-id');
if (newBetElement && newBetId !== lastBetId) {
clearInterval(pollInterval);
const multiplierText = newBetElement.textContent || '';
const multiplier = parseFloat(multiplierText.replace('×', ''));
if (isNaN(multiplier)) {
log("Error parsing multiplier from bet result.", "error");
return resolve(null);
}
const isWin = multiplier > parseFloat(CONFIG.lossMultiplier);
const netProfit = (amountForProfit * multiplier) - amountForProfit;
log(`Bet #${state.betCount + 1}: ${isWin ? 'WIN' : 'LOSS'} (${multiplier}x). Profit: ${netProfit.toFixed(8)}`, betType);
resolve({
win: isWin,
profit: netProfit
});
} else if (++attempts >= 75) {
clearInterval(pollInterval);
updateStatus("ERROR: Bet result timed out!");
log("Bet result timed out.", "error");
resolve(null);
}
}, 200);
});
}
async function runSemiAutoStep() {
const stepButton = document.getElementById('semi-auto-step-btn');
stepButton.disabled = true;
updateStatus('Manual Step...');
if (!semiAutoState.isStarted) {
updateConfigFromUI(); // Ensure latest config from UI is used for the session
semiAutoState.isStarted = true;
resetState();
const baseBetInput = document.getElementById('semi-auto-base-bet');
semiAutoState.currentBet = parseFloat(baseBetInput.value) || 0.01;
}
setBetAmount(semiAutoState.currentBet);
const result = await placeBetAndGetResult('manual');
if (result) {
updateStateFromResult(result);
const strategyName = document.getElementById('semi-auto-strategy').value;
if (strategyName !== 'none' && advancedStrategies[strategyName]) {
await executeAdvancedStrategy(advancedStrategies[strategyName], semiAutoState, parseFloat(document.getElementById('semi-auto-base-bet').value));
}
}
updateStatus('Ready for next step.');
stepButton.disabled = false;
}
async function handleManualSeedChange() {
const btn = document.getElementById('manual-change-seed-btn');
btn.disabled = true;
await changeSeedProgrammatically();
btn.disabled = false;
}
function resetSemiAutoProgression() {
semiAutoState.isStarted = false;
const baseBet = parseFloat(document.getElementById('semi-auto-base-bet').value) || 0.01;
semiAutoState.currentBet = baseBet;
setBetAmount(baseBet);
log("Manual progression reset to base bet.", "manual");
}
function saveStrategies() {
localStorage.setItem('stakeWheelBotStrategies', JSON.stringify(advancedStrategies));
populateStrategyDropdowns();
}
function loadStrategies() {
const saved = localStorage.getItem('stakeWheelBotStrategies');
if (saved) {
try {
advancedStrategies = JSON.parse(saved);
} catch (e) {
console.error("Failed to load strategies:", e);
}
}
}
function populateStrategyDropdowns() {
const selects = document.querySelectorAll('#semi-auto-strategy, #advanced-strategy-list, #auto-strategy-select');
selects.forEach(sel => {
const currentVal = sel.value;
sel.innerHTML = sel.id.includes('list') ? '' : `<option value="none">${sel.id.includes('auto-strategy') ? 'None (No auto changes)' : 'None (Manual Betting)'}</option>`;
for (const name in advancedStrategies) {
sel.add(new Option(name, name));
}
sel.value = currentVal;
});
}
function updateSemiAutoDescription() {
const select = document.getElementById('semi-auto-strategy');
const desc = document.getElementById('semi-auto-desc');
desc.textContent = select.value === 'none' ? 'Manual Betting: Bet amount is not changed automatically.' : `Using advanced strategy: ${select.value}`;
}
function applyLogFilters() {
document.getElementById('log-container').querySelectorAll('p').forEach(entry => {
entry.style.display = logFilters[entry.dataset.logType] ? '' : 'none';
});
}
function log(message, type = 'system') {
const logContainer = document.getElementById('log-container');
if (!logContainer) return;
const entry = document.createElement('p');
entry.innerHTML = `[${new Date().toLocaleTimeString()}] ${message}`;
entry.dataset.logType = type;
entry.className = `log-${type}`;
if (!logFilters[type]) entry.style.display = 'none';
logContainer.appendChild(entry);
logEntries.push(`[${new Date().toLocaleTimeString()}] ${message.replace(/<[^>]*>/g, '')}`);
logContainer.scrollTop = logContainer.scrollHeight;
}
function saveLogToFile() {
const blob = new Blob([logEntries.join('\n')], {
type: 'text/plain'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `stake_wheel_bot_log_${new Date().toISOString()}.txt`;
a.click();
URL.revokeObjectURL(url);
}
function exportSelectedStrategy() {
const name = document.getElementById('advanced-strategy-list').value;
if (!name || !advancedStrategies[name]) return showNotification("Please select a valid strategy to export.", "error");
const blob = new Blob([JSON.stringify({
name,
rules: advancedStrategies[name]
}, null, 2)], {
type: 'application/json'
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${name}.strategy.json`;
a.click();
URL.revokeObjectURL(url);
}
function importStrategy() {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json,.strategy';
input.onchange = e => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = re => {
try {
const {
name,
rules
} = JSON.parse(re.target.result);
if (!name || !Array.isArray(rules)) throw new Error("Invalid format.");
if (advancedStrategies[name] && !confirm(`Strategy "${name}" already exists. Overwrite?`)) return;
advancedStrategies[name] = rules;
saveStrategies();
showNotification(`Strategy "${name}" imported successfully!`);
} catch (err) {
showNotification(`Import failed: ${err.message}`, "error");
}
};
reader.readAsText(file);
};
input.click();
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function makeDraggable(element, handle) {
let pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
handle.onmousedown = (e) => {
if (e.target.closest('.window-btn')) return;
e.preventDefault();
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = () => {
document.onmouseup = null;
document.onmousemove = null;
};
document.onmousemove = (e) => {
e.preventDefault();
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
element.style.top = (element.offsetTop - pos2) + "px";
element.style.left = (element.offsetLeft - pos1) + "px";
};
};
}
function makeResizable(element) {
const resizers = element.querySelectorAll('.resizer');
const minimum_size = {
width: parseFloat(getComputedStyle(element, null).getPropertyValue('min-width')),
height: parseFloat(getComputedStyle(element, null).getPropertyValue('min-height'))
};
let original_width = 0;
let original_height = 0;
let original_x = 0;
let original_y = 0;
let original_mouse_x = 0;
let original_mouse_y = 0;
resizers.forEach(currentResizer => {
currentResizer.addEventListener('mousedown', (e) => {
e.preventDefault();
original_width = parseFloat(getComputedStyle(element, null).getPropertyValue('width').replace('px', ''));
original_height = parseFloat(getComputedStyle(element, null).getPropertyValue('height').replace('px', ''));
original_x = element.getBoundingClientRect().left;
original_y = element.getBoundingClientRect().top;
original_mouse_x = e.pageX;
original_mouse_y = e.pageY;
const mouseMoveHandler = (e) => {
const dx = e.pageX - original_mouse_x;
const dy = e.pageY - original_mouse_y;
if (currentResizer.classList.contains('resizer-b') || currentResizer.classList.contains('resizer-br') || currentResizer.classList.contains('resizer-bl')) {
const height = original_height + dy;
if (height > minimum_size.height) {
element.style.height = height + 'px';
}
}
if (currentResizer.classList.contains('resizer-t') || currentResizer.classList.contains('resizer-tr') || currentResizer.classList.contains('resizer-tl')) {
const height = original_height - dy;
if (height > minimum_size.height) {
element.style.height = height + 'px';
element.style.top = original_y + dy + 'px';
}
}
if (currentResizer.classList.contains('resizer-r') || currentResizer.classList.contains('resizer-br') || currentResizer.classList.contains('resizer-tr')) {
const width = original_width + dx;
if (width > minimum_size.width) {
element.style.width = width + 'px';
}
}
if (currentResizer.classList.contains('resizer-l') || currentResizer.classList.contains('resizer-bl') || currentResizer.classList.contains('resizer-tl')) {
const width = original_width - dx;
if (width > minimum_size.width) {
element.style.width = width + 'px';
element.style.left = original_x + dx + 'px';
}
}
};
const mouseUpHandler = () => {
window.removeEventListener('mousemove', mouseMoveHandler);
window.removeEventListener('mouseup', mouseUpHandler);
};
window.addEventListener('mousemove', mouseMoveHandler);
window.addEventListener('mouseup', mouseUpHandler);
});
});
}
function handleKeydown(e) {
const tag = document.activeElement.tagName;
if (['INPUT', 'SELECT', 'TEXTAREA'].includes(tag) || isPickingElement) return;
switch (e.key.toLowerCase()) {
case 's':
document.getElementById('start-stop-btn').click();
break;
case 'd':
document.getElementById('semi-auto-step-btn').click();
break;
case 'r':
document.getElementById('semi-auto-reset-btn').click();
break;
}
}
function applyUiScale(scaleValue = CONFIG.uiScale) {
const scale = scaleValue / 100;
document.getElementById('wheel-bot-window').style.zoom = scale;
document.getElementById('wheel-bot-minimized-bar').style.zoom = scale;
}
function waitForGameAndEnable() {
const interval = setInterval(() => {
if (document.querySelector(CONFIG.betButton)) {
clearInterval(interval);
updateInputDisabledState();
updateStatus("Ready");
log("Game interface found. Bot is active.", "system");
}
}, 1000);
updateInputDisabledState();
}
window.addEventListener('load', () => {
setTimeout(init, 2000);
});
})();