1111from ttkbootstrap .widgets .scrolled import ScrolledText
1212
1313# ================== CONFIG ================== #
14- HOST = "0.0.0.0" # LAN / WAN support
1514PORT = 5050
1615BUFFER_SIZE = 4096
1716DB_FILE = "chat.db"
17+ PASSWORD = "Secret123" # Server password
18+
19+ # ================== UTILITY: AUTO LAN IP ================== #
20+ def get_local_ip ():
21+ """
22+ Detects LAN IP automatically.
23+ Falls back to 127.0.0.1 if none found.
24+ """
25+ try :
26+ s = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
27+ s .connect (("8.8.8.8" , 80 )) # Doesn't actually send packets
28+ ip = s .getsockname ()[0 ]
29+ s .close ()
30+ return ip
31+ except Exception :
32+ return "127.0.0.1"
33+
34+ HOST = get_local_ip ()
1835
1936# ================== DATABASE ================== #
2037def init_db ():
@@ -32,7 +49,6 @@ def init_db():
3249 conn .commit ()
3350 conn .close ()
3451
35-
3652def save_message (sender , content , reply_to = None ):
3753 conn = sqlite3 .connect (DB_FILE )
3854 cur = conn .cursor ()
@@ -43,7 +59,6 @@ def save_message(sender, content, reply_to=None):
4359 conn .commit ()
4460 conn .close ()
4561
46-
4762def load_history (limit = 50 ):
4863 conn = sqlite3 .connect (DB_FILE )
4964 cur = conn .cursor ()
@@ -59,15 +74,19 @@ def load_history(limit=50):
5974clients : Dict [socket .socket , str ] = {}
6075server_running = True
6176
62-
63- def start_server (log_callback ):
77+ def start_secure_server (log_callback ):
6478 init_db ()
6579
6680 server = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
67- server .bind ((HOST , PORT ))
68- server .listen ()
81+ server .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
82+ try :
83+ server .bind ((HOST , PORT ))
84+ except Exception as e :
85+ log_callback (f"Error binding server on { HOST } :{ PORT } : { e } " )
86+ return
6987
70- log_callback (f"Server running on { HOST } :{ PORT } " )
88+ server .listen ()
89+ log_callback (f"Secure server running on { HOST } :{ PORT } (LAN)" )
7190
7291 def broadcast (message ):
7392 for c in list (clients .keys ()):
@@ -78,10 +97,21 @@ def broadcast(message):
7897
7998 def handle_client (conn , addr ):
8099 try :
81- username = conn .recv (BUFFER_SIZE ).decode ()
100+ # Step 1: Password authentication
101+ conn .send ("Enter server password:" .encode ())
102+ pw = conn .recv (BUFFER_SIZE ).decode ().strip ()
103+ if pw != PASSWORD :
104+ conn .send ("❌ Wrong password. Connection closed." .encode ())
105+ conn .close ()
106+ log_callback (f"Rejected connection from { addr } (wrong password)" )
107+ return
108+
109+ # Step 2: Get username
110+ conn .send ("Password accepted. Enter your username:" .encode ())
111+ username = conn .recv (BUFFER_SIZE ).decode ().strip ()
82112 clients [conn ] = username
83113
84- # Send chat history
114+ # Step 3: Send chat history
85115 for sender , content , reply_to , ts in load_history ():
86116 prefix = f"[{ ts } ] { sender } :"
87117 if reply_to :
@@ -92,6 +122,7 @@ def handle_client(conn, addr):
92122 broadcast (f"[SERVER] 🟢 { username } joined the chat" )
93123 log_callback (f"{ username } connected from { addr } " )
94124
125+ # Step 4: Main loop
95126 while server_running :
96127 raw = conn .recv (BUFFER_SIZE ).decode ()
97128 if not raw :
@@ -115,19 +146,33 @@ def handle_client(conn, addr):
115146 name = clients .pop (conn , "Unknown" )
116147 conn .close ()
117148 broadcast (f"[SERVER] 🔴 { name } left the chat" )
149+ log_callback (f"{ name } disconnected from { addr } " )
118150
151+ # Accept clients
119152 while True :
120- conn , addr = server .accept ()
121- threading .Thread (target = handle_client , args = (conn , addr ), daemon = True ).start ()
153+ try :
154+ conn , addr = server .accept ()
155+ threading .Thread (target = handle_client , args = (conn , addr ), daemon = True ).start ()
156+ except Exception as e :
157+ log_callback (f"Server accept error: { e } " )
158+ break
122159
123160# ================== CLIENT ================== #
124161class ChatClient :
125162 def __init__ (self , username , host , message_callback ):
126163 self .username = username
127164 self .sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
128165 self .sock .connect ((host , PORT ))
129- self .sock .send (username .encode ())
130166 self .message_callback = message_callback
167+
168+ # Step 1: Send password
169+ self .sock .recv (BUFFER_SIZE ) # "Enter server password:"
170+ self .sock .send (PASSWORD .encode ())
171+
172+ # Step 2: Send username
173+ self .sock .recv (BUFFER_SIZE ) # "Password accepted. Enter username:"
174+ self .sock .send (username .encode ())
175+
131176 threading .Thread (target = self .listen , daemon = True ).start ()
132177
133178 def listen (self ):
@@ -163,12 +208,11 @@ def send(self, msg, reply_to=None):
163208user_entry = tb .Entry (login )
164209user_entry .pack (fill = tk .X , pady = 5 )
165210
166- tb .Label (login , text = "Server IP (LAN / WAN )" ).pack ()
211+ tb .Label (login , text = "Server IP (LAN)" ).pack ()
167212ip_entry = tb .Entry (login )
168- ip_entry .insert (0 , "127.0.0.1" )
213+ ip_entry .insert (0 , HOST )
169214ip_entry .pack (fill = tk .X , pady = 5 )
170215
171-
172216def join_chat ():
173217 global username , server_ip , client
174218 username = user_entry .get ().strip ()
@@ -179,7 +223,6 @@ def join_chat():
179223 client = ChatClient (username , server_ip , display_message )
180224 display_message (f"[SYSTEM] Connected as { username } " )
181225
182-
183226tb .Button (login , text = "Join Chat" , bootstyle = "success" , command = join_chat ).pack (pady = 15 )
184227
185228# ---------- CHAT ---------- #
@@ -198,7 +241,6 @@ def join_chat():
198241msg_entry = tb .Entry (entry_frame )
199242msg_entry .pack (side = tk .LEFT , fill = tk .X , expand = True , padx = 5 )
200243
201-
202244def send_message ():
203245 global reply_target
204246 text = msg_entry .get ().strip ()
@@ -208,30 +250,26 @@ def send_message():
208250 reply_label .config (text = "" )
209251 msg_entry .delete (0 , tk .END )
210252
211-
212253msg_entry .bind ("<Return>" , lambda e : send_message ())
213254
214-
215255def on_click (event ):
216256 global reply_target
217257 idx = chat_box .text .index ("@%s,%s linestart" % (event .x , event .y ))
218258 line = chat_box .text .get (idx , idx + " lineend" ).strip ()
219259 reply_target = line
220260 reply_label .config (text = f"Replying to: { line [:50 ]} ..." )
221261
222-
223262def display_message (msg ):
224263 chat_box .text .configure (state = "normal" )
225264 chat_box .text .insert ("end" , msg + "\n " )
226265 chat_box .text .see ("end" )
227266 chat_box .text .configure (state = "disabled" )
228267
229-
230268chat_box .text .bind ("<Button-1>" , on_click )
231269
232270tb .Button (entry_frame , text = "Send" , bootstyle = "primary" , command = send_message ).pack (side = tk .RIGHT )
233271
234272# ---------- START SERVER THREAD ---------- #
235- threading .Thread (target = start_server , args = (display_message ,), daemon = True ).start ()
273+ threading .Thread (target = start_secure_server , args = (display_message ,), daemon = True ).start ()
236274
237275app .mainloop ()
0 commit comments