diff --git a/src/core/Client.py b/src/core/Client.py index 983d974..49ab782 100644 --- a/src/core/Client.py +++ b/src/core/Client.py @@ -8,48 +8,80 @@ from core import utils class Client: def __init__(self, reader, writer, core): - self.reader = reader - self.writer = writer - self.down_rw = (None, None) - self.log = utils.get_logger("client(None:0)") - self.addr = writer.get_extra_info("sockname") - self.loop = asyncio.get_event_loop() - self.Core = core - self.cid = -1 - self.key = None - self.nick = None - self.roles = None - self.guest = True - self.alive = True - self.ready = False + self.__reader = reader + self.__writer = writer + self._down_rw = (None, None) + self.__Core = core + self.__alive = True + self._loop = asyncio.get_event_loop() + self._log = utils.get_logger("client(None:0)") + self._addr = writer.get_extra_info("sockname") + self._cid = -1 + self._key = None + self._nick = None + self._roles = None + self._guest = True + self._ready = False + + @property + def log(self): + return self._log + + @property + def addr(self): + return self._addr + + @property + def cid(self): + return self._cid + + @property + def key(self): + return self._key + + @property + def nick(self): + return self._nick + + @property + def roles(self): + return self._roles + + @property + def guest(self): + return self._guest + + @property + def ready(self): + return self._ready def _update_logger(self): - self.log = utils.get_logger(f"{self.nick}:{self.cid})") + self._log = utils.get_logger(f"{self.nick}:{self.cid})") self.log.debug(f"Update logger") def is_disconnected(self): - if not self.alive: + if not self.__alive: return True - res = self.writer.is_closing() + res = self.__writer.is_closing() if res: self.log.debug(f"Disconnected.") - self.alive = False + self.__alive = False return True else: self.log.debug(f"Alive.") - self.alive = True + self.__alive = True return False async def kick(self, reason): - if not self.alive: + if not self.__alive: self.log.debug(f"Kick({reason}) skipped;") return # TODO: i18n self.log.info(f"Kicked with reason: \"{reason}\"") - await self.tcp_send(b"K" + bytes(reason, "utf-8")) - self.alive = False + await self._tcp_send(b"K" + bytes(reason, "utf-8")) + self.__alive = False - async def tcp_send(self, data, to_all=False, writer=None): + async def _tcp_send(self, data, to_all=False, writer=None): # TNetwork.cpp; Line: 383 # BeamMP TCP protocol sends a header of 4 bytes, followed by the data. @@ -58,13 +90,13 @@ class Client: # size data if writer is None: - writer = self.writer + writer = self.__writer if to_all: - for client in self.Core.clients: + for client in self.__Core.clients: if not client: continue - await client.tcp_send(data) + await client._tcp_send(data) return if len(data) == 10: @@ -76,11 +108,11 @@ class Client: await writer.drain() except ConnectionError: self.log.debug('tcp_send: Disconnected') - self.alive = False + self.__alive = False - async def recv(self): + async def _recv(self): try: - header = await self.reader.read(4) + header = await self.__reader.read(4) int_header = 0 for i in range(len(header)): @@ -89,7 +121,7 @@ class Client: if int_header <= 0: await asyncio.sleep(0.1) self.is_disconnected() - if self.alive: + if self.__alive: self.log.debug(f"Header: {header}") await self.kick("Invalid packet - header negative") return b"" @@ -100,7 +132,7 @@ class Client: f"assuming malicious intent and disconnecting the client.") return b"" - data = await self.reader.read(100 * MB) + data = await self.__reader.read(100 * MB) self.log.debug(f"header: `{header}`; int_header: `{int_header}`; data: `{data}`;") if len(data) != int_header: @@ -113,13 +145,13 @@ class Client: return data return data except ConnectionError: - self.alive = False + self.__alive = False return b"" - # TODO: Speed limiter async def _split_load(self, start, end, d_sock, filename): + # TODO: Speed limiter real_size = end - start - writer = self.down_rw[1] if d_sock else self.writer + writer = self._down_rw[1] if d_sock else self.__writer who = 'dwn' if d_sock else 'srv' if config.Server["debug"]: self.log.debug(f"[{who}] Real size: {real_size / MB}mb; {real_size == end}, {real_size * 2 == end}") @@ -132,20 +164,20 @@ class Client: await writer.drain() self.log.debug(f"[{who}] File sent.") except ConnectionError: - self.alive = False + self.__alive = False self.log.debug(f"[{who}] Disconnected.") return real_size - async def sync_resources(self): - while self.alive: - data = await self.recv() + async def _sync_resources(self): + while self.__alive: + data = await self._recv() self.log.debug(f"data: {data!r}") if data.startswith(b"f"): file = data[1:].decode("utf-8") # TODO: i18n self.log.info(f"Requested mode: {file!r}") size = -1 - for mod in self.Core.mods_list: + for mod in self.__Core.mods_list: if type(mod) == int: continue if mod.get('path') == file: @@ -154,12 +186,12 @@ class Client: break self.log.debug(f"Mode size: {size}") if size == -1: - await self.tcp_send(b"CO") + await self._tcp_send(b"CO") await self.kick(f"Not allowed mod: " + file) return - await self.tcp_send(b"AG") + await self._tcp_send(b"AG") t = 0 - while not self.down_rw[0]: + while not self._down_rw[0]: await asyncio.sleep(0.1) t += 1 if t > 50: @@ -177,14 +209,14 @@ class Client: lost = size - sent self.log.debug(f"SplitLoad_0: {sl0}; SplitLoad_1: {sl1}; At all ({ok}): Sent: {sent}; Lost: {lost}") if not ok: - self.alive = False + self.__alive = False # TODO: i18n self.log.error(f"Error while sending.") return elif data.startswith(b"SR"): path_list = '' size_list = '' - for mod in self.Core.mods_list: + for mod in self.__Core.mods_list: if type(mod) == int: continue path_list += f"{mod['path']};" @@ -192,21 +224,22 @@ class Client: mod_list = path_list + size_list self.log.debug(f"Mods List: {mod_list}") if len(mod_list) == 0: - await self.tcp_send(b"-") + await self._tcp_send(b"-") else: - await self.tcp_send(bytes(mod_list, "utf-8")) + await self._tcp_send(bytes(mod_list, "utf-8")) elif data == b"Done": - await self.tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json") + await self._tcp_send(b"M/levels/" + bytes(config.Game['map'], 'utf-8') + b"/info.json") break return - async def looper(self): - await self.tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID - await self.sync_resources() - while self.alive: - data = await self.recv() + async def _looper(self): + await self._tcp_send(b"P" + bytes(f"{self.cid}", "utf-8")) # Send clientID + await self._sync_resources() + # TODO: GlobalParser + while self.__alive: + data = await self._recv() if data == b"": - if not self.alive: + if not self.__alive: break else: await asyncio.sleep(.1) @@ -221,29 +254,33 @@ class Client: await self.tcp_send(b"Sn" + bytes(self.nick, "utf-8"), to_all=True) case "C": # Chat + self.log.info(f"Received message: {data!r}") # TODO: Handle chat event ev_data = ev.call_event("chat_receive", f"{data}") d2 = await ev.call_async_event("chat_receive", f"{data}") ev_data.extend(d2) - await self.tcp_send(data, to_all=True) + self.log.info(f"TODO: Handle chat event; {ev_data}") + await self._tcp_send(data, to_all=True) + case _: + pass - async def remove_me(self): + async def _remove_me(self): await asyncio.sleep(0.3) - self.alive = False + self.__alive = False if (self.cid > 0 or self.nick is not None) and \ - self.Core.clients_by_nick.get(self.nick): + self.__Core.clients_by_nick.get(self.nick): # if self.ready: # await self.tcp_send(b"", to_all=True) # I'm disconnected. self.log.debug(f"Removing client {self.nick}:{self.cid}") # TODO: i18n self.log.info("Disconnected") - self.Core.clients[self.cid] = None - self.Core.clients_by_id.pop(self.cid) - self.Core.clients_by_nick.pop(self.nick) + self.__Core.clients[self.cid] = None + self.__Core.clients_by_id.pop(self.cid) + self.__Core.clients_by_nick.pop(self.nick) else: self.log.debug(f"Removing client; Closing connection...") - if not self.writer.is_closing(): - self.writer.close() - _, down_w = self.down_rw + if not self.__writer.is_closing(): + self.__writer.close() + _, down_w = self._down_rw if down_w and not down_w.is_closing(): down_w.close() diff --git a/src/core/Client.pyi b/src/core/Client.pyi index 6e0fccb..aa6b40e 100644 --- a/src/core/Client.pyi +++ b/src/core/Client.pyi @@ -8,26 +8,50 @@ from core import Core, utils class Client: def __init__(self, reader: StreamReader, writer: StreamWriter, core: Core) -> "Client": - self.reader = reader - self.writer = writer - self.down_rw: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None) - self.log = utils.get_logger("client(id: )") - self.addr = writer.get_extra_info("sockname") - self.loop = asyncio.get_event_loop() - self.Core = core - self.cid: int = -1 - self.key: str = None - self.nick: str = None - self.roles: str = None - self.guest = True - self.alive = True - self.ready = False + self.__reader = reader + self.__writer = writer + self._down_rw: Tuple[StreamReader, StreamWriter] | Tuple[None, None] = (None, None) + self._log = utils.get_logger("client(id: )") + self._addr = writer.get_extra_info("sockname") + self._loop = asyncio.get_event_loop() + self.__Core = core + self._cid: int = -1 + self._key: str = None + self._nick: str = None + self._roles: str = None + self._guest = True + self.__alive = True + self._ready = False + @property + def log(self): + return self._log + @property + def addr(self): + return self._addr + @property + def cid(self): + return self._cid + @property + def key(self): + return self._key + @property + def nick(self): + return self._nick + @property + def roles(self): + return self._roles + @property + def guest(self): + return self._guest + @property + def ready(self): + return self._ready def is_disconnected(self) -> bool: ... async def kick(self, reason: str) -> None: ... - async def tcp_send(self, data: bytes, to_all:bool = False, writer: StreamWriter = None) -> None: ... - async def sync_resources(self) -> None: ... - async def recv(self) -> bytes: ... + async def _tcp_send(self, data: bytes, to_all:bool = False, writer: StreamWriter = None) -> None: ... + async def _sync_resources(self) -> None: ... + async def _recv(self) -> bytes: ... async def _split_load(self, start: int, end: int, d_sock: bool, filename: str) -> None: ... - async def looper(self) -> None: ... + async def _looper(self) -> None: ... def _update_logger(self) -> None: ... - async def remove_me(self) -> None: ... + async def _remove_me(self) -> None: ... diff --git a/src/core/core.py b/src/core/core.py index bb8ea6a..1e00fc3 100644 --- a/src/core/core.py +++ b/src/core/core.py @@ -65,7 +65,7 @@ class Core: break await asyncio.sleep(random.randint(3, 9) * 0.01) if not self.clients[cid]: - client.cid = cid + client._cid = cid self.clients_by_nick.update({client.nick: client}) self.log.debug(f"Inserting client: {client.nick}:{client.cid}") self.clients_by_id.update({client.cid: client}) @@ -85,7 +85,7 @@ class Core: for client in self.clients: if not client: continue - out += f"{client.nick}" + out += f"{client._nick}" if need_cid: out += f":{client.cid}" out += "," @@ -104,7 +104,7 @@ class Core: if not client.ready: client.is_disconnected() continue - await client.tcp_send(bytes(ca, "utf-8")) + await client._tcp_send(bytes(ca, "utf-8")) @staticmethod def start_web(): diff --git a/src/core/tcp_server.py b/src/core/tcp_server.py index af89f34..f91a537 100644 --- a/src/core/tcp_server.py +++ b/src/core/tcp_server.py @@ -25,36 +25,36 @@ class TCPServer: client = self.Core.create_client(reader, writer) # TODO: i18n self.log.info(f"Identifying new ClientConnection...") - data = await client.recv() + data = await client._recv() self.log.debug(f"Version: {data}") if data.decode("utf-8") != f"VC{self.Core.client_major_version}": # TODO: i18n await client.kick("Outdated Version.") return False, client else: - await client.tcp_send(b"S") # Accepted client version + await client._tcp_send(b"S") # Accepted client version - data = await client.recv() + data = await client._recv() self.log.debug(f"Key: {data}") if len(data) > 50: # TODO: i18n await client.kick("Invalid Key (too long)!") return False, client - client.key = data.decode("utf-8") + client._key = data.decode("utf-8") ev.call_event("auth_sent_key", client) try: async with aiohttp.ClientSession() as session: url = 'https://auth.beammp.com/pkToUser' - async with session.post(url, data={'key': client.key}) as response: + async with session.post(url, data={'key': client._key}) as response: res = await response.json() self.log.debug(f"res: {res}") if res.get("error"): # TODO: i18n await client.kick('Invalid key! Please restart your game.') return False, client - client.nick = res["username"] - client.roles = res["roles"] - client.guest = res["guest"] + client._nick = res["username"] + client._roles = res["roles"] + client._guest = res["guest"] # noinspection PyProtectedMember client._update_logger() except Exception as e: @@ -89,7 +89,7 @@ class TCPServer: cid = (await reader.read(1))[0] client = self.Core.get_client(cid=cid) if client: - client.down_rw = (reader, writer) + client._down_rw = (reader, writer) self.log.debug(f"Client: {client.nick}:{cid} - HandleDownload!") else: writer.close() @@ -102,7 +102,7 @@ class TCPServer: case "C": result, client = await self.auth_client(reader, writer) if result: - await client.looper() + await client._looper() return result, client case "D": await self.set_down_rw(reader, writer) @@ -128,7 +128,7 @@ class TCPServer: # await asyncio.wait([task], return_when=asyncio.FIRST_EXCEPTION) _, cl = await self.handle_code(code, reader, writer) if cl: - await cl.remove_me() + await cl._remove_me() del cl break except Exception as e: