Command-line chat application using Python
Learn how to create a Command-line chat application using the Python sockets module.
Photo by Volodymyr Hryshchenko on Unsplash
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