From 6a044ecaf87e092388b3a1ac3659be876ac0745e Mon Sep 17 00:00:00 2001 From: mirai Date: Sun, 3 Dec 2023 16:18:09 +0300 Subject: [PATCH] Working on 1.1.22 --- README.MD | 8 ++ cmd_chat/client/client.py | 103 +++++++---------------- cmd_chat/client/config.py | 3 +- cmd_chat/client/core/abs/abs_renderer.py | 24 ++++++ cmd_chat/client/core/default_renderer.py | 59 +++++++++++++ cmd_chat/client/core/rich_renderer.py | 77 +++++++++++++++++ requirements.txt | 3 +- setup.py | 2 +- 8 files changed, 204 insertions(+), 75 deletions(-) create mode 100644 cmd_chat/client/core/abs/abs_renderer.py create mode 100644 cmd_chat/client/core/default_renderer.py create mode 100644 cmd_chat/client/core/rich_renderer.py diff --git a/README.MD b/README.MD index 79752de..da1c1c2 100644 --- a/README.MD +++ b/README.MD @@ -58,3 +58,11 @@ How does encryption work? * Sometime WS just drop connection * Client input message problem. To start input, you need to press enter first, only after that you got pop up with message. Tried to fix, but nothing worked. + +# 1.1.22 + +- Renderer logics have been separated +- A new renderer have been implemented +- Thread closing logics have been changed. Now, it is easy to quit from the client without any unexpected behavior +- Now, displaying messages is limited to the last N messages. The default value is 5 +- WS dropped connection, probably fixed \ No newline at end of file diff --git a/cmd_chat/client/client.py b/cmd_chat/client/client.py index dd1781b..52b21ab 100644 --- a/cmd_chat/client/client.py +++ b/cmd_chat/client/client.py @@ -1,25 +1,24 @@ -import os import ast import time -import platform import threading -from colorama import init from websocket import create_connection from cmd_chat.client.core.crypto import RSAService -from cmd_chat.client.config import ( - COLORS, - RENDER_TIME -) +from cmd_chat.client.core.default_renderer import DefaultClientRenderer +from cmd_chat.client.core.rich_renderer import RichClientRenderer + +from cmd_chat.client.config import RENDER_TIME -init() +class Client(RSAService, RichClientRenderer): - -class Client(RSAService): - - def __init__(self, server: str, port: int, username: str): + def __init__( + self, + server: str, + port: int, + username: str + ): super().__init__() # Server info self.server = server @@ -35,77 +34,34 @@ class Client(RSAService): "action": "close", "username": self.username }) - - def __get_os(self) -> str: - """ checking what kind of platform you need - """ - if "Linux" in str(platform.platform()): - return "Linux" - return "Windows" + # Threads + self.__stop_threads = False def send_info(self): """ sending message to websocket """ ws = create_connection(f"{self.ws_url}/talk") - while True: + while not self.__stop_threads: try: user_input = input("You're message: ") + if user_input == "q": + self.__stop_threads = True message = f'{self.username}: {user_input}' socket_message = str({ "text": self._encrypt(message), "username": self.username }) - ws.send( - payload=socket_message.encode() - ) + ws.send(payload=socket_message.encode()) except KeyboardInterrupt: ws.send(self.close_response) ws.close() - quit() + self.__stop_threads = True except Exception as exc: ws.send(self.close_response) ws.close() print("Something went wrong! ", exc) - quit() - - def __print_message(self, message: str) -> str: - """ generating string with message in required format - """ - message = message.split(":") - if message[0] == self.username: - return COLORS["my_username_color"] + message[0] + ": " + message[1] + COLORS["text_color"] - return message[0] + ": " + message[1] + COLORS["text_color"] - - def __clear_console(self): - # For windows clear command its cls - # For linux clear command its clear - if self.__get_os() == "Linux": - os.system("clear") - else: - os.system("cls") - - def __print_ip( - self, - ip: str - ) -> str: - return f"IP: " + COLORS["ip_color"] + ip + COLORS["text_color"] - - def __print_username( - self, - username: str - ) -> str: - return f"USERNAME: " + COLORS["ip_color"] + username + COLORS["username_color"] - - def __print_chat(self, response: list[str]) -> str: - for i, msg in enumerate(response["messages"]): - actual_message = self._decrypt(msg) - if i == 0: - for user in response["users_in_chat"]: - print(self.__print_ip(user.split(",")[0])) - print(self.__print_username(user.split(",")[1])) - print(f"\n{self.__print_message(actual_message)}") - else: - print(f"{self.__print_message(actual_message)}") + self.__stop_threads = True + raise exc def update_info(self): """ connecting to websocket, @@ -114,28 +70,31 @@ class Client(RSAService): """ ws = create_connection(f"{self.ws_url}/update") last_try = None - while True: + while not self.__stop_threads: try: time.sleep(RENDER_TIME) response = ast.literal_eval(ws.recv().decode('utf-8')) if last_try == response: continue last_try = response - self.__clear_console() + self.clear_console() if len(last_try["messages"]) > 0: - self.__print_chat( - response = last_try - ) + self.print_chat(response = last_try) except KeyboardInterrupt: ws.send(self.close_response) ws.close() - quit() + self.__stop_threads = True + except ConnectionAbortedError: + # Reconnect if somehow client was disconnected + ws = create_connection(f"{self.ws_url}/update") + continue except Exception as exc: ws.send(self.close_response) ws.close() print("Something went wrong! ", exc) - quit() - + self.__stop_threads = True + raise exc + def _validate_keys(self) -> None: self._request_key(self.key_url, self.username) self._remove_keys() diff --git a/cmd_chat/client/config.py b/cmd_chat/client/config.py index 3493884..2a24f18 100644 --- a/cmd_chat/client/config.py +++ b/cmd_chat/client/config.py @@ -7,4 +7,5 @@ COLORS = { "username_color": Fore.GREEN } -RENDER_TIME = 0.05 \ No newline at end of file +RENDER_TIME = 0.05 +MESSAGES_TO_SHOW = 5 \ No newline at end of file diff --git a/cmd_chat/client/core/abs/abs_renderer.py b/cmd_chat/client/core/abs/abs_renderer.py new file mode 100644 index 0000000..eb04191 --- /dev/null +++ b/cmd_chat/client/core/abs/abs_renderer.py @@ -0,0 +1,24 @@ +from abc import ABC, abstractmethod + + +class ClientRenderer(ABC): + + @abstractmethod + def print_message(self, message: str) -> str: + raise NotImplementedError("Need to implement print_message") + + @abstractmethod + def clear_console(self, message: str) -> str: + raise NotImplementedError("Need to implement clear_console") + + @abstractmethod + def print_ip(self, url: str, username: str): + raise NotImplementedError("Need to implement print_ip") + + @abstractmethod + def print_username(self): + raise NotImplementedError("Need to implement print_username") + + @abstractmethod + def print_chat(self) -> list[str]: + raise NotImplementedError("Need to implement print_chat") diff --git a/cmd_chat/client/core/default_renderer.py b/cmd_chat/client/core/default_renderer.py new file mode 100644 index 0000000..f6a130c --- /dev/null +++ b/cmd_chat/client/core/default_renderer.py @@ -0,0 +1,59 @@ +import os +import platform + +from cmd_chat.client.core.abs.abs_renderer import ClientRenderer +from cmd_chat.client.config import COLORS + +from colorama import init + +init() + + +class DefaultClientRenderer(ClientRenderer): + + def __get_os(self) -> str: + """ checking what kind of platform you need + """ + if "Linux" in str(platform.platform()): + return "Linux" + return "Windows" + + def print_message(self, message: str) -> str: + """ generating string with message in required format + """ + message = message.split(":") + if message[0] == self.username: + return COLORS["my_username_color"] + message[0] + ": " + message[1] + COLORS["text_color"] + return message[0] + ": " + message[1] + COLORS["text_color"] + + def clear_console(self): + # For windows clear command its cls + # For linux clear command its clear + if self.__get_os() == "Linux": + os.system("clear") + else: + os.system("cls") + + def print_ip( + self, + ip: str + ) -> str: + return f"IP: " + COLORS["ip_color"] + ip + COLORS["text_color"] + + def print_username( + self, + username: str + ) -> str: + return f"USERNAME: " + COLORS["ip_color"] + username + COLORS["username_color"] + + def print_chat(self, response: list[str]) -> str: + for i, msg in enumerate(response["messages"]): + actual_message = self._decrypt(msg) + if i == 0: + for user in response["users_in_chat"]: + print(self.print_ip(user.split(",")[0])) + print(self.print_username(user.split(",")[1])) + print("Write 'q' to quit from chat") + print(f"\n{self.print_message(actual_message)}") + else: + print(f"{self.print_message(actual_message)}") diff --git a/cmd_chat/client/core/rich_renderer.py b/cmd_chat/client/core/rich_renderer.py new file mode 100644 index 0000000..246eb2b --- /dev/null +++ b/cmd_chat/client/core/rich_renderer.py @@ -0,0 +1,77 @@ +import os +import platform + +from rich.text import Text +from rich.style import Style +from rich.console import Console + +from rich.table import Table +from cmd_chat.client.core.abs.abs_renderer import ClientRenderer +from cmd_chat.client.config import MESSAGES_TO_SHOW + + +console = Console(width=75) + + +class RichClientRenderer(ClientRenderer): + + def __get_os(self) -> str: + """ checking what kind of platform you need + """ + if "Linux" in str(platform.platform()): + return "Linux" + return "Windows" + + def print_message(self, message: str) -> Text: + """ generating string with message in required format + """ + message = message.split(":") + if message[0] == self.username: + return \ + Text(text=message[0], style="bold") + \ + Text(text=": ", style="bold") + \ + Text(text=message[1], style="underline") + return \ + Text(text=message[0], style="bold") + \ + Text(text=": ", style="bold") + \ + Text(text=message[1], style="underline") + + def clear_console(self): + # For windows clear command its cls + # For linux clear command its clear + if self.__get_os() == "Linux": + os.system("clear") + else: + os.system("cls") + + def print_ip( + self, + ip: str + ) -> str: + return ip + + def print_username( + self, + username: str + ) -> str: + return username + + def print_chat(self, response: list[str]) -> str: + self.clear_console() + for i, msg in enumerate(response["messages"][-MESSAGES_TO_SHOW:]): + actual_message = self._decrypt(msg) + if i == 0: + console.print("Users in chat:", justify="left") + table = Table(show_header=True, header_style="bold magenta") + table.add_column("IP", style="dim", width=12) + table.add_column("USERNAME") + for user in response["users_in_chat"]: + table.add_row( + self.print_ip(user.split(',')[0]), + self.print_username(user.split(",")[1]) + ) + console.print(table) + console.print("Write 'q' to quit from chat", justify="left") + console.print(f"\n{self.print_message(actual_message)}") + else: + console.print(f"{self.print_message(actual_message)}") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c039dcd..a3cdb17 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ cryptography colorama pydantic websocket-client -flask \ No newline at end of file +flask +rich \ No newline at end of file diff --git a/setup.py b/setup.py index acb5d13..abe0895 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh: setuptools.setup( name="secured_console_chat", - version="1.1.21", + version="1.1.22", author="dinosaurtirex", author_email="sneakybeaky18@gmail.com", packages=[