122 lines
4.1 KiB
Python
122 lines
4.1 KiB
Python
# 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)
|
|
)
|