-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgui.py
More file actions
269 lines (221 loc) · 10.5 KB
/
gui.py
File metadata and controls
269 lines (221 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
import time
import tkinter as tk # Main GUI library
from tkinter import ttk # For themed widgets like Notebook (tabs)
from tkinter import scrolledtext # For scrollable text areas (used in log tab)
from tkinter import filedialog # For file chooser dialogs
import threading # Enables background threads for listening to incoming messages
import config # Loads user ID, port, and other global settings
from core.socket_manager import SocketManager # Handles sending/receiving UDP messages
from core.message_handler import MessageHandler # Handles logic for parsing and responding to LSNP messages
from core.peer_manager import PeerManager # Tracks known peers and their IPs
from utils.logger import Logger # Outputs logs to console and GUI log window
from utils.token import TokenManager # Generates and validates scoped tokens for secure messaging
# Feature modules
from features.post_feature import send_post
from features.dm_feature import send_dm
from features.follow_feature import send_follow_action
from features.like_feature import send_like_action
from features.file_transfer import FileTransfer
from features.group_feature import GroupFeature
from features.game_feature import GameFeature
# GUI client for LSNP using function-based features from the /features directory.
class LSNP_GUI:
def __init__(self, root):
self.root = root
self.root.title("LSNP GUI Client")
self.root.geometry("900x700")
# Core logic
self.logger = Logger(verbose=True)
self.socket_manager = SocketManager(config.PORT, self.logger)
self.peer_manager = PeerManager(self.logger)
self.token_manager = TokenManager()
# Feature modules that require state
self.file_transfer = FileTransfer(
self.socket_manager,
self.peer_manager,
self.logger,
self.token_manager
)
self.group_feature = GroupFeature(self)
self.game_feature = GameFeature(self)
# UI setup
self.setup_ui()
# Core message handler
self.handler = MessageHandler(self.socket_manager, self.peer_manager, self.logger)
self.handler.gui = self
self.handler.file_manager = self.file_transfer
self.handler.group_manager = self.group_feature
self.handler.game_manager = self.game_feature
# Start background receiver thread
threading.Thread(target=self.listen_loop, daemon=True).start()
# Start periodic ping to keep connections alive
threading.Thread(target=self.ping_loop, daemon=True).start()
# GUI setup
def setup_ui(self):
notebook = ttk.Notebook(self.root)
notebook.pack(expand=1, fill="both")
# create tabs for different features
self.tabs = {
"Messaging": tk.Frame(notebook),
"Follow & Like": tk.Frame(notebook),
"File & Group": tk.Frame(notebook),
"Game": tk.Frame(notebook),
"Log": tk.Frame(notebook)
}
for name, tab in self.tabs.items():
notebook.add(tab, text=name)
# setup each tab's content
self.setup_messaging_tab()
self.setup_follow_like_tab()
self.setup_file_group_tab()
self.setup_game_tab()
self.setup_log_tab()
# messaging tab setup
def setup_messaging_tab(self):
tab = self.tabs["Messaging"]
tk.Label(tab, text="Recipient (for DM):").pack()
self.dm_to = tk.Entry(tab, width=60)
self.dm_to.pack()
tk.Label(tab, text="Message:").pack()
self.message_entry = tk.Entry(tab, width=80)
self.message_entry.pack(pady=5)
btn_frame = tk.Frame(tab)
btn_frame.pack()
tk.Button(btn_frame, text="Send POST", command=self.handle_post).grid(row=0, column=0, padx=5)
tk.Button(btn_frame, text="Send DM", command=self.handle_dm).grid(row=0, column=1, padx=5)
tk.Button(btn_frame, text="Broadcast PROFILE", command=self.send_profile).grid(row=0, column=2, padx=5)
# tk.Button(btn_frame, text="Send PING", command=self.send_ping).grid(row=0, column=3, padx=5)
tk.Label(tab, text="Token to Revoke:").pack(pady=(10, 0))
self.revoke_token = tk.Entry(tab, width=80)
self.revoke_token.pack()
tk.Button(tab, text="Send REVOKE", command=self.handle_revoke).pack(pady=5)
# follow and like tab setup
def setup_follow_like_tab(self):
tab = self.tabs["Follow & Like"]
tk.Label(tab, text="User ID to Follow/Unfollow:").pack()
self.follow_to = tk.Entry(tab, width=60)
self.follow_to.pack()
tk.Button(tab, text="Follow", command=lambda: self.handle_follow("FOLLOW")).pack()
tk.Button(tab, text="Unfollow", command=lambda: self.handle_follow("UNFOLLOW")).pack()
tk.Label(tab, text="LIKE/UNLIKE — Target User ID:").pack()
self.like_to = tk.Entry(tab, width=60)
self.like_to.pack()
tk.Label(tab, text="Post Timestamp:").pack()
self.like_time = tk.Entry(tab, width=60)
self.like_time.pack()
tk.Button(tab, text="Like", command=lambda: self.handle_like("LIKE")).pack()
tk.Button(tab, text="Unlike", command=lambda: self.handle_like("UNLIKE")).pack()
# file and group features tab
def setup_file_group_tab(self):
tab = self.tabs["File & Group"]
# FILE
tk.Label(tab, text="Send File — Recipient ID:").pack()
self.file_to = tk.Entry(tab, width=60)
self.file_to.pack()
tk.Button(tab, text="Choose File and Send", command=self.handle_file_send).pack(pady=5)
# GROUP
self.group_feature.setup_ui(tab)
# handles sending group messages
def handle_group_msg(self):
group_id = self.group_id_entry.get().strip()
content = self.group_msg_entry.get().strip()
if not group_id or not content:
self.log("[ERROR] Group ID or message is empty.")
return
token = self.token_manager.generate(config.USER_ID, "group", config.TOKEN_TTL)
mid = uuid.uuid4().hex[:8]
now = int(time.time())
msg = f"TYPE: GROUP_MESSAGE\nGROUP_ID: {group_id}\nFROM: {config.USER_ID}\nCONTENT: {content}\nTOKEN: {token}\nMESSAGE_ID: {mid}\nTIMESTAMP: {now}"
self.socket_manager.send(msg, broadcast=True)
self.log(f"[GROUP_MESSAGE] Sent to {group_id}: {content}")
# tictactoe game tab setup
def setup_game_tab(self):
self.game_feature.setup_ui(self.tabs["Game"])
# log tab setup
def setup_log_tab(self):
self.log_output = scrolledtext.ScrolledText(self.tabs["Log"], state="disabled", height=30)
self.log_output.pack(fill="both", expand=True)
# write to the log tab
def log(self, text):
self.log_output.configure(state="normal")
self.log_output.insert(tk.END, text + "\n")
self.log_output.configure(state="disabled")
self.log_output.see(tk.END)
# background thread to listen for incoming messages
def listen_loop(self):
while True:
msg, addr = self.socket_manager.receive()
self.handler.handle_message(msg, addr)
if not msg.startswith("TYPE: PING"):
self.log(f"[RECV from {addr[0]}] {msg.strip().splitlines()[0]}")
# periodic ping to keep connections alive [loop]
def ping_loop(self):
while True:
msg = f"TYPE: PING\nUSER_ID: {config.USER_ID}\n"
self.socket_manager.send(msg.strip(), broadcast=True)
# if config.VERBOSE:
# self.log("[PING] Auto-sent")
time.sleep(5) # send ping every 10 seconds
# for external updates from message handler
def update_board_display(self, pos, symbol):
self.game_feature.update_board_display(pos, symbol)
# Action Button Handlers
def handle_post(self):
content = self.message_entry.get()
send_post(self.socket_manager, self.token_manager, config.USER_ID, content, self.peer_manager)
self.log(f"[POST] You: {content}")
def handle_dm(self):
to = self.dm_to.get().strip()
content = self.message_entry.get()
send_dm(self.socket_manager, self.token_manager, self.peer_manager, config.USER_ID, to, content)
self.log(f"[DM] To {to}: {content}")
def handle_follow(self, action):
to = self.follow_to.get().strip()
send_follow_action(self.socket_manager, self.token_manager, self.peer_manager, config.USER_ID, to, action)
self.log(f"[{action}] You {action.lower()}ed {to}")
def handle_like(self, action):
to = self.like_to.get().strip()
ts = self.like_time.get().strip()
send_like_action(self.socket_manager, self.token_manager, self.peer_manager, config.USER_ID, to, ts, action)
self.log(f"[{action}] You {action.lower()}d a post from {to}")
def handle_file_send(self):
to = self.file_to.get().strip()
filepath = filedialog.askopenfilename()
if filepath:
self.file_transfer.send_file(to, filepath, config.USER_ID, config.TOKEN_TTL)
def handle_revoke(self):
token_str = self.revoke_token.get().strip()
if token_str:
msg = f"TYPE: REVOKE\nTOKEN: {token_str}\n"
self.socket_manager.send(msg.strip(), broadcast=True)
self.log(f"[REVOKE] Token revoked: {token_str}")
def send_profile(self):
msg = (
f"TYPE: PROFILE\n"
f"USER_ID: {config.USER_ID}\n"
f"DISPLAY_NAME: {config.DISPLAY_NAME}\n"
f"STATUS: {config.STATUS}\n"
)
# Add avatar info only if it's under ~20 KB
if config.AVATAR_DATA:
avatar_bytes = config.AVATAR_DATA.encode("utf-8")
if len(avatar_bytes) <= 20000:
msg += (
f"AVATAR_TYPE: {config.AVATAR_TYPE}\n"
f"AVATAR_ENCODING: {config.AVATAR_ENCODING}\n"
f"AVATAR_DATA: {config.AVATAR_DATA}\n"
)
else:
print("[PROFILE] Avatar too large to include in PROFILE message.")
print(f"[DEBUG] PROFILE message size: {len(msg.encode('utf-8'))} bytes")
self.socket_manager.send(msg.strip(), broadcast=True)
self.log("[PROFILE] Broadcast sent")
def send_ping(self):
msg = f"TYPE: PING\nUSER_ID: {config.USER_ID}\n"
self.socket_manager.send(msg.strip(), broadcast=True)
self.log("[PING] Sent")
# app entry point
if __name__ == "__main__":
root = tk.Tk()
app = LSNP_GUI(root)
root.mainloop()