diff --git a/misc/tdel.py b/misc/tdel.py new file mode 100644 index 0000000..2e203ad --- /dev/null +++ b/misc/tdel.py @@ -0,0 +1,78 @@ +import asyncio +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from utils.misc import modules_help, prefix + +@Client.on_message(filters.command("tdel", prefix) & filters.me) +async def tdel_message(_, message: Message): + if len(message.command) <= 2: + await message.edit( + "Error: You must specify time (seconds) and message text\n" + "Example: .tdel 10 Hello everyone!", + parse_mode=enums.ParseMode.HTML, + ) + await asyncio.sleep(5) + await message.delete() + return + + try: + delete_time = int(message.command[1]) + text = " ".join(message.command[2:]) + + if delete_time <= 0: + await message.edit( + "Error: Time must be a positive number!", + parse_mode=enums.ParseMode.HTML, + ) + await asyncio.sleep(3) + await message.delete() + return + + if delete_time > 86400: + await message.edit( + "Error: Maximum time is 24 hours (86400 seconds)!", + parse_mode=enums.ParseMode.HTML, + ) + await asyncio.sleep(5) + await message.delete() + return + + if not text.strip(): + await message.edit( + "Error: Message text cannot be empty!", + parse_mode=enums.ParseMode.HTML, + ) + await asyncio.sleep(3) + await message.delete() + return + + await message.edit( + text, + parse_mode=enums.ParseMode.HTML, + ) + + await asyncio.sleep(delete_time) + await message.delete() + + except ValueError: + await message.edit( + "Error: Time must be an integer!\n" + "Example: .tdel 30 This message will be deleted in 30 seconds", + parse_mode=enums.ParseMode.HTML, + ) + await asyncio.sleep(5) + await message.delete() + except Exception as e: + await message.edit( + f"Unexpected error: {str(e)}", + parse_mode=enums.ParseMode.HTML, + ) + await asyncio.sleep(5) + await message.delete() + +modules_help["timed_delete"] = { + "tdel [time] [text]*": "send self-deleting message\n" + "time: seconds until deletion\n" + "text: message content\n" + "example: .tdel 60 this message will delete in one minute" +} diff --git a/utils/anon.py b/utils/anon.py new file mode 100644 index 0000000..440c6b5 --- /dev/null +++ b/utils/anon.py @@ -0,0 +1,73 @@ +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from pyrogram.errors import FloodWait, ChannelInvalid, PeerIdInvalid +from utils.misc import modules_help, prefix + +@Client.on_message(filters.command("anon", prefix) & filters.me) +async def anon(client: Client, message: Message): + if len(message.command) < 2: + await message.edit( + "Usage: anon [channel_id] [message] or reply to a message to forward it.", + parse_mode=enums.ParseMode.HTML, + ) + return + + try: + source_chat_id = message.chat.id + anon_channel_id = int(message.command[1]) + text_content = " ".join(message.command[2:]) if len(message.command) > 2 else None + except (ValueError, IndexError): + await message.edit( + "Error: Invalid command format. Use anon [channel_id] [text]", + parse_mode=enums.ParseMode.HTML, + ) + return + + replied_to_message = message.reply_to_message + + if not replied_to_message and not text_content: + await message.edit( + "Error: You must provide text or reply to a message/media.", + parse_mode=enums.ParseMode.HTML, + ) + return + + original_message_id = message.id + + try: + await message.delete() + + if replied_to_message: + sent_message = await replied_to_message.copy(anon_channel_id) + else: + sent_message = await client.send_message( + chat_id=anon_channel_id, + text=text_content, + parse_mode=enums.ParseMode.MARKDOWN, + ) + + await sent_message.forward(source_chat_id) + + except (ChannelInvalid, PeerIdInvalid): + await client.send_message( + chat_id=source_chat_id, + text=f"Error: The ID {anon_channel_id} is invalid or I am not an admin in that channel.", + parse_mode=enums.ParseMode.HTML, + ) + except FloodWait as e: + await client.send_message( + chat_id=source_chat_id, + text=f"FloodWait: Please wait for {e.value} seconds.", + parse_mode=enums.ParseMode.HTML, + ) + except Exception as e: + await client.send_message( + chat_id=source_chat_id, + text=f"An unexpected error occurred:\n{e}", + parse_mode=enums.ParseMode.HTML, + ) + +modules_help["anonymizer"] = { + "anon [id] [message]": "Sends a text message to the specified channel/group ID and then forwards it to the current chat, hiding the original sender. The original command is deleted.", + "anon [id] (as reply)": "Copies the replied-to message/media to the specified channel/group ID and then forwards it to the current chat, hiding the original sender. The original command is deleted.", +} diff --git a/utils/fidsend.py b/utils/fidsend.py new file mode 100644 index 0000000..81e7861 --- /dev/null +++ b/utils/fidsend.py @@ -0,0 +1,46 @@ +import asyncio +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from utils.misc import modules_help, prefix + +@Client.on_message(filters.command("fidsend", prefix) & filters.me) +async def fidsend(_, message: Message): + if len(message.command) <= 1: + return + + file_id = " ".join(message.command[1:]) + + try: + try: + await message.reply_photo(file_id) + except: + try: + await message.reply_video(file_id) + except: + try: + await message.reply_sticker(file_id) + except: + try: + await message.reply_voice(file_id) + except: + try: + await message.reply_video_note(file_id) + except: + try: + await message.reply_audio(file_id) + except: + try: + await message.reply_animation(file_id) + except: + await message.reply_document(file_id) + + await message.delete() + except Exception as e: + await message.edit( + f"Error: {e}", + parse_mode=enums.ParseMode.HTML, + ) + +modules_help["file_sender"] = { + "fidsend [file_id]*": "send any media file using its file ID\nSupports: photo, video, sticker, voice, video note, audio, animation, document" +} \ No newline at end of file diff --git a/utils/numsend.py b/utils/numsend.py new file mode 100644 index 0000000..ad85e14 --- /dev/null +++ b/utils/numsend.py @@ -0,0 +1,42 @@ +import asyncio +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from utils.misc import modules_help, prefix + +@Client.on_message(filters.command("numsend", prefix) & filters.me) +async def numsend(client: Client, message: Message): + if len(message.command) <= 2: + return + + try: + user_id = int(message.command[1]) + text = " ".join(message.command[2:]) + + if message.reply_to_message: + if message.reply_to_message.media: + await client.copy_message( + chat_id=user_id, + from_chat_id=message.chat.id, + message_id=message.reply_to_message.id, + caption=text if text else None + ) + else: + await client.send_message( + chat_id=user_id, + text=text if text else message.reply_to_message.text + ) + else: + await client.send_message(chat_id=user_id, text=text) + + await message.delete() + + except ValueError: + await message.edit("Invalid user ID format", parse_mode=enums.ParseMode.HTML) + except Exception as e: + await message.edit(f"Error: {e}", parse_mode=enums.ParseMode.HTML) + +modules_help["numsend"] = { + "numsend [user_id] [message]*": "send message to user by numeric ID\n" + "Reply to media to forward it with optional caption\n" + "Original message will be deleted after sending" +} diff --git a/utils/online.py b/utils/online.py new file mode 100644 index 0000000..07d7f96 --- /dev/null +++ b/utils/online.py @@ -0,0 +1,174 @@ +import asyncio +from pyrogram import Client, filters, enums +from pyrogram.types import Message +from datetime import datetime, timedelta +from utils.misc import modules_help, prefix +import re + +online_task = None + +def parse_time_duration(time_str): + relative_pattern = r'^(\d+)(s|min|h|m)$' + match = re.match(relative_pattern, time_str.lower()) + + if match: + value = int(match.group(1)) + unit = match.group(2) + + if unit == 's': + return value + elif unit == 'min': + return value * 60 + elif unit == 'h': + return value * 3600 + elif unit == 'm': # month + return value * 30 * 24 * 3600 + + absolute_pattern = r'^(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2})$' + match = re.match(absolute_pattern, time_str) + + if match: + year = int(match.group(1)) + month = int(match.group(2)) + day = int(match.group(3)) + hour = int(match.group(4)) + minute = int(match.group(5)) + + if hour >= 24: + hour = 23 + if minute >= 60: + minute = 59 + + try: + target_time = datetime(year, month, day, hour, minute) + current_time = datetime.now() + + if target_time <= current_time: + return None # Time has already passed + + delta = target_time - current_time + return int(delta.total_seconds()) + except ValueError: + return None + + return None + +async def keep_online(duration): + try: + await asyncio.sleep(duration) + except asyncio.CancelledError: + pass + +def format_time_remaining(seconds): + if seconds < 60: + return f"{seconds} seconds" + elif seconds < 3600: + minutes = seconds // 60 + return f"{minutes} minutes" + elif seconds < 86400: + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + return f"{hours} hours and {minutes} minutes" + else: + days = seconds // 86400 + hours = (seconds % 86400) // 3600 + return f"{days} days and {hours} hours" + +@Client.on_message(filters.command("onl", prefix) & filters.me) +async def online_keeper(client: Client, message: Message): + global online_task + + if len(message.command) <= 1: + if online_task and not online_task.done(): + await message.edit( + "đŸŸĸ Account is currently being kept online.\n" + "Use the /onl stop command to stop.", + parse_mode=enums.ParseMode.HTML + ) + else: + await message.edit( + "🔴 Account is not in auto-online mode.\n" + "Example usage:\n" + "/onl 30min - for 30 minutes\n" + "/onl 2h - for 2 hours\n" + "/onl 2025-12-31 23:59 - until a specific date", + parse_mode=enums.ParseMode.HTML + ) + return + + time_arg = " ".join(message.command[1:]) + + if time_arg.lower() == "stop": + if online_task and not online_task.done(): + online_task.cancel() + await message.edit( + "âšī¸ Online keeping has been stopped.", + parse_mode=enums.ParseMode.HTML + ) + else: + await message.edit( + "❌ No online keeping process is active.", + parse_mode=enums.ParseMode.HTML + ) + return + + duration = parse_time_duration(time_arg) + + if duration is None: + await message.edit( + "❌ Invalid time format!\n\n" + "Valid formats:\n" + "â€ĸ 10s - 10 seconds\n" + "â€ĸ 30min - 30 minutes\n" + "â€ĸ 2h - 2 hours\n" + "â€ĸ 1m - 1 month\n" + "â€ĸ 2025-12-31 23:59 - until a specific date\n" + "â€ĸ stop - to stop the process", + parse_mode=enums.ParseMode.HTML + ) + return + + max_duration = 30 * 24 * 3600 # 30 days + if duration > max_duration: + await message.edit( + "âš ī¸ The maximum allowed duration is 30 days.", + parse_mode=enums.ParseMode.HTML + ) + return + + if online_task and not online_task.done(): + online_task.cancel() + + online_task = asyncio.create_task(keep_online(duration)) + + time_formatted = format_time_remaining(duration) + + await message.edit( + f"đŸŸĸ Account will be kept online.\n" + f"âąī¸ Duration: {time_formatted}\n" + f"🔧 Argument: {time_arg}\n\n" + f"To stop: {prefix}onl stop", + parse_mode=enums.ParseMode.HTML + ) + + try: + await online_task + await client.send_message( + message.chat.id, + "⏰ The online keeping period has ended.", + parse_mode=enums.ParseMode.HTML + ) + except asyncio.CancelledError: + pass + +modules_help["online_keeper"] = { + "onl": "Display online status", + "onl [time]*": "Keep account online for a specified duration.\n" + "Time formats:\n" + "â€ĸ 10s - 10 seconds\n" + "â€ĸ 30min - 30 minutes\n" + "â€ĸ 2h - 2 hours\n" + "â€ĸ 1m - 1 month\n" + "â€ĸ 2025-12-31 23:59 - until a specific date", + "onl stop": "Stop keeping the account online" +} diff --git a/utils/raw.py b/utils/raw.py new file mode 100644 index 0000000..952ba37 --- /dev/null +++ b/utils/raw.py @@ -0,0 +1,147 @@ +from pyrogram import Client, filters, enums +from pyrogram.types import Message +import json +from utils.misc import modules_help, prefix + +def send_chunked_messages(client, data, chat_id="me"): + chunk_size = 4080 + + if len(data) <= chunk_size: + return [f"```json\n{data}\n```"] + + chunks = [] + for i in range(0, len(data), chunk_size): + chunk = data[i:i+chunk_size] + chunks.append(f"```json\n{chunk}\n```") + + return chunks + +@Client.on_message(filters.command("raw", prefix) & filters.me) +async def get_raw_message(client: Client, message: Message): + if not message.reply_to_message: + await message.delete() + return + + raw_data = str(message.reply_to_message) + json_data = json.dumps(json.loads(raw_data), indent=2, ensure_ascii=False) + + chunks = send_chunked_messages(client, json_data) + + for chunk in chunks: + await client.send_message("me", chunk, parse_mode=enums.ParseMode.MARKDOWN) + + await message.delete() + +@Client.on_message(filters.command("uraw", prefix) & filters.me) +async def get_user_raw_data(client: Client, message: Message): + target_user = None + + if message.reply_to_message: + target_user = message.reply_to_message.from_user + + elif message.entities: + for entity in message.entities: + if entity.type == enums.MessageEntityType.MENTION: + username = message.text[entity.offset:entity.offset + entity.length].replace('@', '') + try: + target_user = await client.get_users(username) + except: + continue + elif entity.type == enums.MessageEntityType.TEXT_MENTION: + target_user = entity.user + + if not target_user: + await message.edit("No user found to extract data. Reply to a message or mention a user.") + return + + try: + full_user = await client.get_users(target_user.id) + raw_data = str(full_user) + json_data = json.dumps(json.loads(raw_data), indent=2, ensure_ascii=False) + + chunks = send_chunked_messages(client, json_data) + + for chunk in chunks: + await client.send_message("me", chunk, parse_mode=enums.ParseMode.MARKDOWN) + + await message.delete() + + except Exception as e: + await message.edit(f"Error getting user data: {str(e)}") + +@Client.on_message(filters.command("picraw", prefix) & filters.me) +async def get_profile_pics_raw(client: Client, message: Message): + target_user = None + + if message.reply_to_message: + target_user = message.reply_to_message.from_user + + elif message.entities: + for entity in message.entities: + if entity.type == enums.MessageEntityType.MENTION: + username = message.text[entity.offset:entity.offset + entity.length].replace('@', '') + try: + target_user = await client.get_users(username) + except: + continue + elif entity.type == enums.MessageEntityType.TEXT_MENTION: + target_user = entity.user + + if not target_user: + await message.edit("No user found to extract photos. Reply to a message or mention a user.") + return + + try: + photos = [] + async for photo in client.get_chat_photos(target_user.id): + photos.append(photo) + + if not photos: + await message.edit("This user has no profile photos.") + return + + photo_data = [] + for i, photo in enumerate(photos): + photo_info = { + "index": i + 1, + "file_id": photo.file_id, + "file_unique_id": photo.file_unique_id, + "date": photo.date.isoformat() if photo.date else None, + "sizes": [] + } + + for size in photo.thumbs: + size_info = { + "type": size.type if hasattr(size, 'type') else "unknown", + "width": size.width if hasattr(size, 'width') else None, + "height": size.height if hasattr(size, 'height') else None, + "file_size": size.file_size if hasattr(size, 'file_size') else None + } + photo_info["sizes"].append(size_info) + + photo_data.append(photo_info) + + json_data = json.dumps({ + "user_id": target_user.id, + "username": target_user.username, + "first_name": target_user.first_name, + "last_name": target_user.last_name, + "total_photos": len(photos), + "photos": photo_data + }, indent=2, ensure_ascii=False) + + chunks = send_chunked_messages(client, json_data) + + for chunk in chunks: + await client.send_message("me", chunk, parse_mode=enums.ParseMode.MARKDOWN) + + await message.delete() + + except Exception as e: + await message.edit(f"Error getting profile photos: {str(e)}") + +modules_help["raw_json"] = { + "raw [reply]": "Get raw JSON data of replied message and send to saved messages", + "uraw [reply/mention]": "Get raw user data from replied or mentioned user", + "picraw [reply/mention]": "Get file_id of all profile pictures from user", +} diff --git a/utils/tagall.py b/utils/tagall.py index 518ef96..ad92a66 100644 --- a/utils/tagall.py +++ b/utils/tagall.py @@ -1,32 +1,192 @@ import asyncio +from typing import List, Optional from pyrogram import Client, filters, enums -from pyrogram.types import Message +from pyrogram.types import Message, ChatMember +from pyrogram.errors import FloodWait, ChatAdminRequired, UserNotParticipant from utils.misc import modules_help, prefix +class TaggerBot: + def __init__(self, batch_size: int = 5, delay: float = 2.0): + self.batch_size = batch_size + self.delay = delay + + async def get_members_by_role( + self, + client: Client, + chat_id: int, + admin_only: bool = False, + max_members: Optional[int] = None + ) -> List[ChatMember]: + members = [] + count = 0 + + try: + async for member in client.get_chat_members(chat_id): + if max_members and count >= max_members: + break + + if admin_only: + if member.status in [ + enums.ChatMemberStatus.OWNER, + enums.ChatMemberStatus.ADMINISTRATOR + ]: + members.append(member) + count += 1 + else: + if not member.user.is_bot and not member.user.is_deleted: + members.append(member) + count += 1 + + except UserNotParticipant: + print("Bot is not a member of this chat!") + except Exception as e: + print(f"Error fetching members: {e}") + + return members + + async def send_batch_tags( + self, + client: Client, + chat_id: int, + members: List[ChatMember] + ) -> bool: + if not members: + return False + + for i in range(0, len(members), self.batch_size): + batch = members[i:i + self.batch_size] + tags = [] + + for member in batch: + if member.user.username: + tags.append(f"@{member.user.username}") + else: + tags.append(member.user.mention) + + if tags: + message_text = "\n".join(tags) + try: + await client.send_message( + chat_id, + text=message_text, + parse_mode=enums.ParseMode.HTML + ) + + if i + self.batch_size < len(members): + await asyncio.sleep(self.delay) + + except FloodWait as e: + await asyncio.sleep(e.value + 1) + continue + except Exception as e: + print(f"Error sending batch {i//self.batch_size + 1}: {e}") + continue + + return True + + +tagger = TaggerBot(batch_size=5, delay=2.0) + + @Client.on_message(filters.command("tagall", prefix) & filters.me) async def tagall(client: Client, message: Message): await message.delete() - chat_id = message.chat.id - string = "" - limit = 1 - icm = client.get_chat_members(chat_id) - async for member in icm: - tag = member.user.username - if limit <= 5: - string += f"@{tag}\n" if tag != None else f"{member.user.mention}\n" - limit += 1 + + if message.chat.type == enums.ChatType.PRIVATE: + await client.send_message( + message.chat.id, + "❌ This command only works in groups and supergroups!" + ) + return + + status_msg = await client.send_message( + message.chat.id, + "🔄 Fetching member list..." + ) + + try: + members = await tagger.get_members_by_role( + client, + message.chat.id, + admin_only=False, + max_members=200 + ) + + if not members: + await status_msg.edit("❌ No active members found!") + return + + await status_msg.edit(f"🏷 Starting to tag {len(members)} members...") + + success = await tagger.send_batch_tags(client, message.chat.id, members) + + if success: + await status_msg.edit(f"✅ Successfully tagged {len(members)} members!") else: - await client.send_message( - chat_id, text=string, parse_mode=enums.ParseMode.HTML - ) - limit = 1 - string = "" - await asyncio.sleep(2) + await status_msg.edit("❌ Error tagging members!") + + except ChatAdminRequired: + await status_msg.edit("❌ I need to be a group admin to execute this command!") + except Exception as e: + await status_msg.edit(f"❌ Command execution error: {str(e)}") + + +@Client.on_message(filters.command("tagadmins", prefix) & filters.me) +async def tagadmins(client: Client, message: Message): + await message.delete() + + if message.chat.type == enums.ChatType.PRIVATE: + await client.send_message( + message.chat.id, + "❌ This command only works in groups and supergroups!" + ) + return + + status_msg = await client.send_message( + message.chat.id, + "🔄 Fetching admin list..." + ) + + try: + admins = await tagger.get_members_by_role( + client, + message.chat.id, + admin_only=True + ) + + if not admins: + await status_msg.edit("❌ No admins found!") + return + + await status_msg.edit(f"🏷 Starting to tag {len(admins)} admins...") + + success = await tagger.send_batch_tags(client, message.chat.id, admins) + + if success: + await status_msg.edit(f"✅ Successfully tagged {len(admins)} admins!") + else: + await status_msg.edit("❌ Error tagging admins!") + + except ChatAdminRequired: + await status_msg.edit("❌ I need to be a group admin to execute this command!") + except Exception as e: + await status_msg.edit(f"❌ Command execution error: {str(e)}") + + +@Client.on_message(filters.command("tagstop", prefix) & filters.me) +async def tagstop(client: Client, message: Message): + await message.delete() + await client.send_message( + message.chat.id, + "âš ī¸ To stop the operation, restart the bot or wait for it to finish" + ) modules_help["tagall"] = { - "tagall": "Tag all members", + "tagall": "Tag all active group members (max 200)", + "tagadmins": "Tag only admins and group owner", + "tagstop": "Instructions to stop tagging operation" }