Files
grepo-remote/routes/api.py
2026-04-26 02:02:50 +03:00

258 lines
9.4 KiB
Python

from flask import Blueprint, request, jsonify
from db import get_db
import json
from datetime import datetime
api = Blueprint('api', __name__)
# ------------------------------------------------------------------
# POST /api/state
# Tampermonkey pushes a full town snapshot every poll cycle.
# ------------------------------------------------------------------
@api.route('/api/state', methods=['POST'])
def receive_state():
data = request.get_json(silent=True)
if not data:
return jsonify({'error': 'no data'}), 400
towns = data.get('towns', [])
player = data.get('player', '')
player_id = data.get('player_id', '')
alliance_id = str(data.get('alliance_id', '') or '')
world_id = data.get('world_id', '')
conn = get_db()
c = conn.cursor()
for town in towns:
x = town.get('x')
y = town.get('y')
sea = town.get('sea')
c.execute('''
INSERT INTO town_state
(town_id, town_name, player, player_id, alliance_id, world_id, x, y, sea, data, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(town_id) DO UPDATE SET
town_name = excluded.town_name,
player = excluded.player,
player_id = excluded.player_id,
alliance_id = excluded.alliance_id,
world_id = excluded.world_id,
x = excluded.x,
y = excluded.y,
sea = excluded.sea,
data = excluded.data,
updated_at = excluded.updated_at
''', (
str(town['town_id']),
town.get('town_name', ''),
player,
player_id,
alliance_id,
world_id,
x, y, sea,
json.dumps(town),
datetime.utcnow().isoformat()
))
conn.commit()
conn.close()
return jsonify({'ok': True, 'towns_updated': len(towns)})
# ------------------------------------------------------------------
# GET /api/commands/pending
# Tampermonkey polls this to get the next command to execute.
# Returns one 'build' AND one 'recruit' command independently,
# so both queues are served in parallel without blocking each other.
# ------------------------------------------------------------------
def _fetch_pending_of_type(c, cmd_type, player_id):
row = c.execute('''
SELECT * FROM commands
WHERE status = 'pending' AND type = ? AND player_id = ?
ORDER BY id ASC
LIMIT 1
''', (cmd_type, player_id)).fetchone()
if not row:
return None
c.execute('''
UPDATE commands
SET status = 'executing', updated_at = ?
WHERE id = ?
''', (datetime.utcnow().isoformat(), row['id']))
return {
'id': row['id'],
'town_id': row['town_id'],
'type': row['type'],
'payload': json.loads(row['payload'])
}
@api.route('/api/commands/pending', methods=['GET'])
def get_pending_command():
player_id = request.args.get('player_id')
if not player_id:
return jsonify({'error': 'no player_id provided'}), 400
conn = get_db()
c = conn.cursor()
build_cmd = _fetch_pending_of_type(c, 'build', player_id)
recruit_cmd = _fetch_pending_of_type(c, 'recruit', player_id)
market_cmd = _fetch_pending_of_type(c, 'market_offer', player_id)
farm_cmd = _fetch_pending_of_type(c, 'farm_loot', player_id)
farm_upgrade_cmd = _fetch_pending_of_type(c, 'farm_upgrade', player_id)
research_cmd = _fetch_pending_of_type(c, 'research', player_id)
scan_market_cmd = _fetch_pending_of_type(c, 'scan_market', player_id)
accept_market_offer_cmd = _fetch_pending_of_type(c, 'accept_market_offer', player_id)
sync_req = _check_and_reset_sync(c, player_id)
# Also return current farm settings so TM knows loot_option
farm_row = c.execute(
'SELECT enabled, loot_option FROM farm_settings WHERE player_id = ?', (player_id,)
).fetchone()
farm_settings = {
'enabled': bool(farm_row['enabled']) if farm_row else False,
'loot_option': farm_row['loot_option'] if farm_row else 1
}
conn.commit()
conn.close()
return jsonify({
'build': build_cmd,
'recruit': recruit_cmd,
'market': market_cmd,
'scan_market': scan_market_cmd,
'accept_market_offer': accept_market_offer_cmd,
'research': research_cmd,
'farm': farm_cmd,
'farm_upgrade': farm_upgrade_cmd,
'farm_settings': farm_settings,
'sync_requested': sync_req
})
def _check_and_reset_sync(c, player_id):
key = f'sync_request_{player_id}'
row = c.execute("SELECT value FROM kv_store WHERE key = ?", (key,)).fetchone()
if row and row['value'] == '1':
c.execute("UPDATE kv_store SET value = '0', updated_at = ? WHERE key = ?", (datetime.utcnow().isoformat(), key))
return True
return False
# ------------------------------------------------------------------
# POST /api/sync-request
# Dashboard requests an immediate state update from the client.
# ------------------------------------------------------------------
@api.route('/api/sync-request', methods=['POST'])
def sync_request():
player_id = request.args.get('player_id')
if not player_id:
return jsonify({'error': 'no player_id provided'}), 400
conn = get_db()
conn.execute('''
INSERT INTO kv_store (key, value, updated_at)
VALUES (?, '1', ?)
ON CONFLICT(key) DO UPDATE SET value = '1', updated_at = excluded.updated_at
''', (f'sync_request_{player_id}', datetime.utcnow().isoformat()))
conn.commit()
conn.close()
return jsonify({'ok': True})
# ------------------------------------------------------------------
# POST /api/commands/<id>/result
# Tampermonkey reports back whether the command succeeded or failed.
# ------------------------------------------------------------------
@api.route('/api/commands/<int:cmd_id>/result', methods=['POST'])
def command_result(cmd_id):
data = request.get_json(silent=True) or {}
status = data.get('status', 'done') # 'done' | 'failed'
msg = data.get('message', '')
conn = get_db()
conn.execute('''
UPDATE commands
SET status = ?, result_msg = ?, updated_at = ?
WHERE id = ?
''', (status, msg, datetime.utcnow().isoformat(), 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():
player_id = request.args.get('player_id')
if not player_id:
return jsonify({'error': 'no player_id provided'}), 400
data = request.get_json(silent=True) or {}
detected = bool(data.get('detected', False))
kv_key = f'captcha_active_{player_id}'
conn = get_db()
conn.execute('''
INSERT INTO kv_store (key, value, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at
''', (kv_key, '1' if detected else '0', datetime.utcnow().isoformat()))
conn.commit()
conn.close()
return jsonify({'ok': True})
# ------------------------------------------------------------------
# POST /api/market_data
# Tampermonkey uploads the market scan data.
# ------------------------------------------------------------------
@api.route('/api/market_data', methods=['POST'])
def upload_market_data():
player_id = request.args.get('player_id')
if not player_id:
return jsonify({'error': 'no player_id provided'}), 400
data = request.get_json(silent=True) or {}
kv_key = f'market_data_{player_id}'
conn = get_db()
conn.execute('''
INSERT INTO kv_store (key, value, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at
''', (kv_key, json.dumps(data), datetime.utcnow().isoformat()))
conn.commit()
conn.close()
return jsonify({'ok': True})
# ------------------------------------------------------------------
# POST/GET /api/farm_status — TM reports warehouse_full; dashboard reads it
# ------------------------------------------------------------------
@api.route('/api/farm_status', methods=['POST', 'GET'])
def farm_status():
player_id = request.args.get('player_id')
if not player_id:
return jsonify({'error': 'no player_id'}), 400
kv_key = f'farm_status_{player_id}'
conn = get_db()
if request.method == 'POST':
data = request.get_json(silent=True) or {}
conn.execute('''
INSERT INTO kv_store (key, value, updated_at) VALUES (?, ?, ?)
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at
''', (kv_key, json.dumps(data), datetime.utcnow().isoformat()))
conn.commit()
conn.close()
return jsonify({'ok': True})
else:
row = conn.execute('SELECT value FROM kv_store WHERE key=?', (kv_key,)).fetchone()
conn.close()
return jsonify(json.loads(row['value']) if row else {'warehouse_full': False})