Initial commit: POE2 automated trade bot
Monitors pathofexile.com/trade2 for new listings, travels to seller hideouts, buys items from public stash tabs, and stores them. Includes persistent C# OCR daemon for fast screen capture + Windows native OCR, web dashboard for managing trade links and settings, and full game automation via Win32 SendInput. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
41d174195e
28 changed files with 6449 additions and 0 deletions
670
src/dashboard/index.html
Normal file
670
src/dashboard/index.html
Normal file
|
|
@ -0,0 +1,670 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>POE2 Trade Bot</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
background: #0d1117;
|
||||
color: #e6edf3;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.container { max-width: 900px; margin: 0 auto; padding: 20px; }
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #30363d;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
header h1 { font-size: 20px; font-weight: 600; }
|
||||
.header-right { display: flex; align-items: center; gap: 14px; }
|
||||
.settings-btn {
|
||||
background: none; border: none; cursor: pointer; padding: 4px;
|
||||
color: #8b949e; transition: color 0.15s; line-height: 1;
|
||||
}
|
||||
.settings-btn:hover { background: none; color: #e6edf3; }
|
||||
.settings-btn svg { display: block; }
|
||||
.status-dot {
|
||||
width: 10px; height: 10px; border-radius: 50%;
|
||||
display: inline-block; margin-right: 8px;
|
||||
}
|
||||
.status-dot.running { background: #3fb950; box-shadow: 0 0 6px #3fb950; }
|
||||
.status-dot.paused { background: #d29922; box-shadow: 0 0 6px #d29922; }
|
||||
.status-dot.idle { background: #8b949e; }
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.stat-card {
|
||||
background: #161b22;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
.stat-card .value { font-size: 24px; font-weight: 700; color: #58a6ff; }
|
||||
.stat-card .label { font-size: 11px; color: #8b949e; text-transform: uppercase; margin-top: 4px; }
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
button {
|
||||
padding: 8px 20px;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
background: #21262d;
|
||||
color: #e6edf3;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
button:hover { background: #30363d; }
|
||||
button.primary { background: #238636; border-color: #2ea043; }
|
||||
button.primary:hover { background: #2ea043; }
|
||||
button.danger { background: #da3633; border-color: #f85149; }
|
||||
button.danger:hover { background: #f85149; }
|
||||
button.warning { background: #9e6a03; border-color: #d29922; }
|
||||
button.warning:hover { background: #d29922; }
|
||||
|
||||
.section { margin-bottom: 20px; }
|
||||
.section-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
color: #8b949e;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.add-link {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.add-link input {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
background: #0d1117;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
color: #e6edf3;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.add-link input:focus { border-color: #58a6ff; }
|
||||
.add-link input::placeholder { color: #484f58; }
|
||||
|
||||
.links-list { display: flex; flex-direction: column; gap: 6px; }
|
||||
.link-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #161b22;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.link-left { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
|
||||
.link-info { flex: 1; min-width: 0; }
|
||||
.link-name { font-size: 13px; font-weight: 600; color: #e6edf3; }
|
||||
.link-label { font-size: 12px; color: #8b949e; }
|
||||
.link-url {
|
||||
font-size: 11px; color: #484f58;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||
max-width: 600px;
|
||||
}
|
||||
.link-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
||||
.link-item button { padding: 4px 12px; font-size: 12px; }
|
||||
.link-item.inactive { opacity: 0.5; }
|
||||
|
||||
/* Toggle switch */
|
||||
.toggle { position: relative; width: 36px; height: 20px; cursor: pointer; flex-shrink: 0; }
|
||||
.toggle input { opacity: 0; width: 0; height: 0; }
|
||||
.toggle .slider {
|
||||
position: absolute; inset: 0; background: #30363d;
|
||||
border-radius: 20px; transition: background 0.2s;
|
||||
}
|
||||
.toggle .slider::before {
|
||||
content: ''; position: absolute; left: 2px; top: 2px;
|
||||
width: 16px; height: 16px; background: #8b949e;
|
||||
border-radius: 50%; transition: transform 0.2s, background 0.2s;
|
||||
}
|
||||
.toggle input:checked + .slider { background: #238636; }
|
||||
.toggle input:checked + .slider::before { transform: translateX(16px); background: #fff; }
|
||||
|
||||
.log-panel {
|
||||
background: #161b22;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 8px;
|
||||
height: 280px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Cascadia Code', 'Fira Code', monospace;
|
||||
font-size: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
.log-line { padding: 2px 0; line-height: 1.5; }
|
||||
.log-line .time { color: #484f58; }
|
||||
.log-line.info .msg { color: #58a6ff; }
|
||||
.log-line.warn .msg { color: #d29922; }
|
||||
.log-line.error .msg { color: #f85149; }
|
||||
.log-line.debug .msg { color: #8b949e; }
|
||||
|
||||
.empty-state {
|
||||
color: #484f58;
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
.settings-grid.full { grid-template-columns: 1fr; }
|
||||
.setting-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
.setting-row label {
|
||||
font-size: 11px;
|
||||
color: #8b949e;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.setting-row input {
|
||||
padding: 6px 10px;
|
||||
background: #0d1117;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
color: #e6edf3;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.setting-row input:focus { border-color: #58a6ff; }
|
||||
.settings-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.saved-badge {
|
||||
font-size: 12px;
|
||||
color: #3fb950;
|
||||
margin-right: 12px;
|
||||
line-height: 32px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
.saved-badge.show { opacity: 1; }
|
||||
|
||||
/* Modal overlay */
|
||||
.modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 100;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.modal-overlay.open { display: flex; }
|
||||
.modal {
|
||||
background: #161b22;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 10px;
|
||||
width: 480px;
|
||||
max-width: 90vw;
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
padding: 24px;
|
||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.modal-header h2 { font-size: 16px; font-weight: 600; }
|
||||
.modal-close {
|
||||
background: none; border: none; color: #8b949e;
|
||||
cursor: pointer; padding: 4px; font-size: 18px; line-height: 1;
|
||||
}
|
||||
.modal-close:hover { background: none; color: #e6edf3; }
|
||||
|
||||
.debug-panel {
|
||||
background: #161b22;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
}
|
||||
.debug-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.debug-row:last-of-type { margin-bottom: 0; }
|
||||
.debug-row input {
|
||||
padding: 6px 10px;
|
||||
background: #0d1117;
|
||||
border: 1px solid #30363d;
|
||||
border-radius: 6px;
|
||||
color: #e6edf3;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
.debug-row input:focus { border-color: #58a6ff; }
|
||||
.debug-row input[type="text"] { flex: 1; }
|
||||
.debug-result {
|
||||
margin-top: 8px;
|
||||
font-family: 'Cascadia Code', 'Fira Code', monospace;
|
||||
font-size: 12px;
|
||||
color: #8b949e;
|
||||
white-space: pre-wrap;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.debug-result:empty { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>POE2 Trade Bot</h1>
|
||||
<div class="header-right">
|
||||
<div id="statusBadge">
|
||||
<span class="status-dot idle"></span>
|
||||
<span id="statusText">Connecting...</span>
|
||||
</div>
|
||||
<button class="settings-btn" onclick="openSettings()" title="Settings">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="10" cy="10" r="3"/>
|
||||
<path d="M10 1.5v2M10 16.5v2M3.4 3.4l1.4 1.4M15.2 15.2l1.4 1.4M1.5 10h2M16.5 10h2M3.4 16.6l1.4-1.4M15.2 4.8l1.4-1.4"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-card">
|
||||
<div class="value" id="stateValue">IDLE</div>
|
||||
<div class="label">State</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="value" id="linksValue">0</div>
|
||||
<div class="label">Active Links</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="value" id="completedValue">0</div>
|
||||
<div class="label">Trades Done</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="value" id="failedValue">0</div>
|
||||
<div class="label">Failed</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls" id="controls">
|
||||
<button class="warning" id="pauseBtn" onclick="togglePause()">Pause</button>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Trade Links</div>
|
||||
<div class="add-link">
|
||||
<input type="text" id="nameInput" placeholder="Name (optional)" style="max-width:180px" />
|
||||
<input type="text" id="urlInput" placeholder="Paste trade URL..." />
|
||||
<button class="primary" onclick="addLink()">Add</button>
|
||||
</div>
|
||||
<div class="links-list" id="linksList">
|
||||
<div class="empty-state">No trade links added yet</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Debug Tools</div>
|
||||
<div class="debug-panel">
|
||||
<div class="debug-row">
|
||||
<button onclick="debugScreenshot()">Screenshot</button>
|
||||
<button onclick="debugOcr()">OCR Screen</button>
|
||||
</div>
|
||||
<div class="debug-row">
|
||||
<input type="text" id="debugTextInput" placeholder="Text to find (e.g. Stash, Ange)" />
|
||||
<button onclick="debugFindText()">Find</button>
|
||||
<button class="primary" onclick="debugFindAndClick()">Find & Click</button>
|
||||
</div>
|
||||
<div class="debug-row">
|
||||
<input type="number" id="debugClickX" placeholder="X" style="width:80px" />
|
||||
<input type="number" id="debugClickY" placeholder="Y" style="width:80px" />
|
||||
<button onclick="debugClick()">Click At</button>
|
||||
</div>
|
||||
<div class="debug-result" id="debugResult"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Activity Log</div>
|
||||
<div class="log-panel" id="logPanel"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-overlay" id="settingsModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h2>Settings</h2>
|
||||
<button class="modal-close" onclick="closeSettings()">×</button>
|
||||
</div>
|
||||
<div class="settings-grid full">
|
||||
<div class="setting-row">
|
||||
<label>POE2 Client.txt Path</label>
|
||||
<input type="text" id="settLogPath" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-grid" style="margin-top:10px">
|
||||
<div class="setting-row">
|
||||
<label>Window Title</label>
|
||||
<input type="text" id="settWindowTitle" />
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<label>Travel Timeout (ms)</label>
|
||||
<input type="number" id="settTravelTimeout" />
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<label>Wait for More Items (ms)</label>
|
||||
<input type="number" id="settWaitMore" />
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<label>Delay Between Trades (ms)</label>
|
||||
<input type="number" id="settTradeDelay" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-actions">
|
||||
<span class="saved-badge" id="savedBadge">Saved</span>
|
||||
<button class="primary" onclick="saveSettings()">Save Settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let ws;
|
||||
let status = { paused: false, state: 'IDLE', links: [], tradesCompleted: 0, tradesFailed: 0, uptime: 0, settings: {} };
|
||||
let settingsLoaded = false;
|
||||
|
||||
function connect() {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
ws = new WebSocket(`${proto}//${location.host}`);
|
||||
ws.onmessage = (e) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
if (msg.type === 'status') {
|
||||
status = msg.data;
|
||||
render();
|
||||
} else if (msg.type === 'log') {
|
||||
addLog(msg.data);
|
||||
}
|
||||
};
|
||||
ws.onclose = () => {
|
||||
addLog({ level: 'warn', message: 'Dashboard disconnected. Reconnecting...', time: new Date().toISOString() });
|
||||
setTimeout(connect, 2000);
|
||||
};
|
||||
ws.onerror = () => {};
|
||||
}
|
||||
|
||||
function render() {
|
||||
// Status badge
|
||||
const dot = document.querySelector('.status-dot');
|
||||
const text = document.getElementById('statusText');
|
||||
dot.className = 'status-dot ' + (status.paused ? 'paused' : status.state === 'IDLE' ? 'idle' : 'running');
|
||||
text.textContent = status.paused ? 'Paused' : status.state;
|
||||
|
||||
// Stats
|
||||
document.getElementById('stateValue').textContent = status.state;
|
||||
document.getElementById('linksValue').textContent = status.links.length;
|
||||
document.getElementById('completedValue').textContent = status.tradesCompleted;
|
||||
document.getElementById('failedValue').textContent = status.tradesFailed;
|
||||
|
||||
// Pause button
|
||||
const btn = document.getElementById('pauseBtn');
|
||||
btn.textContent = status.paused ? 'Resume' : 'Pause';
|
||||
btn.className = status.paused ? 'primary' : 'warning';
|
||||
|
||||
// Settings (populate once on first status)
|
||||
if (status.settings) populateSettings(status.settings);
|
||||
|
||||
// Active links count
|
||||
document.getElementById('linksValue').textContent = status.links.filter(l => l.active).length;
|
||||
|
||||
// Links list
|
||||
const list = document.getElementById('linksList');
|
||||
if (status.links.length === 0) {
|
||||
list.innerHTML = '<div class="empty-state">No trade links added yet</div>';
|
||||
} else {
|
||||
list.innerHTML = status.links.map(link => `
|
||||
<div class="link-item${link.active ? '' : ' inactive'}">
|
||||
<div class="link-left">
|
||||
<label class="toggle" title="${link.active ? 'Active' : 'Inactive'}">
|
||||
<input type="checkbox" ${link.active ? 'checked' : ''} onchange="toggleLink('${esc(link.id)}', this.checked)" />
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<div class="link-info">
|
||||
<div class="link-name" contenteditable="true" spellcheck="false"
|
||||
onblur="renameLink('${esc(link.id)}', this.textContent)"
|
||||
title="Click to edit name">${esc(link.name || link.label)}</div>
|
||||
<div class="link-url">${esc(link.url)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="link-actions">
|
||||
<button class="danger" onclick="removeLink('${esc(link.id)}')">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
function addLog(data) {
|
||||
const panel = document.getElementById('logPanel');
|
||||
const line = document.createElement('div');
|
||||
line.className = 'log-line ' + (data.level || 'info');
|
||||
const t = new Date(data.time).toLocaleTimeString();
|
||||
line.innerHTML = `<span class="time">${t}</span> <span class="msg">${esc(data.message)}</span>`;
|
||||
panel.appendChild(line);
|
||||
if (panel.children.length > 500) panel.removeChild(panel.firstChild);
|
||||
panel.scrollTop = panel.scrollHeight;
|
||||
}
|
||||
|
||||
async function togglePause() {
|
||||
const endpoint = status.paused ? '/api/resume' : '/api/pause';
|
||||
await fetch(endpoint, { method: 'POST' });
|
||||
}
|
||||
|
||||
async function addLink() {
|
||||
const urlEl = document.getElementById('urlInput');
|
||||
const nameEl = document.getElementById('nameInput');
|
||||
const url = urlEl.value.trim();
|
||||
if (!url) return;
|
||||
await fetch('/api/links', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url, name: nameEl.value.trim() }),
|
||||
});
|
||||
urlEl.value = '';
|
||||
nameEl.value = '';
|
||||
}
|
||||
|
||||
async function removeLink(id) {
|
||||
await fetch('/api/links/' + encodeURIComponent(id), { method: 'DELETE' });
|
||||
}
|
||||
|
||||
async function toggleLink(id, active) {
|
||||
await fetch('/api/links/' + encodeURIComponent(id) + '/toggle', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ active }),
|
||||
});
|
||||
}
|
||||
|
||||
let renameTimer = null;
|
||||
async function renameLink(id, name) {
|
||||
clearTimeout(renameTimer);
|
||||
renameTimer = setTimeout(async () => {
|
||||
await fetch('/api/links/' + encodeURIComponent(id) + '/name', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: name.trim() }),
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function populateSettings(s) {
|
||||
if (!s || settingsLoaded) return;
|
||||
settingsLoaded = true;
|
||||
document.getElementById('settLogPath').value = s.poe2LogPath || '';
|
||||
document.getElementById('settWindowTitle').value = s.poe2WindowTitle || '';
|
||||
document.getElementById('settTravelTimeout').value = s.travelTimeoutMs || 15000;
|
||||
document.getElementById('settWaitMore').value = s.waitForMoreItemsMs || 20000;
|
||||
document.getElementById('settTradeDelay').value = s.betweenTradesDelayMs || 5000;
|
||||
}
|
||||
|
||||
function openSettings() {
|
||||
// Re-populate from latest status in case it changed
|
||||
if (status.settings) {
|
||||
const s = status.settings;
|
||||
document.getElementById('settLogPath').value = s.poe2LogPath || '';
|
||||
document.getElementById('settWindowTitle').value = s.poe2WindowTitle || '';
|
||||
document.getElementById('settTravelTimeout').value = s.travelTimeoutMs || 15000;
|
||||
document.getElementById('settWaitMore').value = s.waitForMoreItemsMs || 20000;
|
||||
document.getElementById('settTradeDelay').value = s.betweenTradesDelayMs || 5000;
|
||||
}
|
||||
document.getElementById('settingsModal').classList.add('open');
|
||||
}
|
||||
|
||||
function closeSettings() {
|
||||
document.getElementById('settingsModal').classList.remove('open');
|
||||
}
|
||||
|
||||
// Close modal on overlay click
|
||||
document.getElementById('settingsModal').addEventListener('click', (e) => {
|
||||
if (e.target === e.currentTarget) closeSettings();
|
||||
});
|
||||
|
||||
// Close modal on Escape
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') closeSettings();
|
||||
});
|
||||
|
||||
async function saveSettings() {
|
||||
const body = {
|
||||
poe2LogPath: document.getElementById('settLogPath').value,
|
||||
poe2WindowTitle: document.getElementById('settWindowTitle').value,
|
||||
travelTimeoutMs: parseInt(document.getElementById('settTravelTimeout').value) || 15000,
|
||||
waitForMoreItemsMs: parseInt(document.getElementById('settWaitMore').value) || 20000,
|
||||
betweenTradesDelayMs: parseInt(document.getElementById('settTradeDelay').value) || 5000,
|
||||
};
|
||||
await fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const badge = document.getElementById('savedBadge');
|
||||
badge.classList.add('show');
|
||||
setTimeout(() => badge.classList.remove('show'), 2000);
|
||||
}
|
||||
|
||||
// Debug functions
|
||||
async function debugScreenshot() {
|
||||
const res = await fetch('/api/debug/screenshot', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
showDebugResult(data.ok ? `Screenshot saved: ${data.filename}` : `Error: ${data.error}`);
|
||||
}
|
||||
|
||||
async function debugOcr() {
|
||||
showDebugResult('Running OCR...');
|
||||
const res = await fetch('/api/debug/ocr', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
showDebugResult(data.ok ? data.text : `Error: ${data.error}`);
|
||||
}
|
||||
|
||||
async function debugFindText() {
|
||||
const text = document.getElementById('debugTextInput').value.trim();
|
||||
if (!text) return;
|
||||
showDebugResult(`Searching for "${text}"...`);
|
||||
const res = await fetch('/api/debug/find-text', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.found) {
|
||||
showDebugResult(`Found "${text}" at (${data.position.x}, ${data.position.y})`);
|
||||
document.getElementById('debugClickX').value = data.position.x;
|
||||
document.getElementById('debugClickY').value = data.position.y;
|
||||
} else {
|
||||
showDebugResult(`"${text}" not found on screen`);
|
||||
}
|
||||
}
|
||||
|
||||
async function debugFindAndClick() {
|
||||
const text = document.getElementById('debugTextInput').value.trim();
|
||||
if (!text) return;
|
||||
showDebugResult(`Finding and clicking "${text}"...`);
|
||||
const res = await fetch('/api/debug/find-and-click', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text }),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.found) {
|
||||
showDebugResult(`Clicked "${text}" at (${data.position.x}, ${data.position.y})`);
|
||||
} else {
|
||||
showDebugResult(`"${text}" not found on screen`);
|
||||
}
|
||||
}
|
||||
|
||||
async function debugClick() {
|
||||
const x = parseInt(document.getElementById('debugClickX').value);
|
||||
const y = parseInt(document.getElementById('debugClickY').value);
|
||||
if (isNaN(x) || isNaN(y)) return;
|
||||
const res = await fetch('/api/debug/click', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ x, y }),
|
||||
});
|
||||
const data = await res.json();
|
||||
showDebugResult(data.ok ? `Clicked at (${x}, ${y})` : `Error: ${data.error}`);
|
||||
}
|
||||
|
||||
function showDebugResult(text) {
|
||||
document.getElementById('debugResult').textContent = text;
|
||||
}
|
||||
|
||||
// Enter key in debug text input
|
||||
document.getElementById('debugTextInput').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') debugFindText();
|
||||
});
|
||||
|
||||
// Enter key in URL input
|
||||
document.getElementById('urlInput').addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') addLink();
|
||||
});
|
||||
|
||||
connect();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue