Stylized member cards with static fields

This commit is contained in:
2025-11-27 02:44:52 -05:00
parent 317442b8ea
commit 7cf882959b
9 changed files with 435 additions and 165 deletions

View File

@@ -3,31 +3,34 @@ import aiohttp
import json
import asyncio
from pathlib import Path
from config import TORN_API_KEY, ENEMY_FACTION_ID, YOUR_FACTION_ID
from config import TORN_API_KEY
from .ffscouter import fetch_batch_stats
ENEMY_FILE = Path("data/enemy_faction.json")
FRIENDLY_FILE = Path("data/friendly_faction.json")
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")
# Track running tasks + current faction IDs
enemy_task = None
friendly_task = None
# Tasks
friendly_status_task = None
enemy_status_task = None
current_enemy_id = None
current_friendly_id = None
# Locks
friendly_lock = asyncio.Lock()
enemy_lock = asyncio.Lock()
async def fetch_and_save_faction(faction_id: int, file_path: Path) -> bool:
"""
Fetches faction members from Torn, fetches their estimated BS from FFScouter,
and saves everything to a JSON file.
"""
# -----------------------------
# 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"Torn faction fetch error: {resp.status}")
print(f"Error fetching faction {faction_id}: {resp.status}")
return False
data = await resp.json()
@@ -35,92 +38,95 @@ async def fetch_and_save_faction(faction_id: int, file_path: Path) -> bool:
if not members_list:
return False
# Build list of IDs (Torn uses 'id', not 'player_id')
member_ids = [info.get("id") for info in members_list if "id" in info]
member_ids = [m.get("id") for m in members_list if "id" in m]
if not member_ids:
return False
# Fetch batch FFScouter stats
ff_data = await fetch_batch_stats(member_ids) # returns dict keyed by player_id
# Build final faction data
faction_data = []
for info in members_list:
pid = info.get("id")
if pid is None:
continue
# 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": info.get("name", "Unknown"),
"level": info.get("level", 0),
"status": info.get("status", {}).get("state", "Unknown"),
"estimate": est
"name": m.get("name", "Unknown"),
"level": m.get("level", 0),
"estimate": est,
}
faction_data.append(member)
members.append(member)
# initial status
status_data[pid] = {"status": m.get("status", {}).get("state", "Unknown")}
# Save to file
file_path.parent.mkdir(exist_ok=True, parents=True) # ensure folder exists
with open(file_path, "w", encoding="utf-8") as f:
json.dump(faction_data, f, indent=2)
# 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
#Loop for the constant update of members and the stop function
async def stop_task_if_running(task: asyncio.Task | None):
"""Cancel an existing running task safely."""
if task and not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
async def faction_loop(faction_id: int, file_path: Path, interval: int):
"""
Runs fetch_and_save_faction() in a loop forever, waiting `interval`
seconds between iterations.
"""
# -----------------------------
# 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:
await fetch_and_save_faction(faction_id, file_path)
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 during faction loop for {faction_id}: {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)
#Functions to call the loop, maybe add one to just call once?
async def update_enemy_faction(new_faction_id: int, interval: int):
global enemy_task, current_enemy_id
# If faction ID changes → stop old loop
if new_faction_id != current_enemy_id:
print(f"[ENEMY] Changing faction from {current_enemy_id}{new_faction_id}")
await stop_task_if_running(enemy_task)
current_enemy_id = new_faction_id
async def populate_enemy(faction_id: int):
return await populate_faction(faction_id, ENEMY_MEMBERS_FILE, ENEMY_STATUS_FILE)
# Start new loop
enemy_task = asyncio.create_task(
faction_loop(new_faction_id, ENEMY_FILE, interval)
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 update_friendly_faction(new_faction_id: int, interval: int):
global friendly_task, current_friendly_id
if new_faction_id != current_friendly_id:
print(f"[FRIENDLY] Changing faction from {current_friendly_id}{new_faction_id}")
await stop_task_if_running(friendly_task)
current_friendly_id = new_faction_id
friendly_task = asyncio.create_task(
faction_loop(new_faction_id, FRIENDLY_FILE, 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)
)