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)