import os from pyrogram import Client, filters from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, CallbackQuery from pyrogram.errors import UserNotParticipant from config import Config FORCE_SUB_CHANNELS = Config.FORCE_SUB_CHANNELS IMAGE_URL = "https://graph.org/file/a27d85469761da836337c.jpg" async def not_subscribed(_, __, message): # Force sub is completely disabled as requested return False # Anti-spam cache mapping to prevent 100+ error spams when forwarding folders force_sub_spam_cache = {} @Client.on_message(filters.private & filters.create(not_subscribed)) async def forces_sub(client, message): not_joined_channels = [] admin_error = None for channel in FORCE_SUB_CHANNELS: try: peer = int(channel) if str(channel).lstrip('-').isdigit() else channel user = await client.get_chat_member(peer, message.from_user.id) if user.status in {"kicked", "left"}: not_joined_channels.append(channel) except UserNotParticipant: not_joined_channels.append(channel) except Exception as e: # Captures ANY Pyrogram validation crash (PeerIdInvalid, ChatAdminRequired, etc) admin_error = f"[{type(e).__name__}] {str(e)}" not_joined_channels.append(channel) # Anti-spam logic: don't bombard user with 100 errors instantly user_id = message.from_user.id import time last_sent = force_sub_spam_cache.get(user_id, 0) if time.time() - last_sent < 15: # Ignore this message if we already warned them within the last 15 seconds return force_sub_spam_cache[user_id] = time.time() if admin_error: text = ( "⚠️ **Bot Configuration Error!** ⚠️\n\n" f"The bot is failing to verify the Force-Sub channel due to an API error:\n\n" f"`{admin_error}`\n\n" "**Fixes:**\n" "1. Make sure the bot is an Admin.\n" "2. If it's a private channel, you MUST forward at least 1 message from the channel to the bot so it can learn the channel ID!" ) return await message.reply_text(text) buttons = [ [ InlineKeyboardButton( text=f"• ᴊᴏɪɴ Cʜᴀɴɴᴇʟ •", url=f"https://t.me/{str(channel).replace('@', '')}" if not str(channel).lstrip('-').isdigit() else "https://t.me/codeflix_bots" ) ] for channel in not_joined_channels ] buttons.append( [ InlineKeyboardButton( text="• ᴊᴏɪɴᴇᴅ •", callback_data="check_subscription" ) ] ) text = "**ʙᴀᴋᴋᴀ!!, ʏᴏᴜ'ʀᴇ ɴᴏᴛ ᴊᴏɪɴᴇᴅ ᴛᴏ ᴀʟʟ ʀᴇǫᴜɪʀᴇᴅ ᴄʜᴀɴɴᴇʟs, ᴊᴏɪɴ ᴛʜᴇ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟs ᴛᴏ ᴄᴏɴᴛɪɴᴜᴇ**" await message.reply_photo( photo=IMAGE_URL, caption=text, reply_markup=InlineKeyboardMarkup(buttons) ) @Client.on_callback_query(filters.regex("check_subscription")) async def check_subscription(client, callback_query: CallbackQuery): user_id = callback_query.from_user.id not_joined_channels = [] admin_error = False for channel in FORCE_SUB_CHANNELS: try: peer = int(channel) if str(channel).lstrip('-').isdigit() else channel user = await client.get_chat_member(peer, user_id) if user.status in {"kicked", "left"}: not_joined_channels.append(channel) except UserNotParticipant: not_joined_channels.append(channel) except Exception as e: if "CHAT_ADMIN_REQUIRED" in str(e): admin_error = True not_joined_channels.append(channel) if admin_error: return await callback_query.answer("⚠️ The bot is not an admin in the Force Sub channel!", show_alert=True) if not not_joined_channels: new_text = "**ʏᴏᴜ ʜᴀᴠᴇ ᴊᴏɪɴᴇᴅ ᴀʟʟ ᴛʜᴇ ʀᴇǫᴜɪʀᴇᴅ ᴄʜᴀɴɴᴇʟs. ᴛʜᴀɴᴋ ʏᴏᴜ! 😊 /start ɴᴏᴡ**" if callback_query.message.caption != new_text: await callback_query.message.edit_caption( caption=new_text, reply_markup=InlineKeyboardMarkup([ [InlineKeyboardButton("• ɴᴏᴡ ᴄʟɪᴄᴋ ʜᴇʀᴇ •", callback_data='help')] ]) ) else: buttons = [ [ InlineKeyboardButton( text=f"• ᴊᴏɪɴ Cʜᴀɴɴᴇʟ •", url=f"https://t.me/{str(channel).replace('@', '')}" if not str(channel).lstrip('-').isdigit() else "https://t.me/codeflix_bots", ) ] for channel in not_joined_channels ] buttons.append( [ InlineKeyboardButton( text="• ᴊᴏɪɴᴇᴅ •", callback_data="check_subscription" ) ] ) text = "**ʏᴏᴜ ʜᴀᴠᴇ ᴊᴏɪɴᴇᴅ ᴀʟʟ ᴛʜᴇ ʀᴇǫᴜɪʀᴇᴅ ᴄʜᴀɴɴᴇʟs. ᴘʟᴇᴀsᴇ ᴊᴏɪɴ ᴛʜᴇ ᴜᴘᴅᴀᴛᴇ ᴄʜᴀɴɴᴇʟs ᴛᴏ ᴄᴏɴᴛɪɴᴜᴇ**" if callback_query.message.caption != text: await callback_query.message.edit_caption( caption=text, reply_markup=InlineKeyboardMarkup(buttons) )