144 lines
5.7 KiB
Python
144 lines
5.7 KiB
Python
import curses, socket, threading, json, hashlib, getpass, os
|
|
from pathlib import Path
|
|
|
|
chat_messages, chat_users, chat_list = [], [], []
|
|
user_status = {}
|
|
lock = threading.Lock()
|
|
current_user, current_chat = None, None
|
|
selected_chat, sock, scroll_offset = 0, None, 0
|
|
|
|
def send(obj):
|
|
sock.sendall((json.dumps(obj) + "\n").encode())
|
|
|
|
def clear():
|
|
os.system("cls" if os.name=="nt" else "clear")
|
|
|
|
def login():
|
|
passwd_file = Path("passwd.json")
|
|
if not passwd_file.exists(): return None
|
|
try: data = json.loads(passwd_file.read_text())
|
|
except: data = {}
|
|
user = input("Gebruikersnaam: ")
|
|
pw = getpass.getpass("Wachtwoord: ")
|
|
if user in data and data[user] == hashlib.sha256(pw.encode()).hexdigest():
|
|
return user
|
|
return None
|
|
|
|
def signup():
|
|
passwd_file = Path("passwd.json")
|
|
if passwd_file.exists():
|
|
try: data = json.loads(passwd_file.read_text())
|
|
except: data = {}
|
|
else: data = {}
|
|
user = input("Gebruiker: ")
|
|
if user in data: return
|
|
pw = getpass.getpass("Wachtwoord: ")
|
|
data[user] = hashlib.sha256(pw.encode()).hexdigest()
|
|
passwd_file.write_text(json.dumps(data, indent=4))
|
|
|
|
while not current_user:
|
|
clear()
|
|
print("1. Inloggen\n2. Nieuw account\n")
|
|
k = input(" > ")
|
|
if k=="1":
|
|
current_user = login()
|
|
if not current_user: input("Login mislukt...")
|
|
elif k=="2": signup()
|
|
|
|
def receiver():
|
|
global chat_messages, chat_users, chat_list, current_chat
|
|
buffer = ""
|
|
while True:
|
|
try:
|
|
buffer += sock.recv(4096).decode()
|
|
while "\n" in buffer:
|
|
line, buffer = buffer.split("\n",1)
|
|
d = json.loads(line)
|
|
t = d.get("type")
|
|
with lock:
|
|
if t=="chat_list":
|
|
chat_list[:] = d["chats"]
|
|
if not current_chat and chat_list:
|
|
current_chat = chat_list[0]
|
|
send({"action":"open_chat","chat_name":current_chat})
|
|
elif t=="chat_data" and d.get("chat")==current_chat:
|
|
chat_messages[:] = d["messages"][-100:]
|
|
chat_users[:] = d["users"]
|
|
elif t=="new_message" and d.get("chat")==current_chat:
|
|
chat_messages.append(d["message"])
|
|
elif t=="new_chat":
|
|
chat_list.append(d["chat"])
|
|
elif t=="users_list" and d.get("chat")==current_chat:
|
|
chat_users[:] = d["users"]
|
|
elif t in ("user_online","user_offline"):
|
|
user_status[d["user"]] = t=="user_online"
|
|
except: break
|
|
|
|
def draw(chat_win, users_win, input_win, input_buf):
|
|
chat_win.erase(); users_win.erase(); input_win.erase()
|
|
chat_win.box(); users_win.box(); input_win.box()
|
|
chat_win.addstr(0,2,f" Chat: {current_chat} ")
|
|
users_win.addstr(0,2," Users ")
|
|
input_win.addstr(0,2," Input ")
|
|
with lock:
|
|
h,w=chat_win.getmaxyx()
|
|
for i,m in enumerate(chat_messages[-100:]):
|
|
if i>=h-2: break
|
|
chat_win.addstr(i+1,1,f"[{m['user']}] {m['message']}"[:w-2])
|
|
uh,uw=users_win.getmaxyx()
|
|
for i,u in enumerate(chat_users[:uh-3]):
|
|
status = "✔" if user_status.get(u,False) else "✗"
|
|
users_win.addstr(i+1,1,f"{status} {u}"[:uw-2])
|
|
offset = min(len(chat_users),uh-3)
|
|
users_win.addstr(offset,1,"Chats:")
|
|
for i,c in enumerate(chat_list):
|
|
marker = ">" if i==selected_chat else " "
|
|
users_win.addstr(offset+1+i,1,f"{marker} {c}"[:uw-2])
|
|
input_win.addstr(1,1,input_buf[:w-2])
|
|
chat_win.refresh(); users_win.refresh(); input_win.refresh()
|
|
|
|
def popup_input(stdscr,title,prompt):
|
|
h,w=stdscr.getmaxyx(); pw,ph=50,7
|
|
win=curses.newwin(ph,pw,h//2-ph//2,w//2-pw//2)
|
|
win.box(); win.addstr(1,2,title); win.addstr(3,2,prompt)
|
|
curses.echo(); win.refresh()
|
|
inp=win.getstr(3,len(prompt)+3,30).decode().strip()
|
|
curses.noecho(); return inp
|
|
|
|
def ui(stdscr):
|
|
global current_chat, selected_chat, scroll_offset
|
|
curses.curs_set(1); stdscr.nodelay(True); stdscr.timeout(50)
|
|
h,w=stdscr.getmaxyx(); right_w,input_h=30,3
|
|
chat_win=curses.newwin(h-input_h,w-right_w,0,0)
|
|
users_win=curses.newwin(h-input_h,right_w,0,w-right_w)
|
|
input_win=curses.newwin(input_h,w,h-input_h,0)
|
|
input_buf=""
|
|
threading.Thread(target=receiver,daemon=True).start()
|
|
while True:
|
|
draw(chat_win,users_win,input_win,input_buf)
|
|
ch=stdscr.getch()
|
|
if ch==9 and chat_list:
|
|
selected_chat=(selected_chat+1)%len(chat_list)
|
|
current_chat=chat_list[selected_chat]
|
|
with lock: chat_messages.clear(); chat_users.clear()
|
|
send({"action":"open_chat","chat_name":current_chat})
|
|
elif ch==10:
|
|
msg=input_buf.strip(); input_buf=""
|
|
if not msg or not current_chat: continue
|
|
send({"action":"message","chat_name":current_chat,"message":msg})
|
|
elif ch in (127,curses.KEY_BACKSPACE): input_buf=input_buf[:-1]
|
|
elif 32<=ch<=126: input_buf+=chr(ch)
|
|
elif ch==curses.KEY_F2:
|
|
nc=popup_input(stdscr,"Nieuwe chat","Naam:")
|
|
if nc: send({"action":"new_chat","chat_name":nc})
|
|
elif ch==curses.KEY_F3:
|
|
u=popup_input(stdscr,"User toevoegen","Username:")
|
|
if u and current_chat: send({"action":"add_user","chat":current_chat,"user":u})
|
|
elif ch==curses.KEY_F4:
|
|
u=popup_input(stdscr,"User verwijderen","Username:")
|
|
if u and current_chat: send({"action":"rm_user","chat":current_chat,"user":u})
|
|
|
|
sock=socket.socket(); sock.connect(("127.0.0.1",44705))
|
|
sock.sendall((current_user+"\n").encode())
|
|
curses.wrapper(ui)
|