diff --git a/nightwatch/__main__.py b/nightwatch/__main__.py deleted file mode 100644 index 74e27b3..0000000 --- a/nightwatch/__main__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2024 iiPython - -from nightwatch.config import fetch_config - -# Initialization -def main() -> None: - from argparse import ArgumentParser - from nightwatch.client import start_client - - # Handle CLI options - ap = ArgumentParser( - prog = "nightwatch", - description = "The chatting application to end all chatting applications.\nhttps://github.com/iiPythonx/nightwatch", - epilog = "Copyright (c) 2024 iiPython" - ) - ap.add_argument("-a", "--address", help = "the nightwatch server to connect to") - ap.add_argument("-u", "--username", help = "the username to use") - ap.add_argument("-r", "--reset", action = "store_true", help = "reset the configuration file") - - # Launch client - args = ap.parse_args() - if args.reset: - fetch_config("config").reset() - - start_client(args.address, args.username) diff --git a/nightwatch/client/__init__.py b/nightwatch/client/__init__.py index 80319dd..26c6be5 100644 --- a/nightwatch/client/__init__.py +++ b/nightwatch/client/__init__.py @@ -1,138 +1,4 @@ # Copyright (c) 2024 iiPython -# Modules -import os -import re -import typing -import requests -from threading import Thread - -import urwid -import websockets -from websockets.sync.client import connect - -from nightwatch import __version__, HEX_COLOR_REGEX from nightwatch.config import fetch_config - -from .extra.ui import NightwatchUI -from .extra.select import menu -from .extra.wswrap import ORJSONWebSocket - -# Initialization -config = fetch_config("config") - -if os.name == "nt": - urwid.set_encoding("utf-8") - -# Connection handler -def connect_loop(host: str, port: int, username: str) -> None: - protocol, url = "s" if port == 443 else "", f"{host}:{port}" - - # Perform authentication - resp = requests.post(f"http{protocol}://{url}/api/join", json = {"username": username, "hex": config["client.color"]}).json() - if resp.get("code") != 200: - exit(f"\nCould not connect to {url}. Additional details:\n{resp}") - - destination = f"ws{protocol}://{url}/api/ws?authorization={resp['authorization']}" - try: - with connect(destination) as ws: - ws = ORJSONWebSocket(ws) - - # Handle fetching server information - response = ws.recv() - - # Create UI - ui = NightwatchUI(ws) - loop = urwid.MainLoop(ui.frame, [ - ("yellow", "yellow", ""), - ("gray", "dark gray", "", "", "#555753", ""), - ("green", "dark green", "") - ]) - loop.screen.set_terminal_properties(2 ** 24) # Activate 24-bit color mode - - # Individual components - loop.screen.register_palette_entry("time", "dark green", "", foreground_high = config["colors.time"] or "#00FF00") - loop.screen.register_palette_entry("sep", "dark gray", "", foreground_high = config["colors.sep"] or "#555753") - - # Handle messages - def message_loop(ws: ORJSONWebSocket, ui: NightwatchUI) -> None: - try: - while ws.ws: - ui.on_message(ws.recv()) - - except websockets.exceptions.ConnectionClosed: - pass - - Thread(target = message_loop, args = [ws, ui]).start() - - # Start mainloop - ui.on_ready(loop, response["data"]) - loop.run() - - except websockets.exceptions.InvalidURI: - exit(f"\nCould not connect to {destination} due to an HTTP redirect.\nPlease ensure you entered the correct address.") - - except OSError: - exit(f"\nCould not connect to {destination} due to an OSError.\nThis is more then likely because the server is not running.") - -# Entrypoint -def start_client( - address: typing.Optional[str] = None, - username: typing.Optional[str] = None -): - username = username or config["client.username"] - - # Start main UI - print(f"\033[H\033[2J✨ Nightwatch | v{__version__}\n") - if username is None: - print("Hello! It seems that this is your first time using Nightwatch.") - print("Before you can connect to a server, please set your desired username.\n") - - username = input("Username: ") - config.set("client.username", username) - print("\033[4A\033[0J", end = "") # Reset back up to the Nightwatch label - - # Handle color setup - color = config["client.color"] or "" - if not re.match(HEX_COLOR_REGEX, color): - while True: - print("For fun, you can select a color for your username.") - print("Please enter the HEX code (6 long) you would like to have as your color.") - color = (input("> #") or "ffffff").lstrip("#") - - # Validate their color choice - if re.match(HEX_COLOR_REGEX, color): - break - - print("\033[3A\033[0J", end = "") - - print("\033[3A\033[0J", end = "") - config.set("client.color", color) - - # Handle server address - if address is None: - servers = config["client.servers"] - if servers is None: - servers = ["nightwatch.iipython.dev"] - config.set("client.servers", servers) - - print(f"Hello, {username}. Please select a Nightwatch server to connect to:") - address = menu.show(servers) - print() - - print(f"Establishing connection to {address} ...") - - # Parse said address - if ":" not in address: - host, port = address, 443 - - else: - host, port = address.split(":") - - # Connect to server - try: - connect_loop(host, int(port), username) - - except KeyboardInterrupt: - print("\033[5A\033[0J", end = "") # Reset back up to the Nightwatch label - print(f"Goodbye, {username}.") +config = fetch_config("client") diff --git a/nightwatch/client/__main__.py b/nightwatch/client/__main__.py new file mode 100644 index 0000000..3fb8c33 --- /dev/null +++ b/nightwatch/client/__main__.py @@ -0,0 +1,155 @@ +# Copyright (c) 2024 iiPython + +# Modules +import os +import re +import typing +import requests +from threading import Thread +from argparse import ArgumentParser + +import urwid +import websockets +from websockets.sync.client import connect + +from nightwatch import __version__, HEX_COLOR_REGEX + +from . import config +from .extra.ui import NightwatchUI +from .extra.select import menu +from .extra.wswrap import ORJSONWebSocket + +# Initialization +if os.name == "nt": + urwid.set_encoding("utf-8") + +# Connection handler +def connect_loop(host: str, port: int, username: str) -> None: + protocol, url = "s" if port == 443 else "", f"{host}:{port}" + + # Perform authentication + resp = requests.post(f"http{protocol}://{url}/api/join", json = {"username": username, "hex": config["client.color"]}).json() + if resp.get("code") != 200: + exit(f"\nCould not connect to {url}. Additional details:\n{resp}") + + destination = f"ws{protocol}://{url}/api/ws?authorization={resp['authorization']}" + try: + with connect(destination) as ws: + ws = ORJSONWebSocket(ws) + + # Handle fetching server information + response = ws.recv() + + # Create UI + ui = NightwatchUI(ws) + loop = urwid.MainLoop(ui.frame, [ + ("yellow", "yellow", ""), + ("gray", "dark gray", "", "", "#555753", ""), + ("green", "dark green", "") + ]) + loop.screen.set_terminal_properties(2 ** 24) # Activate 24-bit color mode + + # Individual components + loop.screen.register_palette_entry("time", "dark green", "", foreground_high = config["colors.time"] or "#00FF00") + loop.screen.register_palette_entry("sep", "dark gray", "", foreground_high = config["colors.sep"] or "#555753") + + # Handle messages + def message_loop(ws: ORJSONWebSocket, ui: NightwatchUI) -> None: + try: + while ws.ws: + ui.on_message(ws.recv()) + + except websockets.exceptions.ConnectionClosed: + pass + + Thread(target = message_loop, args = [ws, ui]).start() + + # Start mainloop + ui.on_ready(loop, response["data"]) + loop.run() + + except websockets.exceptions.InvalidURI: + exit(f"\nCould not connect to {destination} due to an HTTP redirect.\nPlease ensure you entered the correct address.") + + except OSError: + exit(f"\nCould not connect to {destination} due to an OSError.\nThis is more then likely because the server is not running.") + +# Entrypoint +def start_client( + address: typing.Optional[str] = None, + username: typing.Optional[str] = None +): + username = username or config["client.username"] + + # Start main UI + print(f"\033[H\033[2J✨ Nightwatch | v{__version__}\n") + if username is None: + print("Hello! It seems that this is your first time using Nightwatch.") + print("Before you can connect to a server, please set your desired username.\n") + + username = input("Username: ") + config.set("client.username", username) + print("\033[4A\033[0J", end = "") # Reset back up to the Nightwatch label + + # Handle color setup + color = config["client.color"] or "" + if not re.match(HEX_COLOR_REGEX, color): + while True: + print("For fun, you can select a color for your username.") + print("Please enter the HEX code (6 long) you would like to have as your color.") + color = (input("> #") or "ffffff").lstrip("#") + + # Validate their color choice + if re.match(HEX_COLOR_REGEX, color): + break + + print("\033[3A\033[0J", end = "") + + print("\033[3A\033[0J", end = "") + config.set("client.color", color) + + # Handle server address + if address is None: + servers = config["client.servers"] + if servers is None: + servers = ["nightwatch.iipython.dev"] + config.set("client.servers", servers) + + print(f"Hello, {username}. Please select a Nightwatch server to connect to:") + address = menu.show(servers) + print() + + print(f"Establishing connection to {address} ...") + + # Parse said address + if ":" not in address: + host, port = address, 443 + + else: + host, port = address.split(":") + + # Connect to server + try: + connect_loop(host, int(port), username) + + except KeyboardInterrupt: + print("\033[5A\033[0J", end = "") # Reset back up to the Nightwatch label + print(f"Goodbye, {username}.") + +# Initialization +def main() -> None: + ap = ArgumentParser( + prog = "nightwatch", + description = "The chatting application to end all chatting applications.\nhttps://github.com/iiPythonx/nightwatch", + epilog = "Copyright (c) 2024 iiPython" + ) + ap.add_argument("-a", "--address", help = "the nightwatch server to connect to") + ap.add_argument("-u", "--username", help = "the username to use") + ap.add_argument("-r", "--reset", action = "store_true", help = "reset the configuration file") + + # Launch client + args = ap.parse_args() + if args.reset: + fetch_config("config").reset() + + start_client(args.address, args.username) diff --git a/nightwatch/client/extra/commands/__init__.py b/nightwatch/client/extra/commands/__init__.py index 13a12fa..7fed1f8 100644 --- a/nightwatch/client/extra/commands/__init__.py +++ b/nightwatch/client/extra/commands/__init__.py @@ -4,7 +4,7 @@ from typing import Callable from nightwatch import __version__ -from nightwatch.config import fetch_config +from nightwatch.client import config # Main class class BaseCommand(): @@ -47,7 +47,7 @@ def on_execute(self, args: list[str]) -> None: elif len(args) < 2: return self.print(f"Missing the value to assign to '{args[0]}'.") - fetch_config("config").set(args[0], args[1]) + config.set(args[0], args[1]) self.print(f"{args[0]} has been set to \"{args[1]}\".") class HelpCommand(BaseCommand): diff --git a/nightwatch/client/extra/ui.py b/nightwatch/client/extra/ui.py index 43808d4..3d05517 100644 --- a/nightwatch/client/extra/ui.py +++ b/nightwatch/client/extra/ui.py @@ -13,7 +13,7 @@ from .wswrap import ORJSONWebSocket from ..vendor.scroll import Scrollable, ScrollBar -from nightwatch.config import fetch_config +from nightwatch.client import config # Input edit class class InputEdit(urwid.Edit): @@ -71,7 +71,7 @@ def construct_message(self, author: str, content: str, user_color: str = "gray") visible_author = author if author != self.last_author else " " * self.length(author) now, time_string = datetime.now(), "" if (author != self.last_author) or ((now - self.last_time).total_seconds() > 300): - time_string = now.strftime("%I:%M %p" if fetch_config("config")["client.time_format"] != "24h" else "%H:%M") + " " # Right padding for the scrollbar + time_string = now.strftime("%I:%M %p" if config["client.time_format"] != "24h" else "%H:%M") + " " # Right padding for the scrollbar self.pile.contents.append((urwid.Columns([ (self.length(visible_author), urwid.Text((user_color, visible_author))), diff --git a/pyproject.toml b/pyproject.toml index 7e5468a..b9e23c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,4 +45,4 @@ Homepage = "https://github.com/iiPythonx/nightwatch" Issues = "https://github.com/iiPythonx/nightwatch/issues" [project.scripts] -nightwatch = "nightwatch.__main__:main" +nightwatch = "nightwatch.client.__main__:main"