captsa add alert

This commit is contained in:
2026-04-21 19:17:28 +03:00
parent 7e5bfcefa4
commit b0d933c1a0
9 changed files with 162 additions and 6 deletions

View File

@@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Grepolis Remote Control // @name Grepolis Remote Control
// @namespace http://tampermonkey.net/ // @namespace http://tampermonkey.net/
// @version 2.4 // @version 2.5
// @description Polls grepo.haunter-pets.top for remote commands and executes them in-game // @description Polls grepo.haunter-pets.top for remote commands and executes them in-game
// @author Dimitrios // @author Dimitrios
// @match https://*.grepolis.com/game/* // @match https://*.grepolis.com/game/*
@@ -279,6 +279,43 @@
}).catch(e => log(`reportResult failed: ${e}`)); }).catch(e => log(`reportResult failed: ${e}`));
} }
// ----------------------------------------------------------------
// Captcha detection via MutationObserver
// Watches document.body for #hcaptcha_window being added/removed.
// Confirmed selector from live DOM: DIV#hcaptcha_window > DIV.h-captcha
// ----------------------------------------------------------------
let captchaActive = false;
function reportCaptcha(detected) {
fetch(`${BASE_URL}/api/captcha/alert`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ detected })
}).catch(e => log(`captcha report failed: ${e}`));
}
function detectCaptcha() {
const observer = new MutationObserver(() => {
const hasWindow = !!document.getElementById('hcaptcha_window');
if (hasWindow && !captchaActive) {
captchaActive = true;
paused = true;
const label = document.getElementById('grc_label');
const btn = document.getElementById('grc_btn');
if (label) label.textContent = '⚠ CAPTCHA';
if (btn) btn.style.filter = 'brightness(70%) sepia(100%) hue-rotate(300deg) saturate(1000%) contrast(0.8)';
log('⚠ CAPTCHA detected — bot paused, alerting server');
reportCaptcha(true);
} else if (!hasWindow && captchaActive) {
captchaActive = false;
// Don't auto-resume — let the user click the button manually
log('✅ Captcha resolved — alert cleared (bot remains paused)');
reportCaptcha(false);
}
});
observer.observe(document.body, { childList: true, subtree: false });
}
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// Execute: Build // Execute: Build
// ---------------------------------------------------------------- // ----------------------------------------------------------------
@@ -410,7 +447,10 @@
// Boot // Boot
// ---------------------------------------------------------------- // ----------------------------------------------------------------
window.addEventListener('load', () => { window.addEventListener('load', () => {
log('Grepolis Remote Control v2.4 loaded'); log('Grepolis Remote Control v2.5 loaded');
// Start captcha watcher immediately
detectCaptcha();
// Push state once after load, then every 4590 seconds (randomized) // Push state once after load, then every 4590 seconds (randomized)
setTimeout(pushState, 5000); setTimeout(pushState, 5000);

4
app.py
View File

@@ -4,6 +4,9 @@ from db import init_db
from routes.api import api from routes.api import api
from routes.dashboard import dashboard from routes.dashboard import dashboard
# Initialise DB schema (e.g. creating newly added tables) when the app starts
init_db()
app = Flask(__name__) app = Flask(__name__)
CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=False) CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=False)
@@ -27,6 +30,5 @@ app.register_blueprint(api)
app.register_blueprint(dashboard) app.register_blueprint(dashboard)
if __name__ == '__main__': if __name__ == '__main__':
init_db()
print("✅ Grepolis Remote — DB initialised") print("✅ Grepolis Remote — DB initialised")
app.run(host='0.0.0.0', port=5050, debug=True) app.run(host='0.0.0.0', port=5050, debug=True)

9
db.py
View File

@@ -46,6 +46,15 @@ def init_db():
) )
''') ''')
# Key-value store — generic flags (e.g. captcha_active)
c.execute('''
CREATE TABLE IF NOT EXISTS kv_store (
key TEXT PRIMARY KEY,
value TEXT,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
)
''')
# Migration: add new columns if upgrading an existing database # Migration: add new columns if upgrading an existing database
for _col in [ for _col in [
'ALTER TABLE town_state ADD COLUMN player_id TEXT', 'ALTER TABLE town_state ADD COLUMN player_id TEXT',

View File

@@ -122,3 +122,25 @@ def command_result(cmd_id):
conn.commit() conn.commit()
conn.close() conn.close()
return jsonify({'ok': True}) return jsonify({'ok': True})
# ------------------------------------------------------------------
# POST /api/captcha/alert
# Tampermonkey reports when #hcaptcha_window appears/disappears.
# Body: { detected: true | false }
# ------------------------------------------------------------------
@api.route('/api/captcha/alert', methods=['POST'])
def captcha_alert():
data = request.get_json(silent=True) or {}
detected = bool(data.get('detected', False))
conn = get_db()
conn.execute('''
INSERT INTO kv_store (key, value, updated_at)
VALUES ('captcha_active', ?, ?)
ON CONFLICT(key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at
''', ('1' if detected else '0', datetime.utcnow().isoformat()))
conn.commit()
conn.close()
return jsonify({'ok': True})

View File

@@ -94,6 +94,21 @@ def client_status():
return jsonify({'online': online, 'last_seen': last_seen}) return jsonify({'online': online, 'last_seen': last_seen})
# ------------------------------------------------------------------
# GET /dashboard/captcha-status
# Returns whether a captcha is currently active in the game client.
# ------------------------------------------------------------------
@dashboard.route('/dashboard/captcha-status', methods=['GET'])
def captcha_status():
conn = get_db()
row = conn.execute(
"SELECT value FROM kv_store WHERE key = 'captcha_active'"
).fetchone()
conn.close()
active = bool(row and row['value'] == '1')
return jsonify({'captcha_active': active})
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# GET /dashboard/commands # GET /dashboard/commands
# Returns command history (last 50) for the log panel. # Returns command history (last 50) for the log panel.

View File

@@ -35,6 +35,37 @@ header h1 {
.conn-badge.online { background: #1a4a1a; color: #6fcf6f; } .conn-badge.online { background: #1a4a1a; color: #6fcf6f; }
.conn-badge.offline { background: #4a1a1a; color: #cf6f6f; } .conn-badge.offline { background: #4a1a1a; color: #cf6f6f; }
/* ---- Captcha alert banner ---- */
#captcha-banner {
display: flex;
align-items: center;
gap: 12px;
background: #5a0a0a;
border-bottom: 2px solid #ff4444;
padding: 10px 24px;
font-size: 0.9rem;
color: #ffaaaa;
animation: captchaPulse 1.2s ease-in-out infinite;
}
#captcha-banner span:first-child { font-size: 1.2rem; flex-shrink: 0; }
#captcha-banner span:nth-child(2) { flex: 1; font-weight: 500; }
#captcha-dismiss {
background: #ff4444;
color: #fff;
border: none;
border-radius: 4px;
padding: 5px 12px;
cursor: pointer;
font-size: 0.8rem;
font-weight: 600;
flex-shrink: 0;
}
#captcha-dismiss:hover { background: #cc2222; }
@keyframes captchaPulse {
0%, 100% { background: #5a0a0a; }
50% { background: #7a1010; }
}
.layout { .layout {
display: grid; display: grid;
grid-template-columns: 320px 1fr; grid-template-columns: 320px 1fr;

View File

@@ -172,3 +172,31 @@ window.cancelCommand = async function(id) {
await fetch(`/dashboard/commands/${id}`, { method: 'DELETE' }); await fetch(`/dashboard/commands/${id}`, { method: 'DELETE' });
window.fetchLog(); window.fetchLog();
}; };
window.fetchCaptchaStatus = async function() {
try {
const res = await fetch('/dashboard/captcha-status');
const data = await res.json();
const banner = document.getElementById('captcha-banner');
if (!banner) return;
if (data.captcha_active) {
// Only show it if the user hasn't explicitly clicked 'close' for this specific alert
if (banner.dataset.dismissed !== '1') {
banner.style.display = 'flex';
}
} else {
// Captcha cleared from the game - hide banner and reset dismiss state for next time
banner.style.display = 'none';
banner.dataset.dismissed = '0';
}
} catch (e) {}
};
window.dismissCaptchaBanner = function() {
const banner = document.getElementById('captcha-banner');
if (banner) {
banner.style.display = 'none';
banner.dataset.dismissed = '1';
}
};

View File

@@ -6,7 +6,9 @@ window.addEventListener('DOMContentLoaded', () => {
window.fetchTowns(); window.fetchTowns();
window.fetchLog(); window.fetchLog();
window.fetchClientStatus(); window.fetchClientStatus();
window.fetchCaptchaStatus();
setInterval(window.fetchTowns, window.POLL_INTERVAL); setInterval(window.fetchTowns, window.POLL_INTERVAL);
setInterval(window.fetchLog, window.POLL_INTERVAL); setInterval(window.fetchLog, window.POLL_INTERVAL);
setInterval(window.fetchClientStatus, window.POLL_INTERVAL); setInterval(window.fetchClientStatus, window.POLL_INTERVAL);
setInterval(window.fetchCaptchaStatus, 5000); // check every 5s
}); });

View File

@@ -16,6 +16,13 @@
</div> </div>
</header> </header>
<!-- Captcha alert banner (hidden until captcha detected) -->
<div id="captcha-banner" style="display:none">
<span>⚠️</span>
<span>CAPTCHA εντοπίστηκε στο παιχνίδι — το script έχει παγώσει αυτόματα. Επίλυσε το captcha για να συνεχίσεις.</span>
<button id="captcha-dismiss" onclick="window.dismissCaptchaBanner()">Κλείσιμο</button>
</div>
<div class="layout"> <div class="layout">
<!-- Left: Town list --> <!-- Left: Town list -->