# services/torn_api.py import aiohttp import json import asyncio from pathlib import Path from config import TORN_API_KEY from .ffscouter import fetch_batch_stats FRIENDLY_MEMBERS_FILE = Path("data/friendly_members.json") FRIENDLY_STATUS_FILE = Path("data/friendly_status.json") ENEMY_MEMBERS_FILE = Path("data/enemy_members.json") ENEMY_STATUS_FILE = Path("data/enemy_status.json") # Tasks friendly_status_task = None enemy_status_task = None # Locks friendly_lock = asyncio.Lock() enemy_lock = asyncio.Lock() # ----------------------------- # Static population (once) # ----------------------------- async def populate_faction(faction_id: int, members_file: Path, status_file: Path): """Fetch members + FFScouter estimates once and save static info + initial status.""" 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 data once ff_data = await fetch_batch_stats(member_ids) # Build static member list members = [] status_data = {} for m in members_list: pid = m["id"] est = ff_data.get(str(pid), {}).get("bs_estimate_human", "?") member = { "id": pid, "name": m.get("name", "Unknown"), "level": m.get("level", 0), "estimate": est, } members.append(member) # initial status status_data[pid] = {"status": m.get("status", {}).get("state", "Unknown")} # Save members members_file.parent.mkdir(exist_ok=True, parents=True) with open(members_file, "w", encoding="utf-8") as f: json.dump(members, f, indent=2) # Save initial status with open(status_file, "w", encoding="utf-8") as f: json.dump(status_data, f, indent=2) return True # ----------------------------- # Status refresh loop # ----------------------------- async def refresh_status_loop(faction_id: int, status_file: Path, lock: asyncio.Lock, interval: int): """Refresh only status from Torn API periodically.""" 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", []) status_data = {m["id"]: {"status": m.get("status", {}).get("state", "Unknown")} for m in members_list} # Save status safely async with lock: with open(status_file, "w", encoding="utf-8") as f: json.dump(status_data, f, indent=2) except Exception as e: print(f"Error in status loop for {faction_id}: {e}") await asyncio.sleep(interval) # ----------------------------- # Helper functions for endpoints # ----------------------------- async def populate_friendly(faction_id: int): return await populate_faction(faction_id, FRIENDLY_MEMBERS_FILE, FRIENDLY_STATUS_FILE) async def populate_enemy(faction_id: int): return await populate_faction(faction_id, ENEMY_MEMBERS_FILE, ENEMY_STATUS_FILE) 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_STATUS_FILE, 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_STATUS_FILE, enemy_lock, interval) )