{"id":22680316,"url":"https://github.com/elnurbda/python-chatroom","last_synced_at":"2025-10-24T05:45:44.965Z","repository":{"id":198758601,"uuid":"695968397","full_name":"ElnurBDa/python-chatroom","owner":"ElnurBDa","description":"Basic chatroom using asyncio and socket modules with end to end encryption ","archived":false,"fork":false,"pushed_at":"2023-12-25T18:56:28.000Z","size":43,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-04T14:45:43.414Z","etag":null,"topics":["asyncio","python","socket","socket-programming","suffering"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ElnurBDa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-09-24T18:48:33.000Z","updated_at":"2023-10-07T04:00:18.000Z","dependencies_parsed_at":"2025-02-04T14:38:00.318Z","dependency_job_id":"0c68d39f-5ccd-430a-a812-8ac8f8a5dac6","html_url":"https://github.com/ElnurBDa/python-chatroom","commit_stats":null,"previous_names":["elnurbda/python-chatroom"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElnurBDa%2Fpython-chatroom","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElnurBDa%2Fpython-chatroom/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElnurBDa%2Fpython-chatroom/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ElnurBDa%2Fpython-chatroom/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ElnurBDa","download_url":"https://codeload.github.com/ElnurBDa/python-chatroom/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246193156,"owners_count":20738450,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncio","python","socket","socket-programming","suffering"],"created_at":"2024-12-09T19:13:16.001Z","updated_at":"2025-10-24T05:45:44.429Z","avatar_url":"https://github.com/ElnurBDa.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"﻿# What\r\nbasic chat room with basic functionalities\r\n\r\n# How\r\nrun server\r\n```bash\r\npython server.py\r\n```\r\n\r\nconnect via telnet (with older versions)\r\n```bash\r\ntelnet \u003cip\u003e \u003cport\u003e\r\n```\r\nor\r\nrun client code\r\n```bash\r\npython client.py\r\n```\r\n\r\n# To-Do\r\n- [x] basic chat where client talks to all clients (boradcasting with no chatroom)\r\n- [x] chatrooms where clients can talk to others located in the same chatroom\r\n- [x] rewrite server code, because it looks awful (it is still)\r\n- [x] add client.py code instead of using telnet\r\n- [x] add ids for clients and chats \r\n- [x] add some database\r\n    - python file is db\r\n- [x] end-to-end encryption\r\n    - [x] a lot of errors occur, something need to be done, but it is enough for now\r\n        - end-to-end works perfectly fine\r\n- [x] add some documentation on code\r\n- the code needs a lot of enhancements, I think some parts are not understandable or lacks optimization\r\n\r\n# NOTE!!!\r\nThis project is designed for inspiration! Do not fool your teacher with a little modified my project! \r\n\r\n# Documentation\r\n---\r\n## Some notations\r\nThose are designed by the author\r\n- for exchanging key\r\n\t- `e2ek|||client_id|||public_key`\r\n- for sending encrypted message \r\n\t- `e2em|||client_id|||encrypted_message`\r\n---\r\n## Required modules\r\n```python\r\n# Client side imports\r\nimport socket\r\nimport threading\r\nimport rsa\r\n# Server side imports\r\nimport asyncio \r\nfrom utils import * # it is for id generation by me\r\nfrom enum import Enum # it is not that important, but why not\r\n```\r\n---\r\n## Client Side\r\n---\r\n### Main\r\nit is main part where two threads start\r\n```python\r\ndef main():\r\n    mysocket = MySocket()\r\n    receiver = threading.Thread(target=mysocket.receive_messages)\r\n    mysocket.sock.send(mysocket.public_key.save_pkcs1()) # send public key \r\n    receiver.start()\r\n    try:\r\n        mysocket.send_messages()\r\n    except KeyboardInterrupt:\r\n        print(\"Connection closed by user.\")\r\n    finally:\r\n        mysocket.sock.close()\r\n```\r\n---\r\n### Receiving message\r\n```python\r\ndef receive_messages(self):\r\n\twhile True:\r\n\t\tdata = self.sock.recv(4096)\r\n\t\tmessage = data.decode().strip()\r\n\t\tif \"e2em|||\" in message: # if message is with encrypted message\r\n\t\t\tname, client_id, encrypted_message = message.split(\"|||\") # `e2em|||client_id|||encrypted_message`\r\n\t\t\tdecrypted_message = rsa.decrypt(eval(encrypted_message), self.private_key).decode()\r\n\t\t\tprint(f\"\u003c# {name[:-4]} \u003e{decrypted_message}\") \r\n\t\telif \"e2ek|||\" in message: # if message is with other client's public key \r\n\t\t\tparts = message.split(\"e2ek|||\") # ['', `e2ek|||client_id|||public_key`,`e2ek|||client_id|||public_key`, ...]\r\n\t\t\tfor part in parts[1:]: # `e2ek|||client_id|||public_key`\r\n\t\t\t\tclient_id, public_key_encoded = part.split(\"|||\")\r\n\t\t\t\tself.other_clients_in_chat[client_id] = rsa.PublicKey.load_pkcs1(public_key_encoded) # adds other client's public key\r\n\t\telse:\r\n\t\t\tprint(f\"\u003c$ {message}\") # if message is with no encryption\r\n```\r\n---\r\n### Sending message\r\n```python\r\ndef send_messages(self):\r\n\twhile True:\r\n\t\tmessage = input(\"\u003e \")\r\n\t\tif message.lower() == 'exit': break\r\n\t\tif len(self.other_clients_in_chat) == 0: # if client has not received other client's key\r\n\t\t\tself.sock.send(message.encode())\r\n\t\telse: # we are in room with other clients and chatting is e2e\r\n\t\t\tfor client_id, public_key in self.other_clients_in_chat.items():\r\n\t\t\t\tencrypted_message = rsa.encrypt(message.encode(), public_key)\r\n\t\t\t\tself.sock.send(f\"e2em|||{client_id}|||{encrypted_message}\".encode())\r\n```\r\n---\r\n## Server Side\r\n---\r\n### Some variables\r\n```python\r\nclass Options(Enum): # for chat room functionality\r\n    CREATE = 1\r\n    SELECT = 2\r\n\r\nclients = clients_db # {'id': {'name':\"bob\", 'writer':\u003cwriter\u003e, 'chatroom_id':\"1\", 'publicKey':\"\"}}\r\nchatrooms = chatrooms_db # {'chatroom_id': 'chatroom_name'}\r\n```\r\n---\r\n### Initializing\r\n```python\r\nclass Client:\r\n    def __init__(self, writer=\"\", reader=\"\"):\r\n        self.id = \"\"\r\n        self.writer = writer\r\n        self.reader = reader\r\n        self.name = \"\"\r\n        self.chatroom_id = \"\"\r\n        self.publicKey = \"\"\r\n        self.client_address = writer.get_extra_info('peername')\r\n        print(f\"New connection from {self.client_address}\")\r\n```\r\n---\r\n### Sending and Receiving\r\n```python\r\nasync def send_message(self, message):\r\n\tself.writer.write(message.encode())\r\n\tawait self.writer.drain()\r\n\r\nasync def receive_message(self):\r\n\tdata = await self.reader.read(header)\r\n\treturn data.decode().strip() \r\n\r\nasync def client_req_and_res(self, message): # ask question and receive answer in one function\r\n\tawait self.send_message(message)\r\n\treturn await self.receive_message()\r\n```\r\n---\r\n### Multicasting and Broadcasting\r\n```python\r\nasync def broadcast_to_all(self, message):\r\n\tfor user_id, values in clients.items():\r\n\t\tif user_id != self.id:\r\n\t\t\tawait send_message(values['writer'],f\"{message}\")\r\n\r\nasync def multicast_to_chat(self, message):        \r\n\tfor user_id, values in clients.items():\r\n\t\tif user_id != self.id and values['chatroom_id'] == self.chatroom_id:\r\n\t\t\tawait send_message(values['writer'],f\"{message}\")\r\n```\r\n---\r\n### Main\r\n```python\r\nasync def main():\r\n    print(\"Server is started\")\r\n    try:\r\n        server = await asyncio.start_server(handle_client, ip, port)\r\n        async with server:\r\n            await server.serve_forever()\r\n    finally:\r\n        # handle db\r\n        # ...\r\n```\r\n---\r\n### Handle Client\r\n```python\r\nasync def handle_client(reader, writer):\r\n    client = Client(writer, reader)\r\n    await client.get_publicKey()\r\n    print(f\"{client.publicKey}\")\r\n    await client.choose_name()\r\n    await client.choose_chat()\r\n    clients[client.id] = client.get_user_profile() # adding user into \"db\"\r\n    await client.send_message(f\"Starting end-to-end encryption!\\n\")\r\n    await client.multicast_to_chat(f\"e2ek|||{client.id}|||{client.publicKey}\") # exchange keys\r\n    await client.send_publicKeys_of_chatroom() # exchange keys\r\n    try:\r\n        await client.chat_with_others_in_room()\r\n    except asyncio.CancelledError:\r\n        pass\r\n    except Exception as e:\r\n        print(f\"Error handling client {client.name}: {e}\")\r\n    finally:\r\n        await client.remove_client()\r\n        del client\r\n```\r\n---\r\n### Some functions related to e2e\r\n```python\r\nasync def chat_with_others_in_room(self):\r\n\twhile True:\r\n\t\tmessage = await self.receive_message()\r\n\t\tif \"e2em|||\" in message: # `e2em|||client_id|||encrypted_message` for sending to client with specific id\r\n\t\t\t_, client_id, encrypted_message = message.split(\"|||\")\r\n\t\t\tawait send_message(clients[client_id][\"writer\"], f\"{self.name}{message}\")\r\n\r\nasync def get_publicKey(self): # the first message that will be received \r\n\tself.publicKey = await self.receive_message()\r\n\r\nasync def send_publicKeys_of_chatroom(self): # all public keys of users are sent to client \r\n\tfor user_id, values in clients.items():\r\n\t\tif values['chatroom_id'] == self.chatroom_id and user_id != self.id:\r\n\t\t\tawait self.send_message(f\"e2ek|||{user_id}|||{values['publicKey']}\")\r\n```\r\n---\r\n### Some other functions\r\nif you need\r\n```python\r\nasync def remove_client(self):        \r\n\tdel clients[self.name]\r\n\tawait self.multicast_to_chat(f\"{self.name} has left the chat!\\n\")\r\n\tself.writer.close()\r\n\tawait self.writer.wait_closed()\r\n\tprint(f\"Connection closed for {self.client_address}\")\r\n\r\nasync def choose_name(self):\r\n\tself.name = await self.client_req_and_res(\"Welcome to the chatroom! Please enter your name: \\n\")\r\n\tprint(f\"{self.name} has joined the server\")\r\n\tfor user_id, values in clients.items():\r\n\t\tif values['name'] == self.name:\r\n\t\t\tself.id = user_id\r\n\tif not self.id: \r\n\t\tself.id = generate_secure_user_id()\r\n\r\nasync def choose_chat(self):\r\n\toption = \"\"\r\n\twhile option not in [Options.CREATE, Options.SELECT]:\r\n\t\tchatroom_names = \"\\n\".join(chatrooms.values())\r\n\t\toption = await self.client_req_and_res(f\"\\n1. Create a Chatroom\\n2. Select a Chatroom:\\n{chatroom_names}\\n\")\r\n\t\ttry:\r\n\t\t\toption = Options(int(option))  # Convert user input to enum\r\n\t\texcept ValueError:\r\n\t\t\toption = None\r\n\t\tprint(f\"{option} is chosen\")\r\n\twhile True:\r\n\t\tif option == Options.CREATE: \r\n\t\t\tchatroom_name = await self.client_req_and_res(\"Enter name of a new chatroom: \")\r\n\t\t\tprint(f\"{chatroom_name} chatroom is created\")\r\n\t\t\tself.chatroom_id = generate_secure_chat_id() \r\n\t\t\tchatrooms[self.chatroom_id] = chatroom_name\r\n\t\t\tbreak\r\n\t\telif option == Options.SELECT:\r\n\t\t\tchatroom_name = await self.client_req_and_res(\"Enter name of a chatroom: \")\r\n\t\t\tself.chatroom_id = find_id_by_name(chatroom_name, chatrooms)\r\n\t\t\tif not self.chatroom_id: continue\r\n\t\t\tbreak\r\n\tawait self.multicast_to_chat(f\"\\n{self.name} has joined the chat!\\n\")\r\n\tprint(f\"{self.chatroom_id} chatroom is touched in the server\")\r\n\r\ndef get_user_profile(self):\r\n\treturn {'name': self.name, 'chatroom_id': self.chatroom_id, 'writer': self.writer, 'publicKey': self.publicKey}\r\n```\r\n\r\n# That is all","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felnurbda%2Fpython-chatroom","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felnurbda%2Fpython-chatroom","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felnurbda%2Fpython-chatroom/lists"}