您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automates crimes (neighborhood + car robbery) with a cleaner GUI + live stats bar
// ==UserScript== // @name Ortenskung Script Script / Hack 3.0 // @namespace http://tampermonkey.net/ // @version 3.0 // @description Automates crimes (neighborhood + car robbery) with a cleaner GUI + live stats bar // @match https://www.ortenskung.com/en/* // @grant none // ==/UserScript== (function () { 'use strict'; // ========================= // === Important state ==== // ========================= // Runtime flags and state variables used by the bot loop and UI let running = false; let loopTimer = null; let selectedCrimeId = 1; let autoClose = true; // ========================= // === Create main panel === // ========================= // Build the floating control panel container and style it const panel = document.createElement('div'); panel.id = '__ortens_crime_bot_panel'; Object.assign(panel.style, { position: 'fixed', top: '5px', right: '5px', left: 'auto', width: '260px', background: '#111', color: '#eee', borderRadius: '8px', border: '1px solid rgba(255,255,255,0.05)', boxShadow: '0 4px 16px rgba(0,0,0,0.5)', fontFamily: 'Inter, Roboto, sans-serif', fontSize: '13px', zIndex: 999999, userSelect: 'none', overflow: 'hidden' }); // Panel inner HTML: header, body (tabs), stats, footer panel.innerHTML = ` <div id="__header" style="display:flex;align-items:center;justify-content:space-between;padding:6px 10px;background:#0c0c0c;border-bottom:1px solid rgba(255,255,255,0.05);cursor:move;font-weight:600;font-size:13px"> <div>Crime Bot</div> <div style="display:flex;gap:6px;align-items:center"> <button id="__minBtn" title="Minimize" style="background:transparent;border:none;color:#aaa;cursor:pointer;font-size:14px">—</button> <button id="__closeBtn" title="Close" style="background:transparent;border:none;color:#aaa;cursor:pointer;font-size:14px">✕</button> </div> </div> <div id="__body" style="padding:8px;display:block"> <div style="display:flex;gap:6px;margin-bottom:6px"> <button class="__tab" data-tab="main" style="flex:1;padding:4px;border-radius:4px;border:0;background:#1b1b1b;color:#fff;cursor:pointer">Main</button> <button class="__tab" data-tab="settings" style="flex:1;padding:4px;border-radius:4px;border:0;background:#121212;color:#888;cursor:pointer">Settings</button> </div> <div id="__tab_main" class="__tabpanel"> <label style="font-size:12px">Crime Type</label> <select id="__crimeType" style="width:100%;padding:4px;margin:4px 0 8px;border-radius:4px;border:1px solid rgba(255,255,255,0.06);background:#0b0b0b;color:#fff;font-size:12px"> <option value="neighborhood">Neighborhood</option> <option value="car">Car Robbery</option> </select> <label style="font-size:12px">Crime ID</label> <select id="__crimeId" style="width:100%;padding:4px;margin:4px 0 8px;border-radius:4px;border:1px solid rgba(255,255,255,0.06);background:#0b0b0b;color:#fff;font-size:12px"> ${Array.from({length:30}, (_,i)=>i+1).map(n=>`<option value="${n}">${n}</option>`).join('')} </select> <div style="display:flex;align-items:center;gap:6px;margin-bottom:8px"> <label style="font-size:12px;display:flex;align-items:center;gap:4px"> <input type="checkbox" id="__autoClose" checked /> <span>Close dialogs</span> </label> </div> <button id="__startBtn" style="width:100%;padding:6px;border-radius:6px;border:0;background:linear-gradient(180deg,#2fa84f,#208f3a);color:#fff;font-weight:600;cursor:pointer;font-size:13px">Start</button> </div> <div id="__tab_settings" class="__tabpanel" style="display:none"> <p style="font-size:12px;color:#bbb;margin:0 0 6px">Settings</p> <p style="font-size:11px;color:#999;margin:0 0 6px">Loop delay (ms)</p> <input id="__loopDelay" type="number" value="4000" style="width:100%;padding:4px;border-radius:4px;border:1px solid rgba(255,255,255,0.06);background:#0b0b0b;color:#fff;font-size:12px" /> <p style="font-size:11px;color:#999;margin:6px 0 0">If something breaks, stop and inspect selectors.</p> </div> </div> <div id="__statsBar" style="display:flex;justify-content:space-around;align-items:center;padding:6px 8px;background:#181818;border-top:1px solid rgba(255,255,255,0.05);border-bottom:1px solid rgba(255,255,255,0.05);font-size:12px;color:#eaeaea"> <span id="__diamonds">💎 0</span> <span id="__cash">💵 0</span> <span id="__bullets">🔫 0</span> </div> <div id="__footer" style="font-size:11px;padding:5px 8px;background:#0f0f0f;border-top:1px solid rgba(255,255,255,0.03);color:#9a9a9a"> Status: <span id="__status">Idle</span> </div> `; document.body.appendChild(panel); // ========================= // === Cache DOM nodes ==== // ========================= // Grab commonly used elements once for later event handlers and updates const header = panel.querySelector('#__header'); const body = panel.querySelector('#__body'); const footer = panel.querySelector('#__footer'); const closeBtn = panel.querySelector('#__closeBtn'); const minBtn = panel.querySelector('#__minBtn'); const tabs = panel.querySelectorAll('.__tab'); const tabMain = panel.querySelector('#__tab_main'); const tabSettings = panel.querySelector('#__tab_settings'); const startBtn = panel.querySelector('#__startBtn'); const crimeIdEl = panel.querySelector('#__crimeId'); const crimeTypeEl = panel.querySelector('#__crimeType'); const autoCloseEl = panel.querySelector('#__autoClose'); const statusEl = panel.querySelector('#__status'); const loopDelayEl = panel.querySelector('#__loopDelay'); // Custom sequence state let customSteps = []; // [{mode:'neighborhood'|'car', id: Number}, ...] let currentStepIndex = 0; // Build Custom Loop UI const customContainer = document.createElement('div'); customContainer.style = 'margin-top:8px;border-top:1px dashed rgba(255,255,255,0.04);padding-top:8px'; customContainer.innerHTML = ` <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px"> <div style="font-size:12px">Custom Loop</div> <button id="__addStepBtn" style="padding:4px 6px;border-radius:4px;border:0;background:#253036;color:#fff;cursor:pointer;font-size:12px">Add Step</button> </div> <div id="__stepsList" style="max-height:140px;overflow:auto;display:flex;flex-direction:column;gap:6px"></div> <div style="font-size:11px;color:#999;margin-top:6px">Bot executes one step per loop iteration and advances through the list.</div> `; tabMain.appendChild(customContainer); const addStepBtn = panel.querySelector('#__addStepBtn'); const stepsListEl = panel.querySelector('#__stepsList'); function makeCrimeIdSelect(selected = 1) { const sel = document.createElement('select'); sel.style = 'width:72px;padding:4px;border-radius:4px;border:1px solid rgba(255,255,255,0.06);background:#0b0b0b;color:#fff;font-size:12px'; for (let i = 1; i <= 30; i++) { const o = document.createElement('option'); o.value = i; o.textContent = i; if (i === Number(selected)) o.selected = true; sel.appendChild(o); } return sel; } function saveSteps() { try { sessionStorage.setItem('__ortens_customSteps', JSON.stringify(customSteps)); } catch {} } function loadSteps() { try { const raw = sessionStorage.getItem('__ortens_customSteps'); if (raw) customSteps = JSON.parse(raw) || []; } catch (e) { customSteps = []; } } function renderSteps() { stepsListEl.innerHTML = ''; if (!customSteps.length) { const hint = document.createElement('div'); hint.style = 'font-size:12px;color:#888'; hint.textContent = 'No steps yet — click "Add Step" to create a sequence.'; stepsListEl.appendChild(hint); return; } customSteps.forEach((s, idx) => { const row = document.createElement('div'); row.style = 'display:flex;gap:6px;align-items:center'; const idxLabel = document.createElement('div'); idxLabel.textContent = (idx + 1) + '.'; idxLabel.style = 'width:18px;text-align:right;color:#bbb;font-size:12px'; const modeSel = document.createElement('select'); modeSel.style = 'width:110px;padding:4px;border-radius:4px;border:1px solid rgba(255,255,255,0.06);background:#0b0b0b;color:#fff;font-size:12px'; const optN = document.createElement('option'); optN.value = 'neighborhood'; optN.textContent = 'Neighborhood'; const optC = document.createElement('option'); optC.value = 'car'; optC.textContent = 'Car'; modeSel.appendChild(optN); modeSel.appendChild(optC); modeSel.value = s.mode; const idSel = makeCrimeIdSelect(s.id); const removeBtn = document.createElement('button'); removeBtn.textContent = '✕'; removeBtn.title = 'Remove step'; removeBtn.style = 'padding:4px 6px;border-radius:4px;border:0;background:#331f1f;color:#fff;cursor:pointer;font-size:12px'; modeSel.addEventListener('change', () => { customSteps[idx].mode = modeSel.value; saveSteps(); }); idSel.addEventListener('change', () => { customSteps[idx].id = Number(idSel.value); saveSteps(); }); removeBtn.addEventListener('click', () => { customSteps.splice(idx,1); if (currentStepIndex>=customSteps.length) currentStepIndex=0; saveSteps(); renderSteps(); }); row.appendChild(idxLabel); row.appendChild(modeSel); row.appendChild(idSel); row.appendChild(removeBtn); stepsListEl.appendChild(row); }); } addStepBtn.addEventListener('click', () => { customSteps.push({ mode: 'neighborhood', id: 1 }); saveSteps(); renderSteps(); }); loadSteps(); renderSteps(); // ========================= // === Tab switching ======= // ========================= tabs.forEach(t => t.addEventListener('click', () => { tabs.forEach(x => x.style.background = '#121212'); t.style.background = '#1b1b1b'; tabMain.style.display = t.dataset.tab === 'main' ? 'block' : 'none'; tabSettings.style.display = t.dataset.tab === 'settings' ? 'block' : 'none'; })); // ========================= // === Dragging behavior == // ========================= // Allow the panel to be dragged by its header let dragging = false; let activePointerId = null; let startX = 0, startY = 0, origLeft = 0, origTop = 0; // Ensure panel has explicit left/top so dragging calculations work (function ensurePositionValues() { const rect = panel.getBoundingClientRect(); if (!panel.style.left || panel.style.left === 'auto') panel.style.left = rect.left + 'px'; if (!panel.style.top || panel.style.top === 'auto') panel.style.top = rect.top + 'px'; panel.style.right = 'auto'; })(); // Start dragging when pointer goes down on header (not on interactive children) header.addEventListener('pointerdown', (ev) => { if (ev.target !== header) return; ev.preventDefault(); dragging = true; activePointerId = ev.pointerId; startX = ev.clientX; startY = ev.clientY; const rect = panel.getBoundingClientRect(); origLeft = rect.left; origTop = rect.top; try { header.setPointerCapture(ev.pointerId); } catch (e) {} }); // Update panel position while dragging document.addEventListener('pointermove', (ev) => { if (!dragging || ev.pointerId !== activePointerId) return; const dx = ev.clientX - startX; const dy = ev.clientY - startY; panel.style.left = (origLeft + dx) + 'px'; panel.style.top = (origTop + dy) + 'px'; panel.style.position = 'fixed'; panel.style.right = 'auto'; }); // End dragging on pointer up document.addEventListener('pointerup', (ev) => { if (dragging && ev.pointerId === activePointerId) { try { header.releasePointerCapture(ev.pointerId); } catch (e) {} dragging = false; activePointerId = null; } }); // Prevent interactive elements (buttons/inputs) from initiating drag panel.querySelectorAll('button, input, select, a, textarea, label').forEach(node => { node.addEventListener('pointerdown', (e) => { e.stopPropagation(); }); }); // ========================= // === Minimize / Close ==== // ========================= // Toggle minimized state to hide body/footer and shrink panel let minimized = false; minBtn.addEventListener('click', () => { minimized = !minimized; if (minimized) { body.style.display = 'none'; footer.style.display = 'none'; panel.querySelector('#__statsBar').style.display = 'none'; panel.style.width = '140px'; minBtn.textContent = '▢'; minBtn.title = 'Restore'; } else { body.style.display = 'block'; footer.style.display = 'block'; panel.querySelector('#__statsBar').style.display = 'flex'; panel.style.width = '260px'; minBtn.textContent = '—'; minBtn.title = 'Minimize'; } }); // Close button stops loop (if running) and removes panel closeBtn.addEventListener('click', () => { try { stopLoop(); } catch (e) {} panel.remove(); }); // ---------------- Stats ---------------- // updateStats: find the on-page elements that show diamonds, cash and bullets // and copy their formatted values into our panel display. function updateStats() { // Try to find the page element that shows diamonds (supports multiple language labels) const diamondEl = document.querySelector('.val[data-tip*="diamond"], .val[data-tip*="diamonds"], .val[data-tip*="diamanter"]'); // Try to find the page element that shows cash (supports multiple language labels) const cashEl = document.querySelector('.val[data-tip*="cash"], .val[data-tip*="money"], .val[data-tip*="kontanter"]'); // Try to find the page element that shows bullets/ammo (supports multiple language labels) const bulletEl = document.querySelector('.val[data-tip*="ammunition"], .val[data-tip*="bullets"], .val[data-tip*="ammunitioner"]'); // formatWithSpaces: take a raw value (may include letters/symbols), // strip non-numeric characters, and insert spaces as thousand separators. function formatWithSpaces(raw) { if (raw == null) return ''; // prefer numeric digits from data-number or strip non-digits from text const s = String(raw).replace(/[^\d.-]/g, ''); // keep digits, minus and decimal point if (s === '') return ''; const parts = s.split('.'); let int = parts[0]; const sign = int.startsWith('-') ? '-' : ''; if (sign) int = int.slice(1); // remove sign for grouping // add a space every three digits from the right int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ' '); // reassemble sign, integer part and optional decimal part return sign + int + (parts[1] ? '.' + parts[1] : ''); } // getText: read the value for an element. Prefer data-number attribute if present // (often a clean integer), otherwise use the element's text content. function getText(el) { if (!el) return ''; // use data-number when present (clean integer), otherwise use textContent const raw = el.getAttribute('data-number') ?? el.textContent ?? ''; return formatWithSpaces(raw); } // read values from the located elements const diamonds = getText(diamondEl); const cash = getText(cashEl); const bullets = getText(bulletEl); // find destination nodes inside our custom panel where we show the values const dNode = panel.querySelector('#__diamonds'); const cNode = panel.querySelector('#__cash'); const bNode = panel.querySelector('#__bullets'); // update the panel text if both the panel node and the value exist if (dNode && diamonds) dNode.textContent = '💎 ' + diamonds; if (cNode && cash) cNode.textContent = '💵 ' + cash; if (bNode && bullets) bNode.textContent = '🔫 ' + bullets; } // run once immediately and then every 2 seconds to keep stats fresh updateStats(); setInterval(updateStats, 2000); // setStatus: update the status area of the UI with a short message function setStatus(text) { statusEl.textContent = text; } // stopLoop: stop the automation loop, clear timers and update UI function stopLoop() { running = false; if (loopTimer) { clearTimeout(loopTimer); loopTimer = null; } startBtn.textContent = 'Start'; setStatus('Stopped'); } // startLoop: start the automation loop if not already running. // It sets running state, updates the UI and immediately calls performCrime once. async function startLoop() { if (running) return; running = true; startBtn.textContent = 'Stop'; setStatus('Running'); currentStepIndex = 0; autoClose = !!autoCloseEl.checked; if (customSteps.length > 0) { const s = customSteps[currentStepIndex]; mode = s.mode; selectedCrimeId = Number(s.id); } else { mode = crimeTypeEl.value || 'neighborhood'; selectedCrimeId = parseInt(crimeIdEl.value, 10) || selectedCrimeId; } await performCrime(); } // add click handler to the start button to toggle start/stop startBtn.addEventListener('click', () => { if (!running) startLoop(); else stopLoop(); // sleep: helper that returns a promise that resolves after ms milliseconds. // This lets code pause without blocking the whole browser. // Apply custom step (if defined) so globals match this iteration if (customSteps.length > 0) { const step = customSteps[currentStepIndex] || customSteps[0]; mode = step.mode; selectedCrimeId = Number(step.id); setStatus(`Step ${currentStepIndex+1}/${customSteps.length}: ${mode} id ${selectedCrimeId}`); } else { // fallback to UI controls mode = crimeTypeEl.value || mode; selectedCrimeId = parseInt(crimeIdEl.value, 10) || selectedCrimeId; } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // ---------------- Placeholder performCrime ---------------- // This is a stub version of performCrime. Keep your real implementation // below or replace this stub. The UI expects functions named performCrime(), stopLoop(), setStatus() async function performCrime() { if (!running) return; // if stopped, do nothing try { // determine delay between actions from the UI, use at least 50ms const delay = Math.max(50, parseInt(loopDelayEl.value, 10) || 4000); // check page timers (if present) to avoid trying when no attempts remain const timerEl = document.querySelector('#my_timers > div.val') || document.querySelector('.timers .val') || document.querySelector('.timer .val'); if (timerEl) { const txt = timerEl.textContent.trim(); const m = txt.match(/(\d+)\s*\/\s*(\d+)/); // pattern like "2 / 5" if (m) { const used = parseInt(m[1], 10), total = parseInt(m[2], 10); const avail = total - used; if (avail <= 0) { // no attempts left — wait a bit and try again setStatus('No timers left — waiting'); loopTimer = setTimeout(performCrime, 5000); if (customSteps.length > 0) { currentStepIndex = (currentStepIndex + 1) % customSteps.length; } return; } } } // Example action cycle (replace with your real automation steps) setStatus('Performing crime id ' + (selectedCrimeId || crimeIdEl.value)); await sleep(delay); // schedule next iteration if (running) loopTimer = setTimeout(performCrime, delay); if (customSteps.length > 0) { currentStepIndex = (currentStepIndex + 1) % customSteps.length; } } catch (err) { console.error('performCrime error', err); setStatus('Error — stopped'); stopLoop(); } } // Expose panel utilities to the global window if other scripts want to use them window.__ortensPanel = { panel, setStatus, stopLoop }; }); // OR maybe here it starts // (Note: original code shows a duplicate sleep and performCrime; both are commented similarly) // sleep helper (duplicate in the snippet — same behavior as above) function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // performCrime: main automation loop that navigates dialogs and clicks the correct buttons. // This is a fuller implementation — comments explain each block. async function performCrime() { if (!running) return; try { // delay between actions taken from UI; ensure minimum 50ms const delay = Math.max(50, parseInt(loopDelayEl.value, 10) || 4000); // check any page timer element that shows "used / total" const timerEl = document.querySelector('#my_timers > div.val') || document.querySelector('.timers .val') || document.querySelector('.timer .val'); if (timerEl) { const txt = timerEl.textContent.trim(); const m = txt.match(/(\d+)\s*\/\s*(\d+)/); if (m) { const used = parseInt(m[1], 10), total = parseInt(m[2], 10); const avail = total - used; if (avail <= 0) { // nothing available now — wait then retry setStatus('No timers left — waiting'); loopTimer = setTimeout(performCrime, 5000); return; } // Apply custom step (if defined) so globals match this iteration if (customSteps.length > 0) { const step = customSteps[currentStepIndex] || customSteps[0]; mode = step.mode; selectedCrimeId = Number(step.id); setStatus(`Step ${currentStepIndex+1}/${customSteps.length}: ${mode} id ${selectedCrimeId}`); } else { // fallback to UI controls mode = crimeTypeEl.value || mode; selectedCrimeId = parseInt(crimeIdEl.value, 10) || selectedCrimeId; } } } // determine mode from UI: neighborhood (on-foot crimes) or car (vehicle crimes) const mode = (crimeTypeEl.value || 'neighborhood'); if (mode === 'neighborhood') { // If the neighborhood crimes dialog isn't open but there is a control to open it, click it if (!document.querySelector('#go_crimes_dialog') && document.querySelector('#go_crimes')) { try { document.querySelector('#go_crimes').click(); } catch {} await sleep(300); } // try to find a neighborhood tab/button and click it if the neighborhood pane is not visible const nbBtn = Array.from(document.querySelectorAll('.crimes_button, .crimes_button_small, .crimes-tab, .crimes_button')) .find(el => /neighborhood/i.test(el.textContent || el.innerText || '')); if (nbBtn && !document.querySelector('#neighborhood_crimes')) { try { nbBtn.click(); } catch (e) {} await sleep(250); } } else if (mode === 'car') { // For car mode, open the cars dialog if necessary if (!document.querySelector('#dialog_cars') && document.querySelector('#go_cars')) { try { document.querySelector('#go_cars').click(); } catch {} await sleep(300); } else { // try to find any link/button with "car", "cars", "vehicle" or similar text/title const carOpener = Array.from(document.querySelectorAll('a,button')).find(el => /(car|cars|vehicle|rob)/i.test((el.textContent || '') + ' ' + (el.getAttribute('title') || '')) ); if (carOpener && !document.querySelector('#dialog_cars')) { try { carOpener.click(); } catch (e) {} await sleep(300); } } } let commitBtn = null; // button that actually performs the crime if (mode === 'neighborhood') { // collect elements that might have onclick handlers for crimes const btns = Array.from(document.querySelectorAll('.crime_button_small, .crime_button, a, button')) .filter(el => el.getAttribute && (el.getAttribute('onclick') || '').toString().length > 0); // try to find a button whose onclick contains the selectedCrimeId commitBtn = btns.find(el => { const on = (el.getAttribute('onclick') || ''); return new RegExp(`['"]?id['"]?\\s*[:=]\\s*['"]?${selectedCrimeId}['"]?`).test(on); }) || btns.find(el => (el.getAttribute('onclick') || '').includes(`'id':'${selectedCrimeId}'`)); // if not found by onclick, fall back to searching by visible text containing the id if (!commitBtn) { const byText = Array.from(document.querySelectorAll('.crime_button_small, .crime_button, a, button')) .find(el => (el.textContent || '').includes(String(selectedCrimeId))); if (byText) commitBtn = byText; } // final fallback: take the first matching candidate on the page if (!commitBtn) { commitBtn = document.querySelector('.crime_button_small, .crime_button, a, button'); } } else if (mode === 'car') { // Search inside the cars dialog first for elements with onclick containing the same id pattern const carCandidates = Array.from(document.querySelectorAll('#dialog_cars .crime_button_small, #dialog_cars a, #dialog_cars button, .car_list a, .car_list button')) .filter(el => el.getAttribute && (el.getAttribute('onclick') || '').toString().length > 0); commitBtn = carCandidates.find(el => { const on = (el.getAttribute('onclick') || ''); return new RegExp(`['"]?id['"]?\\s*[:=]\\s*['"]?${selectedCrimeId}['"]?`).test(on); }) || carCandidates.find(el => (el.getAttribute('onclick') || '').includes(`'id':'${selectedCrimeId}'`)); // if not found by onclick in dialog, try matching visible text inside dialog if (!commitBtn) { const byText = Array.from(document.querySelectorAll('#dialog_cars .crime_button_small, #dialog_cars a, #dialog_cars button, .car_list a, .car_list button')) .find(el => (el.textContent || '').includes(String(selectedCrimeId))); if (byText) commitBtn = byText; } // broader page-wide search by onclick id pattern (if still not found) if (!commitBtn) { const pageBtns = Array.from(document.querySelectorAll('a,button')) .filter(el => el.getAttribute && (el.getAttribute('onclick') || '').toString().length > 0); commitBtn = pageBtns.find(el => { const on = (el.getAttribute('onclick') || ''); return new RegExp(`['"]?id['"]?\\s*[:=]\\s*['"]?${selectedCrimeId}['"]?`).test(on); }) || pageBtns.find(el => (el.getAttribute('onclick') || '').includes(`'id':'${selectedCrimeId}'`)); } // fallback: search page text for the id if (!commitBtn) { const byText = Array.from(document.querySelectorAll('a,button')) .find(el => (el.textContent || '').includes(String(selectedCrimeId))); if (byText) commitBtn = byText; } // final fallbacks: look for anything that looks like a "rob car" action inside dialog, then first candidate if (!commitBtn) { commitBtn = Array.from(document.querySelectorAll('#dialog_cars .crime_button_small, #dialog_cars a, #dialog_cars button, .car_list a, .car_list button')) .find(el => { const on = (el.getAttribute('onclick') || '') + ' ' + (el.textContent || ''); return /robbing:perform|rob(?:bing)?|rob car|car robbery|vehicle/i.test(on); }); } if (!commitBtn) { commitBtn = document.querySelector('#dialog_cars .crime_button_small') || document.querySelector('#dialog_cars a') || document.querySelector('#dialog_cars button') || document.querySelector('a, button'); } } // If still no commit button was found, wait and retry if (!commitBtn) { setStatus('No crime button found — retrying'); loopTimer = setTimeout(performCrime, 5000); return; } // try to bring the button into view so clicks are more likely to succeed try { commitBtn.scrollIntoView({block:'center', behavior:'auto'}); } catch (e) {} // try to click it; if that fails, dispatch a mouse event as a fallback try { commitBtn.click(); setStatus(`Clicked ${mode} button`); } catch (e) { try { commitBtn.dispatchEvent(new MouseEvent('click', {bubbles:true,cancelable:true})); setStatus('Dispatched click'); } catch (err) { console.error('click failed', err); setStatus('Click failed'); } } // after clicking, wait a short time and try to close dialogs/popups that appear setTimeout(() => { try { if (typeof close_dialog === 'function') { try { close_dialog('social'); } catch {} try { close_dialog('levelup'); } catch {} if (autoClose) try { close_dialog('crimes'); } catch {} if (autoClose) try { close_dialog('cars'); } catch {} } } catch (err) { /* ignore any errors closing dialogs */ } // also try to click any confirmation button or primary dialog button const notif = document.querySelector('.button.first, .ui-button.primary, .ui-dialog .button, .ui-dialog .ui-button'); if (notif) { try { notif.click(); } catch (e) {} } }, 800); // 4) Schedule next run after the configured delay (minimum 500ms) loopTimer = setTimeout(performCrime, Math.max(500, delay)); if (customSteps.length > 0) currentStepIndex = (currentStepIndex + 1) % customSteps.length; } catch (err) { console.error('performCrime error', err); setStatus('Error — retrying'); // on unexpected error, wait a bit and try again loopTimer = setTimeout(performCrime, 5000); } } // expose a small control API on window so external code can start/stop the bot and read status window.__ortensCrimeBot = { stop: stopLoop, start: startLoop, status: () => ({ running, selectedCrimeId, autoClose }) }; // initialize selectedCrimeId from the UI control so the bot starts with the desired crime selectedCrimeId = parseInt(crimeIdEl.value, 10) || selectedCrimeId; })();