From 58770684dbf347b77ab18b5f5234383ec9d7abe4 Mon Sep 17 00:00:00 2001 From: octolinkyt Date: Fri, 10 Oct 2025 23:32:52 +0000 Subject: [PATCH] feature pack 1 --- desktop.py | 115 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 41 deletions(-) diff --git a/desktop.py b/desktop.py index cd8b2b5..5aeb4cb 100644 --- a/desktop.py +++ b/desktop.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 """ Desktop GUI wrapper for chatter.py using Tkinter. -Supports text messages and attachments (files) with clickable filenames. +Supports text messages and attachments with clickable filenames. Polls server every 1.5s for updates. +Includes 24-hour toggle and timezone selection. """ import threading @@ -10,6 +11,7 @@ import tkinter as tk from tkinter import filedialog, scrolledtext, messagebox import requests, os, time, webbrowser from datetime import datetime +from dateutil import parser, tz from plyer import notification import cv2 from PIL import Image, ImageTk @@ -20,6 +22,7 @@ SESSION = requests.Session() LAST_MSG_IDS = set() +# ---------------- ChatApp ---------------- class ChatApp: def __init__(self, root): self.root = root @@ -27,9 +30,12 @@ class ChatApp: root.geometry("800x600") root.minsize(500, 400) + # Settings self.settings = { "popup_notification": True, "camera_notification": False, + "time_24h": False, + "timezone": time.tzname[0] # Default local timezone } self.username = None @@ -88,19 +94,32 @@ class ChatApp: def open_settings(self): win = tk.Toplevel(self.root) win.title("Settings") - win.geometry("300x200") + win.geometry("350x250") win.resizable(False, False) popup_var = tk.BooleanVar(value=self.settings["popup_notification"]) cam_var = tk.BooleanVar(value=self.settings["camera_notification"]) + time24_var = tk.BooleanVar(value=self.settings.get("time_24h", False)) + tz_var = tk.StringVar(value=self.settings.get("timezone", time.tzname[0])) - tk.Label(win, text="Notification Options", font=("Courier", 12, "bold")).pack(pady=10) - tk.Checkbutton(win, text="Popup Notification", variable=popup_var).pack(anchor="w", padx=20, pady=5) - tk.Checkbutton(win, text="Camera Notification", variable=cam_var).pack(anchor="w", padx=20, pady=5) + tk.Label(win, text="Notification Options", font=("Courier", 12, "bold")).pack(pady=5) + tk.Checkbutton(win, text="Popup Notification", variable=popup_var).pack(anchor="w", padx=20) + tk.Checkbutton(win, text="Camera Notification", variable=cam_var).pack(anchor="w", padx=20) + + tk.Label(win, text="Time Display", font=("Courier", 12, "bold")).pack(pady=5) + tk.Checkbutton(win, text="24-Hour Clock", variable=time24_var).pack(anchor="w", padx=20) + + tk.Label(win, text="Timezone", font=("Courier", 12, "bold")).pack(pady=5) + # Common timezones for dropdown + tz_options = ["UTC", "US/Eastern", "US/Central", "US/Mountain", "US/Pacific", "Europe/London", "Europe/Berlin"] + tk.OptionMenu(win, tz_var, *tz_options).pack(anchor="w", padx=20) def save_settings(): self.settings["popup_notification"] = popup_var.get() self.settings["camera_notification"] = cam_var.get() + self.settings["time_24h"] = time24_var.get() + self.settings["timezone"] = tz_var.get() + self.update_chat_timestamps() messagebox.showinfo("Saved", "Settings updated.") win.destroy() @@ -164,7 +183,6 @@ class ChatApp: tk.Label(popup, wrap="400", text=f"{username} sent: {file_name}", font=("Courier", 12, "bold")).pack(pady=10) - # Attempt to preview image inline try: resp = requests.get(file_url) pil_img = Image.open(BytesIO(resp.content)) @@ -189,6 +207,53 @@ class ChatApp: tk.Button(btn_frame, text="Download", command=download_file).pack(side=tk.LEFT, padx=5) tk.Button(btn_frame, text="Preview", command=lambda: webbrowser.open(preview_url)).pack(side=tk.LEFT, padx=5) + # ---------- Timestamp helper ---------- + def format_timestamp(self, ts): + try: + dt = parser.parse(ts) + dt = dt.astimezone(tz.gettz(self.settings.get("timezone", time.tzname[0]))) + if self.settings.get("time_24h"): + return dt.strftime("%Y-%m-%d %H:%M:%S") + else: + return dt.strftime("%Y-%m-%d %I:%M:%S %p") + except Exception: + return str(ts) + + def update_chat_timestamps(self): + """Re-render chat box to apply new timezone or 24h/12h setting.""" + try: + # Re-poll messages and re-render + r = SESSION.get(SERVER + "/api/messages") + if r.status_code != 200: + return + msgs = r.json() + + self.chat_box.config(state='normal') + self.chat_box.delete("1.0", tk.END) + + for m in msgs: + ts = m.get("created_at") or time.time() + ts_str = self.format_timestamp(ts) + + if m.get("attachment"): + attach_name = m["attachment"] + tag_name = f"attach{m['id']}" + self.chat_box.insert(tk.END, f"[{ts_str}] {m['username']} sent a file: ", f"grey{m['id']}") + self.chat_box.tag_config(f"grey{m['id']}", foreground="grey", font=("Courier", 10, "italic")) + self.chat_box.insert(tk.END, f"{attach_name}\n", tag_name) + self.chat_box.tag_config(tag_name, foreground="blue", underline=True) + self.chat_box.tag_bind(tag_name, "", + lambda e, fn=attach_name, un=m["username"]: self.show_attachment_popup(fn, un)) + else: + text = m.get("text", "") + self.chat_box.insert(tk.END, f"[{ts_str}] {m['username']}: {text}\n") + + self.chat_box.config(state='disabled') + self.chat_box.yview(tk.END) + except Exception as e: + print(f"Update timestamp error: {e}") + + # ---------- Poll messages ---------- def poll_messages(self, force=False): try: r = SESSION.get(SERVER + "/api/messages") @@ -199,34 +264,7 @@ class ChatApp: new_msgs = [m for m in msgs if m["id"] not in LAST_MSG_IDS] if new_msgs or force: - self.chat_box.config(state='normal') - self.chat_box.delete("1.0", tk.END) - for m in msgs: - ts = m.get("created_at") or time.time() - try: - ts_str = datetime.fromtimestamp(float(ts)).strftime("%Y-%m-%d %H:%M:%S") - except: - ts_str = str(ts) - - if m.get("attachment"): - attach_name = m["attachment"] - tag_name = f"attach{m['id']}" - - # Grey italic text for "sent a file" - self.chat_box.insert(tk.END, f"[{ts_str}] {m['username']} sent a file: ", f"grey{m['id']}") - self.chat_box.tag_config(f"grey{m['id']}", foreground="grey", font=("Courier", 10, "italic")) - - # Blue clickable filename - self.chat_box.insert(tk.END, f"{attach_name}\n", tag_name) - self.chat_box.tag_config(tag_name, foreground="blue", underline=True) - self.chat_box.tag_bind(tag_name, "", - lambda e, fn=attach_name, un=m["username"]: self.show_attachment_popup(fn, un)) - else: - text = m.get("text", "") - self.chat_box.insert(tk.END, f"[{ts_str}] {m['username']}: {text}\n") - - self.chat_box.config(state='disabled') - self.chat_box.yview(tk.END) + self.update_chat_timestamps() # Notify user about new messages for m in new_msgs: @@ -250,12 +288,7 @@ class ChatApp: def show_system_notification(self, title, message): try: - notification.notify( - title=title, - app_name="chatter", - message=message[:200], - timeout=5 - ) + notification.notify(title=title, app_name="chatter", message=message[:200], timeout=5) except Exception as e: print(f"Notification failed: {e}") @@ -275,4 +308,4 @@ class ChatApp: if __name__ == "__main__": root = tk.Tk() app = ChatApp(root) - root.mainloop() + root.mainloop() \ No newline at end of file