From 80c519a66f21b42d5fa4e001a703f811ad4332f3 Mon Sep 17 00:00:00 2001 From: Whispering Wind <151555003+ReturnFI@users.noreply.github.com> Date: Wed, 14 May 2025 00:34:23 +0330 Subject: [PATCH] feat(telegram): Enhance add user to show Normal-SUB link and QR Modifies the Telegram bot's 'Add User' functionality. After adding a user, the bot now attempts to retrieve and display the Normal-SUB subscription link and its QR code if available. If Normal-SUB is not found, it falls back to showing the direct Hysteria2 IPv4 URI and QR code. The direct URI is also provided as a fallback if Normal-SUB is shown. Usernames are quoted in CLI calls for robustness. Improved input validation for username, traffic, and expiration days. --- core/scripts/telegrambot/utils/adduser.py | 100 ++++++++++++++++------ 1 file changed, 74 insertions(+), 26 deletions(-) diff --git a/core/scripts/telegrambot/utils/adduser.py b/core/scripts/telegrambot/utils/adduser.py index 317c87f..7298f2e 100644 --- a/core/scripts/telegrambot/utils/adduser.py +++ b/core/scripts/telegrambot/utils/adduser.py @@ -3,7 +3,7 @@ import io import json from telebot import types from utils.command import * -from utils.common import create_main_markup +from utils.common import create_main_markup def create_cancel_markup(back_step=None): @@ -24,8 +24,13 @@ def process_add_user_step1(message): return username = message.text.strip() - if username == "": - bot.reply_to(message, "Username cannot be empty. Please enter a valid username.", reply_markup=create_cancel_markup()) + if not username: + bot.reply_to(message, "Username cannot be empty. Please enter a valid username:", reply_markup=create_cancel_markup()) + bot.register_next_step_handler(message, process_add_user_step1) + return + + if '\n' in username or len(username) > 50: + bot.reply_to(message, "Invalid username format. Please use a shorter username without newlines.", reply_markup=create_cancel_markup()) bot.register_next_step_handler(message, process_add_user_step1) return @@ -33,21 +38,19 @@ def process_add_user_step1(message): result = run_cli_command(command) try: - users = json.loads(result) - existing_users = {user.lower() for user in users.keys()} - + users_data = json.loads(result) + existing_users = {user_key.lower() for user_key in users_data.keys()} if username.lower() in existing_users: bot.reply_to(message, f"Username '{username}' already exists. Please choose a different username:", reply_markup=create_cancel_markup()) bot.register_next_step_handler(message, process_add_user_step1) return except json.JSONDecodeError: - if "No such file or directory" in result or result.strip() == "": - bot.reply_to(message, "User list file does not exist. Adding the first user.", reply_markup=create_cancel_markup()) + if "No such file or directory" in result or result.strip() == "" or "Could not find users" in result.lower(): + pass else: - bot.reply_to(message, "Error retrieving user list. Please try again later.") - bot.send_message(message.chat.id, "Returning to main menu...", reply_markup=create_main_markup()) + bot.reply_to(message, "Error checking existing users. Please try again.", reply_markup=create_main_markup()) return - + msg = bot.reply_to(message, "Enter traffic limit (GB):", reply_markup=create_cancel_markup(back_step=process_add_user_step1)) bot.register_next_step_handler(msg, process_add_user_step2, username) @@ -62,10 +65,14 @@ def process_add_user_step2(message, username): try: traffic_limit = int(message.text.strip()) + if traffic_limit < 0: + bot.reply_to(message, "Traffic limit cannot be negative. Please enter a valid number (GB):", reply_markup=create_cancel_markup(back_step=process_add_user_step1)) + bot.register_next_step_handler(message, process_add_user_step2, username) + return msg = bot.reply_to(message, "Enter expiration days:", reply_markup=create_cancel_markup(back_step=process_add_user_step2)) bot.register_next_step_handler(msg, process_add_user_step3, username, traffic_limit) except ValueError: - bot.reply_to(message, "Invalid traffic limit. Please enter a number:", reply_markup=create_cancel_markup(back_step=process_add_user_step1)) + bot.reply_to(message, "Invalid traffic limit. Please enter a number (GB):", reply_markup=create_cancel_markup(back_step=process_add_user_step1)) bot.register_next_step_handler(message, process_add_user_step2, username) def process_add_user_step3(message, username, traffic_limit): @@ -79,25 +86,66 @@ def process_add_user_step3(message, username, traffic_limit): try: expiration_days = int(message.text.strip()) - lower_username = username.lower() - command = f"python3 {CLI_PATH} add-user -u {username} -t {traffic_limit} -e {expiration_days}" - result = run_cli_command(command) + if expiration_days < 0: + bot.reply_to(message, "Expiration days cannot be negative. Please enter a valid number:", reply_markup=create_cancel_markup(back_step=process_add_user_step2)) + bot.register_next_step_handler(message, process_add_user_step3, username, traffic_limit) + return + + add_user_command = f"python3 {CLI_PATH} add-user -u \"{username}\" -t {traffic_limit} -e {expiration_days}" + add_user_feedback = run_cli_command(add_user_command).strip() bot.send_chat_action(message.chat.id, 'typing') - qr_command = f"python3 {CLI_PATH} show-user-uri -u {lower_username} -ip 4" - qr_result = run_cli_command(qr_command).replace("IPv4:\n", "").strip() + + lower_username = username.lower() + uri_info_command = f"python3 {CLI_PATH} show-user-uri -u \"{lower_username}\" -ip 4 -n" + uri_info_output = run_cli_command(uri_info_command) - if not qr_result: - bot.reply_to(message, "Failed to generate QR code.", reply_markup=create_main_markup()) - return + direct_uri = None + normal_sub_link = None - qr_v4 = qrcode.make(qr_result) - bio_v4 = io.BytesIO() - qr_v4.save(bio_v4, 'PNG') - bio_v4.seek(0) - caption = f"{result}\n\n`{qr_result}`" - bot.send_photo(message.chat.id, photo=bio_v4, caption=caption, parse_mode="Markdown", reply_markup=create_main_markup()) + if "IPv4:" in uri_info_output: + try: + parts_after_ipv4 = uri_info_output.split("IPv4:\n", 1)[1] + potential_direct_uri = parts_after_ipv4.split('\n', 1)[0].strip() + if potential_direct_uri.startswith("hy2://"): + direct_uri = potential_direct_uri + except (IndexError, AttributeError): + pass + + if "Normal-SUB Sublink:" in uri_info_output: + try: + parts_after_sublink_label = uri_info_output.split("Normal-SUB Sublink:\n", 1)[1] + potential_sub_link = parts_after_sublink_label.split('\n', 1)[0].strip() + if potential_sub_link.startswith("http://") or potential_sub_link.startswith("https://"): + normal_sub_link = potential_sub_link + except (IndexError, AttributeError): + pass + + caption_text = f"{add_user_feedback}\n" + link_to_generate_qr_for = None + link_type_for_caption = "" + + if normal_sub_link: + link_to_generate_qr_for = normal_sub_link + link_type_for_caption = "Normal Subscription Link" + caption_text += f"\n{link_type_for_caption} for `{username}`:\n`{normal_sub_link}`" + elif direct_uri: + link_to_generate_qr_for = direct_uri + link_type_for_caption = "Hysteria2 IPv4 URI" + caption_text += f"\n{link_type_for_caption} for `{username}`:\n`{direct_uri}`" + + if link_to_generate_qr_for: + qr_img = qrcode.make(link_to_generate_qr_for) + bio = io.BytesIO() + qr_img.save(bio, 'PNG') + bio.seek(0) + bot.send_photo(message.chat.id, photo=bio, caption=caption_text, parse_mode="Markdown", reply_markup=create_main_markup()) + else: + caption_text += "\nCould not retrieve specific Hysteria2 URI or Subscription link details." + bot.send_message(message.chat.id, caption_text, parse_mode="Markdown", reply_markup=create_main_markup()) except ValueError: bot.reply_to(message, "Invalid expiration days. Please enter a number:", reply_markup=create_cancel_markup(back_step=process_add_user_step2)) bot.register_next_step_handler(message, process_add_user_step3, username, traffic_limit) + except Exception as e: + bot.reply_to(message, f"An unexpected error occurred: {str(e)}", reply_markup=create_main_markup()) \ No newline at end of file