Command-line chat application using Python

Learn how to create a Command-line chat application using the Python sockets module.

ยท

14 min read

So I made this cool personal project of Chat application in python, but there's a catch. Here I am making a chat app in the command line while there are already tutorials for the GUI Chat application.

Let me tell you some features of your chat application:

  • File sharing

  • View online group members

  • View all group members

  • Transfer admin privilege

  • Kick members

  • View group admin

  • If a user enters a group name that does not exist, a new group with the user as admin is automatically created.

So with that said let me quickly guide you through it:

First, we need to create a file system for it so do it like this:

<Project Name>
            |__server
                 |__ server.py
            |__cli
                 |__ client.py

First, we will need a server to host these all chats so let's make a server first:

Let's import the modules we are going to use:

import socket
import threading
import pickle
import os
import sys

Let us make some constants for keeping a track of groups and enabling file sharing:

groups = {}
fileTransferCondition = threading.Condition()

Now we need to create a class for 'group' and include the process of sending messages, connecting and disconnecting:

class Group:
    def __init__(self,admin,client):
        self.admin = admin
        self.clients = {}
        self.offlineMessages = {}
        self.allMembers = set()
        self.onlineMembers = set()
        self.joinRequests = set()
        self.waitClients = {}

        self.clients[admin] = client
        self.allMembers.add(admin)
        self.onlineMembers.add(admin)

    def disconnect(self,username):
        self.onlineMembers.remove(username)
        del self.clients[username]

    def connect(self,username,client):
        self.onlineMembers.add(username)
        self.clients[username] = client

    def sendMessage(self,message,username):
        for member in self.onlineMembers:
            if member != username:
                self.clients[member].send(bytes(username + ": " + message,"utf-8"))

Here we are using UTF-8 for encoding and decoding our messages.

Now, let's give some additional features to our chat app like changing admin, kicking a player, and also file sharing.

Receive message:

def TChat(client, username, groupname):
    while True:
        msg = client.recv(1024).decode("utf-8")

View Join requests:

if msg == "/viewRequests":
            client.send(b"/viewRequests")
            client.recv(1024).decode("utf-8")
            if username == groups[groupname].admin:
                client.send(b"/sendingData")
                client.recv(1024)
                client.send(pickle.dumps(groups[groupname].joinRequests))
            else:
                client.send(b"You're not an admin.")

Approve Join Requests:

elif msg == "/approveRequest":
            client.send(b"/approveRequest")
            client.recv(1024).decode("utf-8")
            if username == groups[groupname].admin:
                client.send(b"/proceed")
                usernameToApprove = client.recv(1024).decode("utf-8")
                if usernameToApprove in groups[groupname].joinRequests:
                    groups[groupname].joinRequests.remove(usernameToApprove)
                    groups[groupname].allMembers.add(usernameToApprove)
                    if usernameToApprove in groups[groupname].waitClients:
                        groups[groupname].waitClients[usernameToApprove].send(b"/accepted")
                        groups[groupname].connect(usernameToApprove,groups[groupname].waitClients[usernameToApprove])
                        del groups[groupname].waitClients[usernameToApprove]
                    print("Member Approved:",usernameToApprove,"| Group:",groupname)
                    client.send(b"User has been added to the group.")
                else:
                    client.send(b"The user has not requested to join.")
            else:
                client.send(b"You're not an admin.")

Disconnect:

elif msg == "/disconnect":
            client.send(b"/disconnect")
            client.recv(1024).decode("utf-8")
            groups[groupname].disconnect(username)
            print("User Disconnected:",username,"| Group:",groupname)
            break

Send Message:

elif msg == "/messageSend":
            client.send(b"/messageSend")
            message = client.recv(1024).decode("utf-8")
            groups[groupname].sendMessage(message,username)

Disconnection of waiting to approve clients:

elif msg == "/waitDisconnect":
            client.send(b"/waitDisconnect")
            del groups[groupname].waitClients[username]
            print("Waiting Client:",username,"Disconnected")
            break

View all members:

elif msg == "/allMembers":
            client.send(b"/allMembers")
            client.recv(1024).decode("utf-8")
            client.send(pickle.dumps(groups[groupname].allMembers))

View Online Members:

elif msg == "/onlineMembers":
            client.send(b"/onlineMembers")
            client.recv(1024).decode("utf-8")
            client.send(pickle.dumps(groups[groupname].onlineMembers))

Change Admin:

elif msg == "/changeAdmin":
            client.send(b"/changeAdmin")
            client.recv(1024).decode("utf-8")
            if username == groups[groupname].admin:
                client.send(b"/proceed")
                newAdminUsername = client.recv(1024).decode("utf-8")
                if newAdminUsername in groups[groupname].allMembers:
                    groups[groupname].admin = newAdminUsername
                    print("New Admin:",newAdminUsername,"| Group:",groupname)
                    client.send(b"Your adminship is now transferred to the specified user.")
                else:
                    client.send(b"The user is not a member of this group.")
            else:
                client.send(b"You're not an admin.")

View who is the admin:

elif msg == "/whoAdmin":
            client.send(b"/whoAdmin")
            groupname = client.recv(1024).decode("utf-8")
            client.send(bytes("Admin: "+groups[groupname].admin,"utf-8"))

Kick Member:

elif msg == "/kickMember":
            client.send(b"/kickMember")
            client.recv(1024).decode("utf-8")
            if username == groups[groupname].admin:
                client.send(b"/proceed")
                usernameToKick = client.recv(1024).decode("utf-8")
                if usernameToKick in groups[groupname].allMembers:
                    groups[groupname].allMembers.remove(usernameToKick)
                    if usernameToKick in groups[groupname].onlineMembers:
                        groups[groupname].clients[usernameToKick].send(b"/kicked")
                        groups[groupname].onlineMembers.remove(usernameToKick)
                        del groups[groupname].clients[usernameToKick]
                    print("User Removed:",usernameToKick,"| Group:",groupname)
                    client.send(b"The specified user is removed from the group.")
                else:
                    client.send(b"The user is not a member of this group.")
            else:
                client.send(b"You're not an admin.")

File Transfer:

elif msg == "/fileTransfer":
            client.send(b"/fileTransfer")
            filename = client.recv(1024).decode("utf-8")
            if filename == "~error~":
                continue
            client.send(b"/sendFile")
            remaining = int.from_bytes(client.recv(4),'big')
            f = open(filename,"wb")
            while remaining:
                data = client.recv(min(remaining,4096))
                remaining -= len(data)
                f.write(data)
            f.close()
            print("File received:",filename,"| User:",username,"| Group:",groupname)
            for member in groups[groupname].onlineMembers:
                if member != username:
                    memberClient = groups[groupname].clients[member]
                    memberClient.send(b"/receiveFile")
                    with fileTransferCondition:
                        fileTransferCondition.wait()
                    memberClient.send(bytes(filename,"utf-8"))
                    with fileTransferCondition:
                        fileTransferCondition.wait()
                    with open(filename,'rb') as f:
                        data = f.read()
                        dataLen = len(data)
                        memberClient.send(dataLen.to_bytes(4,'big'))
                        memberClient.send(data)
            client.send(bytes(filename+" successfully sent to all online group members.","utf-8"))
            print("File sent",filename,"| Group: ",groupname)
            os.remove(filename)

Send File:

elif msg == "/sendFilename" or msg == "/sendFile":
            with fileTransferCondition:
                fileTransferCondition.notify()

Handle wrong commands:

else:
            print("UNIDENTIFIED COMMAND:",msg)

Joining members and creating a new group:

def handshake(client):
    username = client.recv(1024).decode("utf-8")
    client.send(b"/sendGroupname")
    groupname = client.recv(1024).decode("utf-8")
    if groupname in groups:
        if username in groups[groupname].allMembers:
            groups[groupname].connect(username,client)
            client.send(b"/ready")
            print("User Connected:",username,"| Group:",groupname)
        else:
            groups[groupname].joinRequests.add(username)
            groups[groupname].waitClients[username] = client
            groups[groupname].sendMessage(username+" has requested to join the group.","TChat")
            client.send(b"/wait")
            print("Join Request:",username,"| Group:",groupname)
        threading.Thread(target=TChat, args=(client, username, groupname,)).start()
    else:
        groups[groupname] = Group(username,client)
        threading.Thread(target=TChat, args=(client, username, groupname,)).start()
        client.send(b"/adminReady")
        print("New Group:",groupname,"| Admin:",username)

Code to run the server:

def main():
    if len(sys.argv) < 3:
        print("USAGE: python server.py <IP> <Port>")
        print("EXAMPLE: python server.py localhost 8000")
        return
    listenSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listenSocket.bind((sys.argv[1], int(sys.argv[2])))
    listenSocket.listen(10)
    print("TChat Server running")
    while True:
        client,_ = listenSocket.accept()
        threading.Thread(target=handshake, args=(client,)).start()

if __name__ == "__main__":
    main()

I know I haven't gone in deep into what these codes mean line by line but this is just an overview.

Whole Server Code:

import socket
import threading
import pickle
import os
import sys

groups = {}
fileTransferCondition = threading.Condition()

class Group:
    def __init__(self,admin,client):
        self.admin = admin
        self.clients = {}
        self.offlineMessages = {}
        self.allMembers = set()
        self.onlineMembers = set()
        self.joinRequests = set()
        self.waitClients = {}

        self.clients[admin] = client
        self.allMembers.add(admin)
        self.onlineMembers.add(admin)

    def disconnect(self,username):
        self.onlineMembers.remove(username)
        del self.clients[username]

    def connect(self,username,client):
        self.onlineMembers.add(username)
        self.clients[username] = client

    def sendMessage(self,message,username):
        for member in self.onlineMembers:
            if member != username:
                self.clients[member].send(bytes(username + ": " + message,"utf-8"))

def TChat(client, username, groupname):
    while True:
        msg = client.recv(1024).decode("utf-8")
        if msg == "/viewRequests":
            client.send(b"/viewRequests")
            client.recv(1024).decode("utf-8")
            if username == groups[groupname].admin:
                client.send(b"/sendingData")
                client.recv(1024)
                client.send(pickle.dumps(groups[groupname].joinRequests))
            else:
                client.send(b"You're not an admin.")
        elif msg == "/approveRequest":
            client.send(b"/approveRequest")
            client.recv(1024).decode("utf-8")
            if username == groups[groupname].admin:
                client.send(b"/proceed")
                usernameToApprove = client.recv(1024).decode("utf-8")
                if usernameToApprove in groups[groupname].joinRequests:
                    groups[groupname].joinRequests.remove(usernameToApprove)
                    groups[groupname].allMembers.add(usernameToApprove)
                    if usernameToApprove in groups[groupname].waitClients:
                        groups[groupname].waitClients[usernameToApprove].send(b"/accepted")
                        groups[groupname].connect(usernameToApprove,groups[groupname].waitClients[usernameToApprove])
                        del groups[groupname].waitClients[usernameToApprove]
                    print("Member Approved:",usernameToApprove,"| Group:",groupname)
                    client.send(b"User has been added to the group.")
                else:
                    client.send(b"The user has not requested to join.")
            else:
                client.send(b"You're not an admin.")
        elif msg == "/disconnect":
            client.send(b"/disconnect")
            client.recv(1024).decode("utf-8")
            groups[groupname].disconnect(username)
            print("User Disconnected:",username,"| Group:",groupname)
            break
        elif msg == "/messageSend":
            client.send(b"/messageSend")
            message = client.recv(1024).decode("utf-8")
            groups[groupname].sendMessage(message,username)
        elif msg == "/waitDisconnect":
            client.send(b"/waitDisconnect")
            del groups[groupname].waitClients[username]
            print("Waiting Client:",username,"Disconnected")
            break
        elif msg == "/allMembers":
            client.send(b"/allMembers")
            client.recv(1024).decode("utf-8")
            client.send(pickle.dumps(groups[groupname].allMembers))
        elif msg == "/onlineMembers":
            client.send(b"/onlineMembers")
            client.recv(1024).decode("utf-8")
            client.send(pickle.dumps(groups[groupname].onlineMembers))
        elif msg == "/changeAdmin":
            client.send(b"/changeAdmin")
            client.recv(1024).decode("utf-8")
            if username == groups[groupname].admin:
                client.send(b"/proceed")
                newAdminUsername = client.recv(1024).decode("utf-8")
                if newAdminUsername in groups[groupname].allMembers:
                    groups[groupname].admin = newAdminUsername
                    print("New Admin:",newAdminUsername,"| Group:",groupname)
                    client.send(b"Your adminship is now transferred to the specified user.")
                else:
                    client.send(b"The user is not a member of this group.")
            else:
                client.send(b"You're not an admin.")
        elif msg == "/whoAdmin":
            client.send(b"/whoAdmin")
            groupname = client.recv(1024).decode("utf-8")
            client.send(bytes("Admin: "+groups[groupname].admin,"utf-8"))
        elif msg == "/kickMember":
            client.send(b"/kickMember")
            client.recv(1024).decode("utf-8")
            if username == groups[groupname].admin:
                client.send(b"/proceed")
                usernameToKick = client.recv(1024).decode("utf-8")
                if usernameToKick in groups[groupname].allMembers:
                    groups[groupname].allMembers.remove(usernameToKick)
                    if usernameToKick in groups[groupname].onlineMembers:
                        groups[groupname].clients[usernameToKick].send(b"/kicked")
                        groups[groupname].onlineMembers.remove(usernameToKick)
                        del groups[groupname].clients[usernameToKick]
                    print("User Removed:",usernameToKick,"| Group:",groupname)
                    client.send(b"The specified user is removed from the group.")
                else:
                    client.send(b"The user is not a member of this group.")
            else:
                client.send(b"You're not an admin.")
        elif msg == "/fileTransfer":
            client.send(b"/fileTransfer")
            filename = client.recv(1024).decode("utf-8")
            if filename == "~error~":
                continue
            client.send(b"/sendFile")
            remaining = int.from_bytes(client.recv(4),'big')
            f = open(filename,"wb")
            while remaining:
                data = client.recv(min(remaining,4096))
                remaining -= len(data)
                f.write(data)
            f.close()
            print("File received:",filename,"| User:",username,"| Group:",groupname)
            for member in groups[groupname].onlineMembers:
                if member != username:
                    memberClient = groups[groupname].clients[member]
                    memberClient.send(b"/receiveFile")
                    with fileTransferCondition:
                        fileTransferCondition.wait()
                    memberClient.send(bytes(filename,"utf-8"))
                    with fileTransferCondition:
                        fileTransferCondition.wait()
                    with open(filename,'rb') as f:
                        data = f.read()
                        dataLen = len(data)
                        memberClient.send(dataLen.to_bytes(4,'big'))
                        memberClient.send(data)
            client.send(bytes(filename+" successfully sent to all online group members.","utf-8"))
            print("File sent",filename,"| Group: ",groupname)
            os.remove(filename)
        elif msg == "/sendFilename" or msg == "/sendFile":
            with fileTransferCondition:
                fileTransferCondition.notify()
        else:
            print("UNIDENTIFIED COMMAND:",msg)
def handshake(client):
    username = client.recv(1024).decode("utf-8")
    client.send(b"/sendGroupname")
    groupname = client.recv(1024).decode("utf-8")
    if groupname in groups:
        if username in groups[groupname].allMembers:
            groups[groupname].connect(username,client)
            client.send(b"/ready")
            print("User Connected:",username,"| Group:",groupname)
        else:
            groups[groupname].joinRequests.add(username)
            groups[groupname].waitClients[username] = client
            groups[groupname].sendMessage(username+" has requested to join the group.","TChat")
            client.send(b"/wait")
            print("Join Request:",username,"| Group:",groupname)
        threading.Thread(target=TChat, args=(client, username, groupname,)).start()
    else:
        groups[groupname] = Group(username,client)
        threading.Thread(target=TChat, args=(client, username, groupname,)).start()
        client.send(b"/adminReady")
        print("New Group:",groupname,"| Admin:",username)

def main():
    if len(sys.argv) < 3:
        print("USAGE: python server.py <IP> <Port>")
        print("EXAMPLE: python server.py localhost 8000")
        return
    listenSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listenSocket.bind((sys.argv[1], int(sys.argv[2])))
    listenSocket.listen(10)
    print("TChat Server running")
    while True:
        client,_ = listenSocket.accept()
        threading.Thread(target=handshake, args=(client,)).start()

if __name__ == "__main__":
    main()

Congratulations ๐ŸŽ‰๐ŸŽ‰! We just completed the server.

Now let's do the client-side:

Let's import the modules we need:

import socket
import threading
import pickle
import sys

Let's make a constant for tracking:

state = {}

We will write some(huge) code for server listening:

def serverListen(serverSocket):
    while True:
        msg = serverSocket.recv(1024).decode("utf-8")
        if msg == "/viewRequests":
            serverSocket.send(bytes(".","utf-8"))
            response = serverSocket.recv(1024).decode("utf-8")
            if response == "/sendingData":
                serverSocket.send(b"/readyForData")
                data = pickle.loads(serverSocket.recv(1024))
                if data == set():
                    print("No pending requests.")
                else:
                    print("Pending Requests:")
                    for element in data:
                        print(element)
            else:
                print(response)
        elif msg == "/approveRequest":
            serverSocket.send(bytes(".","utf-8"))
            response = serverSocket.recv(1024).decode("utf-8")
            if response == "/proceed":
                state["inputMessage"] = False
                print("Please enter the username to approve: ")
                with state["inputCondition"]:
                    state["inputCondition"].wait()
                state["inputMessage"] = True
                serverSocket.send(bytes(state["userInput"],"utf-8"))
                print(serverSocket.recv(1024).decode("utf-8"))
            else:
                print(response)
        elif msg == "/disconnect":
            serverSocket.send(bytes(".","utf-8"))
            state["alive"] = False
            break
        elif msg == "/messageSend":
            serverSocket.send(bytes(state["userInput"],"utf-8"))
            state["sendMessageLock"].release()
        elif msg == "/allMembers":
            serverSocket.send(bytes(".","utf-8"))
            data = pickle.loads(serverSocket.recv(1024))
            print("All Group Members:")
            for element in data:
                print(element)
        elif msg == "/onlineMembers":
            serverSocket.send(bytes(".","utf-8"))
            data = pickle.loads(serverSocket.recv(1024))
            print("Online Group Members:")
            for element in data:
                print(element)
        elif msg == "/changeAdmin":
            serverSocket.send(bytes(".","utf-8"))
            response = serverSocket.recv(1024).decode("utf-8")
            if response == "/proceed":
                state["inputMessage"] = False
                print("Please enter the username of the new admin: ")
                with state["inputCondition"]:
                    state["inputCondition"].wait()
                state["inputMessage"] = True
                serverSocket.send(bytes(state["userInput"],"utf-8"))
                print(serverSocket.recv(1024).decode("utf-8"))
            else:
                print(response)
        elif msg == "/whoAdmin":
            serverSocket.send(bytes(state["groupname"],"utf-8"))
            print(serverSocket.recv(1024).decode("utf-8"))
        elif msg == "/kickMember":
            serverSocket.send(bytes(".","utf-8"))
            response = serverSocket.recv(1024).decode("utf-8")
            if response == "/proceed":
                state["inputMessage"] = False
                print("Please enter the username to kick: ")
                with state["inputCondition"]:
                    state["inputCondition"].wait()
                state["inputMessage"] = True
                serverSocket.send(bytes(state["userInput"],"utf-8"))
                print(serverSocket.recv(1024).decode("utf-8"))
            else:
                print(response)
        elif msg == "/kicked":
            state["alive"] = False
            state["inputMessage"] = False
            print("You have been kicked. Press any key to quit.")
            break
        elif msg == "/fileTransfer":
            state["inputMessage"] = False
            print("Please enter the filename: ")
            with state["inputCondition"]:
                state["inputCondition"].wait()
            state["inputMessage"] = True
            filename = state["userInput"]
            try:
                f = open(filename,'rb')
                f.close()
            except FileNotFoundError:
                print("The requested file does not exist.")
                serverSocket.send(bytes("~error~","utf-8"))
                continue
            serverSocket.send(bytes(filename,"utf-8"))
            serverSocket.recv(1024)
            print("Uploading file to server...")
            with open(filename,'rb') as f:
                data = f.read()
                dataLen = len(data)
                serverSocket.send(dataLen.to_bytes(4,'big'))
                serverSocket.send(data)
            print(serverSocket.recv(1024).decode("utf-8"))
        elif msg == "/receiveFile":
            print("Receiving shared group file...")
            serverSocket.send(b"/sendFilename")
            filename = serverSocket.recv(1024).decode("utf-8")
            serverSocket.send(b"/sendFile")
            remaining = int.from_bytes(serverSocket.recv(4),'big')
            f = open(filename,"wb")
            while remaining:
                data = serverSocket.recv(min(remaining,4096))
                remaining -= len(data)
                f.write(data)
            f.close()
            print("Received file saved as",filename)
        else:
            print(msg)

This is the function for User Input for the commands:

def userInput(serverSocket):
    while state["alive"]:
        state["sendMessageLock"].acquire()
        state["userInput"] = input()
        state["sendMessageLock"].release()
        with state["inputCondition"]:
            state["inputCondition"].notify()
        if state["userInput"] == "/1":
            serverSocket.send(b"/viewRequests")
        elif state["userInput"] == "/2":
            serverSocket.send(b"/approveRequest")
        elif state["userInput"] == "/3":
            serverSocket.send(b"/disconnect")
            break
        elif state["userInput"] == "/4":
            serverSocket.send(b"/allMembers")
        elif state["userInput"] == "/5":
            serverSocket.send(b"/onlineMembers")
        elif state["userInput"] == "/6":
            serverSocket.send(b"/changeAdmin")
        elif state["userInput"] == "/7":
            serverSocket.send(b"/whoAdmin")
        elif state["userInput"] == "/8":
            serverSocket.send(b"/kickMember")
        elif state["userInput"] == "/9":
            serverSocket.send(b"/fileTransfer")
        elif state["inputMessage"]:
            state["sendMessageLock"].acquire()
            serverSocket.send(b"/messageSend")

Code for those who are waiting to be approved:

def waitServerListen(serverSocket):
    while not state["alive"]:
        msg = serverSocket.recv(1024).decode("utf-8")
        if msg == "/accepted":
            state["alive"] = True
            print("Your join request has been approved. Press any key to begin chatting.")
            break
        elif msg == "/waitDisconnect":
            state["joinDisconnect"] = True
            break

Wait for user input:


def waitUserInput(serverSocket):
    while not state["alive"]:
        state["userInput"] = input()
        if state["userInput"] == "/1" and not state["alive"]:
            serverSocket.send(b"/waitDisconnect")
            break

Now this main function for running the client service:

def main():
    if len(sys.argv) < 3:
        print("USAGE: python client.py <IP> <Port>")
        print("EXAMPLE: python client.py localhost 8000")
        return
    serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    serverSocket.connect((sys.argv[1], int(sys.argv[2])))
    state["inputCondition"] = threading.Condition()
    state["sendMessageLock"] = threading.Lock()
    state["username"] = input("Welcome to TChat! Please enter your username: ")
    state["groupname"] = input("Please enter the name of the group you want to create or input the name of the group you want to join: ")
    state["alive"] = False
    state["joinDisconnect"] = False
    state["inputMessage"] = True
    serverSocket.send(bytes(state["username"],"utf-8"))
    serverSocket.recv(1024)
    serverSocket.send(bytes(state["groupname"],"utf-8"))
    response = serverSocket.recv(1024).decode("utf-8")
    if response == "/adminReady":
        print("You have created the group",state["groupname"],"and are now an admin.")
        state["alive"] = True
    elif response == "/ready":
        print("You have joined the group",state["groupname"])
        state["alive"] = True
    elif response == "/wait":
        print("Your request to join the group is pending admin approval.")
        print("Available Commands:\n/1 -> Disconnect\n")
    waitUserInputThread = threading.Thread(target=waitUserInput,args=(serverSocket,))
    waitServerListenThread = threading.Thread(target=waitServerListen,args=(serverSocket,))
    userInputThread = threading.Thread(target=userInput,args=(serverSocket,))
    serverListenThread = threading.Thread(target=serverListen,args=(serverSocket,))
    waitUserInputThread.start()
    waitServerListenThread.start()
    while True:
        if state["alive"] or state["joinDisconnect"]:
            break
    if state["alive"]:
        print("Available Commands:\n/1 -> View Join Requests (Admins)\n/2 -> Approve Join Requests (Admin)\n/3 -> Disconnect\n/4 -> View All Members\n/5 -> View Online Group Members\n/6 -> Transfer Adminship\n/7 -> Check Group Admin\n/8 -> Kick Member\n/9 -> File Transfer\nType anything else to send a message")
        waitUserInputThread.join()
        waitServerListenThread.join()
        userInputThread.start()
        serverListenThread.start()
    while True:
        if state["joinDisconnect"]:
            serverSocket.shutdown(socket.SHUT_RDWR)
            serverSocket.close()
            waitUserInputThread.join()
            waitServerListenThread.join()
            print("Disconnected from TChat.")
            break
        elif not state["alive"]:
            serverSocket.shutdown(socket.SHUT_RDWR)
            serverSocket.close()
            userInputThread.join()
            serverListenThread.join()
            print("Disconnected from TChat.")
            break

if __name__ == "__main__":
    main()

The whole code for clients:

import socket
import threading
import pickle
import sys

state = {}

def serverListen(serverSocket):
    while True:
        msg = serverSocket.recv(1024).decode("utf-8")
        if msg == "/viewRequests":
            serverSocket.send(bytes(".","utf-8"))
            response = serverSocket.recv(1024).decode("utf-8")
            if response == "/sendingData":
                serverSocket.send(b"/readyForData")
                data = pickle.loads(serverSocket.recv(1024))
                if data == set():
                    print("No pending requests.")
                else:
                    print("Pending Requests:")
                    for element in data:
                        print(element)
            else:
                print(response)
        elif msg == "/approveRequest":
            serverSocket.send(bytes(".","utf-8"))
            response = serverSocket.recv(1024).decode("utf-8")
            if response == "/proceed":
                state["inputMessage"] = False
                print("Please enter the username to approve: ")
                with state["inputCondition"]:
                    state["inputCondition"].wait()
                state["inputMessage"] = True
                serverSocket.send(bytes(state["userInput"],"utf-8"))
                print(serverSocket.recv(1024).decode("utf-8"))
            else:
                print(response)
        elif msg == "/disconnect":
            serverSocket.send(bytes(".","utf-8"))
            state["alive"] = False
            break
        elif msg == "/messageSend":
            serverSocket.send(bytes(state["userInput"],"utf-8"))
            state["sendMessageLock"].release()
        elif msg == "/allMembers":
            serverSocket.send(bytes(".","utf-8"))
            data = pickle.loads(serverSocket.recv(1024))
            print("All Group Members:")
            for element in data:
                print(element)
        elif msg == "/onlineMembers":
            serverSocket.send(bytes(".","utf-8"))
            data = pickle.loads(serverSocket.recv(1024))
            print("Online Group Members:")
            for element in data:
                print(element)
        elif msg == "/changeAdmin":
            serverSocket.send(bytes(".","utf-8"))
            response = serverSocket.recv(1024).decode("utf-8")
            if response == "/proceed":
                state["inputMessage"] = False
                print("Please enter the username of the new admin: ")
                with state["inputCondition"]:
                    state["inputCondition"].wait()
                state["inputMessage"] = True
                serverSocket.send(bytes(state["userInput"],"utf-8"))
                print(serverSocket.recv(1024).decode("utf-8"))
            else:
                print(response)
        elif msg == "/whoAdmin":
            serverSocket.send(bytes(state["groupname"],"utf-8"))
            print(serverSocket.recv(1024).decode("utf-8"))
        elif msg == "/kickMember":
            serverSocket.send(bytes(".","utf-8"))
            response = serverSocket.recv(1024).decode("utf-8")
            if response == "/proceed":
                state["inputMessage"] = False
                print("Please enter the username to kick: ")
                with state["inputCondition"]:
                    state["inputCondition"].wait()
                state["inputMessage"] = True
                serverSocket.send(bytes(state["userInput"],"utf-8"))
                print(serverSocket.recv(1024).decode("utf-8"))
            else:
                print(response)
        elif msg == "/kicked":
            state["alive"] = False
            state["inputMessage"] = False
            print("You have been kicked. Press any key to quit.")
            break
        elif msg == "/fileTransfer":
            state["inputMessage"] = False
            print("Please enter the filename: ")
            with state["inputCondition"]:
                state["inputCondition"].wait()
            state["inputMessage"] = True
            filename = state["userInput"]
            try:
                f = open(filename,'rb')
                f.close()
            except FileNotFoundError:
                print("The requested file does not exist.")
                serverSocket.send(bytes("~error~","utf-8"))
                continue
            serverSocket.send(bytes(filename,"utf-8"))
            serverSocket.recv(1024)
            print("Uploading file to server...")
            with open(filename,'rb') as f:
                data = f.read()
                dataLen = len(data)
                serverSocket.send(dataLen.to_bytes(4,'big'))
                serverSocket.send(data)
            print(serverSocket.recv(1024).decode("utf-8"))
        elif msg == "/receiveFile":
            print("Receiving shared group file...")
            serverSocket.send(b"/sendFilename")
            filename = serverSocket.recv(1024).decode("utf-8")
            serverSocket.send(b"/sendFile")
            remaining = int.from_bytes(serverSocket.recv(4),'big')
            f = open(filename,"wb")
            while remaining:
                data = serverSocket.recv(min(remaining,4096))
                remaining -= len(data)
                f.write(data)
            f.close()
            print("Received file saved as",filename)
        else:
            print(msg)

def userInput(serverSocket):
    while state["alive"]:
        state["sendMessageLock"].acquire()
        state["userInput"] = input()
        state["sendMessageLock"].release()
        with state["inputCondition"]:
            state["inputCondition"].notify()
        if state["userInput"] == "/1":
            serverSocket.send(b"/viewRequests")
        elif state["userInput"] == "/2":
            serverSocket.send(b"/approveRequest")
        elif state["userInput"] == "/3":
            serverSocket.send(b"/disconnect")
            break
        elif state["userInput"] == "/4":
            serverSocket.send(b"/allMembers")
        elif state["userInput"] == "/5":
            serverSocket.send(b"/onlineMembers")
        elif state["userInput"] == "/6":
            serverSocket.send(b"/changeAdmin")
        elif state["userInput"] == "/7":
            serverSocket.send(b"/whoAdmin")
        elif state["userInput"] == "/8":
            serverSocket.send(b"/kickMember")
        elif state["userInput"] == "/9":
            serverSocket.send(b"/fileTransfer")
        elif state["inputMessage"]:
            state["sendMessageLock"].acquire()
            serverSocket.send(b"/messageSend")

def waitServerListen(serverSocket):
    while not state["alive"]:
        msg = serverSocket.recv(1024).decode("utf-8")
        if msg == "/accepted":
            state["alive"] = True
            print("Your join request has been approved. Press any key to begin chatting.")
            break
        elif msg == "/waitDisconnect":
            state["joinDisconnect"] = True
            break

def waitUserInput(serverSocket):
    while not state["alive"]:
        state["userInput"] = input()
        if state["userInput"] == "/1" and not state["alive"]:
            serverSocket.send(b"/waitDisconnect")
            break

def main():
    if len(sys.argv) < 3:
        print("USAGE: python client.py <IP> <Port>")
        print("EXAMPLE: python client.py localhost 8000")
        return
    serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    serverSocket.connect((sys.argv[1], int(sys.argv[2])))
    state["inputCondition"] = threading.Condition()
    state["sendMessageLock"] = threading.Lock()
    state["username"] = input("Welcome to TChat! Please enter your username: ")
    state["groupname"] = input("Please enter the name of the group you want to create or input the name of the group you want to join: ")
    state["alive"] = False
    state["joinDisconnect"] = False
    state["inputMessage"] = True
    serverSocket.send(bytes(state["username"],"utf-8"))
    serverSocket.recv(1024)
    serverSocket.send(bytes(state["groupname"],"utf-8"))
    response = serverSocket.recv(1024).decode("utf-8")
    if response == "/adminReady":
        print("You have created the group",state["groupname"],"and are now an admin.")
        state["alive"] = True
    elif response == "/ready":
        print("You have joined the group",state["groupname"])
        state["alive"] = True
    elif response == "/wait":
        print("Your request to join the group is pending admin approval.")
        print("Available Commands:\n/1 -> Disconnect\n")
    waitUserInputThread = threading.Thread(target=waitUserInput,args=(serverSocket,))
    waitServerListenThread = threading.Thread(target=waitServerListen,args=(serverSocket,))
    userInputThread = threading.Thread(target=userInput,args=(serverSocket,))
    serverListenThread = threading.Thread(target=serverListen,args=(serverSocket,))
    waitUserInputThread.start()
    waitServerListenThread.start()
    while True:
        if state["alive"] or state["joinDisconnect"]:
            break
    if state["alive"]:
        print("Available Commands:\n/1 -> View Join Requests (Admins)\n/2 -> Approve Join Requests (Admin)\n/3 -> Disconnect\n/4 -> View All Members\n/5 -> View Online Group Members\n/6 -> Transfer Adminship\n/7 -> Check Group Admin\n/8 -> Kick Member\n/9 -> File Transfer\nType anything else to send a message")
        waitUserInputThread.join()
        waitServerListenThread.join()
        userInputThread.start()
        serverListenThread.start()
    while True:
        if state["joinDisconnect"]:
            serverSocket.shutdown(socket.SHUT_RDWR)
            serverSocket.close()
            waitUserInputThread.join()
            waitServerListenThread.join()
            print("Disconnected from TChat.")
            break
        elif not state["alive"]:
            serverSocket.shutdown(socket.SHUT_RDWR)
            serverSocket.close()
            userInputThread.join()
            serverListenThread.join()
            print("Disconnected from TChat.")
            break

if __name__ == "__main__":
    main()

Tada ๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰! We just completed the whole project.

Instructions (Client)

  • Open a terminal window in the client directory containing main.py and run the following command: python main.py localhost 8000

  • Replace localhost with your IP and 8000 with a TCP port number of your choice.

  • A prompt to enter a username will appear. Enter a username and press enter.

  • Next, a prompt to enter a group name will appear. Enter a group name and press enter.

  • Note: If a group with the specified name does not exist, a new group is automatically created with the current user as admin. Otherwise, a join request is sent to the current group admin.

Type anything to send a message or use the following commands:

Command Behaviour

  • /1 View pending join requests**

  • /2 Approve pending join requests**

  • /3 Disconnect

  • /4 View all group members

  • /5 View online group members

  • /6 Transfer adminship**

  • /7 View group admin

  • /8 Kick member**

  • /9 Send a file to the group

** = Admin only action

Note: As this is an Open-Source Project, feel free to commit changes in this Pro developers.

Instructions (Server)

  • Open a terminal window in the server directory containing server.py and run the following command:

  • python server.py localhost 8000

  • Replace localhost with your IP and 8000 with a TCP port number of your choice.

If a user enters a group name that does not exist, a new group with the user as admin is automatically created

ย