captsa add alert
This commit is contained in:
@@ -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 45–90 seconds (randomized)
|
// Push state once after load, then every 45–90 seconds (randomized)
|
||||||
setTimeout(pushState, 5000);
|
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.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
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
|
# 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',
|
||||||
|
|||||||
@@ -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})
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
window.fetchTowns();
|
window.fetchTowns();
|
||||||
window.fetchLog();
|
window.fetchLog();
|
||||||
window.fetchClientStatus();
|
window.fetchClientStatus();
|
||||||
setInterval(window.fetchTowns, window.POLL_INTERVAL);
|
window.fetchCaptchaStatus();
|
||||||
setInterval(window.fetchLog, window.POLL_INTERVAL);
|
setInterval(window.fetchTowns, window.POLL_INTERVAL);
|
||||||
setInterval(window.fetchClientStatus, 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>
|
</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 -->
|
||||||
|
|||||||
Reference in New Issue
Block a user