From b0d933c1a0c9f468d4e587b6b8d928b5e6baf9c5 Mon Sep 17 00:00:00 2001 From: haunter Date: Tue, 21 Apr 2026 19:17:28 +0300 Subject: [PATCH] captsa add alert --- GrepolisRemoteControl.user.js | 44 +++++++++++++++++++++++++++++++++-- app.py | 4 +++- db.py | 9 +++++++ routes/api.py | 22 ++++++++++++++++++ routes/dashboard.py | 15 ++++++++++++ static/css/styles.css | 31 ++++++++++++++++++++++++ static/js/api.js | 28 ++++++++++++++++++++++ static/js/app.js | 8 ++++--- templates/dashboard.html | 7 ++++++ 9 files changed, 162 insertions(+), 6 deletions(-) diff --git a/GrepolisRemoteControl.user.js b/GrepolisRemoteControl.user.js index 4a8de0b..41cb0d4 100644 --- a/GrepolisRemoteControl.user.js +++ b/GrepolisRemoteControl.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Grepolis Remote Control // @namespace http://tampermonkey.net/ -// @version 2.4 +// @version 2.5 // @description Polls grepo.haunter-pets.top for remote commands and executes them in-game // @author Dimitrios // @match https://*.grepolis.com/game/* @@ -279,6 +279,43 @@ }).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 // ---------------------------------------------------------------- @@ -410,7 +447,10 @@ // Boot // ---------------------------------------------------------------- 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 45–90 seconds (randomized) setTimeout(pushState, 5000); diff --git a/app.py b/app.py index a2846a0..b0c4adb 100644 --- a/app.py +++ b/app.py @@ -4,6 +4,9 @@ from db import init_db from routes.api import api from routes.dashboard import dashboard +# Initialise DB schema (e.g. creating newly added tables) when the app starts +init_db() + app = Flask(__name__) CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=False) @@ -27,6 +30,5 @@ app.register_blueprint(api) app.register_blueprint(dashboard) if __name__ == '__main__': - init_db() print("✅ Grepolis Remote — DB initialised") app.run(host='0.0.0.0', port=5050, debug=True) \ No newline at end of file diff --git a/db.py b/db.py index d037bc8..295d543 100644 --- a/db.py +++ b/db.py @@ -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 for _col in [ 'ALTER TABLE town_state ADD COLUMN player_id TEXT', diff --git a/routes/api.py b/routes/api.py index 8bda1b0..9f01da8 100644 --- a/routes/api.py +++ b/routes/api.py @@ -122,3 +122,25 @@ def command_result(cmd_id): conn.commit() conn.close() 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}) diff --git a/routes/dashboard.py b/routes/dashboard.py index 8f2d433..f87fe9d 100644 --- a/routes/dashboard.py +++ b/routes/dashboard.py @@ -94,6 +94,21 @@ def client_status(): 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 # Returns command history (last 50) for the log panel. diff --git a/static/css/styles.css b/static/css/styles.css index 100305b..6d3d2c1 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -35,6 +35,37 @@ header h1 { .conn-badge.online { background: #1a4a1a; color: #6fcf6f; } .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 { display: grid; grid-template-columns: 320px 1fr; diff --git a/static/js/api.js b/static/js/api.js index 462c666..db294b9 100644 --- a/static/js/api.js +++ b/static/js/api.js @@ -172,3 +172,31 @@ window.cancelCommand = async function(id) { await fetch(`/dashboard/commands/${id}`, { method: 'DELETE' }); 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'; + } +}; diff --git a/static/js/app.js b/static/js/app.js index 51f58a7..c75e31d 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -6,7 +6,9 @@ window.addEventListener('DOMContentLoaded', () => { window.fetchTowns(); window.fetchLog(); window.fetchClientStatus(); - setInterval(window.fetchTowns, window.POLL_INTERVAL); - setInterval(window.fetchLog, window.POLL_INTERVAL); - setInterval(window.fetchClientStatus, window.POLL_INTERVAL); + window.fetchCaptchaStatus(); + setInterval(window.fetchTowns, window.POLL_INTERVAL); + setInterval(window.fetchLog, window.POLL_INTERVAL); + setInterval(window.fetchClientStatus, window.POLL_INTERVAL); + setInterval(window.fetchCaptchaStatus, 5000); // check every 5s }); diff --git a/templates/dashboard.html b/templates/dashboard.html index 2ef7d74..08e3798 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -16,6 +16,13 @@ + + +