Files
pychat/client.py
2026-03-01 17:44:31 +01:00

252 lines
7.4 KiB
Python

import socket
import os
import shutil
import hashlib
import json
import getpass
from pathlib import Path
import threading
import readline
wit = "\x1b[97m"
blauw_bg = "\x1b[44m"
groen = "\x1b[92m"
rood = "\x1b[91m"
reset = "\x1b[0m"
commands = ["/add", "/quit", "/break", "/users", "/rm"]
chat_users = []
chat_messages = []
lock = threading.Lock()
def completer(text, state):
buffer = readline.get_line_buffer()
parts = buffer.split()
if not buffer.startswith("/"):
return None
if len(parts) == 1:
matches = [cmd for cmd in commands if cmd.startswith(parts[0])]
if state < len(matches):
return matches[state]
return None
if parts[0] == "/add" and len(parts) >= 2:
current_input = parts[-1]
matches = [user for user in chat_users if user.startswith(current_input)]
if state < len(matches):
return matches[state]
return None
return None
readline.set_completer_delims(" \t\n")
readline.set_completer(completer)
readline.parse_and_bind("tab: complete")
def clear():
os.system("cls" if os.name == "nt" else "clear")
def term_size():
size = shutil.get_terminal_size()
return size.columns, size.lines
def print_white(text=""):
print(wit + text + reset)
def recv_json(sock):
"""Veilig JSON ontvangen, ook bij grote berichten."""
buffer = b""
while True:
part = sock.recv(4096)
if not part:
break
buffer += part
try:
return json.loads(buffer.decode("utf-8"))
except json.JSONDecodeError:
continue
return {}
def login():
base_dir = Path(__file__).parent
passwd_file = base_dir / "passwd.json"
if not passwd_file.exists():
return None
try:
with passwd_file.open("r", encoding="utf-8") as f:
data = json.load(f)
except:
data = {}
user = input(wit + "user: " + reset)
password = getpass.getpass(wit + "password: " + reset)
password_hash = hashlib.sha256(password.encode("utf-8")).hexdigest()
if user in data and data[user] == password_hash:
return user
return None
def signup():
base_dir = Path(__file__).parent
passwd_file = base_dir / "passwd.json"
if passwd_file.exists():
try:
with passwd_file.open("r", encoding="utf-8") as f:
data = json.load(f)
except:
data = {}
else:
data = {}
user = input(wit + "Gebruiker: " + reset)
if user in data:
return
password = getpass.getpass(wit + "Wachtwoord: " + reset)
data[user] = hashlib.sha256(password.encode("utf-8")).hexdigest()
with passwd_file.open("w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
current_user = None
while not current_user:
clear()
w, _ = term_size()
print(blauw_bg + wit + "Terminal Chat ".center(w) + reset)
print()
print(groen + "========================================")
print(" Welcome to the Terminal Chat")
print("========================================" + reset)
print()
print_white("1. Login")
print_white("2. Sign up")
print()
keuze = input(wit + " > " + reset)
print()
if keuze == "1":
current_user = login()
elif keuze == "2":
signup()
host = "127.0.0.1"
port = 44705
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.sendall(current_user.encode("utf-8"))
except Exception as e:
print("Connection failed:", e)
exit()
chat_data = recv_json(s)
chat_names = list(chat_data.keys())
def draw_chat_window(messages, chatname):
current_input = readline.get_line_buffer() # bewaar wat je typt
clear()
w, h = term_size()
print(blauw_bg + wit + chatname.center(w) + reset)
available_lines = h - 3
to_display = messages[-available_lines:]
for msg in to_display:
user = msg["user"]
text = msg["message"]
if user == current_user:
print(wit + f" {text} [you]".rjust(w) + reset)
else:
print_white(f"[{user}] {text}")
print("-" * w)
readline.insert_text(current_input) # herstel input
readline.redisplay()
def chat_session(chatkeuze):
global chat_messages, chat_users
s.sendall(json.dumps({"action": "openchat", "chat_name": chatkeuze}).encode("utf-8"))
chat_data = recv_json(s)
if chatkeuze in chat_data:
chat_messages = chat_data[chatkeuze].get("messages", [])
chat_users = chat_data[chatkeuze].get("users", [])
def receive_messages(sock):
global chat_messages, chat_users
while True:
try:
decoded = recv_json(sock)
if not decoded:
break
if isinstance(decoded, dict) and decoded.get("type") == "users_list":
users = decoded.get("users", [])
print("\nUsers:", ", ".join(users))
print(wit + "> " + reset, end="", flush=True)
continue
if isinstance(decoded, dict) and chatkeuze in decoded:
with lock:
chat_messages = decoded[chatkeuze].get("messages", [])
chat_users = decoded[chatkeuze].get("users", [])
draw_chat_window(chat_messages, chatkeuze)
print(wit + "> " + reset, end="", flush=True)
except Exception:
break
threading.Thread(target=receive_messages, args=(s,), daemon=True).start()
while True:
draw_chat_window(chat_messages, chatkeuze)
bericht = input(wit + "> " + reset)
if bericht.lower() == "/quit":
exit()
elif bericht.lower() == "/rm":
s.sendall(json.dumps({
"action": "remove_message",
"chat_name": chatkeuze,
"by": current_user
}).encode("utf-8"))
elif bericht.lower() == "/break":
break
elif bericht.lower() == "/users":
s.sendall(json.dumps({
"action": "get_users",
"chat": chatkeuze
}).encode("utf-8"))
elif bericht.lower().startswith("/add "):
target_user = bericht.split(" ", 1)[1]
s.sendall(json.dumps({
"action": "add_user",
"chat": chatkeuze,
"user": target_user,
"by": current_user
}).encode("utf-8"))
else:
with lock:
chat_messages.append({
"user": current_user,
"message": bericht
})
draw_chat_window(chat_messages, chatkeuze)
s.sendall(json.dumps({
"action": "message",
"chat_name": chatkeuze,
"message": bericht
}).encode("utf-8"))
while True:
print_white(" --- Available chats: --- ")
for i, chat in enumerate(chat_names, start=1):
print_white(f"{i}. {chat}")
print_white(f"{len(chat_names)+1}. New chat")
keuze = input(wit + " > " + reset)
if keuze.isdigit() and int(keuze) == len(chat_names)+1:
nieuwe_chat = input("Name of new chat: ")
s.sendall(json.dumps({"action": "new_chat", "chat_name": nieuwe_chat}).encode("utf-8"))
chat_data = recv_json(s)
chat_names = list(chat_data.keys())
elif keuze.isdigit() and 1 <= int(keuze) <= len(chat_names):
chatkeuze = chat_names[int(keuze)-1]
chat_session(chatkeuze)