147 lines
4.4 KiB
Python
147 lines
4.4 KiB
Python
import asyncio
|
|
import json
|
|
import re
|
|
|
|
from websockets.asyncio.server import serve
|
|
|
|
__VERSION__ = "0.0.1"
|
|
|
|
|
|
class Client:
|
|
freqs = {}
|
|
freq_re = re.compile(r"^\d+\.\d{3}$")
|
|
|
|
def __init__(self, websocket):
|
|
self.websocket = websocket
|
|
self.curr_freq = None
|
|
|
|
async def handle(self):
|
|
print(f" >>> New client {self.client} connected")
|
|
exc = None
|
|
try:
|
|
await self._handle_client()
|
|
except Exception as e:
|
|
exc = e
|
|
finally:
|
|
# FIXME: basically handle disconnect / leave from room
|
|
print(f" <<< Client {self.client} disconnected: {exc}")
|
|
|
|
@property
|
|
def client(self):
|
|
ip, port, *_ = self.websocket.remote_address
|
|
if ':' in ip:
|
|
ip = f"[{ip}]"
|
|
return f"{ip}:{port}"
|
|
|
|
@property
|
|
def id(self):
|
|
return str(self.websocket.id)
|
|
|
|
async def _handle_client(self):
|
|
await self._send(type="hello", name="LobbySrv 3000", version=__VERSION__)
|
|
async for data in self.websocket:
|
|
print(f" <-- client {self.client} sent {repr(data)}")
|
|
try:
|
|
data = json.loads(data)
|
|
except json.JSONDecodeError:
|
|
self._send_error("Could not decode message, invalid json")
|
|
continue
|
|
|
|
if not isinstance(data, dict) or "cmd" not in data:
|
|
self._send_error("Invalid format in json")
|
|
continue
|
|
|
|
print(f"{self.client} wrote:", data)
|
|
|
|
match data["cmd"]:
|
|
case "quit":
|
|
break
|
|
case "create":
|
|
await self._create_room(data)
|
|
case "join":
|
|
await self._join_room(data)
|
|
case "list":
|
|
freqs = [{"freq": freq, "players": len(players)}
|
|
for freq, players in self.freqs.items()]
|
|
await self._send(type="freq-list", freqs=freqs)
|
|
case "disconnect":
|
|
pass
|
|
case "morse-state":
|
|
# FIXME: send to all other clients
|
|
pass
|
|
case _:
|
|
await self._send_error("Unknown command")
|
|
|
|
async def _create_room(self, data):
|
|
if self.curr_freq:
|
|
await self._send_error(f"Already on frequency {self.curr_freq}")
|
|
return
|
|
|
|
if "freq" not in data:
|
|
await self._send_error("No frequency in create message")
|
|
return
|
|
freq = data["freq"]
|
|
|
|
if not self.freq_re.match(freq):
|
|
await self._send_error("Invalid frequency")
|
|
return
|
|
|
|
if freq in self.freqs:
|
|
await self._send_error("Frequency already in use")
|
|
return
|
|
|
|
self.curr_freq = freq
|
|
self.freqs[freq] = [self]
|
|
await self._send(type="join", freq=self.curr_freq, others=[])
|
|
|
|
async def _join_room(self, data):
|
|
if self.curr_freq:
|
|
await self._send_error(f"Already on frequency {self.curr_freq}")
|
|
return
|
|
|
|
if "freq" not in data:
|
|
await self._send_error("No frequency in join message")
|
|
return
|
|
freq = data["freq"]
|
|
|
|
if freq not in self.freqs:
|
|
await self._send_error(f"Frequency {freq} not available")
|
|
return
|
|
|
|
self.freqs[freq].append(self)
|
|
# FIXME: do we need locking here?
|
|
await self._send(type="join", freq=self.curr_freq, players=[c.id for c in self._others(freq)])
|
|
for other in self._others(freq):
|
|
await self._send(type="player-joined", player=self.id)
|
|
|
|
def _others(self, freq):
|
|
return [c for c in self.freqs[freq] if c != self.websocket]
|
|
|
|
async def _send(self, **kwargs):
|
|
data = json.dumps(kwargs).encode()
|
|
print(f" --> sending out to {self.client}: {data}")
|
|
await self.websocket.send(json.dumps(kwargs).encode() + b"\n")
|
|
|
|
async def _send_error(self, msg: str):
|
|
await self._send(type="error", message=msg)
|
|
|
|
|
|
async def new_client(websocket):
|
|
try:
|
|
client = Client(websocket)
|
|
await client.handle()
|
|
finally:
|
|
pass
|
|
# async for message in websocket:
|
|
# await websocket.send(message)
|
|
|
|
|
|
async def main():
|
|
HOST, PORT = "localhost", 3784
|
|
async with serve(new_client, HOST, PORT) as server:
|
|
await server.serve_forever()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|