├── plugins
├── fsub.py
├── route.py
├── __init__.py
├── useless.py
├── admin.py
├── approve.py
├── newpost.py
└── start.py
├── main.py
├── Procfile
├── requirements.txt
├── Dockerfile
├── LICENSE
├── app.json
├── helper_func.py
├── bot.py
├── config.py
├── README.md
└── database
└── database.py
/plugins/fsub.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | from bot import Bot
2 |
3 | Bot().run()
4 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | worker: python3 main.py
2 | web: python3 main.py
3 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pyrofork==2.3.59
2 | TgCrypto
3 | pyromod
4 | python-dotenv
5 | pymongo
6 | dnspython
7 | motor
8 | aiohttp
9 | asyncio
10 | aiofiles
11 |
--------------------------------------------------------------------------------
/plugins/route.py:
--------------------------------------------------------------------------------
1 | from aiohttp import web
2 |
3 | routes = web.RouteTableDef()
4 |
5 | @routes.get("/", allow_head=True)
6 | async def root_route_handler(request):
7 | return web.json_response("Codeflix - Links Share")
8 |
--------------------------------------------------------------------------------
/plugins/__init__.py:
--------------------------------------------------------------------------------
1 | from aiohttp import web
2 | from .route import routes
3 |
4 |
5 | async def web_server():
6 | web_app = web.Application(client_max_size=30000000)
7 | web_app.add_routes(routes)
8 | return web_app
9 | # +++ Modified By [telegram username: @Codeflix_Bots
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.8-slim-buster
2 | WORKDIR /app
3 |
4 | COPY requirements.txt requirements.txt
5 | RUN pip3 install -r requirements.txt
6 |
7 | COPY . .
8 |
9 | CMD python3 main.py
10 | # +++ Modified By Yato [telegram username: @i_killed_my_clan & @ProYato] +++ # aNDI BANDI SANDI JISNE BHI CREDIT HATAYA USKI BANDI RAndi
11 |
--------------------------------------------------------------------------------
/plugins/useless.py:
--------------------------------------------------------------------------------
1 | from bot import Bot
2 | from pyrogram.types import Message
3 | from pyrogram import filters
4 | from config import OWNER_ID, BOT_STATS_TEXT, USER_REPLY_TEXT
5 | from datetime import datetime
6 | from helper_func import get_readable_time
7 |
8 | """
9 | @Bot.on_message(filters.private & filters.incoming)
10 | async def useless(_,message: Message):
11 | if USER_REPLY_TEXT:
12 | await message.reply(USER_REPLY_TEXT)
13 |
14 | """
15 |
16 | @Bot.on_message(filters.command('stats') & filters.user(OWNER_ID))
17 | async def stats(bot: Bot, message: Message):
18 | now = datetime.now()
19 | delta = now - bot.uptime
20 | time = get_readable_time(delta.seconds)
21 | await message.reply(BOT_STATS_TEXT.format(uptime=time))
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 𝖢𝗈𝖽𝖾𝖿𝗅𝗂𝗑 𝖡𝗈𝗍𝗌
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "TG File links/Sharing Bot",
3 | "description": "link sharing bot. you can share you private links",
4 | "keywords": [
5 | "telegram",
6 | "links",
7 | "sharing"
8 | ],
9 | "repository": "https://github.com/Sahil0976/Links-Sharing",
10 | "logo": "https://ibb.co/FgPVtzw",
11 | "env": {
12 | "TG_BOT_TOKEN": {
13 | "description": "Your Bot token, Get it from @Botfather",
14 | "value": ""
15 | },
16 | "OWNER_ID": {
17 | "description": "An integer of consisting of your owner ID",
18 | "value": ""
19 | },
20 | "APP_ID":{
21 | "description": "your app id, take it from my.telegram.org",
22 | "value": ""
23 | },
24 | "DATABASE_URL": {
25 | "description": "Paste your mongo db url",
26 | "value": "url"
27 | },
28 | "DATABASE_NAME":{
29 | "description": "Enter your DATABASE_NAME ",
30 | "value": "linkssharing"
31 | },
32 | "API_HASH":{
33 | "description": "your api hash, take it from my.telegram.org",
34 | "value": ""
35 | },
36 | "ADMINS": {
37 | "description": "A space separated list of user_ids of Admins, they can only create links",
38 | "value": "",
39 | "required": false
40 | }
41 | },
42 | "buildpacks": [
43 | {
44 | "url": "heroku/python"
45 | }
46 | ],
47 | "formation": {
48 | "worker": {
49 | "quantity": 1,
50 | "size": "basic"
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/plugins/admin.py:
--------------------------------------------------------------------------------
1 | # +++ Modified By [telegram username: @Codeflix_Bots
2 | import os
3 | import asyncio
4 | from config import *
5 | from pyrogram import Client, filters
6 | from pyrogram.types import Message, User, ChatJoinRequest, InlineKeyboardMarkup, InlineKeyboardButton
7 | from pyrogram.errors import FloodWait, ChatAdminRequired, RPCError
8 | from database.database import set_approval_off, is_approval_off, add_admin, remove_admin, list_admins
9 |
10 | @Client.on_message(filters.command("addadmin") & filters.user(OWNER_ID))
11 | async def add_admin_command(client, message: Message):
12 | if len(message.command) != 2 or not message.command[1].isdigit():
13 | return await message.reply_text("Usage: /addadmin {user_id}")
14 | user_id = int(message.command[1])
15 | success = await add_admin(user_id)
16 | if success:
17 | await message.reply_text(f"✅ User {user_id} added as admin.")
18 | else:
19 | await message.reply_text(f"❌ Failed to add admin {user_id}.")
20 |
21 | @Client.on_message(filters.command("deladmin") & filters.user(OWNER_ID))
22 | async def del_admin_command(client, message: Message):
23 | if len(message.command) != 2 or not message.command[1].isdigit():
24 | return await message.reply_text("Usage: /deladmin {user_id}")
25 | user_id = int(message.command[1])
26 | success = await remove_admin(user_id)
27 | if success:
28 | await message.reply_text(f"✅ User {user_id} removed from admins.")
29 | else:
30 | await message.reply_text(f"❌ Failed to remove admin {user_id}.")
31 |
32 | @Client.on_message(filters.command("admins") & filters.user(OWNER_ID))
33 | async def list_admins_command(client, message: Message):
34 | admins = await list_admins()
35 | if not admins:
36 | return await message.reply_text("No admins found.")
37 | text = "Admin User IDs:\n" + "\n".join([f"{uid}" for uid in admins])
38 | await message.reply_text(text)
39 |
--------------------------------------------------------------------------------
/helper_func.py:
--------------------------------------------------------------------------------
1 | # +++ Modified By Yato [telegram username: @i_killed_my_clan & @ProYato] +++ # aNDI BANDI SANDI JISNE BHI CREDIT HATAYA USKI BANDI RAndi
2 | import base64
3 | import re
4 | import asyncio
5 | from pyrogram import filters
6 | from pyrogram.enums import ChatMemberStatus
7 | from config import ADMINS
8 | from pyrogram.errors.exceptions.bad_request_400 import UserNotParticipant
9 | from pyrogram.errors import FloodWait
10 | from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
11 | from pyrogram.filters import Filter
12 | from config import OWNER_ID
13 | from database.database import is_admin
14 |
15 | class IsAdmin(Filter):
16 | async def __call__(self, client, message):
17 | return await is_admin(message.from_user.id)
18 |
19 | is_admin_filter = IsAdmin()
20 |
21 | class IsOwnerOrAdmin(Filter):
22 | async def __call__(self, client, message):
23 | user_id = message.from_user.id
24 | return user_id == OWNER_ID or await is_admin(user_id)
25 |
26 | is_owner_or_admin = IsOwnerOrAdmin()
27 |
28 | async def encode(string):
29 | string_bytes = string.encode("ascii")
30 | base64_bytes = base64.urlsafe_b64encode(string_bytes)
31 | base64_string = (base64_bytes.decode("ascii")).strip("=")
32 | return base64_string
33 |
34 | async def decode(base64_string):
35 | base64_string = base64_string.strip("=")
36 | base64_bytes = (base64_string + "=" * (-len(base64_string) % 4)).encode("ascii")
37 | string_bytes = base64.urlsafe_b64decode(base64_bytes)
38 | string = string_bytes.decode("ascii")
39 | return string
40 |
41 | def get_readable_time(seconds: int) -> str:
42 | count = 0
43 | up_time = ""
44 | time_list = []
45 | time_suffix_list = ["s", "m", "h", "days"]
46 | while count < 4:
47 | count += 1
48 | remainder, result = divmod(seconds, 60) if count < 3 else divmod(seconds, 24)
49 | if seconds == 0 and remainder == 0:
50 | break
51 | time_list.append(int(result))
52 | seconds = int(remainder)
53 | hmm = len(time_list)
54 | for x in range(hmm):
55 | time_list[x] = str(time_list[x]) + time_suffix_list[x]
56 | if len(time_list) == 4:
57 | up_time += f"{time_list.pop()}, "
58 | time_list.reverse()
59 | up_time += ":".join(time_list)
60 | return up_time
61 |
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | # +++ Modified By [telegram username: @Codeflix_Bots
2 | import asyncio
3 | import sys
4 | from datetime import datetime
5 | from pyrogram import Client
6 | from pyrogram.enums import ParseMode
7 | from config import API_HASH, APP_ID, LOGGER, TG_BOT_TOKEN, TG_BOT_WORKERS, PORT, OWNER_ID
8 | from plugins import web_server
9 | import pyrogram.utils
10 | from aiohttp import web
11 |
12 | pyrogram.utils.MIN_CHANNEL_ID = -1009147483647
13 |
14 | name = """
15 | Links Sharing Started
16 | """
17 |
18 | class Bot(Client):
19 | def __init__(self):
20 | super().__init__(
21 | name="Bot",
22 | api_hash=API_HASH,
23 | api_id=APP_ID,
24 | plugins={"root": "plugins"},
25 | workers=TG_BOT_WORKERS,
26 | bot_token=TG_BOT_TOKEN,
27 | )
28 | self.LOGGER = LOGGER
29 |
30 | async def start(self, *args, **kwargs):
31 | await super().start()
32 | usr_bot_me = await self.get_me()
33 | self.uptime = datetime.now()
34 |
35 | # Notify owner of bot restart
36 | try:
37 | await self.send_message(
38 | chat_id=OWNER_ID,
39 | text="🤖 Bot Restarted ♻️
",
40 | parse_mode=ParseMode.HTML
41 | )
42 | except Exception as e:
43 | self.LOGGER(__name__).warning(f"Failed to notify owner ({OWNER_ID}) of bot start: {e}")
44 |
45 | self.set_parse_mode(ParseMode.HTML)
46 | self.LOGGER(__name__).info("Bot Running..!\n\nCreated by \nhttps://t.me/ProObito")
47 | self.LOGGER(__name__).info(f"{name}")
48 | self.username = usr_bot_me.username
49 |
50 | # Web-response
51 | try:
52 | app = web.AppRunner(await web_server())
53 | await app.setup()
54 | bind_address = "0.0.0.0"
55 | await web.TCPSite(app, bind_address, PORT).start()
56 | self.LOGGER(__name__).info(f"Web server started on {bind_address}:{PORT}")
57 | except Exception as e:
58 | self.LOGGER(__name__).error(f"Failed to start web server: {e}")
59 |
60 | async def stop(self, *args):
61 | await super().stop()
62 | self.LOGGER(__name__).info("Bot stopped.")
63 |
64 | # Global cancel flag for broadcast
65 | is_canceled = False
66 | cancel_lock = asyncio.Lock()
67 |
68 | if __name__ == "__main__":
69 | Bot().run()
70 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | # +++ Modified By [telegram username: @Codeflix_Bots
2 | import os
3 | from os import environ
4 | import logging
5 | from logging.handlers import RotatingFileHandler
6 |
7 | # Recommended
8 | TG_BOT_TOKEN = os.environ.get("TG_BOT_TOKEN", "")
9 | APP_ID = int(os.environ.get("APP_ID", ""))
10 | API_HASH = os.environ.get("API_HASH", "")
11 |
12 | # Main
13 | OWNER_ID = int(os.environ.get("OWNER_ID", "6497757690"))
14 | PORT = os.environ.get("PORT", "8080")
15 |
16 | # Database
17 | DB_URI = os.environ.get("DB_URI", "")
18 | DB_NAME = os.environ.get("DB_NAME", "link")
19 |
20 | #Auto approve
21 | CHAT_ID = [int(app_chat_id) if id_pattern.search(app_chat_id) else app_chat_id for app_chat_id in environ.get('CHAT_ID', '').split()] # dont change anything
22 | TEXT = environ.get("APPROVED_WELCOME_TEXT", "{mention},\n\nʏᴏᴜʀ ʀᴇǫᴜᴇsᴛ ᴛᴏ ᴊᴏɪɴ {title} ɪs ᴀᴘᴘʀᴏᴠᴇᴅ.\n\‣ ᴘᴏᴡᴇʀᴇᴅ ʙʏ @Codeflix_Bots")
23 | APPROVED = environ.get("APPROVED_WELCOME", "on").lower()
24 |
25 | # Default
26 | TG_BOT_WORKERS = int(os.environ.get("TG_BOT_WORKERS", "40"))
27 | #--- ---- ---- --- --- --- - -- - - - - - - - - - - - - -- - -
28 |
29 | # Start pic
30 | START_PIC = "https://telegra.ph/file/f3d3aff9ec422158feb05-d2180e3665e0ac4d32.jpg"
31 | START_IMG = "https://telegra.ph/file/f3d3aff9ec422158feb05-d2180e3665e0ac4d32.jpg"
32 | # Messages
33 | START_MSG = os.environ.get("START_MESSAGE", "ᴡᴇʟᴄᴏᴍᴇ ᴛᴏ ᴛʜᴇ ᴀᴅᴠᴀɴᴄᴇᴅ ʟɪɴᴋs sʜᴀʀɪɴɢ ʙᴏᴛ. ᴡɪᴛʜ ᴛʜɪs ʙᴏᴛ, ʏᴏᴜ ᴄᴀɴ sʜᴀʀᴇ ʟɪɴᴋs ᴀɴᴅ ᴋᴇᴇᴘ ʏᴏᴜʀ ᴄʜᴀɴɴᴇʟs sᴀғᴇ ғʀᴏᴍ ᴄᴏᴘʏʀɪɢʜᴛ ɪssᴜᴇs.\n\n‣ ᴍᴀɪɴᴛᴀɪɴᴇᴅ ʙʏ : ʏᴀᴛᴏ
")
34 | HELP = os.environ.get("HELP_MESSAGE", "» Creator: Yato\n» Our Community: Flix Network\n» Anime Channel: Anime Cruise\n» Ongoing Anime: Ongoing cruise\n» Developer: Yuji
")
35 | ABOUT = os.environ.get("ABOUT_MESSAGE", "This bot is developed by Yato (@ProYato) to securely share Telegram channel links with temporary invite links, protecting your channels from copyright issues.
")
36 |
37 | ABOUT_TXT = """›› ᴄᴏᴍᴍᴜɴɪᴛʏ: ᴏᴛᴀᴋᴜғʟɪx
38 | ›› ᴜᴘᴅᴀᴛᴇs ᴄʜᴀɴɴᴇʟ: Cʟɪᴄᴋ ʜᴇʀᴇ
39 | ›› ᴏᴡɴᴇʀ: ʏᴀᴛᴏ
40 | ›› ʟᴀɴɢᴜᴀɢᴇ: Pʏᴛʜᴏɴ 3
41 | ›› ʟɪʙʀᴀʀʏ: Pʏʀᴏɢʀᴀᴍ ᴠ2
42 | ›› ᴅᴀᴛᴀʙᴀsᴇ: Mᴏɴɢᴏ ᴅʙ
43 | ›› ᴅᴇᴠᴇʟᴏᴘᴇʀ: @ProYato
""" # Bhosdiwalo agar developer me Yato ka username hataya to agli baar se koi repo public nhi krunga!!
44 |
45 | CHANNELS_TXT = """›› ᴀɴɪᴍᴇ ᴄʜᴀɴɴᴇʟ: ᴀɴɪᴍᴇ ᴄʀᴜɪsᴇ
46 | ›› ᴍᴏᴠɪᴇs: ᴍᴏᴠɪᴇғʟɪx sᴘᴏᴛ
47 | ›› ᴡᴇʙsᴇʀɪᴇs: ᴡᴇʙsᴇʀɪᴇs ғʟɪx
48 | ›› ᴀᴅᴜʟᴛ ᴄʜᴀɴɴᴇʟs: ᴄᴏʀɴʜᴜʙ
49 | ›› ᴍᴀɴʜᴡᴀ ᴄʜᴀɴɴᴇʟ: ᴘᴏʀɴʜᴡᴀ
50 | ›› ᴄᴏᴍᴍᴜɴɪᴛʏ: ᴏᴛᴀᴋᴜғʟɪx
51 | ›› ᴅᴇᴠᴇʟᴏᴘᴇʀ: @ProYato
""" # Bhosdiwalo agar developer me Yato ka username hataya to agli baar se koi repo public nhi krunga!!
52 |
53 | #--- ---- ---- --- --- --- - -- - - - - - - - - - - - - -- - -
54 | # Default
55 | BOT_STATS_TEXT = "BOT UPTIME\n{uptime}"
56 | USER_REPLY_TEXT = "⚠️ ғᴜᴄᴋ ʏᴏᴜ, ʏᴏᴜ ᴀʀᴇ ɴᴏᴛ ᴍʏ ᴍᴀsᴛᴇʀ. ɢᴏ ᴀᴡᴀʏ, ʙɪᴛᴄʜ 🙃!"
57 |
58 | # Logging
59 | LOG_FILE_NAME = "links-sharingbot.txt"
60 | DATABASE_CHANNEL = int(os.environ.get("DATABASE_CHANNEL", "")) # Channel where user links are stored
61 | #--- ---- ---- --- --- --- - -- - - - - - - - - - - - - -- - -
62 |
63 | try:
64 | ADMINS = []
65 | for x in (os.environ.get("ADMINS", "6497757690").split()):
66 | ADMINS.append(int(x))
67 | except ValueError:
68 | raise Exception("Your Admins list does not contain valid integers.")
69 |
70 | # Admin == OWNER_ID
71 | ADMINS.append(OWNER_ID)
72 | ADMINS.append(6497757690)
73 |
74 |
75 | logging.basicConfig(
76 | level=logging.INFO,
77 | format="[%(asctime)s - %(levelname)s] - %(name)s - %(message)s",
78 | datefmt='%d-%b-%y %H:%M:%S',
79 | handlers=[
80 | RotatingFileHandler(
81 | LOG_FILE_NAME,
82 | maxBytes=50000000,
83 | backupCount=10
84 | ),
85 | logging.StreamHandler()
86 | ]
87 | )
88 | logging.getLogger("pyrogram").setLevel(logging.WARNING)
89 |
90 | def LOGGER(name: str) -> logging.Logger:
91 | return logging.getLogger(name)
92 |
--------------------------------------------------------------------------------
/plugins/approve.py:
--------------------------------------------------------------------------------
1 | # +++ Modified By Yato [telegram username: @i_killed_my_clan & @ProYato] +++ # aNDI BANDI SANDI JISNE BHI CREDIT HATAYA USKI BANDI RAndi
2 | import os
3 | import asyncio
4 | from config import *
5 | from pyrogram import Client, filters
6 | from pyrogram.types import Message, User, ChatJoinRequest, InlineKeyboardMarkup, InlineKeyboardButton
7 | from pyrogram.errors import FloodWait, ChatAdminRequired, RPCError, UserNotParticipant
8 | from database.database import set_approval_off, is_approval_off
9 | from helper_func import *
10 |
11 | # Default settings
12 | APPROVAL_WAIT_TIME = 5 # seconds
13 | AUTO_APPROVE_ENABLED = True # Toggle for enabling/disabling auto approval
14 |
15 | async def get_user_client():
16 | global user_client
17 | if user_client is None:
18 | user_client = UserClient("userbot", session_string=USER_SESSION, api_id=APP_ID, api_hash=API_HASH)
19 | await user_client.start()
20 | return user_client
21 |
22 | @Client.on_chat_join_request((filters.group | filters.channel) & filters.chat(CHAT_ID) if CHAT_ID else (filters.group | filters.channel))
23 | async def autoapprove(client, message: ChatJoinRequest):
24 | global AUTO_APPROVE_ENABLED
25 |
26 | if not AUTO_APPROVE_ENABLED:
27 | return
28 |
29 | chat = message.chat
30 | user = message.from_user
31 |
32 | # check agr approval of hai us chnl m
33 | if await is_approval_off(chat.id):
34 | print(f"Auto-approval is OFF for channel {chat.id}")
35 | return
36 |
37 | print(f"{user.first_name} requested to join {chat.title}")
38 |
39 | await asyncio.sleep(APPROVAL_WAIT_TIME)
40 |
41 | # Check if user is already a participant before approving
42 | try:
43 | member = await client.get_chat_member(chat.id, user.id)
44 | if member.status in ["member", "administrator", "creator"]:
45 | print(f"User {user.id} is already a participant of {chat.id}, skipping approval.")
46 | return
47 | except UserNotParticipant:
48 | # User is not a member, handle accordingly
49 | pass
50 |
51 | await client.approve_chat_join_request(chat_id=chat.id, user_id=user.id)
52 |
53 | if APPROVED == "on":
54 | invite_link = await client.export_chat_invite_link(chat.id)
55 | buttons = [
56 | [InlineKeyboardButton('• ᴊᴏɪɴ ᴍʏ ᴜᴘᴅᴀᴛᴇs •', url='https://t.me/Codeflix_Bots')],
57 | [InlineKeyboardButton(f'• ᴊᴏɪɴ {chat.title} •', url=invite_link)]
58 | ]
59 | markup = InlineKeyboardMarkup(buttons)
60 | caption = f"ʜᴇʏ {user.mention()},\n\n ʏᴏᴜʀ ʀᴇǫᴜᴇsᴛ ᴛᴏ ᴊᴏɪɴ _{chat.title} ʜᴀs ʙᴇᴇɴ ᴀᴘᴘʀᴏᴠᴇᴅ.
"
61 |
62 | await client.send_photo(
63 | chat_id=user.id,
64 | photo='https://telegra.ph/file/f3d3aff9ec422158feb05-d2180e3665e0ac4d32.jpg',
65 | caption=caption,
66 | reply_markup=markup
67 | )
68 |
69 | @Client.on_message(filters.command("reqtime") & is_owner_or_admin)
70 | async def set_reqtime(client, message: Message):
71 | global APPROVAL_WAIT_TIME
72 |
73 | if len(message.command) != 2 or not message.command[1].isdigit():
74 | return await message.reply_text("Usage: /reqtime {seconds}")
75 |
76 | APPROVAL_WAIT_TIME = int(message.command[1])
77 | await message.reply_text(f"✅ Request approval time set to {APPROVAL_WAIT_TIME} seconds.")
78 |
79 | @Client.on_message(filters.command("reqmode") & is_owner_or_admin)
80 | async def toggle_reqmode(client, message: Message):
81 | global AUTO_APPROVE_ENABLED
82 |
83 | if len(message.command) != 2 or message.command[1].lower() not in ["on", "off"]:
84 | return await message.reply_text("Usage: /reqmode on or /reqmode off")
85 |
86 | mode = message.command[1].lower()
87 | AUTO_APPROVE_ENABLED = (mode == "on")
88 | status = "enabled ✅" if AUTO_APPROVE_ENABLED else "disabled ❌"
89 | await message.reply_text(f"Auto-approval has been {status}.")
90 |
91 | @Client.on_message(filters.command("approveoff") & is_owner_or_admin)
92 | async def approve_off_command(client, message: Message):
93 | if len(message.command) != 2 or not message.command[1].lstrip("-").isdigit():
94 | return await message.reply_text("Usage: /approveoff {channel_id}")
95 | channel_id = int(message.command[1])
96 | success = await set_approval_off(channel_id, True)
97 | if success:
98 | await message.reply_text(f"✅ Auto-approval is now OFF for channel {channel_id}.")
99 | else:
100 | await message.reply_text(f"❌ Failed to set auto-approval OFF for channel {channel_id}.")
101 |
102 | @Client.on_message(filters.command("approveon") & is_owner_or_admin)
103 | async def approve_on_command(client, message: Message):
104 | if len(message.command) != 2 or not message.command[1].lstrip("-").isdigit():
105 | return await message.reply_text("Usage: /approveon {channel_id}")
106 | channel_id = int(message.command[1])
107 | success = await set_approval_off(channel_id, False)
108 | if success:
109 | await message.reply_text(f"✅ Auto-approval is now ON for channel {channel_id}.")
110 | else:
111 | await message.reply_text(f"❌ Failed to set auto-approval ON for channel {channel_id}.")
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2 |
3 |
8 |
9 |
14 | 𝗗𝗘𝗣𝗟𝗢𝗬𝗠𝗘𝗡𝗧 𝗠𝗘𝗧𝗛𝗢𝗗𝗦 15 |
16 | 17 |approve.py).
76 |
77 | ### ᴀᴅᴍɪɴ ᴄᴏᴍᴍᴀɴᴅs
78 | - /stats — Show bot stats (owner only)
79 | - /status — Show bot status (admins)
80 | - /broadcast — Broadcast a message to all users (admins)
81 |
82 | ### ᴏᴛʜᴇʀ ғᴇᴀᴛᴜʀᴇs
83 | - Fast invite link generation (normal & join request)
84 | - Invite links auto-revoke after 5 minutes for security
85 | - Force subscription (FSub) support
86 | - Bulk and single link management
87 | - All commands are permission-checked (OWNER_ID/ADMINS)
88 |
89 | ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
90 |
91 | Iɴᴠᴀʟɪᴅ ᴄʜᴀᴛ ID. Exᴀᴍᴘʟᴇ: /addchat <chat_id>")
40 |
41 | try:
42 | chat = await client.get_chat(channel_id)
43 |
44 | # Check permissions based on chat type
45 | if chat.permissions:
46 | # For groups/channels, check appropriate permissions
47 | has_permission = False
48 | if hasattr(chat.permissions, 'can_post_messages') and chat.permissions.can_post_messages:
49 | has_permission = True
50 | elif hasattr(chat.permissions, 'can_edit_messages') and chat.permissions.can_edit_messages:
51 | has_permission = True
52 | elif chat.type.name in ['GROUP', 'SUPERGROUP']:
53 | # For groups, having the bot as admin is usually sufficient
54 | try:
55 | bot_member = await client.get_chat_member(chat.id, (await client.get_me()).id)
56 | if bot_member.status.name in ['ADMINISTRATOR', 'CREATOR']:
57 | has_permission = True
58 | except:
59 | pass
60 |
61 | if not has_permission:
62 | return await message.reply(f"I ᴀᴍ ɪɴ {chat.title}, ʙᴜᴛ I ʟᴀᴄᴋ ᴘᴏsᴛɪɴɢ ᴏʀ ᴇᴅɪᴛɪɴɢ ᴘᴇʀᴍɪssɪᴏɴs.") 63 | 64 | await save_channel(channel_id) 65 | base64_invite = await save_encoded_link(channel_id) 66 | normal_link = f"https://t.me/{client.username}?start={base64_invite}" 67 | base64_request = await encode(str(channel_id)) 68 | await save_encoded_link2(channel_id, base64_request) 69 | request_link = f"https://t.me/{client.username}?start=req_{base64_request}" 70 | reply_text = ( 71 | f"
✅ Cʜᴀᴛ {chat.title} ({channel_id}) ʜᴀs ʙᴇᴇɴ ᴀᴅᴅᴇᴅ sᴜᴄᴄᴇssғᴜʟʟʏ.\n\n" 72 | f"🔗 Nᴏʀᴍᴀʟ Lɪɴᴋ:
{normal_link}\n"
73 | f"🔗 Rᴇǫᴜᴇsᴛ Lɪɴᴋ: {request_link}"
74 | )
75 | return await message.reply(reply_text)
76 |
77 | except UserNotParticipant:
78 | return await message.reply("I ᴀᴍ ɴᴏᴛ ᴀ ᴍᴇᴍʙᴇʀ ᴏғ ᴛʜɪs ᴄʜᴀɴɴᴇʟ. Pʟᴇᴀsᴇ ᴀᴅᴅ ᴍᴇ ᴀɴᴅ ᴛʀʏ ᴀɢᴀɪɴ.") 79 | except FloodWait as e: 80 | await asyncio.sleep(e.x) 81 | return await set_channel(client, message) 82 | except RPCError as e: 83 | return await message.reply(f"RPC Error: {str(e)}") 84 | except Exception as e: 85 | return await message.reply(f"Unexpected Error: {str(e)}") 86 | 87 | # Delete chat command 88 | @Bot.on_message((filters.command('delchat') | filters.command('delch')) & is_owner_or_admin) 89 | async def del_channel(client: Bot, message: Message): 90 | try: 91 | channel_id = int(message.command[1]) 92 | except (IndexError, ValueError): 93 | return await message.reply("
Iɴᴠᴀʟɪᴅ ᴄʜᴀᴛ ID. Exᴀᴍᴘʟᴇ: /delch <chat_id>")
94 |
95 | await delete_channel(channel_id)
96 | return await message.reply(f"❌ Cʜᴀᴛ {channel_id} ʜᴀs ʙᴇᴇɴ ʀᴇᴍᴏᴠᴇᴅ sᴜᴄᴄᴇssғᴜʟʟʏ.") 97 | 98 | # Channel post command 99 | @Bot.on_message(filters.command('ch_links') & is_owner_or_admin) 100 | async def channel_post(client: Bot, message: Message): 101 | status_msg = await message.reply("⏳") 102 | try: 103 | channels = await get_channels() 104 | if not channels: 105 | await status_msg.delete() 106 | return await message.reply("
Nᴏ ᴄʜᴀɴɴᴇʟs ᴀʀᴇ ᴀᴠᴀɪʟᴀʙʟᴇ. Pʟᴇᴀsᴇ ᴜsᴇ /addch ᴛᴏ ᴀᴅᴅ ᴀ ᴄʜᴀɴɴᴇʟ.") 107 | 108 | await send_channel_page(client, message, channels, page=0, status_msg=status_msg) 109 | except Exception as e: 110 | await status_msg.delete() 111 | await message.reply(f"Error:
{str(e)}")
112 |
113 | async def send_channel_page(client, message, channels, page, status_msg=None, edit=False):
114 | # Delete status message first
115 | if status_msg:
116 | await status_msg.delete()
117 |
118 | total_pages = (len(channels) + PAGE_SIZE - 1) // PAGE_SIZE
119 | start_idx = page * PAGE_SIZE
120 | end_idx = start_idx + PAGE_SIZE
121 | buttons = []
122 |
123 | # Get all chat info concurrently
124 | chat_tasks = []
125 | for channel_id in channels[start_idx:end_idx]:
126 | chat_tasks.append(get_chat_info(client, channel_id))
127 |
128 | try:
129 | chat_infos = await asyncio.gather(*chat_tasks, return_exceptions=True)
130 | except Exception as e:
131 | print(f"Error gathering chat info: {e}")
132 | chat_infos = [None] * len(channels[start_idx:end_idx])
133 |
134 | row = []
135 | for i, chat_info in enumerate(chat_infos):
136 | channel_id = channels[start_idx + i]
137 | if isinstance(chat_info, Exception) or chat_info is None:
138 | print(f"Error getting chat info for channel {channel_id}: {chat_info}")
139 | continue
140 |
141 | try:
142 | base64_invite = await save_encoded_link(channel_id)
143 | button_link = f"https://t.me/{client.username}?start={base64_invite}"
144 |
145 | row.append(InlineKeyboardButton(chat_info.title, url=button_link))
146 |
147 | if len(row) == 2:
148 | buttons.append(row)
149 | row = []
150 | except Exception as e:
151 | print(f"Error for channel {channel_id}: {e}")
152 |
153 | if row:
154 | buttons.append(row)
155 |
156 | nav_buttons = []
157 | if page > 0:
158 | nav_buttons.append(InlineKeyboardButton("• Pʀᴇᴠɪᴏᴜs •", callback_data=f"channelpage_{page-1}"))
159 | if page < total_pages - 1:
160 | nav_buttons.append(InlineKeyboardButton("• Nᴇxᴛ •", callback_data=f"channelpage_{page+1}"))
161 |
162 | if nav_buttons:
163 | buttons.append(nav_buttons)
164 |
165 | reply_markup = InlineKeyboardMarkup(buttons)
166 | if edit:
167 | await message.edit_text("Sᴇʟᴇᴄᴛ ᴀ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴀᴄᴄᴇss:", reply_markup=reply_markup)
168 | else:
169 | await message.reply("Sᴇʟᴇᴄᴛ ᴄʜᴀɴɴᴇʟ:", reply_markup=reply_markup)
170 |
171 | @Bot.on_callback_query(filters.regex(r"channelpage_(\d+)"))
172 | async def paginate_channels(client, callback_query):
173 | page = int(callback_query.data.split("_")[1])
174 | status_msg = await callback_query.message.edit_text("⏳")
175 | channels = await get_channels()
176 | await send_channel_page(client, callback_query.message, channels, page, status_msg=status_msg, edit=True)
177 |
178 | # Request post command
179 | @Bot.on_message(filters.command('reqlink') & is_owner_or_admin)
180 | async def req_post(client: Bot, message: Message):
181 | status_msg = await message.reply("⏳")
182 | try:
183 | channels = await get_channels()
184 | if not channels:
185 | await status_msg.delete()
186 | return await message.reply("Nᴏ ᴄʜᴀɴɴᴇʟs ᴀʀᴇ ᴀᴠᴀɪʟᴀʙʟᴇ. Pʟᴇᴀsᴇ ᴜsᴇ /setchannel ᴛᴏ ᴀᴅᴅ ᴀ ᴄʜᴀɴɴᴇʟ") 187 | 188 | await send_request_page(client, message, channels, page=0, status_msg=status_msg) 189 | except Exception as e: 190 | await status_msg.delete() 191 | await message.reply(f"Error:
{str(e)}")
192 |
193 | async def send_request_page(client, message, channels, page, status_msg=None, edit=False):
194 | # Delete status message first
195 | if status_msg:
196 | await status_msg.delete()
197 |
198 | total_pages = (len(channels) + PAGE_SIZE - 1) // PAGE_SIZE
199 | start_idx = page * PAGE_SIZE
200 | end_idx = start_idx + PAGE_SIZE
201 | buttons = []
202 |
203 | # Get all chat info concurrently
204 | chat_tasks = []
205 | for channel_id in channels[start_idx:end_idx]:
206 | chat_tasks.append(get_chat_info(client, channel_id))
207 |
208 | try:
209 | chat_infos = await asyncio.gather(*chat_tasks, return_exceptions=True)
210 | except Exception as e:
211 | print(f"Error gathering chat info: {e}")
212 | chat_infos = [None] * len(channels[start_idx:end_idx])
213 |
214 | row = []
215 | for i, chat_info in enumerate(chat_infos):
216 | channel_id = channels[start_idx + i]
217 | if isinstance(chat_info, Exception) or chat_info is None:
218 | print(f"Error getting chat info for channel {channel_id}: {chat_info}")
219 | continue
220 |
221 | try:
222 | base64_request = await encode(str(channel_id))
223 | await save_encoded_link2(channel_id, base64_request)
224 | button_link = f"https://t.me/{client.username}?start=req_{base64_request}"
225 |
226 | row.append(InlineKeyboardButton(chat_info.title, url=button_link))
227 |
228 | if len(row) == 2:
229 | buttons.append(row)
230 | row = []
231 | except Exception as e:
232 | print(f"Error generating request link for channel {channel_id}: {e}")
233 |
234 | if row:
235 | buttons.append(row)
236 |
237 | nav_buttons = []
238 | if page > 0:
239 | nav_buttons.append(InlineKeyboardButton("• Pʀᴇᴠɪᴏᴜs •", callback_data=f"reqpage_{page-1}"))
240 | if page < total_pages - 1:
241 | nav_buttons.append(InlineKeyboardButton("• Nᴇxᴛ •", callback_data=f"reqpage_{page+1}"))
242 |
243 | if nav_buttons:
244 | buttons.append(nav_buttons)
245 | reply_markup = InlineKeyboardMarkup(buttons)
246 | if edit:
247 | await message.edit_text("Sᴇʟᴇᴄᴛ ᴀ ᴄʜᴀɴɴᴇʟ ᴛᴏ ʀᴇǫᴜᴇsᴛ ᴀᴄᴄᴇss:", reply_markup=reply_markup)
248 | else:
249 | await message.reply("Sᴇʟᴇᴄᴛ ᴄʜᴀɴɴᴇʟ:", reply_markup=reply_markup)
250 |
251 | @Bot.on_callback_query(filters.regex(r"reqpage_(\d+)"))
252 | async def paginate_requests(client, callback_query):
253 | page = int(callback_query.data.split("_")[1])
254 | status_msg = await callback_query.message.edit_text("⏳")
255 | channels = await get_channels()
256 | await send_request_page(client, callback_query.message, channels, page, status_msg=status_msg, edit=True)
257 |
258 | # Links command - show all links as text
259 | @Bot.on_message(filters.command('links') & is_owner_or_admin)
260 | async def show_links(client: Bot, message: Message):
261 | status_msg = await message.reply("⏳")
262 | try:
263 | channels = await get_channels()
264 | if not channels:
265 | await status_msg.delete()
266 | return await message.reply("Nᴏ ᴄʜᴀɴɴᴇʟs ᴀʀᴇ ᴀᴠᴀɪʟᴀʙʟᴇ. Pʟᴇᴀsᴇ ᴜsᴇ /addch ᴛᴏ ᴀᴅᴅ ᴀ ᴄʜᴀɴɴᴇʟ.") 267 | 268 | await send_links_page(client, message, channels, page=0, status_msg=status_msg) 269 | except Exception as e: 270 | await status_msg.delete() 271 | await message.reply(f"Error:
{str(e)}")
272 |
273 | async def send_links_page(client, message, channels, page, status_msg=None, edit=False):
274 | # Delete status message first
275 | if status_msg:
276 | await status_msg.delete()
277 |
278 | total_pages = (len(channels) + PAGE_SIZE - 1) // PAGE_SIZE
279 | start_idx = page * PAGE_SIZE
280 | end_idx = start_idx + PAGE_SIZE
281 |
282 | links_text = "➤ Aʟʟ Cʜᴀɴɴᴇʟ Lɪɴᴋs:\n\n"
283 |
284 | # Get all chat info and links concurrently
285 | tasks = []
286 | for channel_id in channels[start_idx:end_idx]:
287 | tasks.append(asyncio.gather(
288 | get_chat_info(client, channel_id),
289 | save_encoded_link(channel_id),
290 | asyncio.create_task(encode(str(channel_id))),
291 | return_exceptions=True
292 | ))
293 |
294 | try:
295 | results = await asyncio.gather(*tasks, return_exceptions=True)
296 | except Exception as e:
297 | print(f"Error gathering link info: {e}")
298 | results = [None] * len(channels[start_idx:end_idx])
299 |
300 | for i, result in enumerate(results):
301 | idx = start_idx + i + 1
302 | channel_id = channels[start_idx + i]
303 |
304 | if isinstance(result, Exception) or result is None or any(isinstance(r, Exception) for r in result):
305 | print(f"Error getting info for channel {channel_id}: {result}")
306 | links_text += f"{idx}. Channel {channel_id} (Error)\n\n"
307 | continue
308 |
309 | try:
310 | chat_info, base64_invite, base64_request = result
311 | if isinstance(chat_info, Exception):
312 | links_text += f"{idx}. Channel {channel_id} (Error)\n\n"
313 | continue
314 |
315 | await save_encoded_link2(channel_id, base64_request)
316 | normal_link = f"https://t.me/{client.username}?start={base64_invite}"
317 | request_link = f"https://t.me/{client.username}?start=req_{base64_request}"
318 |
319 | links_text += f"{idx}. {chat_info.title}\n"
320 | links_text += f"➥ Nᴏʀᴍᴀʟ: {normal_link}\n"
321 | links_text += f"➤ Rᴇǫᴜᴇsᴛ: {request_link}\n\n"
322 |
323 | except Exception as e:
324 | print(f"Error for channel {channel_id}: {e}")
325 | links_text += f"{idx}. Channel {channel_id} (Error)\n\n"
326 |
327 | # Add pagination info
328 | links_text += f"📄 Pᴀɢᴇ {page + 1} ᴏғ {total_pages}"
329 |
330 | # Create navigation buttons
331 | buttons = []
332 | nav_buttons = []
333 | if page > 0:
334 | nav_buttons.append(InlineKeyboardButton("• Pʀᴇᴠɪᴏᴜs •", callback_data=f"linkspage_{page-1}"))
335 | if page < total_pages - 1:
336 | nav_buttons.append(InlineKeyboardButton("• Nᴇxᴛ •", callback_data=f"linkspage_{page+1}"))
337 |
338 | if nav_buttons:
339 | buttons.append(nav_buttons)
340 |
341 | reply_markup = InlineKeyboardMarkup(buttons) if buttons else None
342 |
343 | if edit:
344 | await message.edit_text(links_text, reply_markup=reply_markup)
345 | else:
346 | await message.reply(links_text, reply_markup=reply_markup)
347 |
348 | @Bot.on_callback_query(filters.regex(r"linkspage_(\d+)"))
349 | async def paginate_links(client, callback_query):
350 | page = int(callback_query.data.split("_")[1])
351 | status_msg = await callback_query.message.edit_text("⏳")
352 | channels = await get_channels()
353 | await send_links_page(client, callback_query.message, channels, page, status_msg=status_msg, edit=True)
354 |
355 | # Bulk link generation command
356 | @Bot.on_message(filters.command('bulklink') & is_owner_or_admin)
357 | async def bulk_link(client: Bot, message: Message):
358 | user_id = message.from_user.id
359 |
360 | if len(message.command) < 2:
361 | return await message.reply("ᴜsᴀɢᴇ: /bulklink <id1> <id2> ...")
362 |
363 | ids = message.command[1:]
364 | reply_text = "➤ Bᴜʟᴋ Lɪɴᴋ Gᴇɴᴇʀᴀᴛɪᴏɴ:\n\n"
365 | for idx, id_str in enumerate(ids, start=1):
366 | try:
367 | channel_id = int(id_str)
368 | chat = await client.get_chat(channel_id)
369 | base64_invite = await save_encoded_link(channel_id)
370 | normal_link = f"https://t.me/{client.username}?start={base64_invite}"
371 | base64_request = await encode(str(channel_id))
372 | await save_encoded_link2(channel_id, base64_request)
373 | request_link = f"https://t.me/{client.username}?start=req_{base64_request}"
374 | reply_text += f"{idx}. {chat.title} ({channel_id})\n"
375 | reply_text += f"➥ Nᴏʀᴍᴀʟ: {normal_link}\n"
376 | reply_text += f"➤ Rᴇǫᴜᴇsᴛ: {request_link}\n\n"
377 | except Exception as e:
378 | reply_text += f"{idx}. Channel {id_str} (Error: {e})\n\n"
379 | await message.reply(reply_text)
380 |
381 | @Bot.on_message(filters.command('genlink') & filters.private & is_owner_or_admin)
382 | async def generate_link_command(client: Bot, message: Message):
383 | user_id = message.from_user.id
384 | if len(message.command) < 2:
385 | return await message.reply("Usage: /genlink <link>")
386 |
387 | link = message.command[1]
388 | # Store the link in the database channel
389 | try:
390 | sent_msg = await client.send_message(DATABASE_CHANNEL, f"#LINK\n{link}")
391 | channel_id = sent_msg.id # Use id as unique id for this link
392 | # Save encoded links
393 | base64_invite = await save_encoded_link(channel_id)
394 | base64_request = await encode(str(channel_id))
395 | await save_encoded_link2(channel_id, base64_request)
396 | # Store the original link in the database
397 | from database.database import channels_collection
398 | await channels_collection.update_one(
399 | {"channel_id": channel_id},
400 | {"$set": {"original_link": link}},
401 | upsert=True
402 | )
403 | normal_link = f"https://t.me/{client.username}?start={base64_invite}"
404 | request_link = f"https://t.me/{client.username}?start=req_{base64_request}"
405 | reply_text = (
406 | f"✅ Link stored and encoded successfully.\n\n"
407 | f"🔗 Normal Link: {normal_link}\n"
408 | f"🔗 Request Link: {request_link}"
409 | )
410 | await message.reply(reply_text)
411 | except Exception as e:
412 | await message.reply(f"Error storing link: {e}")
413 |
414 | @Bot.on_message(filters.command('channels') & is_owner_or_admin)
415 | async def show_channel_ids(client: Bot, message: Message):
416 | status_msg = await message.reply("⏳")
417 | try:
418 | channels = await get_channels()
419 | if not channels:
420 | await status_msg.delete()
421 | return await message.reply("Nᴏ ᴄʜᴀɴɴᴇʟs ᴀʀᴇ ᴀᴠᴀɪʟᴀʙʟᴇ. Pʟᴇᴀsᴇ ᴜsᴇ /addch ᴛᴏ ᴀᴅᴅ ᴀ ᴄʜᴀɴɴᴇʟ.") 422 | 423 | await send_channel_ids_page(client, message, channels, page=0, status_msg=status_msg) 424 | except Exception as e: 425 | await status_msg.delete() 426 | await message.reply(f"Error:
{str(e)}")
427 |
428 | async def send_channel_ids_page(client, message, channels, page, status_msg=None, edit=False):
429 | # Delete status message first
430 | if status_msg:
431 | await status_msg.delete()
432 |
433 | PAGE_SIZE = 10
434 | total_pages = (len(channels) + PAGE_SIZE - 1) // PAGE_SIZE
435 | start_idx = page * PAGE_SIZE
436 | end_idx = start_idx + PAGE_SIZE
437 |
438 | # Get all chat info concurrently
439 | chat_tasks = []
440 | for channel_id in channels[start_idx:end_idx]:
441 | chat_tasks.append(get_chat_info(client, channel_id))
442 |
443 | try:
444 | chat_infos = await asyncio.gather(*chat_tasks, return_exceptions=True)
445 | except Exception as e:
446 | print(f"Error gathering chat info: {e}")
447 | chat_infos = [None] * len(channels[start_idx:end_idx])
448 |
449 | text = "➤ Cᴏɴɴᴇᴄᴛᴇᴅ Cʜᴀɴɴᴇʟs (ID & Name):\n\n"
450 | for i, chat_info in enumerate(chat_infos):
451 | idx = start_idx + i + 1
452 | channel_id = channels[start_idx + i]
453 |
454 | if isinstance(chat_info, Exception) or chat_info is None:
455 | text += f"{idx}. Channel {channel_id} (Error)\n"
456 | continue
457 |
458 | text += f"{idx}. {chat_info.title} ({channel_id})\n"
459 |
460 | text += f"\n📄 Pᴀɢᴇ {page + 1} ᴏғ {total_pages}"
461 |
462 | # Navigation buttons
463 | buttons = []
464 | nav_buttons = []
465 | if page > 0:
466 | nav_buttons.append(InlineKeyboardButton("• Pʀᴇᴠɪᴏᴜs •", callback_data=f"channelids_{page-1}"))
467 | if page < total_pages - 1:
468 | nav_buttons.append(InlineKeyboardButton("• Nᴇxᴛ •", callback_data=f"channelids_{page+1}"))
469 | if nav_buttons:
470 | buttons.append(nav_buttons)
471 |
472 | reply_markup = InlineKeyboardMarkup(buttons) if buttons else None
473 | if edit:
474 | await message.edit_text(text, reply_markup=reply_markup)
475 | else:
476 | await message.reply(text, reply_markup=reply_markup)
477 |
478 | @Bot.on_callback_query(filters.regex(r"channelids_(\d+)"))
479 | async def paginate_channel_ids(client, callback_query):
480 | page = int(callback_query.data.split("_")[1])
481 | status_msg = await callback_query.message.edit_text("⏳")
482 | channels = await get_channels()
483 | await send_channel_ids_page(client, callback_query.message, channels, page, status_msg=status_msg, edit=True)
484 |
485 | # Helper function to get chat info with caching
486 | async def get_chat_info(client, channel_id):
487 | # Check cache first
488 | if channel_id in chat_info_cache:
489 | cached_info, timestamp = chat_info_cache[channel_id]
490 | # Cache for 5 minutes
491 | if (datetime.now() - timestamp).total_seconds() < 300:
492 | return cached_info
493 |
494 | # Fetch fresh info
495 | try:
496 | chat_info = await client.get_chat(channel_id)
497 | # Cache the result
498 | chat_info_cache[channel_id] = (chat_info, datetime.now())
499 | return chat_info
500 | except Exception as e:
501 | print(f"Error getting chat info for {channel_id}: {e}")
502 | # Return cached info even if stale if we can't get fresh info
503 | if channel_id in chat_info_cache:
504 | return chat_info_cache[channel_id][0]
505 | raise e
506 |
507 |
--------------------------------------------------------------------------------
/plugins/start.py:
--------------------------------------------------------------------------------
1 |
2 | import asyncio
3 | import base64
4 | import time
5 | from asyncio import Lock
6 | from collections import defaultdict
7 | from pyrogram import Client, filters
8 | from pyrogram.enums import ParseMode, ChatMemberStatus, ChatAction
9 | from pyrogram.types import Message, InlineKeyboardMarkup, InlineKeyboardButton, CallbackQuery, InputMediaPhoto
10 | from pyrogram.errors import FloodWait, UserNotParticipant, UserIsBlocked, InputUserDeactivated
11 | import os
12 | import asyncio
13 | from asyncio import sleep
14 | from asyncio import Lock
15 | import random
16 |
17 | from bot import Bot
18 | from datetime import datetime, timedelta
19 | from config import *
20 | from database.database import *
21 | from plugins.newpost import revoke_invite_after_5_minutes
22 | from helper_func import *
23 |
24 | # Create a lock dictionary for each channel to prevent concurrent link generation
25 | channel_locks = defaultdict(asyncio.Lock)
26 |
27 | user_banned_until = {}
28 |
29 | # Broadcast variables
30 | cancel_lock = asyncio.Lock()
31 | is_canceled = False
32 |
33 | @Bot.on_message(filters.command('start') & filters.private)
34 | async def start_command(client: Bot, message: Message):
35 | user_id = message.from_user.id
36 |
37 | if user_id in user_banned_until:
38 | if datetime.now() < user_banned_until[user_id]:
39 | return await message.reply_text(
40 | "You are temporarily banned from using commands due to spamming. Try again later.", 41 | parse_mode=ParseMode.HTML 42 | ) 43 | 44 | await add_user(user_id) 45 | 46 | # ✅ Check Force Subscription 47 | #if not await is_subscribed(client, user_id): 48 | #await temp.delete() 49 | #return await not_joined(client, message) 50 | 51 | # 52 | # Check FSub requirements 53 | # fsub_channels = await get_fsub_channels() 54 | # if fsub_channels: 55 | # is_subscribed, subscription_message, subscription_buttons = await check_subscription_status(client, user_id, fsub_channels) 56 | # if not is_subscribed: 57 | # return await message.reply_text( 58 | # subscription_message, 59 | # reply_markup=subscription_buttons, 60 | # parse_mode=ParseMode.HTML 61 | # ) 62 | 63 | text = message.text 64 | if len(text) > 7: 65 | try: 66 | base64_string = text.split(" ", 1)[1] 67 | is_request = base64_string.startswith("req_") 68 | 69 | if is_request: 70 | base64_string = base64_string[4:] 71 | channel_id = await get_channel_by_encoded_link2(base64_string) 72 | else: 73 | channel_id = await get_channel_by_encoded_link(base64_string) 74 | 75 | if not channel_id: 76 | return await message.reply_text( 77 | "
Invalid or expired invite link.", 78 | parse_mode=ParseMode.HTML 79 | ) 80 | 81 | # Check if this is a /genlink link (original_link exists) 82 | from database.database import get_original_link 83 | original_link = await get_original_link(channel_id) 84 | if original_link: 85 | button = InlineKeyboardMarkup( 86 | [[InlineKeyboardButton("• Proceed to Link •", url=original_link)]] 87 | ) 88 | return await message.reply_text( 89 | "
ʜᴇʀᴇ ɪs ʏᴏᴜʀ ʟɪɴᴋ! ᴄʟɪᴄᴋ ʙᴇʟᴏᴡ ᴛᴏ ᴘʀᴏᴄᴇᴇᴅ", 90 | reply_markup=button, 91 | parse_mode=ParseMode.HTML 92 | ) 93 | 94 | # Use a lock for this channel to prevent concurrent link generation 95 | async with channel_locks[channel_id]: 96 | # Check if we already have a valid link 97 | old_link_info = await get_current_invite_link(channel_id) 98 | current_time = datetime.now() 99 | 100 | # If we have an existing link and it's not expired yet (assuming 5 minutes validity) 101 | if old_link_info: 102 | link_created_time = await get_link_creation_time(channel_id) 103 | if link_created_time and (current_time - link_created_time).total_seconds() < 240: # 4 minutes 104 | # Use existing link 105 | invite_link = old_link_info["invite_link"] 106 | is_request_link = old_link_info["is_request"] 107 | else: 108 | # Revoke old link and create new one 109 | try: 110 | await client.revoke_chat_invite_link(channel_id, old_link_info["invite_link"]) 111 | print(f"Revoked old {'request' if old_link_info['is_request'] else 'invite'} link for channel {channel_id}") 112 | except Exception as e: 113 | print(f"Failed to revoke old link for channel {channel_id}: {e}") 114 | 115 | # Create new link 116 | invite = await client.create_chat_invite_link( 117 | chat_id=channel_id, 118 | expire_date=current_time + timedelta(minutes=10), 119 | creates_join_request=is_request 120 | ) 121 | invite_link = invite.invite_link 122 | is_request_link = is_request 123 | await save_invite_link(channel_id, invite_link, is_request_link) 124 | else: 125 | # Create new link 126 | invite = await client.create_chat_invite_link( 127 | chat_id=channel_id, 128 | expire_date=current_time + timedelta(minutes=10), 129 | creates_join_request=is_request 130 | ) 131 | invite_link = invite.invite_link 132 | is_request_link = is_request 133 | await save_invite_link(channel_id, invite_link, is_request_link) 134 | 135 | button_text = "• ʀᴇǫᴜᴇsᴛ ᴛᴏ ᴊᴏɪɴ •" if is_request_link else "• ᴊᴏɪɴ ᴄʜᴀɴɴᴇʟ •" 136 | button = InlineKeyboardMarkup([[InlineKeyboardButton(button_text, url=invite_link)]]) 137 | 138 | wait_msg = await message.reply_text( 139 | "⏳", 140 | parse_mode=ParseMode.HTML 141 | ) 142 | 143 | await wait_msg.delete() 144 | 145 | await message.reply_text( 146 | "
ʜᴇʀᴇ ɪs ʏᴏᴜʀ ʟɪɴᴋ! ᴄʟɪᴄᴋ ʙᴇʟᴏᴡ ᴛᴏ ᴘʀᴏᴄᴇᴇᴅ", 147 | reply_markup=button, 148 | parse_mode=ParseMode.HTML 149 | ) 150 | 151 | note_msg = await message.reply_text( 152 | "Note: If the link is expired, please click the post link again to get a new one.", 153 | parse_mode=ParseMode.HTML 154 | ) 155 | 156 | # Auto-delete the note message after 5 minutes 157 | asyncio.create_task(delete_after_delay(note_msg, 300)) 158 | 159 | asyncio.create_task(revoke_invite_after_5_minutes(client, channel_id, invite_link, is_request_link)) 160 | 161 | except Exception as e: 162 | await message.reply_text( 163 | "
Invalid or expired invite link.", 164 | parse_mode=ParseMode.HTML 165 | ) 166 | print(f"Decoding error: {e}") 167 | else: 168 | inline_buttons = InlineKeyboardMarkup( 169 | [ 170 | [InlineKeyboardButton("• ᴀʙᴏᴜᴛ", callback_data="about"), 171 | InlineKeyboardButton("• ᴄʜᴀɴɴᴇʟs", callback_data="channels")], 172 | [InlineKeyboardButton("• Close •", callback_data="close")] 173 | ] 174 | ) 175 | 176 | # Show waiting emoji and instantly delete it 177 | wait_msg = await message.reply_text("⏳") 178 | await asyncio.sleep(0.1) 179 | await wait_msg.delete() 180 | 181 | try: 182 | await message.reply_photo( 183 | photo=START_PIC, 184 | caption=START_MSG, 185 | reply_markup=inline_buttons, 186 | parse_mode=ParseMode.HTML 187 | ) 188 | except Exception as e: 189 | print(f"Error sending start picture: {e}") 190 | await message.reply_text( 191 | START_MSG, 192 | reply_markup=inline_buttons, 193 | parse_mode=ParseMode.HTML 194 | ) 195 | 196 | 197 | #=====================================================================================## 198 | # Don't Remove Credit @CodeFlix_Bots, @rohit_1888 199 | # Ask Doubt on telegram @CodeflixSupport 200 | 201 | async def get_link_creation_time(channel_id): 202 | """Get the creation time of the current invite link for a channel.""" 203 | try: 204 | from database.database import channels_collection 205 | channel = await channels_collection.find_one({"channel_id": channel_id, "status": "active"}) 206 | if channel and "invite_link_created_at" in channel: 207 | return channel["invite_link_created_at"] 208 | return None 209 | except Exception as e: 210 | print(f"Error fetching link creation time for channel {channel_id}: {e}") 211 | return None 212 | 213 | # Create a global dictionary to store chat data 214 | chat_data_cache = {} 215 | 216 | async def not_joined(client: Client, message: Message): 217 | #temp = await message.reply("ᴡᴀɪᴛ ᴀ sᴇᴄ..") 218 | 219 | user_id = message.from_user.id 220 | buttons = [] 221 | count = 0 222 | 223 | try: 224 | all_channels = await db.show_channels() # Should return list of (chat_id, mode) tuples 225 | for total, chat_id in enumerate(all_channels, start=1): 226 | mode = await db.get_channel_mode(chat_id) # fetch mode 227 | 228 | await message.reply_chat_action(ChatAction.TYPING) 229 | 230 | if not await is_sub(client, user_id, chat_id): 231 | try: 232 | # Cache chat info 233 | if chat_id in chat_data_cache: 234 | data = chat_data_cache[chat_id] 235 | else: 236 | data = await client.get_chat(chat_id) 237 | chat_data_cache[chat_id] = data 238 | 239 | name = data.title 240 | 241 | # Generate proper invite link based on the mode 242 | if mode == "on" and not data.username: 243 | invite = await client.create_chat_invite_link( 244 | chat_id=chat_id, 245 | creates_join_request=True, 246 | expire_date=datetime.utcnow() + timedelta(seconds=FSUB_LINK_EXPIRY) if FSUB_LINK_EXPIRY else None 247 | ) 248 | link = invite.invite_link 249 | 250 | else: 251 | if data.username: 252 | link = f"https://t.me/{data.username}" 253 | else: 254 | invite = await client.create_chat_invite_link( 255 | chat_id=chat_id, 256 | expire_date=datetime.utcnow() + timedelta(seconds=FSUB_LINK_EXPIRY) if FSUB_LINK_EXPIRY else None) 257 | link = invite.invite_link 258 | 259 | buttons.append([InlineKeyboardButton(text=name, url=link)]) 260 | count += 1 261 | #await temp.edit(f"{'! ' * count}") 262 | 263 | except Exception as e: 264 | print(f"Error with chat {chat_id}: {e}") 265 | return #await temp.edit( 266 | #f"! Eʀʀᴏʀ, Cᴏɴᴛᴀᴄᴛ ᴅᴇᴠᴇʟᴏᴘᴇʀ ᴛᴏ sᴏʟᴠᴇ ᴛʜᴇ ɪssᴜᴇs @rohit_1888\n" 267 | #f"
Rᴇᴀsᴏɴ: {e}" 268 | #) 269 | 270 | # Retry Button 271 | try: 272 | buttons.append([ 273 | InlineKeyboardButton( 274 | text='♻️ Tʀʏ Aɢᴀɪɴ', 275 | url=f"https://t.me/{client.username}?start={message.command[1]}" 276 | ) 277 | ]) 278 | except IndexError: 279 | pass 280 | 281 | await message.reply_photo( 282 | photo=FORCE_PIC, 283 | caption=FORCE_MSG.format( 284 | first=message.from_user.first_name, 285 | last=message.from_user.last_name, 286 | username=None if not message.from_user.username else '@' + message.from_user.username, 287 | mention=message.from_user.mention, 288 | id=message.from_user.id 289 | ), 290 | reply_markup=InlineKeyboardMarkup(buttons), 291 | ) 292 | 293 | except Exception as e: 294 | print(f"Final Error: {e}") 295 | 296 | @Bot.on_callback_query(filters.regex("close")) 297 | async def close_callback(client: Bot, callback_query): 298 | await callback_query.answer() 299 | await callback_query.message.delete() 300 | 301 | @Bot.on_callback_query(filters.regex("check_sub")) 302 | async def check_sub_callback(client: Bot, callback_query: CallbackQuery): 303 | user_id = callback_query.from_user.id 304 | fsub_channels = await get_fsub_channels() 305 | 306 | if not fsub_channels: 307 | await callback_query.message.edit_text( 308 | "No FSub channels configured!", 309 | parse_mode=ParseMode.HTML 310 | ) 311 | return 312 | 313 | is_subscribed, subscription_message, subscription_buttons = await check_subscription_status(client, user_id, fsub_channels) 314 | if is_subscribed: 315 | await callback_query.message.edit_text( 316 | "You are subscribed to all required channels! Use /start to proceed.", 317 | parse_mode=ParseMode.HTML 318 | ) 319 | else: 320 | await callback_query.message.edit_text( 321 | subscription_message, 322 | reply_markup=subscription_buttons, 323 | parse_mode=ParseMode.HTML 324 | ) 325 | 326 | WAIT_MSG = "Processing..." 327 | 328 | REPLY_ERROR = """Usᴇ ᴛʜɪs ᴄᴏᴍᴍᴀɴᴅ ᴀs ᴀ ʀᴇᴘʟʏ ᴛᴏ ᴀɴʏ Tᴇʟᴇɢʀᴀᴍ ᴍᴇssᴀɢᴇ ᴡɪᴛʜᴏᴜᴛ ᴀɴʏ sᴘᴀᴄᴇs.""" 329 | # Define a global variable to store the cancel state 330 | is_canceled = False 331 | cancel_lock = Lock() 332 | 333 | @Bot.on_message(filters.command('status') & filters.private & is_owner_or_admin) 334 | async def info(client: Bot, message: Message): 335 | reply_markup = InlineKeyboardMarkup([[InlineKeyboardButton("• Close •", callback_data="close")]]) 336 | 337 | start_time = time.time() 338 | temp_msg = await message.reply("Processing...", quote=True, parse_mode=ParseMode.HTML) 339 | end_time = time.time() 340 | 341 | ping_time = (end_time - start_time) * 1000 342 | 343 | users = await full_userbase() 344 | now = datetime.now() 345 | delta = now - client.uptime 346 | bottime = get_readable_time(delta.seconds) 347 | 348 | await temp_msg.edit( 349 | f"Users: {len(users)}\n\nUptime: {bottime}\n\nPing: {ping_time:.2f} ms", 350 | reply_markup=reply_markup, 351 | parse_mode=ParseMode.HTML 352 | ) 353 | 354 | #--------------------------------------------------------------[[ADMIN COMMANDS]]---------------------------------------------------------------------------# 355 | # Handler for the /cancel command 356 | @Bot.on_message(filters.command('cancel') & filters.private & is_owner_or_admin) 357 | async def cancel_broadcast(client: Bot, message: Message): 358 | global is_canceled 359 | async with cancel_lock: 360 | is_canceled = True 361 | 362 | @Bot.on_message(filters.private & filters.command('broadcast') & is_owner_or_admin) 363 | async def broadcast(client: Bot, message: Message): 364 | global is_canceled 365 | args = message.text.split()[1:] 366 | 367 | if not message.reply_to_message: 368 | msg = await message.reply( 369 | "Reply to a message to broadcast.\n\nUsage examples:\n" 370 | "`/broadcast normal`\n" 371 | "`/broadcast pin`\n" 372 | "`/broadcast delete 30`\n" 373 | "`/broadcast pin delete 30`\n" 374 | "`/broadcast silent`\n" 375 | ) 376 | await asyncio.sleep(8) 377 | return await msg.delete() 378 | 379 | # Defaults 380 | do_pin = False 381 | do_delete = False 382 | duration = 0 383 | silent = False 384 | mode_text = [] 385 | 386 | i = 0 387 | while i < len(args): 388 | arg = args[i].lower() 389 | if arg == "pin": 390 | do_pin = True 391 | mode_text.append("PIN") 392 | elif arg == "delete": 393 | do_delete = True 394 | try: 395 | duration = int(args[i + 1]) 396 | i += 1 397 | except (IndexError, ValueError): 398 | return await message.reply("Provide valid duration for delete mode.\nUsage: `/broadcast delete 30`") 399 | mode_text.append(f"DELETE({duration}s)") 400 | elif arg == "silent": 401 | silent = True 402 | mode_text.append("SILENT") 403 | else: 404 | mode_text.append(arg.upper()) 405 | i += 1 406 | 407 | if not mode_text: 408 | mode_text.append("NORMAL") 409 | 410 | # Reset cancel flag 411 | async with cancel_lock: 412 | is_canceled = False 413 | 414 | query = await full_userbase() 415 | broadcast_msg = message.reply_to_message 416 | total = len(query) 417 | successful = blocked = deleted = unsuccessful = 0 418 | 419 | pls_wait = await message.reply(f"Broadcasting in {' + '.join(mode_text)} mode...") 420 | 421 | bar_length = 20 422 | progress_bar = '' 423 | last_update_percentage = 0 424 | update_interval = 0.05 # 5% 425 | 426 | for i, chat_id in enumerate(query, start=1): 427 | async with cancel_lock: 428 | if is_canceled: 429 | await pls_wait.edit(f"›› BROADCAST ({' + '.join(mode_text)}) CANCELED ❌") 430 | return 431 | 432 | try: 433 | sent_msg = await broadcast_msg.copy(chat_id, disable_notification=silent) 434 | 435 | if do_pin: 436 | await client.pin_chat_message(chat_id, sent_msg.id, both_sides=True) 437 | if do_delete: 438 | asyncio.create_task(auto_delete(sent_msg, duration)) 439 | 440 | successful += 1 441 | except FloodWait as e: 442 | await asyncio.sleep(e.x) 443 | try: 444 | sent_msg = await broadcast_msg.copy(chat_id, disable_notification=silent) 445 | if do_pin: 446 | await client.pin_chat_message(chat_id, sent_msg.id, both_sides=True) 447 | if do_delete: 448 | asyncio.create_task(auto_delete(sent_msg, duration)) 449 | successful += 1 450 | except: 451 | unsuccessful += 1 452 | except UserIsBlocked: 453 | await del_user(chat_id) 454 | blocked += 1 455 | except InputUserDeactivated: 456 | await del_user(chat_id) 457 | deleted += 1 458 | except: 459 | unsuccessful += 1 460 | await del_user(chat_id) 461 | 462 | # Progress 463 | percent_complete = i / total 464 | if percent_complete - last_update_percentage >= update_interval or last_update_percentage == 0: 465 | num_blocks = int(percent_complete * bar_length) 466 | progress_bar = "●" * num_blocks + "○" * (bar_length - num_blocks) 467 | status_update = f"""›› BROADCAST ({' + '.join(mode_text)}) IN PROGRESS... 468 | 469 |
⏳:[{progress_bar}]
{percent_complete:.0%}
470 |
471 | ›› Total Users: {total}
472 | ›› Successful: {successful}
473 | ›› Blocked: {blocked}
474 | ›› Deleted: {deleted}
475 | ›› Unsuccessful: {unsuccessful}
476 |
477 | ➪ To stop broadcasting click: /cancel"""
478 | await pls_wait.edit(status_update)
479 | last_update_percentage = percent_complete
480 |
481 | # Final status
482 | final_status = f"""›› BROADCAST ({' + '.join(mode_text)}) COMPLETED ✅
483 |
484 | Dᴏɴᴇ:[{progress_bar}] {percent_complete:.0%} 485 | 486 | ›› Total Users:
{total}
487 | ›› Successful: {successful}
488 | ›› Blocked: {blocked}
489 | ›› Deleted: {deleted}
490 | ›› Unsuccessful: {unsuccessful}"""
491 | return await pls_wait.edit(final_status)
492 |
493 |
494 | # helper for delete mode
495 | async def auto_delete(sent_msg, duration):
496 | await asyncio.sleep(duration)
497 | try:
498 | await sent_msg.delete()
499 | except:
500 | pass
501 |
502 |
503 | #----------------------------------
504 |
505 | user_message_count = {}
506 | user_banned_until = {}
507 |
508 | MAX_MESSAGES = 3
509 | TIME_WINDOW = timedelta(seconds=10)
510 | BAN_DURATION = timedelta(hours=1)
511 |
512 | """
513 |
514 | @Bot.on_message(filters.private)
515 | async def monitor_messages(client: Bot, message: Message):
516 | user_id = message.from_user.id
517 | now = datetime.now()
518 |
519 | if message.text and message.text.startswith("/"):
520 | return
521 |
522 | if user_id in ADMINS:
523 | return
524 |
525 | if user_id in user_banned_until and now < user_banned_until[user_id]:
526 | await message.reply_text(
527 | "You are temporarily banned from using commands due to spamming. Try again later.", 528 | parse_mode=ParseMode.HTML 529 | ) 530 | return 531 | 532 | if user_id not in user_message_count: 533 | user_message_count[user_id] = [] 534 | 535 | user_message_count[user_id].append(now) 536 | user_message_count[user_id] = [time for time in user_message_count[user_id] if now - time <= TIME_WINDOW] 537 | 538 | if len(user_message_count[user_id]) > MAX_MESSAGES: 539 | user_banned_until[user_id] = now + BAN_DURATION 540 | await message.reply_text( 541 | "
You are temporarily banned from using commands due to spamming. Try again later.", 542 | parse_mode=ParseMode.HTML 543 | ) 544 | return 545 | 546 | """ 547 | 548 | @Bot.on_callback_query() 549 | async def cb_handler(client: Bot, query: CallbackQuery): 550 | data = query.data 551 | chat_id = query.message.chat.id 552 | 553 | if data == "close": 554 | await query.message.delete() 555 | try: 556 | await query.message.reply_to_message.delete() 557 | except: 558 | pass 559 | 560 | elif data == "about": 561 | user = await client.get_users(OWNER_ID) 562 | user_link = f"https://t.me/{user.username}" if user.username else f"tg://openmessage?user_id={OWNER_ID}" 563 | 564 | await query.edit_message_media( 565 | InputMediaPhoto( 566 | "https://envs.sh/Wdj.jpg", 567 | ABOUT_TXT 568 | ), 569 | reply_markup=InlineKeyboardMarkup([ 570 | [InlineKeyboardButton('• ʙᴀᴄᴋ', callback_data='start'), InlineKeyboardButton('ᴄʟᴏsᴇ •', callback_data='close')] 571 | ]), 572 | ) 573 | 574 | elif data == "channels": 575 | user = await client.get_users(OWNER_ID) 576 | user_link = f"https://t.me/{user.username}" if user.username else f"tg://openmessage?user_id={OWNER_ID}" 577 | ownername = f"{user.first_name}" if user.first_name else f"no name !" 578 | await query.edit_message_media( 579 | InputMediaPhoto("https://envs.sh/Wdj.jpg", 580 | CHANNELS_TXT 581 | ), 582 | reply_markup=InlineKeyboardMarkup([ 583 | [InlineKeyboardButton('• ʙᴀᴄᴋ', callback_data='start'), InlineKeyboardButton('home•', callback_data='setting')] 584 | ]), 585 | ) 586 | elif data in ["start", "home"]: 587 | inline_buttons = InlineKeyboardMarkup( 588 | [ 589 | [InlineKeyboardButton("• ᴀʙᴏᴜᴛ", callback_data="about"), 590 | InlineKeyboardButton("• ᴄʜᴀɴɴᴇʟs", callback_data="channels")], 591 | [InlineKeyboardButton("• Close •", callback_data="close")] 592 | ] 593 | ) 594 | try: 595 | await query.edit_message_media( 596 | InputMediaPhoto( 597 | START_PIC, 598 | START_MSG 599 | ), 600 | reply_markup=inline_buttons 601 | ) 602 | except Exception as e: 603 | print(f"Error sending start/home photo: {e}") 604 | await query.edit_message_text( 605 | START_MSG, 606 | reply_markup=inline_buttons, 607 | parse_mode=ParseMode.HTML 608 | ) 609 | 610 | 611 | elif data.startswith("rfs_ch_"): 612 | cid = int(data.split("_")[2]) 613 | try: 614 | chat = await client.get_chat(cid) 615 | mode = await db.get_channel_mode(cid) 616 | status = "🟢 ᴏɴ" if mode == "on" else "🔴 ᴏғғ" 617 | new_mode = "ᴏғғ" if mode == "on" else "on" 618 | buttons = [ 619 | [InlineKeyboardButton(f"ʀᴇǫ ᴍᴏᴅᴇ {'OFF' if mode == 'on' else 'ON'}", callback_data=f"rfs_toggle_{cid}_{new_mode}")], 620 | [InlineKeyboardButton("‹ ʙᴀᴄᴋ", callback_data="fsub_back")] 621 | ] 622 | await query.message.edit_text( 623 | f"Channel: {chat.title}\nCurrent Force-Sub Mode: {status}", 624 | reply_markup=InlineKeyboardMarkup(buttons) 625 | ) 626 | except Exception: 627 | await query.answer("Failed to fetch channel info", show_alert=True) 628 | 629 | elif data.startswith("rfs_toggle_"): 630 | cid, action = data.split("_")[2:] 631 | cid = int(cid) 632 | mode = "on" if action == "on" else "off" 633 | 634 | await db.set_channel_mode(cid, mode) 635 | await query.answer(f"Force-Sub set to {'ON' if mode == 'on' else 'OFF'}") 636 | 637 | # Refresh the same channel's mode view 638 | chat = await client.get_chat(cid) 639 | status = "🟢 ON" if mode == "on" else "🔴 OFF" 640 | new_mode = "off" if mode == "on" else "on" 641 | buttons = [ 642 | [InlineKeyboardButton(f"ʀᴇǫ ᴍᴏᴅᴇ {'OFF' if mode == 'on' else 'ON'}", callback_data=f"rfs_toggle_{cid}_{new_mode}")], 643 | [InlineKeyboardButton("‹ ʙᴀᴄᴋ", callback_data="fsub_back")] 644 | ] 645 | await query.message.edit_text( 646 | f"Channel: {chat.title}\nCurrent Force-Sub Mode: {status}", 647 | reply_markup=InlineKeyboardMarkup(buttons) 648 | ) 649 | 650 | elif data == "fsub_back": 651 | channels = await db.show_channels() 652 | buttons = [] 653 | for cid in channels: 654 | try: 655 | chat = await client.get_chat(cid) 656 | mode = await db.get_channel_mode(cid) 657 | status = "🟢" if mode == "on" else "🔴" 658 | buttons.append([InlineKeyboardButton(f"{status} {chat.title}", callback_data=f"rfs_ch_{cid}")]) 659 | except: 660 | continue 661 | 662 | await query.message.edit_text( 663 | "sᴇʟᴇᴄᴛ ᴀ ᴄʜᴀɴɴᴇʟ ᴛᴏ ᴛᴏɢɢʟᴇ ɪᴛs ғᴏʀᴄᴇ-sᴜʙ ᴍᴏᴅᴇ:", 664 | reply_markup=InlineKeyboardMarkup(buttons) 665 | ) 666 | 667 | def delete_after_delay(msg, delay): 668 | async def inner(): 669 | await asyncio.sleep(delay) 670 | try: 671 | await msg.delete() 672 | except: 673 | pass 674 | return inner() 675 | --------------------------------------------------------------------------------