133 lines
4.4 KiB
Python
133 lines
4.4 KiB
Python
# 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)
|
|
)
|