170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
import asyncio
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
import socketio
|
|
|
|
from app.models.database import async_session_maker
|
|
from app.models.orm import Character, Clue, Message, Vote
|
|
from app.models.schemas import GamePhase
|
|
from app.services.agent_scheduler import agent_scheduler
|
|
from app.services.game_state_manager import game_state_manager
|
|
from sqlalchemy import select
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
sio = socketio.AsyncServer(
|
|
async_mode="asgi",
|
|
cors_allowed_origins="*",
|
|
logger=False,
|
|
)
|
|
|
|
connected_clients: dict[str, dict] = {}
|
|
_tracked_tasks: set[asyncio.Task] = set()
|
|
|
|
|
|
@sio.event
|
|
async def connect(sid, environ, auth):
|
|
connected_clients[sid] = {"sid": sid, "role": "viewer"}
|
|
await sio.emit("system", {"message": "Connected to Hermes Live Show"}, to=sid)
|
|
|
|
|
|
@sio.event
|
|
async def disconnect(sid):
|
|
connected_clients.pop(sid, None)
|
|
|
|
|
|
@sio.event
|
|
async def set_role(sid, data):
|
|
if sid in connected_clients:
|
|
connected_clients[sid]["role"] = data.get("role", "viewer")
|
|
|
|
|
|
@sio.event
|
|
async def send_message(sid, data):
|
|
character_id = data.get("character_id")
|
|
content = data.get("content", "")
|
|
msg_type = data.get("msg_type", "character")
|
|
game_phase = data.get("game_phase", "")
|
|
target_character_id = data.get("target_character_id")
|
|
|
|
if async_session_maker:
|
|
async with async_session_maker() as session:
|
|
msg = Message(
|
|
character_id=character_id,
|
|
game_phase=game_phase,
|
|
msg_type=msg_type,
|
|
content=content,
|
|
target_character_id=target_character_id,
|
|
)
|
|
session.add(msg)
|
|
await session.commit()
|
|
|
|
await sio.emit("new_message", {
|
|
"character_id": character_id,
|
|
"content": content,
|
|
"msg_type": msg_type,
|
|
"game_phase": game_phase,
|
|
"target_character_id": target_character_id,
|
|
})
|
|
|
|
|
|
@sio.event
|
|
async def dm_command(sid, data):
|
|
command = data.get("command", "")
|
|
args = data.get("args", {})
|
|
|
|
if async_session_maker:
|
|
async with async_session_maker() as session:
|
|
if command == "next_phase":
|
|
state = await game_state_manager.next_phase(session)
|
|
await sio.emit("state_change", {
|
|
"phase": state.current_phase,
|
|
"progress": game_state_manager.get_progress_percent(state),
|
|
})
|
|
|
|
elif command == "prev_phase":
|
|
state = await game_state_manager.prev_phase(session)
|
|
await sio.emit("state_change", {
|
|
"phase": state.current_phase,
|
|
"progress": game_state_manager.get_progress_percent(state),
|
|
})
|
|
|
|
elif command == "unlock_clue":
|
|
clue_id = args.get("clue_id")
|
|
character_id = args.get("character_id")
|
|
if clue_id and character_id:
|
|
result = await session.execute(select(Clue).where(Clue.id == clue_id))
|
|
clue = result.scalars().first()
|
|
if clue:
|
|
clue.is_unlocked = True
|
|
clue.unlocked_by = character_id
|
|
clue.unlocked_at = datetime.utcnow()
|
|
await session.commit()
|
|
await sio.emit("clue_unlocked", {
|
|
"clue_id": clue.id,
|
|
"clue_name": clue.name,
|
|
"content": clue.content,
|
|
"unlocked_by": character_id,
|
|
})
|
|
|
|
elif command == "start_auto":
|
|
state = await game_state_manager.get_state(session)
|
|
script_id = args.get("script_id") or (state.active_script_id if state else None)
|
|
if script_id:
|
|
interval = float(args.get("interval", 8.0))
|
|
rounds = int(args.get("rounds", 1))
|
|
|
|
async def emit(event, data):
|
|
await sio.emit(event, data)
|
|
|
|
task = asyncio.create_task(
|
|
agent_scheduler.run_auto_speaking(session, script_id, interval, rounds, emit)
|
|
)
|
|
_tracked_tasks.add(task)
|
|
task.add_done_callback(_tracked_tasks.discard)
|
|
await sio.emit("system", {"message": "Auto speaking started"})
|
|
|
|
elif command == "stop_auto":
|
|
agent_scheduler.stop()
|
|
await sio.emit("system", {"message": "Auto speaking stopped"})
|
|
|
|
elif command == "trigger_vote":
|
|
state = await game_state_manager.get_state(session)
|
|
script_id = args.get("script_id") or (state.active_script_id if state else None)
|
|
if script_id:
|
|
async def emit(event, data):
|
|
await sio.emit(event, data)
|
|
|
|
votes = await agent_scheduler.trigger_voting(session, script_id, emit)
|
|
|
|
current_state = await game_state_manager.get_or_create_state(session)
|
|
round_num = current_state.round_number if hasattr(current_state, 'round_number') else 1
|
|
for v in votes:
|
|
session.add(Vote(
|
|
voter_id=v["voter_id"],
|
|
target_id=v["target_id"],
|
|
round_number=round_num,
|
|
reason=v.get("reason", ""),
|
|
))
|
|
await session.commit()
|
|
|
|
await sio.emit("voting_results", {"votes": votes})
|
|
|
|
|
|
@sio.event
|
|
async def request_state(sid, data):
|
|
if async_session_maker:
|
|
async with async_session_maker() as session:
|
|
state = await game_state_manager.get_or_create_state(session)
|
|
await sio.emit("state_change", {
|
|
"phase": state.current_phase,
|
|
"progress": game_state_manager.get_progress_percent(state),
|
|
"is_running": state.is_running,
|
|
"is_paused": state.is_paused,
|
|
}, to=sid)
|
|
|
|
|
|
def create_socket_app(app):
|
|
return socketio.ASGIApp(sio, other_app=app)
|