diff --git a/client/gui.py b/client/gui.py index e7097d7..f281ead 100644 --- a/client/gui.py +++ b/client/gui.py @@ -100,7 +100,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.client.send(helpers.prepare_json( { 'type': constants.Types.MESSAGE, - 'content': message + 'content': message.strip() } )) diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..b37bec6 --- /dev/null +++ b/constants.py @@ -0,0 +1,10 @@ +class Requests: + REQUEST_NICK = 'REQUEST_NICK' + REFRESH_CONNECTIONS_LIST = 'REFRESH_CLIENT_LIST' + GET_HISTORY = 'GET_HISTORY' + +class Types: + REQUEST = 'REQUEST' + NICKNAME = 'NICKNAME' + USER_LIST = 'USER_LIST' + MESSAGE = 'MESSAGE' diff --git a/helpers.py b/helpers.py index 5a49db8..d4b192c 100644 --- a/helpers.py +++ b/helpers.py @@ -1,7 +1,19 @@ +import json + HEADER_LENGTH = 10 -def prepare(message: str, encoding='ascii') -> bytes: +def prepare(message: str, encoding='utf-8') -> bytes: """Prepares a message for sending through a socket by adding a proper header and encoding it.""" header = f'{len(message):<{HEADER_LENGTH}}' return (header + message).encode(encoding) + + +def prepare_json(object) -> bytes: + """ + Prepares a object for sending as encoded JSON with a header. + + :param object: A JSON-encodable object + :return: Encoded JSON + """ + return prepare(json.dumps(object)) diff --git a/launch.py b/launch.py index e69de29..63e5928 100644 --- a/launch.py +++ b/launch.py @@ -0,0 +1,8 @@ +from PyQt5.QtWidgets import QApplication +from client.gui import MainWindow + +app = QApplication([]) +app.setApplicationName("TCPChat Client") +m = MainWindow() + +app.exec_() diff --git a/oclient.py b/oclient.py index ce53cf3..699a9de 100644 --- a/oclient.py +++ b/oclient.py @@ -1,6 +1,7 @@ import socket import threading +import constants import helpers from config import config @@ -15,9 +16,9 @@ client.connect(('127.0.0.1', 55555)) def receive(): while True: try: - length = int(client.recv(HEADER_LENGTH).decode('ascii')) - message = client.recv(length).decode('ascii') - if message == 'NICK': + length = int(client.recv(HEADER_LENGTH).decode('utf-8')) + message = client.recv(length).decode('utf-8') + if message == constants.Requests.REQUEST_NICK: client.send(helpers.prepare(nickname)) else: print(message) @@ -30,7 +31,7 @@ def receive(): # Sending Messages To Server def write(): while True: - message = '{}: {}'.format(nickname, input('')) + message = input('> ') client.send(helpers.prepare(message)) diff --git a/oserver.py b/oserver.py index 1ca0e4e..42e19f4 100644 --- a/oserver.py +++ b/oserver.py @@ -1,6 +1,11 @@ +import json import socket import threading +import time +import traceback +import uuid +import constants import helpers from config import config @@ -15,35 +20,60 @@ server.bind((host, port)) server.listen() # Lists For Clients and Their Nicknames -clients = [] -nicknames = [] +clients = {} # Sending Messages To All Connected Clients def broadcast(message): - print(f'Broadcasting: "{message}"') - encoded = helpers.prepare(message) - for client in clients: - client.send(encoded) + print(f'"{message}"') + encoded = helpers.prepare_json({ + 'type': constants.Types.MESSAGE, + 'content': message + }) + for client in clients.values(): + client['client'].send(encoded) # Handling Messages From Clients -def handle(client): +def handle(client_id): + client = clients[client_id]['client'] + nickname = clients[client_id]['nickname'] + while True: try: # Broadcasting Messages - length = int(client.recv(HEADER_LENGTH).decode('ascii')) - message = client.recv(length).decode('ascii') - broadcast(message) + length = int(client.recv(HEADER_LENGTH).decode('utf-8')) + message = json.loads(client.recv(length).decode('utf-8')) + + if message['type'] == constants.Types.REQUEST: + if message['request'] == constants.Requests.REFRESH_CONNECTIONS_LIST: + client.send(helpers.prepare_json( + { + 'type': constants.Types.USER_LIST, + 'users': [ + clients[other]['nickname'] for other in clients.keys() if other != client_id + ] + } + )) + elif message['type'] == constants.Types.NICKNAME: + + nickname = message['nickname'] + if not clients[client_id]['has_nickname']: + print("Nickname is {}".format(nickname)) + broadcast("{} joined!".format(nickname)) + clients[client_id]['has_nickname'] = True + else: + print(f'{clients[client_id]["nickname"]} changed their name to {nickname}') + clients[client_id]['nickname'] = nickname + + elif message['type'] == constants.Types.MESSAGE: + broadcast(f'<{nickname}>: {message["content"]}') + except: # Removing And Closing Clients - index = clients.index(client) - clients.remove(client) client.close() - - nickname = nicknames[index] + del clients[client_id] broadcast('{} left!'.format(nickname)) - nicknames.remove(nickname) break @@ -52,22 +82,33 @@ def receive(): while True: # Accept Connection client, address = server.accept() - print("Connected with {}".format(str(address))) + print("New Client from {}".format(str(address))) # Request And Store Nickname - client.send(helpers.prepare('NICK')) - length = int(client.recv(HEADER_LENGTH).decode('ascii')) - nickname = client.recv(length).decode('ascii') - nicknames.append(nickname) - clients.append(client) + client_id = str(uuid.uuid4()) + client.send(helpers.prepare_json( + { + 'type': constants.Types.REQUEST, + 'request': constants.Requests.REQUEST_NICK + } + )) - # Print And Broadcast Nickname - print("Nickname is {}".format(nickname)) - broadcast("{} joined!".format(nickname)) - client.send(helpers.prepare('Connected to server!')) + clients[client_id] = { + 'client': client, + 'nickname': client_id[:10], + 'first_seen': int(time.time()), + 'has_nickname': False + } + + client.send(helpers.prepare_json( + { + 'type': constants.Types.MESSAGE, + 'content': 'Connected to server!' + } + )) # Start Handling Thread For Client - thread = threading.Thread(target=handle, args=(client,)) + thread = threading.Thread(target=handle, args=(client_id,)) thread.start() diff --git a/server/MainWindow.ui b/server/MainWindow.ui new file mode 100644 index 0000000..d836ca3 --- /dev/null +++ b/server/MainWindow.ui @@ -0,0 +1,120 @@ + + + MainWindow + + + + 0 + 0 + 800 + 651 + + + + MainWindow + + + + + 0 + 0 + + + + + + + Connections + + + + + + + + 0 + 0 + + + + + 16777215 + 80 + + + + + + + + + 0 + 5 + + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + + + + + Chat History + + + + + + + + + 0 + 0 + 800 + 21 + + + + + File + + + + + + + + + + + Connect to... + + + + + Export chat... + + + + + Quit + + + + + +