captsa add alert
This commit is contained in:
@@ -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);
|
||||
|
||||
4
app.py
4
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)
|
||||
9
db.py
9
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',
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -6,7 +6,9 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
window.fetchTowns();
|
||||
window.fetchLog();
|
||||
window.fetchClientStatus();
|
||||
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
|
||||
});
|
||||
|
||||
@@ -16,6 +16,13 @@
|
||||
</div>
|
||||
</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">
|
||||
|
||||
<!-- Left: Town list -->
|
||||
|
||||
Reference in New Issue
Block a user