@@ -170,13 +170,23 @@ async def start_add_server(update: Update, context: ContextTypes.DEFAULT_TYPE) -
170170
171171async def get_server_name (update : Update , context : ContextTypes .DEFAULT_TYPE ) -> int :
172172 """Get server name from user."""
173+ if not update .message or not update .message .text :
174+ await update .message .reply_text ("❌ Please send a text message with the server name." )
175+ return WAITING_SERVER_NAME
176+
173177 server_name = update .message .text .strip ()
174178
175179 if not server_name :
176180 await update .message .reply_text ("❌ Server name cannot be empty. Please enter a valid name:" )
177181 return WAITING_SERVER_NAME
178182
183+ if len (server_name ) > 100 :
184+ await update .message .reply_text ("❌ Server name too long. Please enter a name with less than 100 characters:" )
185+ return WAITING_SERVER_NAME
186+
179187 context .user_data ['server_name' ] = server_name
188+ logger .info (f"User { update .effective_user .id } set server name: { server_name } " )
189+
180190 await update .message .reply_text (
181191 f"✅ Server name set to: { server_name } \n \n "
182192 "Now please enter the API URL (e.g., https://your-server.com:12345/access-keys):"
@@ -186,6 +196,10 @@ async def get_server_name(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
186196
187197async def get_api_url (update : Update , context : ContextTypes .DEFAULT_TYPE ) -> int :
188198 """Get API URL from user."""
199+ if not update .message or not update .message .text :
200+ await update .message .reply_text ("❌ Please send a text message with the API URL." )
201+ return WAITING_API_URL
202+
189203 api_url = update .message .text .strip ()
190204
191205 if not api_url .startswith (('http://' , 'https://' )):
@@ -194,7 +208,13 @@ async def get_api_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int
194208 )
195209 return WAITING_API_URL
196210
211+ if len (api_url ) > 500 :
212+ await update .message .reply_text ("❌ URL too long. Please enter a shorter URL:" )
213+ return WAITING_API_URL
214+
197215 context .user_data ['api_url' ] = api_url
216+ logger .info (f"User { update .effective_user .id } set API URL: { api_url } " )
217+
198218 await update .message .reply_text (
199219 f"✅ API URL set to: { api_url } \n \n "
200220 "Finally, please enter the certificate SHA256 fingerprint:"
@@ -204,6 +224,10 @@ async def get_api_url(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int
204224
205225async def get_cert_sha256 (update : Update , context : ContextTypes .DEFAULT_TYPE ) -> int :
206226 """Get certificate SHA256 and save the server."""
227+ if not update .message or not update .message .text :
228+ await update .message .reply_text ("❌ Please send a text message with the certificate SHA256." )
229+ return WAITING_CERT_SHA256
230+
207231 cert_sha256 = update .message .text .strip ()
208232
209233 if len (cert_sha256 ) != 64 :
@@ -212,36 +236,73 @@ async def get_cert_sha256(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
212236 )
213237 return WAITING_CERT_SHA256
214238
215- # Save the server
216- async with get_db_session () as session :
217- server = Server (
218- name = context . user_data [ 'server_name' ],
219- api_url = context . user_data [ 'api_url' ],
220- cert_sha256 = cert_sha256
239+ # Validate that it's a valid hex string
240+ try :
241+ int ( cert_sha256 , 16 )
242+ except ValueError :
243+ await update . message . reply_text (
244+ "❌ Invalid SHA256 format. Please enter a valid hexadecimal SHA256 hash:"
221245 )
222- session .add (server )
223- await session .commit ()
224-
225- await log_action (
226- update .effective_user .id ,
227- "server_added" ,
228- "server" ,
229- str (server .id ),
230- f"Added server { server .name } "
246+ return WAITING_CERT_SHA256
247+
248+ # Check that we have all required data
249+ if 'server_name' not in context .user_data or 'api_url' not in context .user_data :
250+ await update .message .reply_text (
251+ "❌ Missing server information. Please start over with /addserver"
231252 )
232-
233- escaped_name = escape_markdown (server .name , version = 2 )
234- escaped_api_url = escape_markdown (server .api_url , version = 2 )
235- escaped_cert = escape_markdown (cert_sha256 [:16 ] + "..." , version = 2 )
236-
253+ context .user_data .clear ()
254+ return ConversationHandler .END
255+
256+ # Save the server
257+ try :
258+ async with get_db_session () as session :
259+ # Check if a server with the same API URL already exists
260+ existing_server = await session .execute (
261+ select (Server ).where (Server .api_url == context .user_data ['api_url' ])
262+ )
263+ if existing_server .scalar_one_or_none ():
264+ await update .message .reply_text (
265+ "❌ A server with this API URL already exists. Please use a different URL."
266+ )
267+ return WAITING_API_URL
268+
269+ server = Server (
270+ name = context .user_data ['server_name' ],
271+ api_url = context .user_data ['api_url' ],
272+ cert_sha256 = cert_sha256
273+ )
274+ session .add (server )
275+ await session .commit ()
276+
277+ logger .info (f"User { update .effective_user .id } added server: { server .name } (ID: { server .id } )" )
278+
279+ await log_action (
280+ update .effective_user .id ,
281+ "server_added" ,
282+ "server" ,
283+ str (server .id ),
284+ f"Added server { server .name } "
285+ )
286+
287+ escaped_name = escape_markdown (server .name , version = 2 )
288+ escaped_api_url = escape_markdown (server .api_url , version = 2 )
289+ escaped_cert = escape_markdown (cert_sha256 [:16 ] + "..." , version = 2 )
290+
291+ await update .message .reply_text (
292+ f"✅ Server added successfully\\ !\n \n "
293+ f"**Name:** { escaped_name } \n "
294+ f"**ID:** { server .id } \n "
295+ f"**API URL:** { escaped_api_url } \n "
296+ f"**Cert SHA256:** { escaped_cert } " ,
297+ parse_mode = 'MarkdownV2'
298+ )
299+ except Exception as e :
300+ logger .error (f"Error adding server for user { update .effective_user .id } : { e } " )
237301 await update .message .reply_text (
238- f"✅ Server added successfully\\ !\n \n "
239- f"**Name:** { escaped_name } \n "
240- f"**ID:** { server .id } \n "
241- f"**API URL:** { escaped_api_url } \n "
242- f"**Cert SHA256:** { escaped_cert } " ,
243- parse_mode = 'MarkdownV2'
302+ "❌ An error occurred while adding the server. Please try again later."
244303 )
304+ context .user_data .clear ()
305+ return ConversationHandler .END
245306
246307 # Clear conversation data
247308 context .user_data .clear ()
@@ -255,6 +316,72 @@ async def cancel_add_server(update: Update, context: ContextTypes.DEFAULT_TYPE)
255316 return ConversationHandler .END
256317
257318
319+ async def timeout_add_server (update : Update , context : ContextTypes .DEFAULT_TYPE ) -> int :
320+ """Handle conversation timeout for add server."""
321+ if update .message :
322+ await update .message .reply_text (
323+ "⏰ Server addition timed out due to inactivity. Please run /addserver again to start over."
324+ )
325+ context .user_data .clear ()
326+ return ConversationHandler .END
327+
328+
329+ @admin_only
330+ async def bot_status (update : Update , context : ContextTypes .DEFAULT_TYPE ) -> None :
331+ """Show bot status information for debugging."""
332+ try :
333+ async with get_db_session () as session :
334+ # Count users by role
335+ users_result = await session .execute (select (User ))
336+ users = users_result .scalars ().all ()
337+
338+ user_counts = {
339+ "pending" : sum (1 for u in users if u .role == UserRole .PENDING ),
340+ "sales" : sum (1 for u in users if u .role == UserRole .SALES ),
341+ "blocked" : sum (1 for u in users if u .role == UserRole .BLOCKED ),
342+ "admin" : sum (1 for u in users if u .role == UserRole .ADMIN ),
343+ }
344+
345+ # Count servers
346+ servers_result = await session .execute (select (Server ))
347+ servers = servers_result .scalars ().all ()
348+ active_servers = sum (1 for s in servers if s .is_active )
349+
350+ # Count access keys
351+ keys_result = await session .execute (select (AccessKey ))
352+ keys = keys_result .scalars ().all ()
353+
354+ now = datetime .utcnow ()
355+ active_keys = sum (1 for k in keys if k .disabled_at is None and k .expires_at > now )
356+ expired_keys = sum (1 for k in keys if k .disabled_at is None and k .expires_at <= now )
357+ disabled_keys = sum (1 for k in keys if k .disabled_at is not None )
358+
359+ status_message = (
360+ f"🤖 **Bot Status**\n \n "
361+ f"👥 **Users:**\n "
362+ f" • Total: { len (users )} \n "
363+ f" • Pending: { user_counts ['pending' ]} \n "
364+ f" • Sales: { user_counts ['sales' ]} \n "
365+ f" • Blocked: { user_counts ['blocked' ]} \n "
366+ f" • Admin: { user_counts ['admin' ]} \n \n "
367+ f"🖥️ **Servers:**\n "
368+ f" • Total: { len (servers )} \n "
369+ f" • Active: { active_servers } \n \n "
370+ f"🔑 **Access Keys:**\n "
371+ f" • Total: { len (keys )} \n "
372+ f" • Active: { active_keys } \n "
373+ f" • Expired: { expired_keys } \n "
374+ f" • Disabled: { disabled_keys } \n \n "
375+ f"⏰ **Timestamp:** { datetime .utcnow ().strftime ('%Y-%m-%d %H:%M:%S' )} UTC"
376+ )
377+
378+ await update .message .reply_text (status_message )
379+
380+ except Exception as e :
381+ logger .error (f"Error getting bot status: { e } " )
382+ await update .message .reply_text (f"❌ Error retrieving bot status: { e } " )
383+
384+
258385@admin_only
259386async def remove_server (update : Update , context : ContextTypes .DEFAULT_TYPE ) -> None :
260387 """Remove a VPN server."""
@@ -434,13 +561,4 @@ async def notify_admin_new_user(context: ContextTypes.DEFAULT_TYPE, user: User)
434561 logger .error (f"Failed to notify admin about new user { user .id } : { e } " )
435562
436563
437- # Conversation handler for adding servers
438- add_server_conv_handler = ConversationHandler (
439- entry_points = [], # Will be set in main.py
440- states = {
441- WAITING_SERVER_NAME : [],
442- WAITING_API_URL : [],
443- WAITING_CERT_SHA256 : [],
444- },
445- fallbacks = [],
446- )
564+ # Note: Conversation handler is properly configured in main.py
0 commit comments