agoara update
This commit is contained in:
@@ -120,11 +120,38 @@ def receive_state():
|
||||
except Exception as e:
|
||||
print("Error evaluating blueprints:", e)
|
||||
|
||||
# Upsert active celebrations (party / triumph cooldowns per town)
|
||||
celebrations = data.get('celebrations', [])
|
||||
if celebrations and player_id and world_id:
|
||||
now_iso = datetime.utcnow().isoformat()
|
||||
for cel in celebrations:
|
||||
town_id_cel = str(cel.get('town_id', ''))
|
||||
cel_type = cel.get('celebration_type', '')
|
||||
finished_at = int(cel.get('finished_at', 0))
|
||||
if not town_id_cel or not cel_type:
|
||||
continue
|
||||
# Resolve town_name from what we just upserted
|
||||
t_name_row = c.execute(
|
||||
'SELECT town_name FROM town_state WHERE town_id = ?', (town_id_cel,)
|
||||
).fetchone()
|
||||
t_name = t_name_row['town_name'] if t_name_row else ''
|
||||
c.execute('''\
|
||||
INSERT INTO celebrations
|
||||
(player_id, world_id, town_id, town_name, celebration_type, finished_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(player_id, world_id, town_id, celebration_type) DO UPDATE SET
|
||||
town_name = excluded.town_name,
|
||||
finished_at = excluded.finished_at,
|
||||
updated_at = excluded.updated_at
|
||||
''', (str(player_id), world_id, town_id_cel, t_name, cel_type, finished_at, now_iso))
|
||||
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.
|
||||
@@ -266,11 +293,12 @@ def get_pending_command():
|
||||
''', (two_minutes_ago, player_id))
|
||||
|
||||
build_cmds = _fetch_pending_builds_all_towns(c, player_id, world_id) # one per town
|
||||
recruit_cmd = _fetch_pending_of_type(c, 'recruit', player_id, world_id)
|
||||
recruit_cmd = _fetch_pending_of_type(c, 'recruit', player_id, world_id)
|
||||
market_cmd = _fetch_pending_of_type(c, 'market_offer', player_id, world_id)
|
||||
farm_cmd = _fetch_pending_of_type(c, 'farm_loot', player_id, world_id)
|
||||
farm_cmd = _fetch_pending_of_type(c, 'farm_loot', player_id, world_id)
|
||||
farm_upgrade_cmd = _fetch_pending_of_type(c, 'farm_upgrade', player_id, world_id)
|
||||
research_cmd = _fetch_pending_of_type(c, 'research', player_id, world_id)
|
||||
research_cmd = _fetch_pending_of_type(c, 'research', player_id, world_id)
|
||||
culture_cmd = _fetch_pending_of_type(c, 'culture', player_id, world_id)
|
||||
sync_req = _check_and_reset_sync(c, player_id)
|
||||
|
||||
# Determine player_key for world-specific settings if world_id is provided
|
||||
@@ -324,6 +352,7 @@ def get_pending_command():
|
||||
'research': research_cmd,
|
||||
'farm': farm_cmd,
|
||||
'farm_upgrade': farm_upgrade_cmd,
|
||||
'culture': culture_cmd,
|
||||
'farm_settings': farm_settings,
|
||||
'bot_settings': bot_settings,
|
||||
'enabled_features': enabled_features,
|
||||
@@ -393,11 +422,26 @@ def command_result(cmd_id):
|
||||
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at
|
||||
''', (lf_key, now, now))
|
||||
|
||||
# When a culture command finishes, update the matching culture_log row
|
||||
if cmd and cmd['type'] == 'culture' and cmd['player_id']:
|
||||
log_status = 'success' if status == 'done' else ('failed' if status == 'failed' else 'pending')
|
||||
conn.execute('''\
|
||||
UPDATE culture_log
|
||||
SET status = ?, result_msg = ?, confirmed_at = ?
|
||||
WHERE player_id = ? AND status = 'pending'
|
||||
AND id = (
|
||||
SELECT id FROM culture_log
|
||||
WHERE player_id = ? AND status = 'pending'
|
||||
ORDER BY id DESC LIMIT 1
|
||||
)
|
||||
''', (log_status, msg, now, str(cmd['player_id']), str(cmd['player_id'])))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'ok': True})
|
||||
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /api/captcha/alert
|
||||
# Tampermonkey reports when #hcaptcha_window appears/disappears.
|
||||
|
||||
@@ -97,6 +97,11 @@ def player_farm(player_id, world_id):
|
||||
def player_tracker(player_id, world_id):
|
||||
return render_template('tracker.html', player_id=player_id, world_id=world_id)
|
||||
|
||||
@dashboard.route('/player/<player_id>/<world_id>/agora')
|
||||
@login_required
|
||||
def player_agora(player_id, world_id):
|
||||
return render_template('agora.html', player_id=player_id, world_id=world_id)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /dashboard/farm-settings — returns current farm config
|
||||
# POST /dashboard/farm-settings — updates farm config
|
||||
@@ -464,8 +469,9 @@ def create_command():
|
||||
return jsonify({'error': f'missing field: {field}'}), 400
|
||||
|
||||
cmd_type = data['type']
|
||||
if cmd_type not in ('build', 'recruit', 'market_offer', 'farm_loot', 'farm_upgrade', 'research'):
|
||||
return jsonify({'error': 'type must be build, recruit, market_offer, farm_loot, farm_upgrade, or research'}), 400
|
||||
if cmd_type not in ('build', 'recruit', 'market_offer', 'farm_loot', 'farm_upgrade', 'research', 'culture'):
|
||||
return jsonify({'error': 'type must be build, recruit, market_offer, farm_loot, farm_upgrade, research, or culture'}), 400
|
||||
|
||||
|
||||
# Reject if the Tampermonkey client is offline (no state push in last 150 s)
|
||||
conn = get_db()
|
||||
@@ -682,3 +688,269 @@ def bootcamp_attack_now():
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'ok': True})
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /dashboard/agora
|
||||
# Returns per-town celebration eligibility for the Αγορά tab.
|
||||
# For each town owned by player_id + world_id we calculate:
|
||||
# • party — has 15k wood / 18k stone / 15k iron? on cooldown?
|
||||
# • triumph — has 300+ battle points? on cooldown?
|
||||
# ------------------------------------------------------------------
|
||||
PARTY_COST = {'wood': 15000, 'stone': 18000, 'iron': 15000}
|
||||
TRIUMPH_COST = 300 # battle points
|
||||
|
||||
@dashboard.route('/dashboard/agora', methods=['GET'])
|
||||
@login_required
|
||||
def get_agora():
|
||||
player_id = request.args.get('player_id')
|
||||
world_id = request.args.get('world_id', '')
|
||||
if not player_id:
|
||||
return jsonify({'error': 'missing player_id'}), 400
|
||||
|
||||
conn = get_db()
|
||||
now_ts = int(datetime.utcnow().timestamp())
|
||||
|
||||
# ── Town snapshots ───────────────────────────────────────────────
|
||||
town_rows = conn.execute('''
|
||||
SELECT town_id, town_name, data
|
||||
FROM town_state
|
||||
WHERE player_id = ? AND world_id = ?
|
||||
ORDER BY town_name ASC
|
||||
''', (player_id, world_id)).fetchall()
|
||||
|
||||
# ── Active celebrations (cooldowns) ──────────────────────────────
|
||||
cel_rows = conn.execute('''
|
||||
SELECT town_id, celebration_type, finished_at
|
||||
FROM celebrations
|
||||
WHERE player_id = ? AND world_id = ? AND finished_at > ?
|
||||
''', (player_id, world_id, now_ts)).fetchall()
|
||||
|
||||
# Build a lookup: (town_id, cel_type) → finished_at
|
||||
cooldowns = {}
|
||||
for r in cel_rows:
|
||||
cooldowns[(r['town_id'], r['celebration_type'])] = r['finished_at']
|
||||
|
||||
conn.close()
|
||||
|
||||
# ── Per-town eligibility ─────────────────────────────────────────
|
||||
towns_out = []
|
||||
for row in town_rows:
|
||||
d = json.loads(row['data'])
|
||||
wood = d.get('wood', 0)
|
||||
stone = d.get('stone', 0)
|
||||
iron = d.get('iron', 0)
|
||||
bp = d.get('battle_points', {}).get('available', 0)
|
||||
academy = d.get('buildings', {}).get('academy', 0)
|
||||
|
||||
tid = row['town_id']
|
||||
|
||||
# ── Party (Γιορτή πόλης) ─────────────────────────────────────
|
||||
party_cd = cooldowns.get((tid, 'party'), 0)
|
||||
if party_cd:
|
||||
party_status = 'cooldown'
|
||||
party_reason = f'Ενεργή — λήγει σε {_fmt_seconds(party_cd - now_ts)}'
|
||||
party_ok = False
|
||||
elif academy < 30:
|
||||
party_status = 'unavailable'
|
||||
party_reason = f'Ακαδημία {academy}/30'
|
||||
party_ok = False
|
||||
elif wood < PARTY_COST['wood'] or stone < PARTY_COST['stone'] or iron < PARTY_COST['iron']:
|
||||
missing = []
|
||||
if wood < PARTY_COST['wood']: missing.append(f'ξύλο {wood:,}/{PARTY_COST["wood"]:,}')
|
||||
if stone < PARTY_COST['stone']: missing.append(f'πέτρα {stone:,}/{PARTY_COST["stone"]:,}')
|
||||
if iron < PARTY_COST['iron']: missing.append(f'σίδερο {iron:,}/{PARTY_COST["iron"]:,}')
|
||||
party_status = 'unavailable'
|
||||
party_reason = 'Ανεπαρκείς πόροι: ' + ', '.join(missing)
|
||||
party_ok = False
|
||||
else:
|
||||
party_status = 'available'
|
||||
party_reason = ''
|
||||
party_ok = True
|
||||
|
||||
# ── Triumph (Παρέλαση θριάμβου) ──────────────────────────────
|
||||
triumph_cd = cooldowns.get((tid, 'triumph'), 0)
|
||||
if triumph_cd:
|
||||
triumph_status = 'cooldown'
|
||||
triumph_reason = f'Ενεργή — λήγει σε {_fmt_seconds(triumph_cd - now_ts)}'
|
||||
triumph_ok = False
|
||||
elif bp < TRIUMPH_COST:
|
||||
triumph_status = 'unavailable'
|
||||
triumph_reason = f'Ανεπαρκείς πόντοι μάχης: {bp}/{TRIUMPH_COST}'
|
||||
triumph_ok = False
|
||||
else:
|
||||
triumph_status = 'available'
|
||||
triumph_reason = ''
|
||||
triumph_ok = True
|
||||
|
||||
towns_out.append({
|
||||
'town_id': tid,
|
||||
'town_name': row['town_name'],
|
||||
'resources': {'wood': wood, 'stone': stone, 'iron': iron},
|
||||
'battle_points': bp,
|
||||
'academy': academy,
|
||||
'party': {
|
||||
'status': party_status,
|
||||
'reason': party_reason,
|
||||
'available': party_ok,
|
||||
'cooldown_until': party_cd or None,
|
||||
},
|
||||
'triumph': {
|
||||
'status': triumph_status,
|
||||
'reason': triumph_reason,
|
||||
'available': triumph_ok,
|
||||
'cooldown_until': triumph_cd or None,
|
||||
},
|
||||
})
|
||||
|
||||
return jsonify({'towns': towns_out, 'costs': {'party': PARTY_COST, 'triumph': TRIUMPH_COST}})
|
||||
|
||||
|
||||
def _fmt_seconds(secs):
|
||||
"""Format a duration in seconds to a human-readable Greek string."""
|
||||
secs = max(0, int(secs))
|
||||
h, rem = divmod(secs, 3600)
|
||||
m, s = divmod(rem, 60)
|
||||
if h:
|
||||
return f'{h}ω {m}λ'
|
||||
if m:
|
||||
return f'{m}λ {s}δ'
|
||||
return f'{s}δ'
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# POST /dashboard/culture-command
|
||||
# Dashboard fires when the user confirms a celebration.
|
||||
# Body: { player_id, world_id, town_id, town_name, celebration_type }
|
||||
# • Validates client is online.
|
||||
# • Validates eligibility (resources / battle_points / cooldown).
|
||||
# • Inserts a 'culture' command into the commands queue.
|
||||
# • Inserts a 'pending' row into culture_log.
|
||||
# ------------------------------------------------------------------
|
||||
@dashboard.route('/dashboard/culture-command', methods=['POST'])
|
||||
@login_required
|
||||
def culture_command():
|
||||
data = request.get_json(silent=True) or {}
|
||||
player_id = data.get('player_id')
|
||||
world_id = data.get('world_id', '')
|
||||
town_id = str(data.get('town_id', ''))
|
||||
town_name = data.get('town_name', '')
|
||||
cel_type = data.get('celebration_type', '') # 'party' | 'triumph'
|
||||
|
||||
if not all([player_id, town_id, cel_type]):
|
||||
return jsonify({'error': 'missing player_id, town_id or celebration_type'}), 400
|
||||
if cel_type not in ('party', 'triumph'):
|
||||
return jsonify({'error': 'celebration_type must be party or triumph'}), 400
|
||||
|
||||
conn = get_db()
|
||||
|
||||
# ── Client online check ──────────────────────────────────────────
|
||||
row = conn.execute(
|
||||
'SELECT MAX(updated_at) AS last_seen FROM town_state WHERE player_id = ?', (player_id,)
|
||||
).fetchone()
|
||||
last_seen = row['last_seen'] if row else None
|
||||
client_online = False
|
||||
if last_seen:
|
||||
try:
|
||||
dt = datetime.fromisoformat(last_seen)
|
||||
client_online = (datetime.utcnow() - dt).total_seconds() <= 150
|
||||
except Exception:
|
||||
pass
|
||||
if not client_online:
|
||||
conn.close()
|
||||
return jsonify({'error': 'client_offline',
|
||||
'message': 'Το script είναι offline — δεν μπορείτε να στείλετε εντολές.'}), 503
|
||||
|
||||
# ── Re-validate eligibility server-side ─────────────────────────
|
||||
town_row = conn.execute(
|
||||
'SELECT data FROM town_state WHERE town_id = ? AND player_id = ?', (town_id, player_id)
|
||||
).fetchone()
|
||||
if not town_row:
|
||||
conn.close()
|
||||
return jsonify({'error': 'town not found'}), 404
|
||||
|
||||
d = json.loads(town_row['data'])
|
||||
now_t = int(datetime.utcnow().timestamp())
|
||||
|
||||
# Cooldown check
|
||||
cel_row = conn.execute('''
|
||||
SELECT finished_at FROM celebrations
|
||||
WHERE player_id = ? AND world_id = ? AND town_id = ? AND celebration_type = ? AND finished_at > ?
|
||||
''', (player_id, world_id, town_id, cel_type, now_t)).fetchone()
|
||||
if cel_row:
|
||||
conn.close()
|
||||
return jsonify({'error': 'on_cooldown',
|
||||
'message': f'Η εορτή τρέχει ήδη — λήγει σε {_fmt_seconds(cel_row["finished_at"] - now_t)}'}), 409
|
||||
|
||||
# Cost determination & resource check
|
||||
if cel_type == 'party':
|
||||
cost_wood = PARTY_COST['wood']
|
||||
cost_stone = PARTY_COST['stone']
|
||||
cost_iron = PARTY_COST['iron']
|
||||
cost_bp = 0
|
||||
if (d.get('wood', 0) < cost_wood or d.get('stone', 0) < cost_stone
|
||||
or d.get('iron', 0) < cost_iron):
|
||||
conn.close()
|
||||
return jsonify({'error': 'insufficient_resources',
|
||||
'message': 'Ανεπαρκείς πόροι για τη Γιορτή πόλης.'}), 409
|
||||
else: # triumph
|
||||
cost_wood = cost_stone = cost_iron = 0
|
||||
cost_bp = TRIUMPH_COST
|
||||
available_bp = d.get('battle_points', {}).get('available', 0)
|
||||
if available_bp < TRIUMPH_COST:
|
||||
conn.close()
|
||||
return jsonify({'error': 'insufficient_battle_points',
|
||||
'message': f'Ανεπαρκείς πόντοι μάχης ({available_bp}/{TRIUMPH_COST}).'}), 409
|
||||
|
||||
# ── Insert command into bot queue ────────────────────────────────
|
||||
now_iso = datetime.utcnow().isoformat()
|
||||
c = conn.cursor()
|
||||
c.execute('''
|
||||
INSERT INTO commands (town_id, town_name, type, payload, status, created_at, updated_at, player_id)
|
||||
VALUES (?, ?, 'culture', ?, 'pending', ?, ?, ?)
|
||||
''', (
|
||||
town_id, town_name,
|
||||
json.dumps({'celebration_type': cel_type, 'town_id': int(town_id)}),
|
||||
now_iso, now_iso, str(player_id)
|
||||
))
|
||||
cmd_id = c.lastrowid
|
||||
|
||||
# ── Append culture_log row (status=pending until bot confirms) ───
|
||||
c.execute('''
|
||||
INSERT INTO culture_log
|
||||
(player_id, world_id, town_id, town_name, celebration_type,
|
||||
cost_wood, cost_stone, cost_iron, cost_battle_pts, status, fired_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)
|
||||
''', (str(player_id), world_id, town_id, town_name, cel_type,
|
||||
cost_wood, cost_stone, cost_iron, cost_bp, now_iso))
|
||||
log_id = c.lastrowid
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'ok': True, 'cmd_id': cmd_id, 'log_id': log_id})
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# GET /dashboard/culture-log
|
||||
# Returns the last 50 Αγορά log entries for a player + world.
|
||||
# ------------------------------------------------------------------
|
||||
@dashboard.route('/dashboard/culture-log', methods=['GET'])
|
||||
@login_required
|
||||
def get_culture_log():
|
||||
player_id = request.args.get('player_id')
|
||||
world_id = request.args.get('world_id', '')
|
||||
if not player_id:
|
||||
return jsonify({'error': 'missing player_id'}), 400
|
||||
|
||||
conn = get_db()
|
||||
rows = conn.execute('''
|
||||
SELECT id, town_id, town_name, celebration_type,
|
||||
cost_wood, cost_stone, cost_iron, cost_battle_pts,
|
||||
status, result_msg, fired_at, confirmed_at
|
||||
FROM culture_log
|
||||
WHERE player_id = ? AND world_id = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT 50
|
||||
''', (player_id, world_id)).fetchall()
|
||||
conn.close()
|
||||
return jsonify([dict(r) for r in rows])
|
||||
|
||||
Reference in New Issue
Block a user