feature pack 1

This commit is contained in:
2025-10-10 23:32:52 +00:00
parent b49955a974
commit 58770684db

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Desktop GUI wrapper for chatter.py using Tkinter. 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. Polls server every 1.5s for updates.
Includes 24-hour toggle and timezone selection.
""" """
import threading import threading
@@ -10,6 +11,7 @@ import tkinter as tk
from tkinter import filedialog, scrolledtext, messagebox from tkinter import filedialog, scrolledtext, messagebox
import requests, os, time, webbrowser import requests, os, time, webbrowser
from datetime import datetime from datetime import datetime
from dateutil import parser, tz
from plyer import notification from plyer import notification
import cv2 import cv2
from PIL import Image, ImageTk from PIL import Image, ImageTk
@@ -20,6 +22,7 @@ SESSION = requests.Session()
LAST_MSG_IDS = set() LAST_MSG_IDS = set()
# ---------------- ChatApp ----------------
class ChatApp: class ChatApp:
def __init__(self, root): def __init__(self, root):
self.root = root self.root = root
@@ -27,9 +30,12 @@ class ChatApp:
root.geometry("800x600") root.geometry("800x600")
root.minsize(500, 400) root.minsize(500, 400)
# Settings
self.settings = { self.settings = {
"popup_notification": True, "popup_notification": True,
"camera_notification": False, "camera_notification": False,
"time_24h": False,
"timezone": time.tzname[0] # Default local timezone
} }
self.username = None self.username = None
@@ -88,19 +94,32 @@ class ChatApp:
def open_settings(self): def open_settings(self):
win = tk.Toplevel(self.root) win = tk.Toplevel(self.root)
win.title("Settings") win.title("Settings")
win.geometry("300x200") win.geometry("350x250")
win.resizable(False, False) win.resizable(False, False)
popup_var = tk.BooleanVar(value=self.settings["popup_notification"]) popup_var = tk.BooleanVar(value=self.settings["popup_notification"])
cam_var = tk.BooleanVar(value=self.settings["camera_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.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, 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, pady=5) 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(): def save_settings():
self.settings["popup_notification"] = popup_var.get() self.settings["popup_notification"] = popup_var.get()
self.settings["camera_notification"] = cam_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.") messagebox.showinfo("Saved", "Settings updated.")
win.destroy() 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) tk.Label(popup, wrap="400", text=f"{username} sent: {file_name}", font=("Courier", 12, "bold")).pack(pady=10)
# Attempt to preview image inline
try: try:
resp = requests.get(file_url) resp = requests.get(file_url)
pil_img = Image.open(BytesIO(resp.content)) 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="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) 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, "<Button-1>",
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): def poll_messages(self, force=False):
try: try:
r = SESSION.get(SERVER + "/api/messages") 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] new_msgs = [m for m in msgs if m["id"] not in LAST_MSG_IDS]
if new_msgs or force: if new_msgs or force:
self.chat_box.config(state='normal') self.update_chat_timestamps()
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, "<Button-1>",
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)
# Notify user about new messages # Notify user about new messages
for m in new_msgs: for m in new_msgs:
@@ -250,12 +288,7 @@ class ChatApp:
def show_system_notification(self, title, message): def show_system_notification(self, title, message):
try: try:
notification.notify( notification.notify(title=title, app_name="chatter", message=message[:200], timeout=5)
title=title,
app_name="chatter",
message=message[:200],
timeout=5
)
except Exception as e: except Exception as e:
print(f"Notification failed: {e}") print(f"Notification failed: {e}")
@@ -275,4 +308,4 @@ class ChatApp:
if __name__ == "__main__": if __name__ == "__main__":
root = tk.Tk() root = tk.Tk()
app = ChatApp(root) app = ChatApp(root)
root.mainloop() root.mainloop()