# services/torn_api.py import aiohttp import asyncio from config import TORN_API_KEY from .ffscouter import fetch_batch_stats from .server_state import STATE # ----------------------------- # Tasks # ----------------------------- friendly_status_task = None enemy_status_task = None # Locks for safe async updates friendly_lock = asyncio.Lock() enemy_lock = asyncio.Lock() # ----------------------------- # Populate faction (memory only) # ----------------------------- async def populate_faction(faction_id: int, kind: str): """ Fetch members + FFScouter estimates once and store in STATE. kind: "friendly" or "enemy" """ url = f"https://api.torn.com/v2/faction/{faction_id}?selections=members&key={TORN_API_KEY}" async with aiohttp.ClientSession() as session: async with session.get(url) as resp: if resp.status != 200: print(f"Error fetching faction {faction_id}: {resp.status}") return False data = await resp.json() members_list = data.get("members", []) if not members_list: return False member_ids = [m.get("id") for m in members_list if "id" in m] if not member_ids: return False # Fetch FFScouter estimates ff_data = await fetch_batch_stats(member_ids) received_ids = [] async with (friendly_lock if kind == "friendly" else enemy_lock): for m in members_list: pid = m["id"] est = ff_data.get(str(pid), {}).get("bs_estimate_human", "?") status = m.get("status", {}).get("state", "Unknown") member_data = { "id": pid, "name": m.get("name", "Unknown"), "level": m.get("level", 0), "estimate": est, "status": status } await STATE.upsert_member(member_data, kind) received_ids.append(pid) # Remove missing members from STATE await STATE.remove_missing_members(received_ids, kind) return True # ----------------------------- # Status refresh loop # ----------------------------- async def refresh_status_loop(faction_id: int, kind: str, lock: asyncio.Lock, interval: int): """ Periodically refresh member statuses in STATE. """ while True: try: url = f"https://api.torn.com/v2/faction/{faction_id}?selections=members&key={TORN_API_KEY}" async with aiohttp.ClientSession() as session: async with session.get(url) as resp: if resp.status != 200: print(f"Status fetch error {resp.status}") await asyncio.sleep(interval) continue data = await resp.json() members_list = data.get("members", []) async with lock: coll = STATE.friendly if kind == "friendly" else STATE.enemy for m in members_list: mid = m.get("id") if mid in coll: coll[mid].status = m.get("status", {}).get("state", "Unknown") except Exception as e: print(f"Error in status loop for {kind} faction {faction_id}: {e}") await asyncio.sleep(interval) # ----------------------------- # Public API helpers # ----------------------------- async def populate_friendly(faction_id: int): return await populate_faction(faction_id, "friendly") async def populate_enemy(faction_id: int): return await populate_faction(faction_id, "enemy") async def start_friendly_status_loop(faction_id: int, interval: int): global friendly_status_task if friendly_status_task and not friendly_status_task.done(): friendly_status_task.cancel() friendly_status_task = asyncio.create_task( refresh_status_loop(faction_id, "friendly", friendly_lock, interval) ) async def start_enemy_status_loop(faction_id: int, interval: int): global enemy_status_task if enemy_status_task and not enemy_status_task.done(): enemy_status_task.cancel() enemy_status_task = asyncio.create_task( refresh_status_loop(faction_id, "enemy", enemy_lock, interval) )