From 60d209c138900de5f5485d95ab471182e9e32fb3 Mon Sep 17 00:00:00 2001 From: jerick Date: Sun, 30 Nov 2025 02:58:11 -0500 Subject: [PATCH] Pivoted to in memory only storage --- README.md | 1 + __pycache__/main.cpython-311.pyc | Bin 15151 -> 13282 bytes assignments.json | 334 ++++++++++++++++++ main.py | 142 +++----- .../__pycache__/server_state.cpython-311.pyc | Bin 9295 -> 8769 bytes services/__pycache__/torn_api.cpython-311.pyc | Bin 9191 -> 8329 bytes services/server_state.py | 37 +- services/torn_api.py | 97 +++-- static/dashboard.js | 98 +++-- 9 files changed, 519 insertions(+), 190 deletions(-) create mode 100644 assignments.json diff --git a/README.md b/README.md index b97213d..475a7f8 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,5 @@ ToDo: - since control of the Discord bot would also be through there technically - add status description to member cards + For now let's pivot to the Discord Bot functionality. What the bot is going to do is for each battle group it needs to assign a friendly member to an enemy member. That friendly will then get a ping in Discord by pulling the list of Discord users and matching the player id to the user in the Discord server. It will ping them and say "New target for @user , attack (link to enemy profile) in the next 60 seconds!" If the enemies status does not change to "In Hospital" in the next 60 seconds that enemy will be assigned to the next player in the group that has not received a hit yet. We will also need to keep track of how many hits a friendly has completed. That way if a new friendly enters a pool they will get a chance to attack before the ones that have not had a chance We also need a button on the webpage to start and stop the bot \ No newline at end of file diff --git a/__pycache__/main.cpython-311.pyc b/__pycache__/main.cpython-311.pyc index a905f86f467afddd24bbfb957a951ea262f43f0a..25eff4c80c86b4277ea5a13cf0572eda9af11541 100644 GIT binary patch literal 13282 zcmd@)ZEO?QmNWJ^jz8jjCnO}qAwV!t2Lco*kP>2tgpZ^mfj$gfuklP0gFn(c6CR|2 zH7$K*yR4e0M+MqPrL4Bgg36Ql(I<7aZ&&-XKgZFmutq{cT2aRWL&Wt@{ zJAJ#=N`2Cf@0_{5_ndR@J@?#m?$;-U4+;@hG%#- z$wcsLOR_0j#KvOWp0ua92$yn19I2W}4Nc>c&Qxurmc|`PSIQl6(>R;-L_Cy_H{ykF zO|mZKi}+IYk@{3aq=Di%lZ~mSNE40MCYw_&kroF{b#q*w5- zY&8CvzkEGVwD1k(6p+&R&<;G{)gG1s9(~3?^OtWDnw~U2v;j7}+QYX9^)oiU^%hrF z!%a5k7{j;SV)%ApowbC>W}3GS@;WN=w$Qvz$Xj2L*H81hAg{Y3Z!6R~VARU|LC zeHL7kHc&>pt%7!7P1@~cv^y$jcdkkMMj36ef_B%Mv;$?dJ1b}hfwsEN>?)%j6t;lQ z?5>dZ?ln2=so>BL9QIal*s~^wH_JHetEm6pHEH*k(GCh*q4Wb4rN1dKm-ZQ>Wo5q+ zGk?o;TJ@GT}17DoA_m#G~h!@$pT>6k%RR^u^YY-QZYGk<|9 zY{j)ojapvsrQ-&T;O9s9(37yWHDI^dDf$LRzem)G0ISxl}@V((pUus`Iq){(>M%YTelM^gH3JabaFcWYR$49h*LN^0Xk% zXVRj89Hyk0l!%Wek^(ZUKb}ZmiVaQ+srh6~qDii!F;O^`;e{mRJEo?Gr^5kGZJf`{ zFCa>Eh9rbEpS-5}ib+CRNL^DqMJYz4Vty3bJ2W)2yrPx zt}QwTkUSArYqT%ag-gd$!Vl~KM14eWQ8b>6iK6NxTKnTgKM1|xfcGPKU;Pkn_^?MqC8wd(8?6W}HmJBNmb{Gs8gv7?j2r$heJlV_&G z{*#l#p>Sx?$H&CEirNu(vx3jfpwqYp}*MbSj8vj2zCk+&|K z5d|V%xFnE7{PKlRCcZ$`;zCHcA|x~O_~*h*ERG!?{bh_q`Ghzhlj3vHiy7%cDwaqG z=dY=*Voiu27^9yMcLDeh_+^>zIG60+lH>Xnu3zT*pL<%Kc{ZycMmA##rqm;lKLhai==MI7{>o&MuMat_ru zs#V(1VYktU1poa8AW~~VNCLSMOHKr8)Y@ni^&lEm-O*?&!!IN;?u|y@Ux+3396J!G z_0ec7oz9>Zf$m46WDD?m%#dFAkRAlscnY>dL`0A^{qhaX*~#qW&B=UATXyVrT;|#! z09wxz5Y zdjOvxz*p=5VClHZN+aG+thD=R223uD(o9mV(G;~xRR^H}P3&b=eG@YfppUJrs<%FS zB75Q{yx9|)d=YoCZ6G$E7%1A;qRHj>m=v=ZIS$Z2g7?)5fJ!5`#9S(xJgd3;2o^pc z*v~Te?7!mBf!$&x_%+Glxn0gWz)t`+5hV-Z!?#?#PaqBc4{UoD&GuDUty!~ zF|*7P^IqF^c8Ognm1K%pJ@v0`ciN{k4S&q4ZZHKxGRiNc<{#TsTX3h!rbM*iem@=T zKe!Ji6QTr8d46ay0MdY32j2X#jo@^Q zgaDYEqPAA8J{beN!-#txfNsy->+gpiOy=A>6!#99s}f$&R{j0Lmp7jEA6o4{l+%I`{v*W|D)@7lihTNg2rz zNL}1w=|(ERYAT46+M)^`l6@GgDyS!F{ZdaEmGqCG3WK&a!N;U+4vI!bF_o5Js-D_Z zSu=7HFsUY*wQJq|mIpmKcR+CmWUflc)7pY|eekURz-s@2T>n9({~$CV=R2hM4xx7S zKa!sO#kc!p&lCo3N^3p

Y!OYfW}egz6nWM5 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 Kf9&nl)AV`B6G diff --git a/assignments.json b/assignments.json new file mode 100644 index 0000000..d40adc6 --- /dev/null +++ b/assignments.json @@ -0,0 +1,334 @@ +{ + "groups": { + "1": { + "friendly": [], + "enemy": [] + }, + "2": { + "friendly": [], + "enemy": [] + }, + "3": { + "friendly": [], + "enemy": [] + }, + "4": { + "friendly": [], + "enemy": [] + }, + "5": { + "friendly": [], + "enemy": [] + } + }, + "friendly": { + "2323265": { + "id": 2323265, + "name": "richard2130", + "level": 43, + "estimate": "117k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "2643888": { + "id": 2643888, + "name": "CKDGr8", + "level": 19, + "estimate": "16.6k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "2658249": { + "id": 2658249, + "name": "Saviour01", + "level": 39, + "estimate": "151k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "2877889": { + "id": 2877889, + "name": "Larioon", + "level": 48, + "estimate": "2.11m", + "type": "friendly", + "group": null, + "hits": 0 + }, + "2996876": { + "id": 2996876, + "name": "Dextrooous", + "level": 49, + "estimate": "2.82m", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3097390": { + "id": 3097390, + "name": "HendoAL", + "level": 12, + "estimate": "875", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3119350": { + "id": 3119350, + "name": "DragonRoy", + "level": 41, + "estimate": "143k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3246078": { + "id": 3246078, + "name": "Az4sH", + "level": 40, + "estimate": "166k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3296065": { + "id": 3296065, + "name": "TvTBanshee", + "level": 43, + "estimate": "261k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3571528": { + "id": 3571528, + "name": "TannedOrange", + "level": 35, + "estimate": "157k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3572068": { + "id": 3572068, + "name": "Pasketti", + "level": 32, + "estimate": "111k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3585878": { + "id": 3585878, + "name": "Dougo", + "level": 41, + "estimate": "414k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3612970": { + "id": 3612970, + "name": "VitaSoyMilk10", + "level": 19, + "estimate": "14.9k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3615091": { + "id": 3615091, + "name": "CoinsOperated", + "level": 36, + "estimate": "1.04m", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3627374": { + "id": 3627374, + "name": "Brouhaha", + "level": 33, + "estimate": "87.8k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3639240": { + "id": 3639240, + "name": "AAAG", + "level": 23, + "estimate": "127k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3779299": { + "id": 3779299, + "name": "arcflips", + "level": 22, + "estimate": "12.2k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3803470": { + "id": 3803470, + "name": "LioKey", + "level": 22, + "estimate": "21.6k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3925208": { + "id": 3925208, + "name": "Snakiter", + "level": 17, + "estimate": "905", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3925604": { + "id": 3925604, + "name": "Vyktimizer", + "level": 20, + "estimate": "17.4k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3929969": { + "id": 3929969, + "name": "JayzBondz12", + "level": 23, + "estimate": "54.1k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3930001": { + "id": 3930001, + "name": "TopBiscuit", + "level": 21, + "estimate": "16k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3931799": { + "id": 3931799, + "name": "IVerbYourNoun", + "level": 26, + "estimate": "78.9k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3956441": { + "id": 3956441, + "name": "herfieez", + "level": 19, + "estimate": "12.4k", + "type": "friendly", + "group": null, + "hits": 0 + }, + "3999413": { + "id": 3999413, + "name": "Rarcham", + "level": 16, + "estimate": "1.71k", + "type": "friendly", + "group": null, + "hits": 0 + } + }, + "enemy": { + "2292261": { + "id": 2292261, + "name": "Dalil1ne", + "level": 31, + "estimate": "56.5k", + "type": "enemy", + "group": null, + "hits": 0 + }, + "2323821": { + "id": 2323821, + "name": "Celwind", + "level": 16, + "estimate": "2.2k", + "type": "enemy", + "group": null, + "hits": 0 + }, + "3876859": { + "id": 3876859, + "name": "sleezihax", + "level": 4, + "estimate": "51", + "type": "enemy", + "group": null, + "hits": 0 + }, + "3927657": { + "id": 3927657, + "name": "665monk17", + "level": 10, + "estimate": "787", + "type": "enemy", + "group": null, + "hits": 0 + }, + "3931946": { + "id": 3931946, + "name": "Jonaketski", + "level": 8, + "estimate": "550", + "type": "enemy", + "group": null, + "hits": 0 + }, + "3937815": { + "id": 3937815, + "name": "Babetch", + "level": 15, + "estimate": "1.35k", + "type": "enemy", + "group": null, + "hits": 0 + }, + "3972935": { + "id": 3972935, + "name": "_Fox__", + "level": 6, + "estimate": "301", + "type": "enemy", + "group": null, + "hits": 0 + }, + "3999582": { + "id": 3999582, + "name": "Azrien", + "level": 6, + "estimate": "481", + "type": "enemy", + "group": null, + "hits": 0 + }, + "4010926": { + "id": 4010926, + "name": "_blackFOX_", + "level": 3, + "estimate": "None", + "type": "enemy", + "group": null, + "hits": 0 + } + } +} \ No newline at end of file diff --git a/main.py b/main.py index e5cdf08..ebdf27f 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -# main.py (updated) +# main.py import discord from discord.ext import commands from config import ALLOWED_CHANNEL_ID, HIT_CHECK_INTERVAL, REASSIGN_DELAY @@ -7,20 +7,19 @@ from cogs.commands import HitCommands import asyncio import uvicorn -import json -from pathlib import Path -from typing import Optional - from fastapi import FastAPI, Request, HTTPException from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from pydantic import BaseModel -# import server state -from services.server_state import STATE, Member - -from services.torn_api import populate_friendly, populate_enemy, start_friendly_status_loop, start_enemy_status_loop +from services.server_state import STATE +from services.torn_api import ( + populate_friendly, + populate_enemy, + start_friendly_status_loop, + start_enemy_status_loop, +) # ============================================================ # FastAPI Setup @@ -56,109 +55,80 @@ class RemoveAssignmentRequest(BaseModel): class BotControl(BaseModel): action: str # "start" or "stop" -# ================================ -# 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) -# ----------------------------- +# ============================================================ +# Populate endpoints (memory-only) +# ============================================================ @app.post("/api/populate_friendly") async def api_populate_friendly(data: FactionRequest): - # call the Torn populater (should write data/friendly_faction.json) await populate_friendly(data.faction_id) - # sync STATE from file - await sync_state_from_file(Path("data/friendly_faction.json"), "friendly") - return {"status": "friendly populated", "id": data.faction_id} + # Return full member info including status + members = [m.model_dump() for m in STATE.friendly.values()] + return {"status": "friendly populated", "id": data.faction_id, "members": members} @app.post("/api/populate_enemy") async def api_populate_enemy(data: FactionRequest): await populate_enemy(data.faction_id) - await sync_state_from_file(Path("data/enemy_faction.json"), "enemy") - return {"status": "enemy populated", "id": data.faction_id} + members = [m.model_dump() for m in STATE.enemy.values()] + return {"status": "enemy populated", "id": data.faction_id, "members": members} -# ----------------------------- + +# ============================================================ # Start status refresh loops -# ----------------------------- +# ============================================================ @app.post("/api/start_friendly_status") 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) return {"status": "friendly status loop started", "id": data.faction_id, "interval": data.interval} @app.post("/api/start_enemy_status") 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) return {"status": "enemy status loop started", "id": data.faction_id, "interval": data.interval} -# ============================= -# Member JSON endpoints (unchanged) -# ============================= +# ============================================================ +# Member endpoints +# ============================================================ @app.get("/api/friendly_members") async def get_friendly_members(): - # Return list, but prefer STATE if populated - if STATE.friendly: - return [m.model_dump() for m in STATE.friendly.values()] - # fallback to file - path = Path("data/friendly_faction.json") - return _load_json_list(path) + """ + Return a list of all friendly members with all their info, + including current status. + """ + return [member.model_dump() for member in STATE.friendly.values()] + @app.get("/api/enemy_members") async def get_enemy_members(): - if STATE.enemy: - return [m.model_dump() for m in STATE.enemy.values()] - path = Path("data/enemy_faction.json") - return _load_json_list(path) + """ + Return a list of all enemy members with all their info, + including current status. + """ + return [member.model_dump() for member in STATE.enemy.values()] -# ============================= -# Status JSON endpoints (unchanged) -# ============================= +# ============================================================ +# Status endpoints +# ============================================================ @app.get("/api/friendly_status") async def api_friendly_status(): - path = Path("data/friendly_status.json") - if not path.exists(): - return {} - with open(path, "r", encoding="utf-8") as f: - return json.load(f) + """ + Return a dictionary of friendly member IDs to their current status. + Example: { 12345: "Online", 67890: "Offline", ... } + """ + return {mid: member.status for mid, member in STATE.friendly.items()} + @app.get("/api/enemy_status") async def api_enemy_status(): - path = Path("data/enemy_status.json") - if not path.exists(): - return {} - with open(path, "r", encoding="utf-8") as f: - return json.load(f) + """ + Return a dictionary of enemy member IDs to their current status. + """ + return {mid: member.status for mid, member in STATE.enemy.items()} -# ============================= -# Assignment endpoints (server-side) -# ============================= +# ============================================================ +# Assignment endpoints +# ============================================================ @app.get("/api/assignments") async def api_get_assignments(): - """ - Return assignments snapshot: - { "1": { "friendly": [...], "enemy": [...] }, "2": {...}, ... } - """ snap = await STATE.get_assignments_snapshot() return snap @@ -172,17 +142,14 @@ async def api_assign_member(req: AssignMemberRequest): if group_id not in STATE.groups: raise HTTPException(status_code=400, detail="invalid group_id") - # Validate member exists in STATE (if not present, attempt to load from file) + # Validate kind if kind not in ("friendly", "enemy"): raise HTTPException(status_code=400, detail="invalid kind") + # Validate member exists in STATE coll = STATE.friendly if kind == "friendly" else STATE.enemy if member_id not in coll: - # Try to load members from file into STATE and re-check - file_path = Path("data/friendly_faction.json") if kind == "friendly" else Path("data/enemy_faction.json") - await sync_state_from_file(file_path, kind) - if member_id not in coll: - raise HTTPException(status_code=404, detail="member not found") + raise HTTPException(status_code=404, detail="member not found") await STATE.assign_member(member_id, kind, group_id) return {"status": "ok", "group": group_id, "kind": kind, "member_id": member_id} @@ -198,9 +165,9 @@ async def api_clear_assignments(): await STATE.clear_all_assignments() return {"status": "ok"} -# ============================= +# ============================================================ # Bot control endpoint -# ============================= +# ============================================================ @app.post("/api/bot_control") async def api_bot_control(req: BotControl): if req.action not in ("start", "stop"): @@ -208,7 +175,6 @@ async def api_bot_control(req: BotControl): STATE.bot_running = (req.action == "start") return {"status": "ok", "bot_running": STATE.bot_running} - # ============================================================ # Discord Bot Setup # ============================================================ diff --git a/services/__pycache__/server_state.cpython-311.pyc b/services/__pycache__/server_state.cpython-311.pyc index 3a33c31a9fba5d4a0863f9b90760469e53a30e80..89b93eed8cbe8e9cd9a0e64c66ca9491e1716c5a 100644 GIT binary patch delta 3377 zcmbVOZ%iCT6yMps-MhUzIJi5wI}SLGE6TM}D9}(Sl*pg{vmyz!S{qUcyQx_IIeXAn zuO6|+V1n8@CjCN_CK{}1KQyUnFwqazCiaukv@zG!Px{3e6OV+Xq#um$?P9s3z)@%K zelt7s_RYNcy?Jkb+W%cg?iV>Zb`DDI!)=2-?hCoUkkc3be8XdZRaQmSqBxpiOp$uH zsFNKf%^ux)c;w{B=&2E1(qjFwaV=_R7fBaS^Vx5Nk}-Ce@AD_!!O6W-?emr({O%eE z4H~#H$Mbxs95m7hLMebQouH%RW123~{*e))vQ5se|AP9#KPXP9;o6J zcxHKyy9$4^oa2==O$yh;MYtUt_Og^UXoL5t*hRzarmf;IEMnl4dwcyvP5rT(!obwr^nCg|42*B$+PXOH^dYFW~2ko6@m~X*Lj9T3Rwz>04*5tle>t zaO@+;3my^xJIyk|(O_Iq2>Z*?z#cd{+3U^%kb1|t6Ywi%#nUr(1Q`2L=vvu#*m5My zAc@vvYbS!i3sPeEW7LwyO9S@q0!Vki>pAUSqwj1cKP>F3yiuKdijdEl%UfiaSjdvD zkN{?~8OL+%nY&M~{eOo|6I-8@|!7*hMG zVVf>Zpo403#YVxj5zW)du!e`lI9m8#z*^k}Y==A2vKL;plbJ@QIIA4m1SHu7_lrWZ zAi#ciA4~`&CzQ&`_;LXiEpAfVCMH~01l+NH-9ckObAzGh+k$Pa!mU<&yKMd5N!n%h zx2FkM+Uh+@2%Go$*^Jl4&UpiS6k&#slQ@riK3yLt0}vsyMU%`}(uJ|alIEE-47$Mc zGTl?R3=L|rWrx%)+DQKxUDG2k6|}c>BU#ZW$?~I#Ss=+u3i}eSC*_bNKa!M1!6xQd zSHuVxFav~(HRoY@zGi-sXL;Vh&b0WvcBG$$4Y&$_v%Cu|DFU1E)j`ys_q_~sR{EoY zs7S2WUnf`<8~f2;%x3)+f~?q~9VQNz@^2FyFj5)_5gQxv2aY)vDKk@C%bB&oJn%Qm zSt%~Xg}B8?lUaRXb!KTpZ^(tr0?KGDf)SBqY++WWc?b>!9>EFlh_q4{zT^W$ZS2jW z$Ud`eCM8;jgDV-Q0A6+Bgc>u00Ir<+@YCfiv>!W+EY!$;E~O^9fMx&-fEx9 z#{iH8*l_UIMABD&G4V;_^6Be=yT0A`eY;cna$)WQ2e61RwKwJE0->pn^PUe5q#_wA z7jTBfY}0JGlD-Uu>n_>wzl}uA@X%rJs1j~h**hAnx0^^u1M`-&xmNijQ%jJ#n=yP6u1D3OeT1@8 zq52-ou2Ip9Dvy0Zx8QY(w&0j#<^fc&%s#YqIW~ISyt^3sH3>S)UV{XIm#=?9t-D8| zL)#C2v6OK=5#`bpmj&4AvQ`J51A+kOg(SOGR-F*B!g*sHUuPu;;ohL1JEtX;6RMNgzv8)AeR&g zqD2VB{{bF$jlkYcK7LZHvOL`D5o@pXq&TSN1f#lRRz^DwwmQW+lz?hA6x&2Nx{twY zW0%TH5;hT{dnOdR#n6=)+~35|9B6@RHB?Gs>6~|N;F~~-gXW!xQLPK6rG}BlUJ~Ql zAGPaJj5S8K)I5vk8&L^mbg`%R`QFY|d-TZGJUpt552<@-ANw$JXu?K-xhG-j=mPdG pt_*oqLcrtJ#s#B)F@3aK;AHQV<1(4ImEX0M-?x=diuf2-{0on3?>+zk delta 4078 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 literal 9191 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 N8~AKMk*`x6`ai!LFd_f| diff --git a/services/server_state.py b/services/server_state.py index f8c6cb5..e5eace0 100644 --- a/services/server_state.py +++ b/services/server_state.py @@ -9,8 +9,9 @@ class Member(BaseModel): level: int estimate: str type: str # "friendly" or "enemy" - group: Optional[str] = None # "group1", "group2", ... or None + group: Optional[str] = None # "1", "2", ... hits: int = 0 + status: str = "Unknown" # Added status field for Torn API status class ServerState: def __init__(self, group_count: int = 5): @@ -30,16 +31,11 @@ class ServerState: # concurrency lock for async safety self.lock = asyncio.Lock() - # Assignment helpers (all use the lock when called from async endpoints) + # Assignment helpers async def assign_member(self, member_id: int, kind: str, group_key: str): - """ - Assign a member (by id) of kind ('friendly'|'enemy') to group_key ("1".."N"). - This removes it from any previous group of the same kind. - """ async with self.lock: if kind not in ("friendly", "enemy"): raise ValueError("invalid kind") - if group_key not in self.groups: raise ValueError("invalid group_key") @@ -48,7 +44,7 @@ class ServerState: if member_id in buckets[kind]: buckets[kind].remove(member_id) - # add to new group if not present + # add to new group if member_id not in self.groups[group_key][kind]: self.groups[group_key][kind].append(member_id) @@ -58,7 +54,6 @@ class ServerState: coll[member_id].group = group_key async def remove_member_assignment(self, member_id: int): - """Remove member from any group (both friendly and enemy).""" async with self.lock: for gk, buckets in self.groups.items(): if member_id in buckets["friendly"]: @@ -66,33 +61,24 @@ class ServerState: if member_id in buckets["enemy"]: buckets["enemy"].remove(member_id) - # clear group attr if member in maps + # clear group attribute if member_id in self.friendly: self.friendly[member_id].group = None if member_id in self.enemy: self.enemy[member_id].group = None async def clear_all_assignments(self): - """Clear all group lists and member.group fields and save to disk.""" async with self.lock: - # clear in-memory groups for gk in self.groups: self.groups[gk]["friendly"].clear() self.groups[gk]["enemy"].clear() - - # clear group for known members for m in self.friendly.values(): m.group = None for m in self.enemy.values(): m.group = None - # save to disk so clients get empty state - self.save_assignments() - async def get_assignments_snapshot(self) -> Dict[str, Dict[str, List[int]]]: - """Return a copy snapshot of the groups dictionary.""" async with self.lock: - # shallow copy of structure snap = {} for gk, buckets in self.groups.items(): snap[gk] = { @@ -101,12 +87,8 @@ class ServerState: } return snap - # member add/update helpers + # member helpers async def upsert_member(self, member_data: dict, kind: str): - """ - Insert or update member. member_data expected to have id,name,level,estimate. - kind must be 'friendly' or 'enemy'. - """ async with self.lock: if kind not in ("friendly", "enemy"): raise ValueError("invalid kind") @@ -121,19 +103,18 @@ class ServerState: estimate=str(member_data.get("estimate", "?")), type=kind, group=existing_group, - hits=int(member_data.get("hits", 0)) if "hits" in member_data else 0 + hits=int(member_data.get("hits", 0)) if "hits" in member_data else 0, + status=member_data.get("status", "Unknown") # Initialize status if provided ) coll[mid] = member async def remove_missing_members(self, received_ids: List[int], kind: str): - """Remove any existing members not present in received_ids (useful after a populate).""" async with self.lock: coll = self.friendly if kind == "friendly" else self.enemy to_remove = [mid for mid in coll.keys() if mid not in set(received_ids)] for mid in to_remove: - # remove from groups too await self.remove_member_assignment(mid) del coll[mid] -# single global state (5 groups) +# Single global state STATE = ServerState(group_count=5) diff --git a/services/torn_api.py b/services/torn_api.py index a9989d8..83d9358 100644 --- a/services/torn_api.py +++ b/services/torn_api.py @@ -1,30 +1,28 @@ # 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 +from .server_state import STATE -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 +# Locks for safe async updates friendly_lock = asyncio.Lock() enemy_lock = asyncio.Lock() - # ----------------------------- -# Static population (once) +# Populate faction (memory only) # ----------------------------- -async def populate_faction(faction_id: int, members_file: Path, status_file: Path): - """Fetch members + FFScouter estimates once and save static info + initial status.""" +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: @@ -42,42 +40,37 @@ async def populate_faction(faction_id: int, members_file: Path, status_file: Pat if not member_ids: return False - # Fetch FFScouter data once + # Fetch FFScouter estimates 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")} + 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) - # 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) + # 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, status_file: Path, lock: asyncio.Lock, interval: int): - """Refresh only status from Torn API periodically.""" +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}" @@ -90,43 +83,39 @@ async def refresh_status_loop(faction_id: int, status_file: Path, lock: asyncio. 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) + 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 {faction_id}: {e}") + print(f"Error in status loop for {kind} faction {faction_id}: {e}") await asyncio.sleep(interval) - # ----------------------------- -# Helper functions for endpoints +# Public API helpers # ----------------------------- async def populate_friendly(faction_id: int): - return await populate_faction(faction_id, FRIENDLY_MEMBERS_FILE, FRIENDLY_STATUS_FILE) - + return await populate_faction(faction_id, "friendly") async def populate_enemy(faction_id: int): - return await populate_faction(faction_id, ENEMY_MEMBERS_FILE, ENEMY_STATUS_FILE) - + 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_STATUS_FILE, friendly_lock, interval) + 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_STATUS_FILE, enemy_lock, interval) + refresh_status_loop(faction_id, "enemy", enemy_lock, interval) ) diff --git a/static/dashboard.js b/static/dashboard.js index 23711dc..e69d9f1 100644 --- a/static/dashboard.js +++ b/static/dashboard.js @@ -425,39 +425,97 @@ function setupDropZones() { } // --------------------------- -// Populate & Status functions (unchanged behavior) +// Populate & Status functions // --------------------------- async function populateFriendly() { const id = toInt(document.getElementById("friendly-id").value); if (!id) return alert("Enter Friendly Faction ID"); - await fetch("/api/populate_friendly", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ faction_id: id, interval: 0 }) - }); + try { + const res = await fetch("/api/populate_friendly", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ faction_id: id, interval: 0 }) + }); + const data = await res.json(); - // reload members and assignments - await loadMembers("friendly"); - await loadMembers("enemy"); // in case population changed cross lists - await pollAssignments(); - await refreshStatus("friendly"); + // Update in-memory map & DOM + if (data.members) { + for (const m of data.members) { + let existing = friendlyMembers.get(m.id); + if (existing) { + existing.name = m.name; + existing.level = m.level; + existing.estimate = m.estimate; + existing.status = m.status || "Unknown"; + updateMemberCard(existing); + } else { + const newMember = { + id: m.id, + name: m.name, + level: m.level, + estimate: m.estimate, + status: m.status || "Unknown", + domElement: null + }; + friendlyMembers.set(m.id, newMember); + const card = createMemberCard(newMember, "friendly"); + friendlyContainer.appendChild(card); + } + } + } + + // Refresh assignments & status UI + await loadMembers("enemy"); // in case population changed cross lists + await pollAssignments(); + } catch (err) { + console.error("populateFriendly error:", err); + } } async function populateEnemy() { const id = toInt(document.getElementById("enemy-id").value); if (!id) return alert("Enter Enemy Faction ID"); - await fetch("/api/populate_enemy", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ faction_id: id, interval: 0 }) - }); + try { + const res = await fetch("/api/populate_enemy", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ faction_id: id, interval: 0 }) + }); + const data = await res.json(); - await loadMembers("enemy"); - await loadMembers("friendly"); - await pollAssignments(); - await refreshStatus("enemy"); + if (data.members) { + for (const m of data.members) { + let existing = enemyMembers.get(m.id); + if (existing) { + existing.name = m.name; + existing.level = m.level; + existing.estimate = m.estimate; + existing.status = m.status || "Unknown"; + updateMemberCard(existing); + } else { + const newMember = { + id: m.id, + name: m.name, + level: m.level, + estimate: m.estimate, + status: m.status || "Unknown", + domElement: null + }; + enemyMembers.set(m.id, newMember); + const card = createMemberCard(newMember, "enemy"); + enemyContainer.appendChild(card); + } + } + } + + // Refresh assignments & status UI + await loadMembers("friendly"); + await pollAssignments(); + } catch (err) { + console.error("populateEnemy error:", err); + } } // status refresh (pulls from server status json)