Compare commits
2 Commits
60d209c138
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8779ad3d3b | |||
| c9808759f0 |
Binary file not shown.
168
main.py
168
main.py
@@ -1,4 +1,4 @@
|
|||||||
# main.py
|
# main.py (updated)
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from config import ALLOWED_CHANNEL_ID, HIT_CHECK_INTERVAL, REASSIGN_DELAY
|
from config import ALLOWED_CHANNEL_ID, HIT_CHECK_INTERVAL, REASSIGN_DELAY
|
||||||
@@ -7,19 +7,20 @@ from cogs.commands import HitCommands
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from fastapi import FastAPI, Request, HTTPException
|
from fastapi import FastAPI, Request, HTTPException
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from services.server_state import STATE
|
# import server state
|
||||||
from services.torn_api import (
|
from services.server_state import STATE, Member
|
||||||
populate_friendly,
|
|
||||||
populate_enemy,
|
from services.torn_api import populate_friendly, populate_enemy, start_friendly_status_loop, start_enemy_status_loop
|
||||||
start_friendly_status_loop,
|
|
||||||
start_enemy_status_loop,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# FastAPI Setup
|
# FastAPI Setup
|
||||||
@@ -55,80 +56,108 @@ class RemoveAssignmentRequest(BaseModel):
|
|||||||
class BotControl(BaseModel):
|
class BotControl(BaseModel):
|
||||||
action: str # "start" or "stop"
|
action: str # "start" or "stop"
|
||||||
|
|
||||||
# ============================================================
|
# ================================
|
||||||
# Populate endpoints (memory-only)
|
# Helper: load JSON file into STATE
|
||||||
# ============================================================
|
# ================================
|
||||||
|
def _load_json_list(path: Path):
|
||||||
|
if not path.exists():
|
||||||
|
return []
|
||||||
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
async def sync_state_from_file(path: Path, kind: str):
|
||||||
|
"""
|
||||||
|
Read JSON file (list of members dicts) and upsert into STATE.
|
||||||
|
Expected member dict keys: id, name, level, estimate, optionally status/hits.
|
||||||
|
"""
|
||||||
|
arr = _load_json_list(path)
|
||||||
|
received_ids = []
|
||||||
|
for m in arr:
|
||||||
|
try:
|
||||||
|
await STATE.upsert_member(m, kind)
|
||||||
|
received_ids.append(int(m["id"]))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Skipping bad member entry while syncing: {m} -> {e}")
|
||||||
|
await STATE.remove_missing_members(received_ids, kind)
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
|
# Populate endpoints (populate JSON once)
|
||||||
|
# -----------------------------
|
||||||
@app.post("/api/populate_friendly")
|
@app.post("/api/populate_friendly")
|
||||||
async def api_populate_friendly(data: FactionRequest):
|
async def api_populate_friendly(data: FactionRequest):
|
||||||
await populate_friendly(data.faction_id)
|
await populate_friendly(data.faction_id)
|
||||||
# Return full member info including status
|
# sync STATE from file
|
||||||
members = [m.model_dump() for m in STATE.friendly.values()]
|
await sync_state_from_file(Path("data/friendly_members.json"), "friendly")
|
||||||
return {"status": "friendly populated", "id": data.faction_id, "members": members}
|
return {"status": "friendly populated", "id": data.faction_id}
|
||||||
|
|
||||||
@app.post("/api/populate_enemy")
|
@app.post("/api/populate_enemy")
|
||||||
async def api_populate_enemy(data: FactionRequest):
|
async def api_populate_enemy(data: FactionRequest):
|
||||||
await populate_enemy(data.faction_id)
|
await populate_enemy(data.faction_id)
|
||||||
members = [m.model_dump() for m in STATE.enemy.values()]
|
await sync_state_from_file(Path("data/enemy_memberes.json"), "enemy")
|
||||||
return {"status": "enemy populated", "id": data.faction_id, "members": members}
|
return {"status": "enemy populated", "id": data.faction_id}
|
||||||
|
|
||||||
|
# -----------------------------
|
||||||
# ============================================================
|
|
||||||
# Start status refresh loops
|
# Start status refresh loops
|
||||||
# ============================================================
|
# -----------------------------
|
||||||
@app.post("/api/start_friendly_status")
|
@app.post("/api/start_friendly_status")
|
||||||
async def api_start_friendly_status(data: FactionRequest):
|
async def api_start_friendly_status(data: FactionRequest):
|
||||||
|
from services.torn_api import start_friendly_status_loop
|
||||||
await start_friendly_status_loop(data.faction_id, data.interval)
|
await start_friendly_status_loop(data.faction_id, data.interval)
|
||||||
return {"status": "friendly status loop started", "id": data.faction_id, "interval": data.interval}
|
return {"status": "friendly status loop started", "id": data.faction_id, "interval": data.interval}
|
||||||
|
|
||||||
@app.post("/api/start_enemy_status")
|
@app.post("/api/start_enemy_status")
|
||||||
async def api_start_enemy_status(data: FactionRequest):
|
async def api_start_enemy_status(data: FactionRequest):
|
||||||
|
from services.torn_api import start_enemy_status_loop
|
||||||
await start_enemy_status_loop(data.faction_id, data.interval)
|
await start_enemy_status_loop(data.faction_id, data.interval)
|
||||||
return {"status": "enemy status loop started", "id": data.faction_id, "interval": data.interval}
|
return {"status": "enemy status loop started", "id": data.faction_id, "interval": data.interval}
|
||||||
|
|
||||||
# ============================================================
|
# =============================
|
||||||
# Member endpoints
|
# Member JSON endpoints (unchanged)
|
||||||
# ============================================================
|
# =============================
|
||||||
@app.get("/api/friendly_members")
|
@app.get("/api/friendly_members")
|
||||||
async def get_friendly_members():
|
async def get_friendly_members():
|
||||||
"""
|
# Return list, but prefer STATE if populated
|
||||||
Return a list of all friendly members with all their info,
|
if STATE.friendly:
|
||||||
including current status.
|
return [m.model_dump() for m in STATE.friendly.values()]
|
||||||
"""
|
# fallback to file
|
||||||
return [member.model_dump() for member in STATE.friendly.values()]
|
path = Path("data/friendly_members.json")
|
||||||
|
return _load_json_list(path)
|
||||||
|
|
||||||
@app.get("/api/enemy_members")
|
@app.get("/api/enemy_members")
|
||||||
async def get_enemy_members():
|
async def get_enemy_members():
|
||||||
"""
|
if STATE.enemy:
|
||||||
Return a list of all enemy members with all their info,
|
return [m.model_dump() for m in STATE.enemy.values()]
|
||||||
including current status.
|
path = Path("data/enemy_members.json")
|
||||||
"""
|
return _load_json_list(path)
|
||||||
return [member.model_dump() for member in STATE.enemy.values()]
|
|
||||||
|
|
||||||
# ============================================================
|
# =============================
|
||||||
# Status endpoints
|
# Status JSON endpoints (unchanged)
|
||||||
# ============================================================
|
# =============================
|
||||||
@app.get("/api/friendly_status")
|
@app.get("/api/friendly_status")
|
||||||
async def api_friendly_status():
|
async def api_friendly_status():
|
||||||
"""
|
path = Path("data/friendly_status.json")
|
||||||
Return a dictionary of friendly member IDs to their current status.
|
if not path.exists():
|
||||||
Example: { 12345: "Online", 67890: "Offline", ... }
|
return {}
|
||||||
"""
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
return {mid: member.status for mid, member in STATE.friendly.items()}
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/enemy_status")
|
@app.get("/api/enemy_status")
|
||||||
async def api_enemy_status():
|
async def api_enemy_status():
|
||||||
"""
|
path = Path("data/enemy_status.json")
|
||||||
Return a dictionary of enemy member IDs to their current status.
|
if not path.exists():
|
||||||
"""
|
return {}
|
||||||
return {mid: member.status for mid, member in STATE.enemy.items()}
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
# ============================================================
|
# =============================
|
||||||
# Assignment endpoints
|
# Assignment endpoints (server-side)
|
||||||
# ============================================================
|
# =============================
|
||||||
@app.get("/api/assignments")
|
@app.get("/api/assignments")
|
||||||
async def api_get_assignments():
|
async def api_get_assignments():
|
||||||
|
"""
|
||||||
|
Return assignments snapshot:
|
||||||
|
{ "1": { "friendly": [...], "enemy": [...] }, "2": {...}, ... }
|
||||||
|
"""
|
||||||
snap = await STATE.get_assignments_snapshot()
|
snap = await STATE.get_assignments_snapshot()
|
||||||
return snap
|
return snap
|
||||||
|
|
||||||
@@ -142,14 +171,17 @@ async def api_assign_member(req: AssignMemberRequest):
|
|||||||
if group_id not in STATE.groups:
|
if group_id not in STATE.groups:
|
||||||
raise HTTPException(status_code=400, detail="invalid group_id")
|
raise HTTPException(status_code=400, detail="invalid group_id")
|
||||||
|
|
||||||
# Validate kind
|
# Validate member exists in STATE (if not present, attempt to load from file)
|
||||||
if kind not in ("friendly", "enemy"):
|
if kind not in ("friendly", "enemy"):
|
||||||
raise HTTPException(status_code=400, detail="invalid kind")
|
raise HTTPException(status_code=400, detail="invalid kind")
|
||||||
|
|
||||||
# Validate member exists in STATE
|
|
||||||
coll = STATE.friendly if kind == "friendly" else STATE.enemy
|
coll = STATE.friendly if kind == "friendly" else STATE.enemy
|
||||||
if member_id not in coll:
|
if member_id not in coll:
|
||||||
raise HTTPException(status_code=404, detail="member not found")
|
# Try to load members from file into STATE and re-check
|
||||||
|
file_path = Path("data/friendly_members.json") if kind == "friendly" else Path("data/enemy_members.json")
|
||||||
|
await sync_state_from_file(file_path, kind)
|
||||||
|
if member_id not in coll:
|
||||||
|
raise HTTPException(status_code=404, detail="member not found")
|
||||||
|
|
||||||
await STATE.assign_member(member_id, kind, group_id)
|
await STATE.assign_member(member_id, kind, group_id)
|
||||||
return {"status": "ok", "group": group_id, "kind": kind, "member_id": member_id}
|
return {"status": "ok", "group": group_id, "kind": kind, "member_id": member_id}
|
||||||
@@ -165,9 +197,9 @@ async def api_clear_assignments():
|
|||||||
await STATE.clear_all_assignments()
|
await STATE.clear_all_assignments()
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
# ============================================================
|
# =============================
|
||||||
# Bot control endpoint
|
# Bot control endpoint
|
||||||
# ============================================================
|
# =============================
|
||||||
@app.post("/api/bot_control")
|
@app.post("/api/bot_control")
|
||||||
async def api_bot_control(req: BotControl):
|
async def api_bot_control(req: BotControl):
|
||||||
if req.action not in ("start", "stop"):
|
if req.action not in ("start", "stop"):
|
||||||
@@ -175,6 +207,34 @@ async def api_bot_control(req: BotControl):
|
|||||||
STATE.bot_running = (req.action == "start")
|
STATE.bot_running = (req.action == "start")
|
||||||
return {"status": "ok", "bot_running": STATE.bot_running}
|
return {"status": "ok", "bot_running": STATE.bot_running}
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Reset Groups Endpoint
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
@app.post("/api/reset_groups")
|
||||||
|
async def reset_groups():
|
||||||
|
# Load existing data
|
||||||
|
data = load_json("assigned_groups.json")
|
||||||
|
|
||||||
|
# Clear group assignments
|
||||||
|
for g in data["groups"].values():
|
||||||
|
g.clear()
|
||||||
|
|
||||||
|
# Reset friendly assigned_group
|
||||||
|
for f in data["friendly_members"]:
|
||||||
|
f["assigned_group"] = None
|
||||||
|
|
||||||
|
# Reset enemy assigned_group
|
||||||
|
for e in data["enemy_members"]:
|
||||||
|
e["assigned_group"] = None
|
||||||
|
|
||||||
|
# Save back to file
|
||||||
|
save_json("assigned_groups.json", data)
|
||||||
|
|
||||||
|
return { "success": True }
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Discord Bot Setup
|
# Discord Bot Setup
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user