Remove nick, roles from protected;

Change _tcp_send -> _send;
Now _send data may be str;
Rewrite to_all handler;
Add chat handler;
This commit is contained in:
Maxim Khomutov 2023-07-16 02:55:09 +03:00
parent 6d4bc1e72c
commit 479525a66e
2 changed files with 75 additions and 63 deletions

View File

@ -18,12 +18,16 @@ class Client:
self._addr = writer.get_extra_info("sockname") self._addr = writer.get_extra_info("sockname")
self._cid = -1 self._cid = -1
self._key = None self._key = None
self._nick = None self.nick = None
self._roles = None self.roles = None
self._guest = True self._guest = True
self._ready = False self._ready = False
self._cars = [] self._cars = []
@property
def _writer(self):
return self.__writer
@property @property
def log(self): def log(self):
return self._log return self._log
@ -40,14 +44,6 @@ class Client:
def key(self): def key(self):
return self._key return self._key
@property
def nick(self):
return self._nick
@property
def roles(self):
return self._roles
@property @property
def guest(self): def guest(self):
return self._guest return self._guest
@ -79,14 +75,14 @@ class Client:
async def kick(self, reason): async def kick(self, reason):
if not self.__alive: if not self.__alive:
self.log.debug(f"Kick({reason}) skipped;") self.log.debug(f"{self.nick}.kick('{reason}') skipped: Not alive;")
return return
# TODO: i18n # TODO: i18n
self.log.info(f"Kicked with reason: \"{reason}\"") self.log.info(f"Kicked with reason: \"{reason}\"")
await self._tcp_send(b"K" + bytes(reason, "utf-8")) await self._send(b"K" + bytes(reason, "utf-8"))
self.__alive = False self.__alive = False
async def _tcp_send(self, data, to_all=False, to_self=True, to_udp=False, writer=None): async def _send(self, data, to_all=False, to_self=True, to_udp=False, writer=None):
# TNetwork.cpp; Line: 383 # TNetwork.cpp; Line: 383
# BeamMP TCP protocol sends a header of 4 bytes, followed by the data. # BeamMP TCP protocol sends a header of 4 bytes, followed by the data.
@ -94,23 +90,26 @@ class Client:
# ^------^^---...-^ # ^------^^---...-^
# size data # size data
if type(data) == str:
data = bytes(data, "utf-8")
if writer is None: if writer is None:
writer = self.__writer writer = self.__writer
if to_all: if to_all:
code = data[:1] code = chr(data[0])
for client in self.__Core.clients: for client in self.__Core.clients:
if not client or (client == self and not to_self): if not client or (client is self and not to_self):
continue continue
if not to_udp or code in [b'W', b'Y', b'V', b'E']: if not to_udp or code in ['V', 'W', 'Y', 'E']:
if code in [b'O', b'T'] or len(data) > 1000: if code in ['O', 'T'] or len(data) > 1000:
# TODO: Compress data # TODO: Compress data
await client._tcp_send(data) await client._send(data)
else: else:
await client._tcp_send(data) await client._send(data)
else: else:
# TODO: UDP send # TODO: UDP send
pass self.log.debug(f"UDP Part not ready: {code}")
return return
header = len(data).to_bytes(4, "little", signed=True) header = len(data).to_bytes(4, "little", signed=True)
@ -198,10 +197,10 @@ class Client:
break break
self.log.debug(f"Mode size: {size}") self.log.debug(f"Mode size: {size}")
if size == -1: if size == -1:
await self._tcp_send(b"CO") await self._send(b"CO")
await self.kick(f"Not allowed mod: " + file) await self.kick(f"Not allowed mod: " + file)
return return
await self._tcp_send(b"AG") await self._send(b"AG")
t = 0 t = 0
while not self._down_rw[0]: while not self._down_rw[0]:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
@ -236,42 +235,40 @@ class Client:
mod_list = path_list + size_list mod_list = path_list + size_list
self.log.debug(f"Mods List: {mod_list}") self.log.debug(f"Mods List: {mod_list}")
if len(mod_list) == 0: if len(mod_list) == 0:
await self._tcp_send(b"-") await self._send(b"-")
else: else:
await self._tcp_send(bytes(mod_list, "utf-8")) await self._send(bytes(mod_list, "utf-8"))
elif data == b"Done": elif data == b"Done":
await self._tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json") await self._send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json")
break break
return return
async def _looper(self): async def _looper(self):
await self._tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID await self._send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID
await self._sync_resources() await self._sync_resources()
# TODO: GlobalParser
while self.__alive: while self.__alive:
data = await self._recv() data = await self._recv()
if not data: if not data:
self.__alive = False self.__alive = False
break break
# V to Y
if 89 >= data[0] >= 86: if 89 >= data[0] >= 86:
# TODO: Network.SendToAll await self._send(data, to_all=True, to_self=False)
pass
code = data.decode()[0] code = chr(data[0])
self.log.debug(f"Received code: {code}, data: {data}") self.log.debug(f"Received code: {code}, data: {data}")
match code: match code:
case "H": case "H":
# Client connected # Client connected
ev.call_event("player_join", player=self)
await ev.call_async_event("player_join", player=self)
await self._send(f"Sn{self.nick}", to_all=True) # I don't know for what it
await self._send(f"JWelcome {self.nick}!", to_all=True) # Hello message
self._ready = True self._ready = True
ev.call_event("player_join", self)
await ev.call_async_event("player_join", self)
bnick = bytes(self.nick, "utf-8")
await self._tcp_send(b"Sn" + bnick, to_all=True) # I don't know for what it
await self._tcp_send(b"JWelcome" + bnick + b"!", to_all=True) # Hello message
# TODO: Sync cars # TODO: Sync cars
# for client in self.__Core.clients: # for client in self.__Core.clients:
# for car in client.cars: # for car in client.cars:
@ -279,17 +276,40 @@ class Client:
case "C": case "C":
# Chat # Chat
msg = data[2:].decode() msg = data.decode()[4 + len(self.nick):]
if not msg: if not msg:
self.log.debug("Tried to send an empty event, ignoring") self.log.debug("Tried to send an empty event, ignoring")
continue continue
self.log.info(f"Received message: {msg}") self.log.info(f"Received message: {msg}")
# TODO: Handle chat event # TODO: Handle chat event
ev_data = ev.call_event("chat_receive", msg) to_ev = {"message": msg, "player": self}
d2 = await ev.call_async_event("chat_receive", msg) ev_data_list = ev.call_event("chat_receive", **to_ev)
ev_data.extend(d2) d2 = await ev.call_async_event("chat_receive", **to_ev)
self.log.info(f"TODO: Handle chat event; {ev_data}") ev_data_list.extend(d2)
await self._tcp_send(data, to_all=True) need_send = True
for ev_data in ev_data_list:
try:
message = ev_data["message"]
to_all = ev_data.get("to_all")
if to_all is None:
if need_send:
need_send = False
to_all = True
if to_all:
if need_send:
need_send = False
to_self = ev_data.get("to_self")
if to_self is None:
to_self = True
to_client = ev_data.get("to_client")
writer = None
if to_client:
writer = to_client._writer
await self._send(f"C:{message}", to_all=to_all, to_self=to_self, writer=writer)
except KeyError | AttributeError:
self.log.error(f"Returns invalid data: {ev_data}")
if need_send:
await self._send(data, to_all=True)
case "O": case "O":
# TODO: ParseVehicle # TODO: ParseVehicle

View File

@ -1,5 +1,7 @@
import asyncio import asyncio
import string
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
from logging import Logger
from typing import Tuple from typing import Tuple
from core import Core, utils from core import Core, utils
@ -17,38 +19,28 @@ class Client:
self.__Core = core self.__Core = core
self._cid: int = -1 self._cid: int = -1
self._key: str = None self._key: str = None
self._nick: str = None self.nick: str = None
self._roles: str = None self.roles: str = None
self._guest = True self._guest = True
self.__alive = True self.__alive = True
self._ready = False self._ready = False
@property @property
def log(self): def _writer(self) -> StreamWriter: ...
return self._log
@property @property
def addr(self): def log(self) -> Logger: ...
return self._addr
@property @property
def cid(self): def addr(self) -> Tuple[str, int]: ...
return self._cid
@property @property
def key(self): def cid(self) -> int: ...
return self._key
@property @property
def nick(self): def key(self) -> str: ...
return self._nick
@property @property
def roles(self): def guest(self) -> bool: ...
return self._roles
@property @property
def guest(self): def ready(self) -> bool: ...
return self._guest
@property
def ready(self):
return self._ready
def is_disconnected(self) -> bool: ... def is_disconnected(self) -> bool: ...
async def kick(self, reason: str) -> None: ... async def kick(self, reason: str) -> None: ...
async def _tcp_send(self, data: bytes, to_all: bool = False, to_self: bool = True, to_udp: bool = False, writer: StreamWriter = None) -> None: ... async def _send(self, data: bytes | str, to_all: bool = False, to_self: bool = True, to_udp: bool = False, writer: StreamWriter = None) -> None: ...
async def _sync_resources(self) -> None: ... async def _sync_resources(self) -> None: ...
async def _recv(self) -> bytes: ... async def _recv(self) -> bytes: ...
async def _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ... async def _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ...