Pyrogram Bot Not Responding to /start Command - No Errors Logged I'm developing a Telegram bot using Pyrogram, but the /start command doesn't trigger any response, and no errors are logged. The bot starts successfully, and the logs indicate that the bot and user clients are initialized, but the command handler in start.py seems to be ignored. I've tried registering the handler in different ways, but nothing works. Below are the relevant files from my project. Can someone help identify why the /start command isn't registering or responding? Project Structure SmartBot/bot/__main__.py: Main entry point for the bot. SmartBot/bot/__init__.py: Exports client instances. SmartBot/bot/clients.py: Defines bot and user clients. SmartBot/bot/modules/start.py: Handles the /start command. SmartBot/bot/helpers/botutils.py: Utility functions for sending/deleting messages. SmartBot/bot/buttons.py: Custom button builder for inline keyboards. SmartBot/bot/helpers/commands.py: Defines command prefixes. SmartBot/config.py: Configuration and environment variable handling. Code Details SmartBot/bot/__main__.py import asyncio import os import importlib from pyrogram import idle, handlers from bot.helpers.logger import LOGGER from bot.miscs import SmartCallback from bot.clients import SmartGram, SmartUser from bot.core.database import check_restart def import_all_modules_from_dir(package): base_path = os.path.join(os.path.dirname(__file__), "modules") for root, dirs, files in os.walk(base_path): rel_path = os.path.relpath(root, base_path) if rel_path == ".": rel_path = "" for file in files: if file.endswith(".py") and not file.startswith("__"): mod_name = file[:-3] if rel_path: import_name = f"{package}.{rel_path.replace(os.sep, '.')}.{mod_name}" else: import_name = f"{package}.{mod_name}" importlib.import_module(import_name) LOGGER.info(f"Imported module: {import_name}") def register_bot_handlers(): SmartGram.add_handler(handlers.CallbackQueryHandler(SmartCallback)) async def main(): await SmartGram.start() await SmartUser.start() import_all_modules_from_dir("bot.modules") register_bot_handlers() LOGGER.info("Bot Successfully Started!") restart_data = await check_restart.find_one() if restart_data: try: await SmartGram.edit_message_text( chat_id=restart_data["chat_id"], message_id=restart_data["msg_id"], text="**Restarted Successfully **", parse_mode="markdown" ) await check_restart.delete_one({"_id": restart_data["_id"]}) LOGGER.info(f"Restart message updated and cleared from database for chat {restart_data['chat_id']}") except Exception as e: LOGGER.error(f"Failed to update restart message: {e}") await idle() if __name__ == "__main__": loop = asyncio.get_event_loop() try: loop.run_until_complete(main()) except KeyboardInterrupt: LOGGER.info("Stop signal received. Shutting down...") try: stop_tasks = [] if SmartGram.is_connected: stop_tasks.append(SmartGram.stop()) if SmartUser.is_connected: stop_tasks.append(SmartUser.stop()) if stop_tasks: loop.run_until_complete(asyncio.gather(*stop_tasks)) except Exception as e: LOGGER.error(f"Failed to stop clients: {e}") finally: if not loop.is_closed(): loop.close() SmartBot/bot/__init__.py from .clients import SmartGram, SmartUser SmartBot/bot/clients.py from pyrogram import Client from config import API_ID, API_HASH, BOT_TOKEN, SESSION_STRING from bot.helpers.logger import LOGGER LOGGER.info("Creating Bot Client From BOT_TOKEN") SmartGram = Client( "QuickUtilBot", api_id=API_ID, api_hash=API_HASH, bot_token=BOT_TOKEN, workers=1000 ) LOGGER.info("Bot Client Created Successfully!") LOGGER.info("Creating User Client From SESSION_STRING") SmartUserBot = Client( "QuickUserBot", session_string=SESSION_STRING, workers=1000 ) LOGGER.info("User Client Successfully Created!") SmartBot/bot/modules/start.py from bot.clients import SmartGram from bot.helpers.logger import LOGGER from bot.helpers.botutils import send_message, delete_messages from bot.helpers.buttons import SmartButtons from bot.helpers.commands import BotCommand from pyrogram import filters from pyrogram.enums import ParseMode, ChatType import asyncio from config import UPDATE_CHANNEL_URL, BAN_REPLY @SmartGram.on_message(filters.command(["start"], prefixes=BotCommand) & (filters.private | filters.group)) async def start_message(client, message): chat_id = message.chat.id animation_message = await send_message(chat_id, "<b>Starting Smart Tool ⚙️...</b>", parse_mode=ParseMode.HTML) await asyncio.sleep(0.4) loading_message = await send_message( chat_id, "<b>Generating Session Keys Please Wait...</b>", parse_mode=ParseMode.HTML, reply_to_message_id=animation_message.id if animation_message else None ) await asyncio.sleep(0.4) if animation_message: await delete_messages(chat_id, animation_message.id) if message.chat.type == ChatType.PRIVATE: full_name = "User" if message.from_user: first_name = message.from_user.first_name or "" last_name = message.from_user.last_name or "" full_name = f"{first_name} {last_name}".strip() response_text = ( f"<b>Hi {full_name}! Welcome To This Bot</b>\n" "<b>━━━━━━━━━━━━━━━━━━━━━━</b>\n" f"<b>Smart Tool</b> The ultimate toolkit on Telegram, offering education, AI, downloaders, temp mail, credit cards, and more. Simplify your tasks with ease!\n" "<b>━━━━━━━━━━━━━━━━━━━━━━</b>\n" f"<b>Don't forget to <a href='{UPDATE_CHANNEL_URL}'>Join Here</a> for updates!</b>" ) else: group_name = message.chat.title if message.chat.title else "this group" if message.from_user: first_name = message.from_user.first_name or "" last_name = message.from_user.last_name or "" full_name = f"{first_name} {last_name}".strip() response_text = ( f"<b>Hi {full_name}! Welcome To This Bot</b>\n" "<b>━━━━━━━━━━━━━━━━━━━━━━</b>\n" f"<b>Smart Tool</b> The ultimate toolkit on Telegram, offering education, AI, downloaders, temp mail, credit cards, and more. Simplify your tasks with ease!\n" "<b>━━━━━━━━━━━━━━━━━━━━━━</b>\n" f"<b>Don't forget to <a href='{UPDATE_CHANNEL_URL}'>Join Here</a> for updates!</b>" ) else: response_text = ( f"<b>Hi! Welcome {group_name} To This Bot</b>\n" "<b>━━━━━━━━━━━━━━━━━━━━━━</b>\n" f"<b>Smart Tool</b> The ultimate toolkit on Telegram, offering education, AI, downloaders, temp mail, credit cards, and more. Simplify your tasks with ease!\n" "<b>━━━━━━━━━━━━━━━━━━━━━━</b>\n" f"<b>Don't forget to <a href='{UPDATE_CHANNEL_URL}'>Join Here</a> for updates!</b>" ) buttons = SmartButtons() buttons.button("⚙️ Main Menu", callback_data="main_menu") buttons.button("ℹ️ About Me", callback_data="about_me") buttons.button(" Policy & Terms", callback_data="policy_terms") await send_message( chat_id=chat_id, text=response_text, parse_mode=ParseMode.HTML, reply_markup=buttons.build_menu(b_cols=2), disable_web_page_preview=True ) SmartBot/bot/helpers/botutils.py from bot import SmartGram from bot.helpers import LOGGER from pyrogram.types import Message from pyrogram.enums import ParseMode async def send_message(chat_id, text, parse_mode=ParseMode.HTML, reply_markup=None, reply_to_message_id=None, disable_web_page_preview=False): try: return await SmartGram.send_message( chat_id=chat_id, text=text, parse_mode=parse_mode, reply_markup=reply_markup, reply_to_message_id=reply_to_message_id, disable_web_page_preview=disable_web_page_preview ) except Exception as e: LOGGER.error(f"Failed to send message to {chat_id}: {e}") return None def get_args(message: Message): if not message.text: return [] text = message.text.split(None, 1) if len(text) < 2: return [] args = text[1].strip() if not args: return [] result = [] current = "" in_quotes = False quote_char = None i = 0 while i < len(args): char = args if char in ('"', "'") and (i == 0 or args[i-1] != '\\'): if in_quotes and char == quote_char: in_quotes = False quote_char = None if current: result.append(current) current = "" else: in_quotes = True quote_char = char elif char == ' ' and not in_quotes: if current: result.append(current) current = "" else: current += char i += 1 if current: result.append(current) return result async def delete_messages(chat_id, message_ids): try: if isinstance(message_ids, int): message_ids = [message_ids] await SmartGram.delete_messages(chat_id=chat_id, message_ids=message_ids) LOGGER.info(f"Deleted messages {message_ids} in chat {chat_id}") return True except Exception as e: LOGGER.error(f"Failed to delete messages {message_ids} in chat {chat_id}: {e}") return False SmartBot/bot/buttons.py from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton, WebAppInfo, LoginUrl, CallbackGame from ..helpers import LOGGER class SmartButtons: def __init__(self): self._button = [] self._header_button = [] self._footer_button = [] def button(self, text, callback_data=None, url=None, web_app=None, login_url=None, user_id=None, switch_inline_query=None, switch_inline_query_current_chat=None, callback_game=None, copy_text=None, position=None): kwargs = {} if callback_data is not None: kwargs["callback_data"] = callback_data elif url is not None: kwargs["url"] = url elif web_app is not None: kwargs["web_app"] = web_app elif login_url is not None: kwargs["login_url"] = login_url elif user_id is not None: kwargs["user_id"] = user_id elif switch_inline_query is not None: kwargs["switch_inline_query"] = switch_inline_query elif switch_inline_query_current_chat is not None: kwargs["switch_inline_query_current_chat"] = switch_inline_query_current_chat elif callback_game is not None: kwargs["callback_game"] = callback_game elif copy_text is not None: kwargs["copy_text"] = copy_text else: LOGGER.error("At least one optional parameter must be provided for InlineKeyboardButton") return button = InlineKeyboardButton(text=text, **kwargs) if not position: self._button.append(button) elif position == "header": self._header_button.append(button) elif position == "footer": self._footer_button.append(button) def build_menu(self, b_cols=1, h_cols=8, f_cols=8): menu = [self._button[i:i + b_cols] for i in range(0, len(self._button), b_cols)] if self._header_button: h_cnt = len(self._header_button) if h_cnt > h_cols: header_buttons = [self._header_button[i:i + h_cols] for i in range(0, len(self._header_button), h_cols)] menu = header_buttons + menu else: menu.insert(0, self._header_button) if self._footer_button: if len(self._footer_button) > f_cols: [menu.append(self._footer_button[i:i + f_cols]) for i in range(0, len(self._footer_button), f_cols)] else: menu.append(self._footer_button) return InlineKeyboardMarkup(menu) def reset(self): self._button = [] self._header_button = [] self._footer_button = [] SmartBot/bot/helpers/commands.py from config import COMMAND_PREFIX BotCommand = COMMAND_PREFIX SmartBot/config.py #Copyright @ISmartCoder #Updates Channel: https://t.me/TheSmartDev import os from dotenv import load_dotenv load_dotenv() def get_env_or_default(key, default=None, cast_func=str): value = os.getenv(key) if value is not None and value.strip() != "": try: return cast_func(value) except (ValueError, TypeError) as e: print(f"Error casting {key} with value '{value}' to {cast_func.__name__}: {e}") return default return default API_ID = get_env_or_default("API_ID", "Your_API_ID_Here") API_HASH = get_env_or_default("API_HASH", "Your_API_HASH_Here") BOT_TOKEN = get_env_or_default("BOT_TOKEN", "Your_BOT_TOKEN_Here") SESSION_STRING = get_env_or_default("SESSION_STRING", "Your_SESSION_STRING_Here") OWNER_ID = get_env_or_default("OWNER_ID", "Your_OWNER_ID_Here", int) DEVELOPER_USER_ID = get_env_or_default("DEVELOPER_USER_ID", "Your_DEVELOPER_USER_ID_Here", int) MONGO_URL = get_env_or_default("MONGO_URL", "Your_MONGO_URL_Here") DATABASE_URL = get_env_or_default("DATABASE_URL", "Your_DATABASE_URL_Here") DB_URL = get_env_or_default("DB_URL", "Your_DB_URL_Here") OPENAI_API_KEY = get_env_or_default("OPENAI_API_KEY", "Your_OPENAI_API_KEY_Here") REPLICATE_API_TOKEN = get_env_or_default("REPLICATE_API_TOKEN", "Your_REPLICATE_API_TOKEN_Here") GOOGLE_API_KEY = get_env_or_default("GOOGLE_API_KEY", "Your_GOOGLE_API_KEY_Here") OCR_API_KEY = get_env_or_default("OCR_API_KEY", "Your_OCR_API_KEY_Here") TRANS_API_KEY = get_env_or_default("TRANS_API_KEY", "Your_TRANS_API_KEY_Here") MODEL_NAME = get_env_or_default("MODEL_NAME", "gemini-2.0-flash") CC_SCRAPPER_LIMIT = get_env_or_default("CC_SCRAPPER_LIMIT", 5000, int) SUDO_CCSCR_LIMIT = get_env_or_default("SUDO_CCSCR_LIMIT", 10000, int) MULTI_CCSCR_LIMIT = get_env_or_default("MULTI_CCSCR_LIMIT", 2000, int) MAIL_SCR_LIMIT = get_env_or_default("MAIL_SCR_LIMIT", 10000, int) SUDO_MAILSCR_LIMIT = get_env_or_default("SUDO_MAILSCR_LIMIT", 15000, int) CC_GEN_LIMIT = get_env_or_default("CC_GEN_LIMIT", 2000, int) MULTI_CCGEN_LIMIT = get_env_or_default("MULTI_CCGEN_LIMIT", 5000, int) GOOGLE_API_KEY = get_env_or_default("GOOGLE_API_KEY", "Your_GOOGLE_API_KEY_Here") MODEL_NAME = get_env_or_default("MODEL_NAME", "gemini-1.5 HEM") GROQ_API_KEY = get_env_or_default("GROQ_API_KEY", "Your_GROQ_API_KEY_Here") GROQ_API_URL = get_env_or_default("GROQ_API_URL", "https://api.groq.com/openai/v1/chat/completions") DOMAIN_API_KEY = get_env_or_default("DOMAIN_API_KEY", "Your_DOMAIN_API_KEY_Here") DOMAIN_API_URL = get_env_or_default("DOMAIN_API_URL", "https://www.whoisxmlapi.com/whoisserver/WhoisService") TEXT_MODEL = get_env_or_default("TEXT_MODEL", "deepseek-r1-distill-llama-70b") NEWS_API_KEY = get_env_or_default("NEWS_API_KEY", "Your_NEWS_API_KEY_Here") BIN_KEY = get_env_or_default("BIN_KEY", "Your_BIN_KEY_Here") raw_prefixes = get_env_or_default("COMMAND_PREFIX", "!|.|#|,|/") COMMAND_PREFIX = [prefix.strip() for prefix in raw_prefixes.split("|") if prefix.strip()] UPDATE_CHANNEL_URL = get_env_or_default("UPDATE_CHANNEL_URL", "https://t.me/TheSmartDev") LOG_CHANNEL_ID = get_env_or_default("LOG_CHANNEL_ID", "-100432424242") IMGAI_SIZE_LIMIT = get_env_or_default("IMGAI_SIZE_LIMIT", 5242880, int) MAX_TXT_SIZE = get_env_or_default("MAX_TXT_SIZE", 15728640, int) MAX_VIDEO_SIZE = get_env_or_default("MAX_VIDEO_SIZE", 2147483648, int) YT_COOKIES_PATH = get_env_or_default("YT_COOKIES_PATH", "./cookies/ISmartToolBot.txt") VIDEO_RESOLUTION = get_env_or_default("VIDEO_RESOLUTION", "1280x720", lambda x: tuple(map(int, x.split('x')))) DOMAIN_CHK_LIMIT = get_env_or_default("DOMAIN_CHK_LIMIT", 20, int) PROXY_CHECK_LIMIT = get_env_or_default("PROXY_CHECK_LIMIT", 20, int) BAN_REPLY = get_env_or_default("BAN_REPLY", "**Sorry Bro You're Banned From Using Me ❌**") required_vars = { "API_ID": API_ID, "API_HASH": API_HASH, "BOT_TOKEN": BOT_TOKEN, "SESSION_STRING": SESSION_STRING, "OWNER_ID": OWNER_ID, "DEVELOPER_USER_ID": DEVELOPER_USER_ID, "MONGO_URL": MONGO_URL, "DATABASE_URL": DATABASE_URL } for var_name, var_value in required_vars.items(): if var_value is None or var_value == f"Your_{var_name}_Here" or (isinstance(var_value, str) and var_value.strip() == ""): raise ValueError(f"Required variable {var_name} is missing or invalid. Set it in .env (VPS), config.py (VPS), or Heroku config vars.") print("Loaded COMMAND_PREFIX:", COMMAND_PREFIX) gpt_api_key_source = "environment variable" if os.getenv("OPENAI_API_KEY") else "config default" print(f"OPENAI_API_KEY loaded from {gpt_api_key_source}: {'*' * 10}{OPENAI_API_KEY[-10:]}") if not COMMAND_PREFIX: raise ValueError("No command prefixes found. Set COMMAND_PREFIX in .env, config.py, or Heroku config vars.") Issue Description The bot starts without issues, and logs confirm that SmartGram and SmartUser clients are created and started. The start.py module is imported via import_all_modules_from_dir("bot.modules"), and I see logs confirming the module is loaded. The /start command (with prefixes like !, ., #, ,, /) does not trigger the start_message function in start.py. No errors are logged in the console or the logger when sending the /start command. The SmartCallback handler for callback queries is registered in __main__.py, but I suspect the message handler for /start is not being registered properly. I tried moving the handler registration to the main() function, but it had no effect. Environment Python: 3.11 Pyrogram: Pyrofork Latest 2.3.68 Deployment: Local machine (tested on VPS as well) OS: Ubuntu What I've Tried Verified that BotCommand in commands.py correctly loads the prefixes from config.py. Ensured SmartGram is the correct client instance used in start.py. Checked that start.py is imported correctly by the import_all_modules_from_dir function. Tested with different command prefixes (/start, !start, etc.) in both private and group chats. Added debug logs in start.py to confirm the handler is reached (none are triggered). Ensured all required environment variables (API_ID, API_HASH, BOT_TOKEN, etc.) are set correctly in the .env file. Questions Why is the /start command handler not being triggered despite the module being imported? Is there an issue with how I'm registering handlers or importing modules? Could there be a problem with the Pyrogram client configuration or the filter setup in start.py? How can I debug this further to pinpoint why the handler isn't responding? Any help from experienced Pyrogram developers would be greatly appreciated! Please let me know if you need more details or additional files (e.g., logger.py or miscs.py). Continue reading...