├── .gitignore ├── Procfile ├── bot ├── plugins │ ├── __init__.py │ ├── on_media.py │ ├── admin.py │ ├── ping.py │ ├── thumbnail.py │ ├── rename.py │ ├── video_info.py │ └── callbacks.py ├── __main__.py ├── __init__.py ├── core │ ├── new │ │ ├── __init__.py │ │ ├── send_flooded_message.py │ │ ├── upload_document.py │ │ ├── upload_video.py │ │ ├── normal_rename.py │ │ └── custom_uploader.py │ ├── utils │ │ ├── audio_info.py │ │ ├── rm.py │ │ ├── thumbnail_info.py │ │ ├── executor.py │ │ └── video_info.py │ ├── fixes.py │ ├── db │ │ ├── add.py │ │ └── database.py │ ├── handlers │ │ ├── time_gap.py │ │ ├── settings.py │ │ ├── not_big.py │ │ ├── broadcast.py │ │ └── big_rename.py │ ├── ffmpeg.py │ ├── file_info.py │ └── display.py └── client.py ├── .replit ├── requirements.txt ├── configs.py ├── app.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 -m bot -------------------------------------------------------------------------------- /bot/plugins/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | language = "bash" 2 | run = "pip3 install -r requirements.txt; python3 -m bot" -------------------------------------------------------------------------------- /bot/__main__.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import bot 4 | 5 | 6 | if __name__ == "__main__": 7 | bot.bot.run() 8 | -------------------------------------------------------------------------------- /bot/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from dotenv import load_dotenv 4 | import bot.client 5 | 6 | load_dotenv() 7 | bot = bot.client.Client() 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Pyrogram==1.4.16 2 | TgCrypto 3 | pyromod 4 | python-dotenv 5 | dnspython 6 | motor 7 | Pillow 8 | hachoir 9 | mutagen 10 | psutil 11 | aiofiles 12 | -------------------------------------------------------------------------------- /bot/core/new/__init__.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from .normal_rename import NormalRename 4 | from .custom_uploader import CustomUploader 5 | from .upload_video import UploadVideo 6 | from .upload_document import UploadDocument 7 | from .send_flooded_message import SendFloodedMessage 8 | 9 | 10 | class New( 11 | NormalRename, 12 | CustomUploader, 13 | SendFloodedMessage, 14 | UploadVideo, 15 | UploadDocument 16 | ): 17 | """ New Methods for Pyrogram """ 18 | -------------------------------------------------------------------------------- /bot/core/utils/audio_info.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from mutagen import mp3, wave, aac 4 | 5 | 6 | async def get_audio_info(audio_path: str): 7 | file_ext = audio_path.rsplit(".", 1)[-1].upper() 8 | if file_ext not in ["MP3", "WAVE", "AAC"]: 9 | return 0 10 | if file_ext == "MP3": 11 | audio = mp3.MP3(audio_path) 12 | return audio.info.length 13 | if file_ext == "WAVE": 14 | audio = wave.WAVE(audio_path) 15 | return audio.info.length 16 | if file_ext == "AAC": 17 | audio = aac.AAC(audio_path) 18 | return audio.info.length 19 | -------------------------------------------------------------------------------- /bot/core/fixes.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from PIL import Image 4 | from hachoir.parser import createParser 5 | from hachoir.metadata import extractMetadata 6 | 7 | 8 | async def fix_thumbnail(thumb_path: str, height: int = 0): 9 | if not height: 10 | metadata = extractMetadata(createParser(thumb_path)) 11 | if metadata.has("height"): 12 | height = metadata.get("height") 13 | else: 14 | height = 0 15 | Image.open(thumb_path).convert("RGB").save(thumb_path) 16 | img = Image.open(thumb_path) 17 | img.resize((320, height)) 18 | img.save(thumb_path, "JPEG") 19 | return thumb_path 20 | -------------------------------------------------------------------------------- /bot/core/db/add.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from configs import Config 4 | from .database import db 5 | from pyrogram import Client 6 | from pyrogram.types import Message 7 | 8 | 9 | async def add_user_to_database(bot: Client, cmd: Message): 10 | if not await db.is_user_exist(cmd.from_user.id): 11 | await db.add_user(cmd.from_user.id) 12 | if Config.LOG_CHANNEL is not None: 13 | await bot.send_flooded_message( 14 | int(Config.LOG_CHANNEL), 15 | f"#NEW_USER: \n\nNew User [{cmd.from_user.first_name}](tg://user?id={cmd.from_user.id}) started @{(await bot.get_me()).username} !!" 16 | ) 17 | -------------------------------------------------------------------------------- /bot/core/utils/rm.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import shutil 4 | import aiofiles.os 5 | from configs import Config 6 | 7 | 8 | async def rm_dir(root: str = f"{Config.DOWNLOAD_DIR}"): 9 | """ 10 | Delete a Folder. 11 | 12 | :param root: Pass DIR Path 13 | """ 14 | 15 | try: 16 | shutil.rmtree(root) 17 | except Exception as e: 18 | Config.LOGGER.getLogger(__name__).error(e) 19 | 20 | 21 | async def rm_file(file_path: str): 22 | """ 23 | Delete a File. 24 | 25 | :param file_path: Pass File Path 26 | """ 27 | try: 28 | await aiofiles.os.remove(file_path) 29 | except: 30 | pass 31 | -------------------------------------------------------------------------------- /bot/core/handlers/time_gap.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | GAP = {} 4 | 5 | 6 | async def check_time_gap(user_id: int): 7 | """A Function for checking user time gap! 8 | :parameter user_id Telegram User ID""" 9 | 10 | if str(user_id) in GAP: 11 | current_time = time.time() 12 | previous_time = GAP[str(user_id)] 13 | if round(current_time - previous_time) < 120: 14 | return True, round(previous_time - current_time + 120) 15 | elif round(current_time - previous_time) >= 120: 16 | del GAP[str(user_id)] 17 | return False, None 18 | elif str(user_id) not in GAP: 19 | GAP[str(user_id)] = time.time() 20 | return False, None 21 | -------------------------------------------------------------------------------- /bot/core/utils/thumbnail_info.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from hachoir.parser import createParser 4 | from hachoir.metadata import extractMetadata 5 | 6 | 7 | async def get_thumbnail_info(thumb_path: str): 8 | try: 9 | metadata = extractMetadata(createParser(thumb_path)) 10 | except: return 0, 0 11 | try: 12 | if metadata.has("height"): 13 | height = metadata.get("height") 14 | else: 15 | height = 0 16 | except: 17 | height = 0 18 | try: 19 | if metadata.has("width"): 20 | width = metadata.get("width") 21 | else: 22 | width = 0 23 | except: 24 | width = 0 25 | 26 | return height, width 27 | -------------------------------------------------------------------------------- /bot/core/utils/executor.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import shlex 4 | import asyncio 5 | from typing import Tuple 6 | 7 | 8 | async def execute(cmnd: str) -> Tuple[str, str, int, int]: 9 | """ 10 | Execute a Command as Async. 11 | 12 | :param cmnd: Pass Command as String. 13 | """ 14 | 15 | cmnds = shlex.split(cmnd) 16 | process = await asyncio.create_subprocess_exec( 17 | *cmnds, 18 | stdout=asyncio.subprocess.PIPE, 19 | stderr=asyncio.subprocess.PIPE 20 | ) 21 | stdout, stderr = await process.communicate() 22 | return (stdout.decode('utf-8', 'replace').strip(), 23 | stderr.decode('utf-8', 'replace').strip(), 24 | process.returncode, 25 | process.pid) 26 | -------------------------------------------------------------------------------- /configs.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import os 4 | import logging 5 | 6 | logging.basicConfig( 7 | format='%(name)s - %(levelname)s - %(message)s', 8 | handlers=[logging.FileHandler('log.txt'), 9 | logging.StreamHandler()], 10 | level=logging.INFO 11 | ) 12 | 13 | 14 | class Config(object): 15 | API_ID = int(os.environ.get("API_ID", "")) 16 | API_HASH = os.environ.get("API_HASH", "") 17 | BOT_TOKEN = os.environ.get("BOT_TOKEN", "") 18 | DOWNLOAD_DIR = os.environ.get("DOWNLOAD_DIR", "./downloads") 19 | LOGGER = logging 20 | OWNER_ID = int(os.environ.get("OWNER_ID", 1445283714)) 21 | PRO_USERS = list(set(int(x) for x in os.environ.get("PRO_USERS", "0").split())) 22 | PRO_USERS.append(OWNER_ID) 23 | MONGODB_URI = os.environ.get("MONGODB_URI", "") 24 | LOG_CHANNEL = int(os.environ.get("LOG_CHANNEL", "-100")) 25 | BROADCAST_AS_COPY = bool(os.environ.get("BROADCAST_AS_COPY", "False")) 26 | -------------------------------------------------------------------------------- /bot/client.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from typing import Union 4 | from pyromod import listen 5 | from pyrogram import Client as RawClient 6 | from pyrogram.storage import Storage 7 | from configs import Config 8 | from bot.core.new import New 9 | 10 | LOGGER = Config.LOGGER 11 | log = LOGGER.getLogger(__name__) 12 | 13 | 14 | class Client(RawClient, New): 15 | """ Custom Bot Class """ 16 | 17 | def __init__(self, session_name: Union[str, Storage] = "RenameBot"): 18 | super().__init__( 19 | session_name, 20 | api_id=Config.API_ID, 21 | api_hash=Config.API_HASH, 22 | bot_token=Config.BOT_TOKEN, 23 | plugins=dict( 24 | root="bot/plugins" 25 | ) 26 | ) 27 | 28 | async def start(self): 29 | await super().start() 30 | log.info("Bot Started!") 31 | 32 | async def stop(self, *args): 33 | await super().stop() 34 | log.info("Bot Stopped!") 35 | -------------------------------------------------------------------------------- /bot/plugins/on_media.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import asyncio 4 | from bot.client import Client 5 | from bot.core.db.add import ( 6 | add_user_to_database 7 | ) 8 | from pyrogram import ( 9 | filters, 10 | types 11 | ) 12 | 13 | 14 | @Client.on_message((filters.video | filters.audio | filters.document) & ~filters.channel & ~filters.edited) 15 | async def on_media_handler(c: Client, m: "types.Message"): 16 | if not m.from_user: 17 | return await m.reply_text("I don't know about you sar :(") 18 | await add_user_to_database(c, m) 19 | await asyncio.sleep(3) 20 | await c.send_flooded_message( 21 | chat_id=m.chat.id, 22 | text="**Should I show File Information?**", 23 | reply_markup=types.InlineKeyboardMarkup( 24 | [[types.InlineKeyboardButton("Yes", callback_data="showFileInfo"), 25 | types.InlineKeyboardButton("No", callback_data="closeMessage")]] 26 | ), 27 | disable_web_page_preview=True, 28 | reply_to_message_id=m.message_id 29 | ) 30 | -------------------------------------------------------------------------------- /bot/plugins/admin.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import shutil 4 | import psutil 5 | from pyrogram import filters 6 | from pyrogram.types import ( 7 | Message 8 | ) 9 | from configs import Config 10 | from bot.client import Client 11 | from bot.core.db.database import db 12 | from bot.core.display import humanbytes 13 | from bot.core.handlers.broadcast import broadcast_handler 14 | 15 | 16 | @Client.on_message(filters.command("status") & filters.user(Config.OWNER_ID) & ~filters.edited) 17 | async def status_handler(_, m: Message): 18 | total, used, free = shutil.disk_usage(".") 19 | total = humanbytes(total) 20 | used = humanbytes(used) 21 | free = humanbytes(free) 22 | cpu_usage = psutil.cpu_percent() 23 | ram_usage = psutil.virtual_memory().percent 24 | disk_usage = psutil.disk_usage('/').percent 25 | total_users = await db.total_users_count() 26 | await m.reply_text( 27 | text=f"**Total Disk Space:** {total} \n" 28 | f"**Used Space:** {used}({disk_usage}%) \n" 29 | f"**Free Space:** {free} \n" 30 | f"**CPU Usage:** {cpu_usage}% \n" 31 | f"**RAM Usage:** {ram_usage}%\n\n" 32 | f"**Total Users in DB:** `{total_users}`", 33 | parse_mode="Markdown", 34 | quote=True 35 | ) 36 | 37 | 38 | @Client.on_message(filters.command("broadcast") & filters.user(Config.OWNER_ID) & filters.reply & ~filters.edited) 39 | async def broadcast_in(_, m: Message): 40 | await broadcast_handler(m) 41 | -------------------------------------------------------------------------------- /bot/core/ffmpeg.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import os 4 | import time 5 | import asyncio 6 | from typing import Optional 7 | 8 | 9 | async def take_screen_shot(video_file, output_directory, ttl) -> Optional[str]: 10 | """ 11 | Take Screenshot from Video. 12 | 13 | Source: https://stackoverflow.com/a/13891070/4723940 14 | 15 | :param video_file: Pass Video File Path. 16 | :param output_directory: Pass output folder path for screenshot. If folders not exists, this will create folders. 17 | :param ttl: Time! 18 | 19 | :return: This will return screenshot image path. 20 | """ 21 | 22 | output_dir = f'{output_directory}/{time.time()}/' 23 | if not os.path.exists(output_dir): 24 | os.makedirs(output_dir) 25 | output_filepath = output_dir + "thumbnail.jpg" 26 | file_genertor_command = [ 27 | "ffmpeg", 28 | "-ss", 29 | str(ttl), 30 | "-i", 31 | video_file, 32 | "-vframes", 33 | "1", 34 | output_filepath 35 | ] 36 | # width = "90" 37 | process = await asyncio.create_subprocess_exec( 38 | *file_genertor_command, 39 | # stdout must a pipe to be accessible as process.stdout 40 | stdout=asyncio.subprocess.PIPE, 41 | stderr=asyncio.subprocess.PIPE, 42 | ) 43 | # Wait for the subprocess to finish 44 | stdout, stderr = await process.communicate() 45 | e_response = stderr.decode().strip() 46 | t_response = stdout.decode().strip() 47 | return output_filepath if os.path.lexists(output_filepath) else None 48 | -------------------------------------------------------------------------------- /bot/plugins/ping.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from bot.client import Client 4 | from pyrogram import filters 5 | from pyrogram import types 6 | from bot.core.db.add import add_user_to_database 7 | 8 | 9 | @Client.on_message(filters.command(["start", "ping"]) & filters.private & ~filters.edited) 10 | async def ping_handler(c: Client, m: "types.Message"): 11 | if not m.from_user: 12 | return await m.reply_text("I don't know about you sar :(") 13 | await add_user_to_database(c, m) 14 | await c.send_flooded_message( 15 | chat_id=m.chat.id, 16 | text="Hi, I am Rename Bot!\n\n" 17 | "I can rename media without downloading it!\n" 18 | "Speed depends on your media DC.\n\n" 19 | "Just send me media and reply to it with /rename command.", 20 | reply_markup=types.InlineKeyboardMarkup([[ 21 | types.InlineKeyboardButton("Show Settings", 22 | callback_data="showSettings") 23 | ]]) 24 | ) 25 | 26 | 27 | @Client.on_message(filters.command("help") & filters.private & ~filters.edited) 28 | async def help_handler(c: Client, m: "types.Message"): 29 | if not m.from_user: 30 | return await m.reply_text("I don't know about you sar :(") 31 | await add_user_to_database(c, m) 32 | await c.send_flooded_message( 33 | chat_id=m.chat.id, 34 | text="I can rename media without downloading it!\n" 35 | "Speed depends on your media DC.\n\n" 36 | "Just send me media and reply to it with /rename command.\n\n" 37 | "To set custom thumbnail reply to any image with /set_thumbnail\n\n" 38 | "To see custom thumbnail press /show_thumbnail", 39 | reply_markup=types.InlineKeyboardMarkup([[ 40 | types.InlineKeyboardButton("Show Settings", 41 | callback_data="showSettings")]]) 42 | ) 43 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Telegram Files Rename Bot", 3 | "description": "A Very Simple Telegram Files Rename Bot by @AbirHasan2005", 4 | "keywords": [ 5 | "telegram", 6 | "files", 7 | "rename", 8 | "bot" 9 | ], 10 | "repository": "https://github.com/AbirHasan2005/Rename-Bot", 11 | "website": "https://abirhasan.wtf", 12 | "success_url": "https://t.me/AH_RenameBot", 13 | "env": { 14 | "API_ID": { 15 | "description": "Get this value from my.telegram.org or @TeleORG_Bot" 16 | }, 17 | "API_HASH": { 18 | "description": "Get this value from my.telegram.org or @TeleORG_Bot" 19 | }, 20 | "BOT_TOKEN": { 21 | "description": "Get this from @BotFather XD" 22 | }, 23 | "MONGODB_URI": { 24 | "description": "MongoDB Database URI for Saving UserID for Broadcast. Tutorial: https://www.youtube.com/watch?v=aXlF80Cn7iU" 25 | }, 26 | "LOG_CHANNEL": { 27 | "description": "Logs Channel ID for some Tracking XD. Example: -100123456789" 28 | }, 29 | "DOWNLOAD_DIR": { 30 | "description": "Files download path. You can keep default. Should not end with '/'", 31 | "required": false, 32 | "value": "./downloads" 33 | }, 34 | "BROADCAST_AS_COPY": { 35 | "description": "Broadcast as Copy or with Forward Tag. Value should be True/False.", 36 | "required": false, 37 | "value": "False" 38 | }, 39 | "OWNER_ID": { 40 | "description": "Bot Owner UserID" 41 | } 42 | }, 43 | "buildpacks": [ 44 | { 45 | "url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest" 46 | }, 47 | { 48 | "url": "heroku/python" 49 | } 50 | ], 51 | "formation": { 52 | "worker": { 53 | "quantity": 1, 54 | "size": "free" 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /bot/plugins/thumbnail.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from bot.client import Client 4 | from pyrogram import filters 5 | from pyrogram import types 6 | from bot.core.db.database import db 7 | from bot.core.db.add import add_user_to_database 8 | 9 | 10 | @Client.on_message(filters.command("show_thumbnail") & filters.private & ~filters.edited) 11 | async def show_thumbnail(c: Client, m: "types.Message"): 12 | if not m.from_user: 13 | return await m.reply_text("I don't know about you sar :(") 14 | await add_user_to_database(c, m) 15 | thumbnail = await db.get_thumbnail(m.from_user.id) 16 | if not thumbnail: 17 | return await m.reply_text("You didn't set custom thumbnail!") 18 | await c.send_photo(m.chat.id, thumbnail, caption="Custom Thumbnail", 19 | reply_markup=types.InlineKeyboardMarkup( 20 | [[types.InlineKeyboardButton("Delete Thumbnail", 21 | callback_data="deleteThumbnail")]] 22 | )) 23 | 24 | 25 | @Client.on_message(filters.command("set_thumbnail") & filters.private & ~filters.edited) 26 | async def set_thumbnail(c: Client, m: "types.Message"): 27 | if (not m.reply_to_message) or (not m.reply_to_message.photo): 28 | return await m.reply_text("Reply to any image to save in as custom thumbnail!") 29 | if not m.from_user: 30 | return await m.reply_text("I don't know about you sar :(") 31 | await add_user_to_database(c, m) 32 | await db.set_thumbnail(m.from_user.id, m.reply_to_message.photo.file_id) 33 | await m.reply_text("Okay,\n" 34 | "I will use this image as custom thumbnail.", 35 | reply_markup=types.InlineKeyboardMarkup( 36 | [[types.InlineKeyboardButton("Delete Thumbnail", 37 | callback_data="deleteThumbnail")]] 38 | )) 39 | 40 | 41 | @Client.on_message(filters.command("delete_thumbnail") & filters.private & ~filters.edited) 42 | async def delete_thumbnail(c: Client, m: "types.Message"): 43 | if not m.from_user: 44 | return await m.reply_text("I don't know about you sar :(") 45 | await add_user_to_database(c, m) 46 | await db.set_thumbnail(m.from_user.id, None) 47 | await m.reply_text("Okay,\n" 48 | "I deleted custom thumbnail from my database.") 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rename-Bot 2 | This is a very simple Telegram Files Rename Bot by [@AbirHasan2005](https://t.me/AbirHasan2005). 3 | 4 | ## Features 5 | - Rename Videos, Documents or Audios without downloading the file. 6 | - Change video title, audio track title, subtitle track title. 7 | - Permanent Custom Thumbnail Support. 8 | - Very User-Friendly. 9 | - [MongoDB](https://mongodb.com) Based Database. 10 | - Time Gap Support. 11 | - Broadcast Message to Users in Database. 12 | - Use Default Video Thumbnail after Rename. It will not Generate New if Video already has thumbnail. 13 | - Heroku Deployable :) 14 | 15 | ### BotFather Commands: 16 | ``` 17 | start - Start Bot 18 | help - How to use this bot? 19 | rename - Rename file 20 | video_info - Change video metadata 21 | set_thumbnail - Set Custom Thumbnail 22 | show_thumbnail - Show Custom Thumbnail 23 | delete_thumbnail - Delete Custom Thumbnail 24 | broadcast - Broadcast Message. (Admin Only) 25 | status - Show Users Count in DB. (Admin Only) 26 | ``` 27 | 28 | ### Support Group 29 | 30 | 31 | ## Deploy to Heroku 32 | Easy to Deploy to Heroku. 33 | 34 | ### Video Tutorial: 35 | [![YouTube](https://img.shields.io/badge/YouTube-Video%20Tutorial-red?logo=youtube)](https://youtu.be/edcOa_cZWg4) 36 | 37 | 38 | Press Below Button to Deploy! 39 | 40 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/AbirHasan2005/Rename-Bot) 41 | 42 | #### Thanks to [subinps](https://github.com/subinps) for helping 43 | #### Inspired from [@HK_Rename_Bot](https://t.me/HK_Rename_Bot) & [@AHToolsBot](https://t.me/AHToolsBot) 44 | 45 | ### Follow on: 46 |

47 | 48 |

49 |

50 | 51 |

52 |

53 | 54 |

55 |

56 | 57 |

58 | -------------------------------------------------------------------------------- /bot/core/handlers/settings.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import asyncio 4 | from pyrogram import types, errors 5 | from configs import Config 6 | from bot.core.db.database import db 7 | 8 | 9 | async def show_settings(m: "types.Message"): 10 | usr_id = m.chat.id 11 | user_data = await db.get_user_data(usr_id) 12 | if not user_data: 13 | await m.edit("Failed to fetch your data from database!") 14 | return 15 | upload_as_doc = user_data.get("upload_as_doc", False) 16 | caption = user_data.get("caption", None) 17 | apply_caption = user_data.get("apply_caption", True) 18 | thumbnail = user_data.get("thumbnail", None) 19 | buttons_markup = [ 20 | [types.InlineKeyboardButton(f"Upload as Doc {'✅' if upload_as_doc else '❌'}", 21 | callback_data="triggerUploadMode")], 22 | [types.InlineKeyboardButton(f"Apply Caption {'✅' if apply_caption else '❌'}", 23 | callback_data="triggerApplyCaption")], 24 | [types.InlineKeyboardButton(f"Apply Default Caption {'❌' if caption else '✅'}", 25 | callback_data="triggerApplyDefaultCaption")], 26 | [types.InlineKeyboardButton("Set Custom Caption", 27 | callback_data="setCustomCaption")], 28 | [types.InlineKeyboardButton(f"{'Change' if thumbnail else 'Set'} Thumbnail", 29 | callback_data="setThumbnail")] 30 | ] 31 | if thumbnail: 32 | buttons_markup.append([types.InlineKeyboardButton("Show Thumbnail", 33 | callback_data="showThumbnail")]) 34 | if caption: 35 | buttons_markup.append([types.InlineKeyboardButton("Show Caption", 36 | callback_data="showCaption")]) 37 | buttons_markup.append([types.InlineKeyboardButton("Close Message", 38 | callback_data="closeMessage")]) 39 | 40 | try: 41 | await m.edit( 42 | text="**Here you can setup your settings:**", 43 | reply_markup=types.InlineKeyboardMarkup(buttons_markup), 44 | disable_web_page_preview=True, 45 | parse_mode="Markdown" 46 | ) 47 | except errors.MessageNotModified: pass 48 | except errors.FloodWait as e: 49 | await asyncio.sleep(e.x) 50 | await show_settings(m) 51 | except Exception as err: 52 | Config.LOGGER.getLogger(__name__).error(err) 53 | -------------------------------------------------------------------------------- /bot/core/db/database.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import datetime 4 | import motor.motor_asyncio 5 | from configs import Config 6 | 7 | 8 | class Database: 9 | def __init__(self, uri, database_name): 10 | self._client = motor.motor_asyncio.AsyncIOMotorClient(uri) 11 | self.db = self._client[database_name] 12 | self.col = self.db.users 13 | 14 | def new_user(self, id): 15 | return dict( 16 | id=id, 17 | join_date=datetime.date.today().isoformat(), 18 | apply_caption=True, 19 | upload_as_doc=False, 20 | thumbnail=None, 21 | caption=None 22 | ) 23 | 24 | async def add_user(self, id): 25 | user = self.new_user(id) 26 | await self.col.insert_one(user) 27 | 28 | async def is_user_exist(self, id): 29 | user = await self.col.find_one({'id': int(id)}) 30 | return bool(user) 31 | 32 | async def total_users_count(self): 33 | count = await self.col.count_documents({}) 34 | return count 35 | 36 | async def get_all_users(self): 37 | return self.col.find({}) 38 | 39 | async def delete_user(self, user_id): 40 | await self.col.delete_many({'id': int(user_id)}) 41 | 42 | async def set_apply_caption(self, id, apply_caption): 43 | await self.col.update_one({'id': id}, {'$set': {'apply_caption': apply_caption}}) 44 | 45 | async def get_apply_caption(self, id): 46 | user = await self.col.find_one({'id': int(id)}) 47 | return user.get('apply_caption', True) 48 | 49 | async def set_upload_as_doc(self, id, upload_as_doc): 50 | await self.col.update_one({'id': id}, {'$set': {'upload_as_doc': upload_as_doc}}) 51 | 52 | async def get_upload_as_doc(self, id): 53 | user = await self.col.find_one({'id': int(id)}) 54 | return user.get('upload_as_doc', False) 55 | 56 | async def set_thumbnail(self, id, thumbnail): 57 | await self.col.update_one({'id': id}, {'$set': {'thumbnail': thumbnail}}) 58 | 59 | async def get_thumbnail(self, id): 60 | user = await self.col.find_one({'id': int(id)}) 61 | return user.get('thumbnail', None) 62 | 63 | async def set_caption(self, id, caption): 64 | await self.col.update_one({'id': id}, {'$set': {'caption': caption}}) 65 | 66 | async def get_caption(self, id): 67 | user = await self.col.find_one({'id': int(id)}) 68 | return user.get('caption', None) 69 | 70 | async def get_user_data(self, id) -> dict: 71 | user = await self.col.find_one({'id': int(id)}) 72 | return user or None 73 | 74 | 75 | db = Database(Config.MONGODB_URI, "Rename-Bot") 76 | -------------------------------------------------------------------------------- /bot/core/handlers/not_big.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from configs import Config 4 | from bot.client import Client 5 | from pyrogram.types import Message 6 | from bot.core.db.database import db 7 | 8 | 9 | async def handle_not_big( 10 | c: Client, 11 | m: Message, 12 | file_id: str, 13 | file_name: str, 14 | editable: Message, 15 | upload_mode: str = "document", 16 | thumb: str = None, 17 | ): 18 | reply_markup = m.reply_to_message.reply_markup \ 19 | if m.reply_to_message.reply_markup \ 20 | else None 21 | _db_caption = await db.get_caption(m.from_user.id) 22 | apply_caption = await db.get_apply_caption(m.from_user.id) 23 | if (not _db_caption) and (apply_caption is True): 24 | caption = m.reply_to_message.caption.markdown \ 25 | if m.reply_to_message.caption \ 26 | else "**Developer: @AbirHasan2005**" 27 | elif _db_caption and (apply_caption is True): 28 | caption = _db_caption 29 | else: 30 | caption = "" 31 | parse_mode = "Markdown" 32 | if thumb: 33 | _thumb = await c.download_media(thumb, f"{Config.DOWNLOAD_DIR}/{m.from_user.id}/{m.message_id}/") 34 | else: 35 | _thumb = None 36 | upload_as_doc = await db.get_upload_as_doc(m.from_user.id) 37 | 38 | if (upload_as_doc is False) and (upload_mode == "video"): 39 | performer = None 40 | title = None 41 | duration = m.reply_to_message.video.duration \ 42 | if m.reply_to_message.video.duration \ 43 | else 0 44 | width = m.reply_to_message.video.width \ 45 | if m.reply_to_message.video.width \ 46 | else 0 47 | height = m.reply_to_message.video.height \ 48 | if m.reply_to_message.video.height \ 49 | else 0 50 | elif (upload_as_doc is False) and (upload_mode == "audio"): 51 | width = None 52 | height = None 53 | duration = m.reply_to_message.audio.duration \ 54 | if m.reply_to_message.audio.duration \ 55 | else None 56 | performer = m.reply_to_message.audio.performer \ 57 | if m.reply_to_message.audio.performer \ 58 | else None 59 | title = m.reply_to_message.audio.title \ 60 | if m.reply_to_message.audio.title \ 61 | else None 62 | else: 63 | duration = None 64 | width = None 65 | height = None 66 | performer = None 67 | title = None 68 | 69 | await c.normal_rename( 70 | file_id, 71 | file_name, 72 | editable, 73 | m.chat.id, 74 | upload_mode, 75 | _thumb, 76 | caption, 77 | parse_mode, 78 | reply_markup=reply_markup, 79 | duration=duration, 80 | width=width, 81 | height=height, 82 | performer=performer, 83 | title=title 84 | ) 85 | 86 | -------------------------------------------------------------------------------- /bot/core/file_info.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from pyrogram.types import Message 4 | 5 | 6 | def get_media_file_name(message: Message): 7 | """ 8 | Pass Message object of audio or document or sticker or video or animation to get file_name. 9 | """ 10 | 11 | media = message.audio or \ 12 | message.document or \ 13 | message.sticker or \ 14 | message.video or \ 15 | message.animation 16 | 17 | if media and media.file_name: 18 | return media.file_name 19 | else: 20 | return None 21 | 22 | 23 | def get_media_file_size(message: Message): 24 | """ 25 | Pass Message object of audio or document or photo or sticker or video or animation or voice or video_note to get file_size. 26 | """ 27 | 28 | media = message.audio or \ 29 | message.document or \ 30 | message.photo or \ 31 | message.sticker or \ 32 | message.video or \ 33 | message.animation or \ 34 | message.voice or \ 35 | message.video_note 36 | 37 | if media and media.file_size: 38 | return media.file_size 39 | else: 40 | return None 41 | 42 | 43 | def get_media_mime_type(message: Message): 44 | """ 45 | Pass Message object of audio or document or video to get mime_type 46 | """ 47 | 48 | media = message.audio or \ 49 | message.document or \ 50 | message.video 51 | 52 | if media and media.mime_type: 53 | return media.mime_type 54 | else: 55 | return None 56 | 57 | 58 | def get_media_file_id(message: Message): 59 | """ 60 | Pass Message object of audio or document or photo or sticker or video or animation or voice or video_note to get file_id. 61 | """ 62 | 63 | media = message.audio or \ 64 | message.document or \ 65 | message.photo or \ 66 | message.sticker or \ 67 | message.video or \ 68 | message.animation or \ 69 | message.voice or \ 70 | message.video_note 71 | 72 | if media and media.file_id: 73 | return media.file_id 74 | else: 75 | return None 76 | 77 | 78 | def get_file_type(message: Message): 79 | if message.document: 80 | return "document" 81 | if message.video: 82 | return "video" 83 | if message.audio: 84 | return "audio" 85 | 86 | 87 | def get_file_attr(message: Message): 88 | 89 | """ 90 | Combine audio or video or document 91 | """ 92 | 93 | media = message.audio or \ 94 | message.video or \ 95 | message.document 96 | 97 | return media 98 | 99 | 100 | def get_thumb_file_id(message: Message): 101 | media = message.audio or \ 102 | message.video or \ 103 | message.document 104 | if media and media.thumbs: 105 | return media.thumbs[0].file_id 106 | else: 107 | return None 108 | -------------------------------------------------------------------------------- /bot/core/display.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import math 4 | import time 5 | import asyncio 6 | import bot 7 | from typing import Union 8 | from pyrogram.types import Message, CallbackQuery 9 | from pyrogram.errors import FloodWait 10 | 11 | PROGRESS = """ 12 | ⏳ **Percentage:** `{0}%` 13 | ✅ **Done:** `{1}` 14 | 💠 **Total:** `{2}` 15 | 📶 **Speed:** `{3}/s` 16 | 🕰 **ETA:** `{4}` 17 | """ 18 | 19 | 20 | async def progress_for_pyrogram( 21 | current, 22 | total, 23 | ud_type, 24 | message: Union[Message, CallbackQuery], 25 | start 26 | ): 27 | now = time.time() 28 | diff = now - start 29 | if round(diff % 10.00) == 0 or current == total: 30 | percentage = current * 100 / total 31 | speed = current / diff 32 | elapsed_time = round(diff) * 1000 33 | time_to_completion = round((total - current) / speed) * 1000 34 | estimated_total_time = elapsed_time + time_to_completion 35 | 36 | elapsed_time = TimeFormatter(milliseconds=elapsed_time) 37 | estimated_total_time = TimeFormatter(milliseconds=estimated_total_time) 38 | 39 | progress = "[{0}{1}] \n".format( 40 | ''.join(["●" for _ in range(math.floor(percentage / 5))]), 41 | ''.join(["○" for _ in range(20 - math.floor(percentage / 5))]) 42 | ) 43 | 44 | tmp = progress + PROGRESS.format( 45 | round(percentage, 2), 46 | humanbytes(current), 47 | humanbytes(total), 48 | humanbytes(speed), 49 | estimated_total_time if estimated_total_time != '' else "0 s" 50 | ) 51 | try: 52 | try: 53 | _ = message.message_id 54 | await message.edit( 55 | text="**{}**\n\n {}".format( 56 | ud_type, 57 | tmp 58 | ), 59 | parse_mode='markdown' 60 | ) 61 | except AttributeError: 62 | await bot.bot.edit_inline_caption( 63 | inline_message_id=message.inline_message_id, 64 | caption="**{}**\n\n {}".format( 65 | ud_type, 66 | tmp 67 | ), 68 | parse_mode='markdown' 69 | ) 70 | except FloodWait as e: 71 | await asyncio.sleep(e.x) 72 | except Exception: 73 | pass 74 | 75 | 76 | def humanbytes(size): 77 | # https://stackoverflow.com/a/49361727/4723940 78 | # 2**10 = 1024 79 | if not size: 80 | return "" 81 | power = 2**10 82 | n = 0 83 | Dic_powerN = {0: ' ', 1: 'Ki', 2: 'Mi', 3: 'Gi', 4: 'Ti'} 84 | while size > power: 85 | size /= power 86 | n += 1 87 | return str(round(size, 2)) + " " + Dic_powerN[n] + 'B' 88 | 89 | 90 | def TimeFormatter(milliseconds: int) -> str: 91 | seconds, milliseconds = divmod(int(milliseconds), 1000) 92 | minutes, seconds = divmod(seconds, 60) 93 | hours, minutes = divmod(minutes, 60) 94 | days, hours = divmod(hours, 24) 95 | tmp = ((str(days) + " days, ") if days else "") + \ 96 | ((str(hours) + " hours, ") if hours else "") + \ 97 | ((str(minutes) + " min, ") if minutes else "") + \ 98 | ((str(seconds) + " sec, ") if seconds else "") + \ 99 | ((str(milliseconds) + " millisec, ") if milliseconds else "") 100 | return tmp[:-2] 101 | -------------------------------------------------------------------------------- /bot/core/new/send_flooded_message.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import asyncio 4 | from typing import ( 5 | List, 6 | Union, 7 | Optional 8 | ) 9 | from pyrogram.types import ( 10 | Message, 11 | MessageEntity, 12 | InlineKeyboardMarkup, 13 | ReplyKeyboardRemove, 14 | ReplyKeyboardMarkup, 15 | ForceReply 16 | ) 17 | from pyrogram.errors import ( 18 | FloodWait 19 | ) 20 | 21 | 22 | class SendFloodedMessage: 23 | async def send_flooded_message( 24 | self, 25 | chat_id: Union[int, str], 26 | text: str, 27 | parse_mode: Optional[str] = object, 28 | entities: Optional[List[MessageEntity]] = None, 29 | disable_web_page_preview: Optional[bool] = None, 30 | disable_notification: Optional[bool] = None, 31 | reply_to_message_id: Optional[int] = None, 32 | schedule_date: Optional[int] = None, 33 | reply_markup: Union[ 34 | InlineKeyboardMarkup, 35 | ReplyKeyboardMarkup, 36 | ReplyKeyboardRemove, 37 | ForceReply, None] = None 38 | ) -> Optional[Message]: 39 | """ 40 | Try sending Text Message. But if FloodWait raises, than sleep x time and continue. 41 | 42 | :param chat_id: Unique identifier (int) or username (str) of the target chat. For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). 43 | :param text: Text of the message to be sent. 44 | :param parse_mode: By default, texts are parsed using both Markdown and HTML styles. You can combine both syntax together. Pass "markdown" or "md" to enable Markdown-style parsing only. Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. 45 | :param entities: List of special entities that appear in message text, which can be specified instead of *parse_mode*. 46 | :param disable_web_page_preview: Disables link previews for links in this message. 47 | :param disable_notification: Sends the message silently. Users will receive a notification with no sound. 48 | :param reply_to_message_id: If the message is a reply, ID of the original message. 49 | :param schedule_date: Date when the message will be automatically sent. Unix time. 50 | :param reply_markup: Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. 51 | 52 | :return: On success, the sent text message is returned. 53 | """ 54 | 55 | try: 56 | __SEND = await self.send_message( 57 | chat_id=chat_id, 58 | text=text, 59 | parse_mode=parse_mode, 60 | entities=entities, 61 | disable_web_page_preview=disable_web_page_preview, 62 | disable_notification=disable_notification, 63 | reply_to_message_id=reply_to_message_id, 64 | schedule_date=schedule_date, 65 | reply_markup=reply_markup 66 | ) 67 | return __SEND 68 | except FloodWait as e: 69 | if e.x > 120: 70 | return None 71 | print(f"Sleeping for {e.x}s") 72 | await asyncio.sleep(e.x) 73 | return await self.send_flooded_message( 74 | chat_id, 75 | text, 76 | parse_mode, 77 | entities, 78 | disable_web_page_preview, 79 | disable_notification, 80 | reply_to_message_id, 81 | schedule_date, 82 | reply_markup 83 | ) 84 | -------------------------------------------------------------------------------- /bot/core/handlers/broadcast.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import time 4 | import string 5 | import random 6 | import asyncio 7 | import datetime 8 | import aiofiles 9 | import traceback 10 | import aiofiles.os 11 | from configs import Config 12 | from bot.core.db.database import db 13 | from pyrogram.types import Message 14 | from pyrogram.errors import FloodWait, InputUserDeactivated, UserIsBlocked, PeerIdInvalid 15 | 16 | broadcast_ids = {} 17 | 18 | 19 | async def send_msg(user_id, message): 20 | try: 21 | if Config.BROADCAST_AS_COPY is False: 22 | await message.forward(chat_id=user_id) 23 | elif Config.BROADCAST_AS_COPY is True: 24 | await message.copy(chat_id=user_id) 25 | return 200, None 26 | except FloodWait as e: 27 | await asyncio.sleep(e.x) 28 | return send_msg(user_id, message) 29 | except InputUserDeactivated: 30 | return 400, f"{user_id} : deactivated\n" 31 | except UserIsBlocked: 32 | return 400, f"{user_id} : blocked the bot\n" 33 | except PeerIdInvalid: 34 | return 400, f"{user_id} : user id invalid\n" 35 | except Exception as e: 36 | return 500, f"{user_id} : {traceback.format_exc()}\n" 37 | 38 | 39 | async def broadcast_handler(m: Message): 40 | all_users = await db.get_all_users() 41 | broadcast_msg = m.reply_to_message 42 | while True: 43 | broadcast_id = ''.join([random.choice(string.ascii_letters) for i in range(3)]) 44 | if not broadcast_ids.get(broadcast_id): 45 | break 46 | out = await m.reply_text( 47 | text=f"Broadcast Started! You will be notified with log file when all the users are notified." 48 | ) 49 | start_time = time.time() 50 | total_users = await db.total_users_count() 51 | done = 0 52 | failed = 0 53 | success = 0 54 | broadcast_ids[broadcast_id] = dict( 55 | total=total_users, 56 | current=done, 57 | failed=failed, 58 | success=success 59 | ) 60 | async with aiofiles.open('broadcast.txt', 'w') as broadcast_log_file: 61 | async for user in all_users: 62 | sts, msg = await send_msg( 63 | user_id=int(user['id']), 64 | message=broadcast_msg 65 | ) 66 | if msg is not None: 67 | await broadcast_log_file.write(msg) 68 | if sts == 200: 69 | success += 1 70 | else: 71 | failed += 1 72 | if sts == 400: 73 | await db.delete_user(user['id']) 74 | done += 1 75 | if broadcast_ids.get(broadcast_id) is None: 76 | break 77 | else: 78 | broadcast_ids[broadcast_id].update( 79 | dict( 80 | current=done, 81 | failed=failed, 82 | success=success 83 | ) 84 | ) 85 | if broadcast_ids.get(broadcast_id): 86 | broadcast_ids.pop(broadcast_id) 87 | completed_in = datetime.timedelta(seconds=int(time.time() - start_time)) 88 | await asyncio.sleep(3) 89 | await out.delete() 90 | if failed == 0: 91 | await m.reply_text( 92 | text=f"broadcast completed in `{completed_in}`\n\n" 93 | f"Total users {total_users}.\n" 94 | f"Total done {done}, {success} success and {failed} failed.", 95 | quote=True 96 | ) 97 | else: 98 | await m.reply_document( 99 | document='broadcast.txt', 100 | caption=f"broadcast completed in `{completed_in}`\n\n" 101 | f"Total users {total_users}.\n" 102 | f"Total done {done}, {success} success and {failed} failed.", 103 | quote=True 104 | ) 105 | await aiofiles.os.remove('broadcast.txt') 106 | -------------------------------------------------------------------------------- /bot/core/utils/video_info.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import json 4 | import asyncio 5 | import subprocess 6 | 7 | 8 | def convert_sexagesimal_to_sec(text): 9 | if isinstance(text, float): 10 | num = str(text) 11 | nums = num.split('.') 12 | else: 13 | nums = text.split(':') 14 | if len(nums) == 2: 15 | st_sn = int(nums[0]) * 60 + float(nums[1]) 16 | return st_sn 17 | elif len(nums) == 3: 18 | st_sn = int(nums[0]) * 3600 + int(nums[1]) * 60 + float(nums[2]) 19 | return st_sn 20 | else: 21 | return 0 22 | 23 | 24 | async def get_video_info(filename): 25 | result = subprocess.check_output( 26 | f'ffprobe -v quiet -show_streams -select_streams v:0 -of json "{filename}"', 27 | shell=True).decode() 28 | fields = json.loads(result)['streams'][0] 29 | return fields['duration'], fields['height'], fields['width'] 30 | 31 | 32 | async def get_audio_or_video_duration(path: str, provider: str = "ffprobe"): 33 | duration = 0 34 | if provider == "ffprobe": 35 | process = await asyncio.create_subprocess_shell( 36 | f"ffprobe -v quiet -print_format json -show_format -show_streams '{path}'", 37 | stdout=asyncio.subprocess.PIPE, 38 | stderr=asyncio.subprocess.PIPE 39 | ) 40 | stdout, stderr = await process.communicate() 41 | stdout = stdout.decode('utf-8', 'replace').strip() 42 | stderr = stderr.decode('utf-8', 'replace').strip() 43 | if not stdout: 44 | duration = 0 45 | else: 46 | try: 47 | json_data = json.loads(stdout) 48 | stream_tags = json_data.get('streams')[0].get('tags') 49 | duration = int(convert_sexagesimal_to_sec(stream_tags.get('DURATION'))) 50 | except: 51 | duration = 0 52 | if (provider == "mediainfo") or (not duration): 53 | process = await asyncio.create_subprocess_shell( 54 | f'mediainfo --Inform="General;%Duration%" "{path}"', 55 | stdout=asyncio.subprocess.PIPE, 56 | stderr=asyncio.subprocess.PIPE 57 | ) 58 | stdout, stderr = await process.communicate() 59 | stdout = stdout.decode('utf-8', 'replace').strip() 60 | stderr = stderr.decode('utf-8', 'replace').strip() 61 | if not stdout: 62 | duration = 0 63 | else: 64 | try: 65 | duration = int(int(stdout) / 1000) 66 | except: pass 67 | return duration 68 | 69 | 70 | async def get_video_height(path: str): 71 | process = await asyncio.create_subprocess_shell( 72 | f"ffprobe -v quiet -print_format json -show_format -show_streams '{path}'", 73 | stdout=asyncio.subprocess.PIPE, 74 | stderr=asyncio.subprocess.PIPE 75 | ) 76 | stdout, stderr = await process.communicate() 77 | stdout = stdout.decode('utf-8', 'replace').strip() 78 | stderr = stderr.decode('utf-8', 'replace').strip() 79 | if not stdout: 80 | return 0 81 | try: 82 | json_data = json.loads(stdout) 83 | height = json_data.get('streams')[0].get('height') 84 | return height or 0 85 | except: return 0 86 | 87 | 88 | async def get_video_width(path: str): 89 | process = await asyncio.create_subprocess_shell( 90 | f"ffprobe -v quiet -print_format json -show_format -show_streams '{path}'", 91 | stdout=asyncio.subprocess.PIPE, 92 | stderr=asyncio.subprocess.PIPE 93 | ) 94 | stdout, stderr = await process.communicate() 95 | stdout = stdout.decode('utf-8', 'replace').strip() 96 | stderr = stderr.decode('utf-8', 'replace').strip() 97 | if not stdout: 98 | return 0 99 | try: 100 | json_data = json.loads(stdout) 101 | width = json_data.get('streams')[0].get('width') 102 | return width or 0 103 | except: return 0 104 | -------------------------------------------------------------------------------- /bot/core/new/upload_document.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import os 4 | import time 5 | from typing import ( 6 | List, 7 | Union, 8 | Optional, 9 | BinaryIO 10 | ) 11 | from pyrogram.types import ( 12 | Message, 13 | MessageEntity, 14 | ForceReply, 15 | ReplyKeyboardMarkup, 16 | ReplyKeyboardRemove, 17 | InlineKeyboardMarkup 18 | ) 19 | from bot.core.display import progress_for_pyrogram 20 | 21 | 22 | class UploadDocument: 23 | async def upload_document( 24 | self, 25 | chat_id: Union[int, str], 26 | document: str, 27 | editable_message: Message, 28 | caption: str = "", 29 | parse_mode: Optional[str] = object, 30 | caption_entities: List[MessageEntity] = None, 31 | thumb: Union[str, BinaryIO] = None, 32 | file_name: str = None, 33 | disable_notification: bool = None, 34 | reply_to_message_id: int = None, 35 | schedule_date: int = None, 36 | reply_markup: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None] = None, 37 | status_message: str = "📤 Uploading as Document ..." 38 | ): 39 | """ 40 | Advanced Document Uploader Function. 41 | 42 | :param chat_id: Unique identifier (int) or username (str) of the target chat. For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). 43 | :param document: Document to send. Pass a file path as string to upload a new document that exists on your local machine. 44 | :param editable_message: Pass editable Message object for updating with progress text. 45 | :param parse_mode: By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. Pass "markdown" or "md" to enable Markdown-style parsing only. Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. 46 | :param caption: Video caption, 0-1024 characters. 47 | :param caption_entities: List of special entities that appear in the caption, which can be specified instead of *parse_mode*. 48 | :param thumb: Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. By default function will take random screenshot from video and use it as thumbnail. 49 | :param file_name: File name of the video sent. Defaults to file's path basename. 50 | :param disable_notification: Sends the message silently. Users will receive a notification with no sound. 51 | :param reply_to_message_id: If the message is a reply, ID of the original message. 52 | :param schedule_date: Date when the message will be automatically sent. Unix time. 53 | :param reply_markup: Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. 54 | :param status_message: Pass status string. Default: "📤 Uploading as Video ..." 55 | """ 56 | 57 | if not caption: 58 | caption = f"**File Name:** `{os.path.basename(document)}`" \ 59 | "\n\n**@AH_RenameBot**" 60 | c_time = time.time() 61 | await self.send_document( 62 | chat_id=chat_id, 63 | document=document, 64 | caption=caption, 65 | parse_mode=parse_mode, 66 | caption_entities=caption_entities, 67 | thumb=thumb, 68 | file_name=file_name, 69 | disable_notification=disable_notification, 70 | reply_to_message_id=reply_to_message_id, 71 | schedule_date=schedule_date, 72 | reply_markup=reply_markup, 73 | progress=progress_for_pyrogram, 74 | progress_args=( 75 | status_message, 76 | editable_message, 77 | c_time 78 | ) 79 | ) 80 | await editable_message.edit("Uploaded Successfully!") 81 | -------------------------------------------------------------------------------- /bot/plugins/rename.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import time 4 | import mimetypes 5 | import traceback 6 | from bot.client import ( 7 | Client 8 | ) 9 | from pyrogram import filters 10 | from pyrogram.file_id import FileId 11 | from pyrogram.types import Message 12 | from bot.core.file_info import ( 13 | get_media_file_id, 14 | get_media_file_size, 15 | get_media_file_name, 16 | get_file_type, 17 | get_file_attr 18 | ) 19 | from configs import Config 20 | from bot.core.display import progress_for_pyrogram 21 | from bot.core.db.database import db 22 | from bot.core.db.add import add_user_to_database 23 | from bot.core.handlers.not_big import handle_not_big 24 | from bot.core.handlers.time_gap import check_time_gap 25 | from bot.core.handlers.big_rename import handle_big_rename 26 | 27 | 28 | @Client.on_message(filters.command(["rename", "r"]) & filters.private & ~filters.edited) 29 | async def rename_handler(c: Client, m: Message): 30 | # Checks 31 | if not m.from_user: 32 | return await m.reply_text("I don't know about you sar :(") 33 | if m.from_user.id not in Config.PRO_USERS: 34 | is_in_gap, sleep_time = await check_time_gap(m.from_user.id) 35 | if is_in_gap: 36 | await m.reply_text("Sorry Sir,\n" 37 | "No Flooding Allowed!\n\n" 38 | f"Send After `{str(sleep_time)}s` !!", 39 | quote=True) 40 | return 41 | await add_user_to_database(c, m) 42 | if (not m.reply_to_message) or (not m.reply_to_message.media) or (not get_file_attr(m.reply_to_message)): 43 | return await m.reply_text("Reply to any document/video/audio to rename it!", quote=True) 44 | 45 | # Proceed 46 | editable = await m.reply_text("Now send me new file name!", quote=True) 47 | user_input_msg: Message = await c.listen(m.chat.id) 48 | if user_input_msg.text is None: 49 | await editable.edit("Process Cancelled!") 50 | return await user_input_msg.continue_propagation() 51 | if user_input_msg.text and user_input_msg.text.startswith("/"): 52 | await editable.edit("Process Cancelled!") 53 | return await user_input_msg.continue_propagation() 54 | _raw_file_name = get_media_file_name(m.reply_to_message) 55 | if not _raw_file_name: 56 | _file_ext = mimetypes.guess_extension(get_file_attr(m.reply_to_message).mime_type) 57 | _raw_file_name = "UnknownFileName" + _file_ext 58 | if user_input_msg.text.rsplit(".", 1)[-1].lower() != _raw_file_name.rsplit(".", 1)[-1].lower(): 59 | file_name = user_input_msg.text.rsplit(".", 1)[0][:255] + "." + _raw_file_name.rsplit(".", 1)[-1].lower() 60 | else: 61 | file_name = user_input_msg.text[:255] 62 | await editable.edit("Please Wait ...") 63 | is_big = get_media_file_size(m.reply_to_message) > (10 * 1024 * 1024) 64 | if not is_big: 65 | _default_thumb_ = await db.get_thumbnail(m.from_user.id) 66 | if not _default_thumb_: 67 | _m_attr = get_file_attr(m.reply_to_message) 68 | _default_thumb_ = _m_attr.thumbs[0].file_id \ 69 | if (_m_attr and _m_attr.thumbs) \ 70 | else None 71 | await handle_not_big(c, m, get_media_file_id(m.reply_to_message), file_name, 72 | editable, get_file_type(m.reply_to_message), _default_thumb_) 73 | return 74 | file_type = get_file_type(m.reply_to_message) 75 | _c_file_id = FileId.decode(get_media_file_id(m.reply_to_message)) 76 | try: 77 | c_time = time.time() 78 | file_id = await c.custom_upload( 79 | file_id=_c_file_id, 80 | file_size=get_media_file_size(m.reply_to_message), 81 | file_name=file_name, 82 | progress=progress_for_pyrogram, 83 | progress_args=( 84 | "Uploading ...\n" 85 | f"DC: {_c_file_id.dc_id}", 86 | editable, 87 | c_time 88 | ) 89 | ) 90 | if not file_id: 91 | return await editable.edit("Failed to Rename!\n\n" 92 | "Maybe your file corrupted :(") 93 | await handle_big_rename(c, m, file_id, file_name, editable, file_type) 94 | except Exception as err: 95 | await editable.edit("Failed to Rename File!\n\n" 96 | f"**Error:** `{err}`\n\n" 97 | f"**Traceback:** `{traceback.format_exc()}`") 98 | -------------------------------------------------------------------------------- /bot/plugins/video_info.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import os 4 | import time 5 | import json 6 | import shlex 7 | import shutil 8 | from bot.client import ( 9 | Client 10 | ) 11 | from configs import Config 12 | from pyrogram import filters 13 | from pyrogram.types import Message 14 | from bot.core.file_info import ( 15 | get_media_file_name 16 | ) 17 | from bot.core.db.database import db 18 | from bot.core.utils.rm import rm_dir 19 | from bot.core.utils.executor import execute 20 | from bot.core.db.add import add_user_to_database 21 | from bot.core.display import progress_for_pyrogram 22 | from bot.core.file_info import get_file_attr 23 | 24 | 25 | def filesystem_free(path='.'): 26 | _, __, free = shutil.disk_usage(".") 27 | return free 28 | 29 | 30 | @Client.on_message(filters.command("video_info") & filters.private & ~filters.edited) 31 | async def video_info_handler(c: Client, m: Message): 32 | await add_user_to_database(c, m) 33 | if filesystem_free() < 5000000000: 34 | return await m.reply_text( 35 | "Because of less server space I can't do this task right now !!\n\n" 36 | "Please try again after some time or use @AHToolsBot to do same task.", 37 | True 38 | ) 39 | if (not m.reply_to_message) or (len(m.command) == 1): 40 | await m.reply_text(f"Reply to video with,\n/{m.command[0]} `--change-title` new title `--change-video-title` new video title `--change-audio-title` new audio title `--change-subtitle-title` new subtitle title `--change-file-name` new file name", True) 41 | return 42 | title = None 43 | video_title = None 44 | audio_title = None 45 | subtitle_title = None 46 | default_f_name = get_media_file_name(m.reply_to_message) 47 | new_file_name = f"{default_f_name.rsplit('.', 1)[0] if default_f_name else 'output'}.mkv" 48 | if len(m.command) <= 1: 49 | return 50 | 51 | flags = [i.strip() for i in m.text.split('--')] 52 | for f in flags: 53 | if "change-file-name" in f: 54 | new_file_name = f[len("change-file-name"):].strip().rsplit(".", 1)[0] + ".mkv" 55 | if "change-title" in f: 56 | title = f[len("change-title"):].strip() 57 | if "change-video-title" in f: 58 | video_title = f[len("change-video-title"):].strip() 59 | if "change-audio-title" in f: 60 | audio_title = f[len("change-audio-title"):].strip() 61 | if "change-subtitle-title" in f: 62 | subtitle_title = f[len("change-subtitle-title"):].strip() 63 | file_type = m.reply_to_message.video or m.reply_to_message.document 64 | if not file_type.mime_type.startswith("video/"): 65 | await m.reply_text("This is not a Video!", True) 66 | return 67 | editable = await m.reply_text("Downloading Video ...", quote=True) 68 | dl_loc = Config.DOWNLOAD_DIR + "/" + str(m.from_user.id) + "/" + str(m.message_id) + "/" 69 | root_dl_loc = dl_loc 70 | if not os.path.isdir(dl_loc): 71 | os.makedirs(dl_loc) 72 | c_time = time.time() 73 | the_media = await c.download_media( 74 | message=m.reply_to_message, 75 | file_name=dl_loc, 76 | progress=progress_for_pyrogram, 77 | progress_args=( 78 | "Downloading ...", 79 | editable, 80 | c_time 81 | ) 82 | ) 83 | await editable.edit("Trying to Fetch Media Metadata ...") 84 | output = await execute(f"ffprobe -hide_banner -show_streams -print_format json {shlex.quote(the_media)}") 85 | if not output: 86 | await rm_dir(root_dl_loc) 87 | return await editable.edit("Can't fetch media info!") 88 | 89 | try: 90 | details = json.loads(output[0]) 91 | middle_cmd = f"ffmpeg -i {shlex.quote(the_media)} -c copy -map 0" 92 | if title: 93 | middle_cmd += f' -metadata title="{title}"' 94 | for stream in details["streams"]: 95 | if (stream["codec_type"] == "video") and video_title: 96 | middle_cmd += f' -metadata:s:{stream["index"]} title="{video_title}"' 97 | elif (stream["codec_type"] == "audio") and audio_title: 98 | middle_cmd += f' -metadata:s:{stream["index"]} title="{audio_title}"' 99 | elif (stream["codec_type"] == "subtitle") and subtitle_title: 100 | middle_cmd += f' -metadata:s:{stream["index"]} title="{subtitle_title}"' 101 | dl_loc = dl_loc + str(time.time()).replace(".", "") + "/" 102 | if not os.path.isdir(dl_loc): 103 | os.makedirs(dl_loc) 104 | middle_cmd += f" {shlex.quote(dl_loc + new_file_name)}" 105 | await editable.edit("Please Wait ...\n\nProcessing Video ...") 106 | await execute(middle_cmd) 107 | await editable.edit("Renamed Successfully!") 108 | except: 109 | # Clean Up 110 | await editable.edit("Failed to process video!") 111 | await rm_dir(root_dl_loc) 112 | return 113 | try: os.remove(the_media) 114 | except: pass 115 | upload_as_doc = await db.get_upload_as_doc(m.from_user.id) 116 | _default_thumb_ = await db.get_thumbnail(m.from_user.id) 117 | if not _default_thumb_: 118 | _m_attr = get_file_attr(m.reply_to_message) 119 | _default_thumb_ = _m_attr.thumbs[0].file_id \ 120 | if (_m_attr and _m_attr.thumbs) \ 121 | else None 122 | if _default_thumb_: 123 | _default_thumb_ = await c.download_media(_default_thumb_, root_dl_loc) 124 | if (not upload_as_doc) and m.reply_to_message.video: 125 | await c.upload_video( 126 | chat_id=m.chat.id, 127 | video=f"{dl_loc}{new_file_name}", 128 | thumb=_default_thumb_ or None, 129 | editable_message=editable, 130 | ) 131 | else: 132 | await c.upload_document( 133 | chat_id=m.chat.id, 134 | document=f"{dl_loc}{new_file_name}", 135 | editable_message=editable, 136 | thumb=_default_thumb_ or None 137 | ) 138 | await rm_dir(root_dl_loc) 139 | -------------------------------------------------------------------------------- /bot/plugins/callbacks.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | from pyrogram import types 4 | from bot.client import Client 5 | from bot.core.db.database import db 6 | from bot.core.file_info import ( 7 | get_media_file_name, 8 | get_media_file_size, 9 | get_file_type, 10 | get_file_attr 11 | ) 12 | from bot.core.display import humanbytes 13 | from bot.core.handlers.settings import show_settings 14 | 15 | 16 | @Client.on_callback_query() 17 | async def cb_handlers(c: Client, cb: "types.CallbackQuery"): 18 | if cb.data == "showSettings": 19 | await cb.answer() 20 | await show_settings(cb.message) 21 | elif cb.data == "showThumbnail": 22 | thumbnail = await db.get_thumbnail(cb.from_user.id) 23 | if not thumbnail: 24 | await cb.answer("You didn't set any custom thumbnail!", show_alert=True) 25 | else: 26 | await cb.answer() 27 | await c.send_photo(cb.message.chat.id, thumbnail, "Custom Thumbnail", 28 | reply_markup=types.InlineKeyboardMarkup([[ 29 | types.InlineKeyboardButton("Delete Thumbnail", 30 | callback_data="deleteThumbnail") 31 | ]])) 32 | elif cb.data == "deleteThumbnail": 33 | await db.set_thumbnail(cb.from_user.id, None) 34 | await cb.answer("Okay, I deleted your custom thumbnail. Now I will apply default thumbnail.", show_alert=True) 35 | await cb.message.delete(True) 36 | elif cb.data == "setThumbnail": 37 | await cb.answer() 38 | await cb.message.edit("Send me any photo to set that as custom thumbnail.\n\n" 39 | "Press /cancel to cancel process.") 40 | from_user_thumb: "types.Message" = await c.listen(cb.message.chat.id) 41 | if not from_user_thumb.photo: 42 | await cb.message.edit("Process Cancelled!") 43 | return await from_user_thumb.continue_propagation() 44 | else: 45 | await db.set_thumbnail(cb.from_user.id, from_user_thumb.photo.file_id) 46 | await cb.message.edit("Okay!\n" 47 | "Now I will apply this thumbnail to next uploads.", 48 | reply_markup=types.InlineKeyboardMarkup( 49 | [[types.InlineKeyboardButton("Show Settings", 50 | callback_data="showSettings")]] 51 | )) 52 | elif cb.data == "setCustomCaption": 53 | await cb.answer() 54 | await cb.message.edit("Okay,\n" 55 | "Send me your custom caption.\n\n" 56 | "Press /cancel to cancel process.") 57 | user_input_msg: "types.Message" = await c.listen(cb.message.chat.id) 58 | if not user_input_msg.text: 59 | await cb.message.edit("Process Cancelled!") 60 | return await user_input_msg.continue_propagation() 61 | if user_input_msg.text and user_input_msg.text.startswith("/"): 62 | await cb.message.edit("Process Cancelled!") 63 | return await user_input_msg.continue_propagation() 64 | await db.set_caption(cb.from_user.id, user_input_msg.text.markdown) 65 | await cb.message.edit("Custom Caption Added Successfully!", 66 | reply_markup=types.InlineKeyboardMarkup( 67 | [[types.InlineKeyboardButton("Show Settings", 68 | callback_data="showSettings")]] 69 | )) 70 | elif cb.data == "triggerApplyCaption": 71 | await cb.answer() 72 | apply_caption = await db.get_apply_caption(cb.from_user.id) 73 | if not apply_caption: 74 | await db.set_apply_caption(cb.from_user.id, True) 75 | else: 76 | await db.set_apply_caption(cb.from_user.id, False) 77 | await show_settings(cb.message) 78 | elif cb.data == "triggerApplyDefaultCaption": 79 | await db.set_caption(cb.from_user.id, None) 80 | await cb.answer("Okay, now I will keep default caption.", show_alert=True) 81 | await show_settings(cb.message) 82 | elif cb.data == "showCaption": 83 | caption = await db.get_caption(cb.from_user.id) 84 | if not caption: 85 | await cb.answer("You didn't set any custom caption!", show_alert=True) 86 | else: 87 | await cb.answer() 88 | await cb.message.edit( 89 | text=caption, 90 | parse_mode="Markdown", 91 | reply_markup=types.InlineKeyboardMarkup([[ 92 | types.InlineKeyboardButton("Go Back", callback_data="showSettings") 93 | ]]) 94 | ) 95 | elif cb.data == "triggerUploadMode": 96 | await cb.answer() 97 | upload_as_doc = await db.get_upload_as_doc(cb.from_user.id) 98 | if upload_as_doc: 99 | await db.set_upload_as_doc(cb.from_user.id, False) 100 | else: 101 | await db.set_upload_as_doc(cb.from_user.id, True) 102 | await show_settings(cb.message) 103 | elif cb.data == "showFileInfo": 104 | replied_m = cb.message.reply_to_message 105 | _file_name = get_media_file_name(replied_m) 106 | text = f"**File Name:** `{_file_name}`\n\n" \ 107 | f"**File Extension:** `{_file_name.rsplit('.', 1)[-1].upper()}`\n\n" \ 108 | f"**File Type:** `{get_file_type(replied_m).upper()}`\n\n" \ 109 | f"**File Size:** `{humanbytes(get_media_file_size(replied_m))}`\n\n" \ 110 | f"**File MimeType:** `{get_file_attr(replied_m).mime_type}`" 111 | await cb.message.edit( 112 | text=text, 113 | parse_mode="Markdown", 114 | disable_web_page_preview=True, 115 | reply_markup=types.InlineKeyboardMarkup( 116 | [[types.InlineKeyboardButton("Close Message", callback_data="closeMessage")]] 117 | ) 118 | ) 119 | elif cb.data == "closeMessage": 120 | await cb.message.delete(True) 121 | -------------------------------------------------------------------------------- /bot/core/new/upload_video.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import os 4 | import time 5 | import random 6 | import traceback 7 | from typing import ( 8 | List, 9 | Union, 10 | Optional, 11 | BinaryIO 12 | ) 13 | from PIL import Image 14 | from pyrogram.types import ( 15 | InlineKeyboardMarkup, 16 | ReplyKeyboardMarkup, 17 | ReplyKeyboardRemove, 18 | ForceReply, 19 | MessageEntity, 20 | Message 21 | ) 22 | from hachoir.parser import createParser 23 | from hachoir.metadata import extractMetadata 24 | 25 | from configs import Config 26 | from bot.core.ffmpeg import take_screen_shot 27 | from bot.core.utils.video_info import get_audio_or_video_duration, get_video_height, get_video_width 28 | from bot.core.display import progress_for_pyrogram 29 | 30 | 31 | class UploadVideo: 32 | async def upload_video( 33 | self, 34 | chat_id: Union[int, str], 35 | video: str, 36 | editable_message: Message, 37 | caption: str = "", 38 | parse_mode: Optional[str] = object, 39 | caption_entities: List[MessageEntity] = None, 40 | ttl_seconds: Optional[int] = None, 41 | duration: int = 0, 42 | width: int = 0, 43 | height: int = 0, 44 | thumb: Union[str, BinaryIO] = None, 45 | file_name: str = None, 46 | supports_streaming: bool = True, 47 | disable_notification: bool = None, 48 | reply_to_message_id: int = None, 49 | schedule_date: int = None, 50 | reply_markup: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None] = None, 51 | status_message: str = "📤 Uploading as Video ..." 52 | ) -> Optional[Message]: 53 | """ 54 | Advanced Video Uploader Function. 55 | 56 | :param chat_id: Unique identifier (int) or username (str) of the target chat. For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). 57 | :param video: Video to send. Pass a video file path as string to upload a new video that exists on your local machine. 58 | :param editable_message: Pass editable Message object for updating with progress text. 59 | :param parse_mode: By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. Pass "markdown" or "md" to enable Markdown-style parsing only. Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. 60 | :param caption: Video caption, 0-1024 characters. 61 | :param caption_entities: List of special entities that appear in the caption, which can be specified instead of *parse_mode*. 62 | :param ttl_seconds: Self-Destruct Timer. If you set a timer, the video will self-destruct in *ttl_seconds* seconds after it was viewed. 63 | :param duration: Duration of sent video in seconds. By default function will try to get duration data from video. 64 | :param width: Video width. By default function will try to get width data from video. 65 | :param height: Video height. By default function will try to get height data from video. 66 | :param thumb: Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. By default function will take random screenshot from video and use it as thumbnail. 67 | :param file_name: File name of the video sent. Defaults to file's path basename. 68 | :param supports_streaming: Pass True, if the uploaded video is suitable for streaming. Defaults to True. 69 | :param disable_notification: Sends the message silently. Users will receive a notification with no sound. 70 | :param reply_to_message_id: If the message is a reply, ID of the original message. 71 | :param schedule_date: Date when the message will be automatically sent. Unix time. 72 | :param reply_markup: Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. 73 | :param status_message: Pass status string. Default: "📤 Uploading as Video ..." 74 | """ 75 | 76 | duration = duration or (await get_audio_or_video_duration(video)) 77 | height = height or (await get_video_height(video)) 78 | width = width or (await get_video_width(video)) 79 | metadata = extractMetadata(createParser(video)) 80 | if metadata.has("duration"): 81 | duration = metadata.get("duration").seconds 82 | if thumb is None: 83 | if not os.path.exists(video): 84 | return None 85 | try: 86 | ss_dir = f"{Config.DOWN_PATH}/Thumbnails/{str(time.time())}/" 87 | thumbnail = await take_screen_shot( 88 | video_file=video, 89 | output_directory=ss_dir, 90 | ttl=random.randint(0, (duration or 1) - 1) 91 | ) 92 | if metadata.has("width"): 93 | t_width = metadata.get("width") 94 | else: 95 | t_width = width or 90 96 | if metadata.has("height"): 97 | t_height = metadata.get("height") 98 | else: 99 | t_height = height or 90 100 | if os.path.exists(thumbnail): 101 | Image.open(thumbnail).convert("RGB").save(thumbnail) 102 | img = Image.open(thumbnail) 103 | img.resize((width, height)) 104 | img.save(thumbnail, "JPEG") 105 | else: 106 | thumbnail = None 107 | except Exception as error: 108 | print("Unable to Get Video Data!\n\n" 109 | f"Error: {error}") 110 | traceback.print_exc() 111 | thumbnail = None 112 | else: 113 | thumbnail = thumb 114 | if not caption: 115 | caption = f"**File Name:** `{os.path.basename(video)}`" \ 116 | "\n\n**@AH_RenameBot**" 117 | c_time = time.time() 118 | await self.send_video( 119 | chat_id=chat_id, 120 | video=video, 121 | caption=caption, 122 | parse_mode=parse_mode, 123 | caption_entities=caption_entities, 124 | ttl_seconds=ttl_seconds, 125 | duration=duration, 126 | width=width or 90, 127 | height=height or 90, 128 | thumb=thumbnail, 129 | file_name=file_name, 130 | supports_streaming=supports_streaming, 131 | disable_notification=disable_notification, 132 | reply_to_message_id=reply_to_message_id, 133 | schedule_date=schedule_date, 134 | reply_markup=reply_markup, 135 | progress=progress_for_pyrogram, 136 | progress_args=( 137 | status_message, 138 | editable_message, 139 | c_time 140 | ) 141 | ) 142 | await editable_message.edit("Uploaded Successfully!") 143 | -------------------------------------------------------------------------------- /bot/core/new/normal_rename.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import os 4 | import time 5 | from typing import ( 6 | Union, 7 | Optional, 8 | List, 9 | BinaryIO 10 | ) 11 | from pyrogram import ( 12 | types, 13 | raw, 14 | utils, 15 | StopTransmission 16 | ) 17 | from pyrogram.scaffold import Scaffold 18 | from pyrogram.errors import ( 19 | FilePartMissing, 20 | MessageNotModified 21 | ) 22 | from configs import Config 23 | from bot.core.utils.rm import rm_file 24 | from bot.core.fixes import fix_thumbnail 25 | from bot.core.db.database import db 26 | from bot.core.display import progress_for_pyrogram 27 | from bot.core.utils.audio_info import get_audio_info 28 | from bot.core.utils.video_info import get_video_info 29 | from bot.core.utils.thumbnail_info import get_thumbnail_info 30 | 31 | 32 | class NormalRename(Scaffold): 33 | async def normal_rename( 34 | self, 35 | file_id: str, 36 | file_name: str, 37 | editable: "types.Message", 38 | chat_id: Union[int, str], 39 | upload_mode: str = "document", 40 | thumb: Union[str, BinaryIO] = None, 41 | caption: str = "", 42 | parse_mode: Optional[str] = object, 43 | caption_entities: List["types.MessageEntity"] = None, 44 | disable_notification: bool = None, 45 | reply_to_message_id: int = None, 46 | schedule_date: int = None, 47 | reply_markup: Union[ 48 | "types.InlineKeyboardMarkup", 49 | "types.ReplyKeyboardMarkup", 50 | "types.ReplyKeyboardRemove", 51 | "types.ForceReply" 52 | ] = None, 53 | **kwargs 54 | ): 55 | try: 56 | c_time = time.time() 57 | dl_file_path = await self.download_media( 58 | file_id, 59 | f"{Config.DOWNLOAD_DIR}/{chat_id}/{time.time()}/", 60 | progress=progress_for_pyrogram, 61 | progress_args=( 62 | "Downloading ...", 63 | editable, 64 | c_time 65 | ) 66 | ) 67 | if not os.path.exists(dl_file_path): 68 | return None, "File not found!" 69 | try: 70 | await editable.edit("Please Wait ...") 71 | except MessageNotModified: pass 72 | 73 | try: 74 | c_time = time.time() 75 | file = await self.save_file(dl_file_path, progress=progress_for_pyrogram, progress_args=( 76 | "Uploading ...", 77 | editable, 78 | c_time 79 | )) 80 | 81 | await editable.edit("Processing Thumbnail ...") 82 | upload_as_doc = await db.get_upload_as_doc(chat_id) 83 | has_db_thumb = await db.get_thumbnail(chat_id) 84 | width = kwargs.get("width", 0) 85 | height = kwargs.get("height", 0) 86 | if has_db_thumb or (not upload_as_doc): 87 | if (not width) or (not height): 88 | height, width = await get_thumbnail_info(thumb) 89 | resize_thumb = kwargs.get("resize_thumb", False) 90 | if resize_thumb: 91 | thumb = await fix_thumbnail(thumb, height) 92 | _thumb = await self.save_file(thumb) 93 | await rm_file(thumb) 94 | else: 95 | _thumb = None 96 | 97 | if (upload_as_doc is True) or (upload_mode == "document"): 98 | media = raw.types.InputMediaUploadedDocument( 99 | mime_type=self.guess_mime_type(dl_file_path) or "application/zip", 100 | file=file, 101 | thumb=_thumb, 102 | attributes=[ 103 | raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(dl_file_path)) 104 | ] 105 | ) 106 | elif (upload_as_doc is False) and (upload_mode == "video"): 107 | duration = kwargs.get("duration", 0) 108 | if not duration: 109 | await editable.edit("Fetching Video Duration ...") 110 | duration, _, __ = await get_video_info(dl_file_path) 111 | media = raw.types.InputMediaUploadedDocument( 112 | mime_type=self.guess_mime_type(dl_file_path) or "video/mp4", 113 | file=file, 114 | thumb=_thumb, 115 | attributes=[ 116 | raw.types.DocumentAttributeVideo( 117 | supports_streaming=True, 118 | duration=duration, 119 | w=width, 120 | h=height 121 | ), 122 | raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(dl_file_path)) 123 | ] 124 | ) 125 | elif (upload_as_doc is False) and (upload_mode == "audio"): 126 | 127 | duration = kwargs.get("duration", 0) 128 | if not duration: 129 | duration = await get_audio_info(dl_file_path) 130 | performer = kwargs.get("performer", None) 131 | title = kwargs.get("title", None) 132 | 133 | media = raw.types.InputMediaUploadedDocument( 134 | mime_type=self.guess_mime_type(dl_file_path) or "audio/mpeg", 135 | file=file, 136 | thumb=_thumb, 137 | attributes=[ 138 | raw.types.DocumentAttributeAudio( 139 | duration=duration, 140 | performer=performer, 141 | title=title 142 | ), 143 | raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(dl_file_path)) 144 | ] 145 | ) 146 | 147 | else: 148 | await editable.edit("I can't rename this type of media!") 149 | await rm_file(dl_file_path) 150 | return None, "InvalidMedia" 151 | 152 | while True: 153 | try: 154 | r = await self.send( 155 | raw.functions.messages.SendMedia( 156 | peer=await self.resolve_peer(chat_id), 157 | media=media, 158 | silent=disable_notification or None, 159 | reply_to_msg_id=reply_to_message_id, 160 | random_id=self.rnd_id(), 161 | schedule_date=schedule_date, 162 | reply_markup=await reply_markup.write(self) if reply_markup else None, 163 | **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) 164 | ) 165 | ) 166 | except FilePartMissing as e: 167 | await self.save_file(dl_file_path, file_id=file.id, file_part=e.x) 168 | else: 169 | await editable.edit("Uploaded Successfully!") 170 | await rm_file(dl_file_path) 171 | return True, False 172 | except StopTransmission: 173 | await rm_file(dl_file_path) 174 | return None, "StopTransmission" 175 | except Exception as err: 176 | Config.LOGGER.getLogger(__name__).error(err) 177 | return None, f"{err}" 178 | -------------------------------------------------------------------------------- /bot/core/handlers/big_rename.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 2 | 3 | import traceback 4 | from typing import Union 5 | from bot.client import Client 6 | from pyrogram import ( 7 | raw, 8 | utils 9 | ) 10 | from pyrogram.types import ( 11 | Message 12 | ) 13 | from configs import Config 14 | from bot.core.utils.rm import rm_dir 15 | from bot.core.fixes import fix_thumbnail 16 | from bot.core.db.database import db 17 | from bot.core.file_info import ( 18 | get_thumb_file_id, 19 | get_media_mime_type 20 | ) 21 | 22 | 23 | async def handle_big_rename( 24 | c: Client, 25 | m: Message, 26 | file_id: Union[ 27 | "raw.types.InputFileBig", 28 | "raw.types.InputFile" 29 | ], 30 | file_name: str, 31 | editable: Message, 32 | file_type: str 33 | ): 34 | await editable.edit("Sending to you ...") 35 | upload_as_doc = await db.get_upload_as_doc(m.from_user.id) 36 | 37 | if (upload_as_doc is False) and (file_type == "video"): 38 | ttl_seconds = None 39 | supports_streaming = m.reply_to_message.video.supports_streaming \ 40 | if m.reply_to_message.video.supports_streaming \ 41 | else None 42 | duration = m.reply_to_message.video.duration \ 43 | if m.reply_to_message.video.duration \ 44 | else 0 45 | width = m.reply_to_message.video.width \ 46 | if m.reply_to_message.video.width \ 47 | else 0 48 | height = m.reply_to_message.video.height \ 49 | if m.reply_to_message.video.height \ 50 | else 0 51 | mime_type = m.reply_to_message.video.mime_type \ 52 | if m.reply_to_message.video.mime_type \ 53 | else "video/mp4" 54 | _f_thumb = m.reply_to_message.video.thumbs[0] \ 55 | if m.reply_to_message.video.thumbs \ 56 | else None 57 | _db_thumb = await db.get_thumbnail(m.from_user.id) 58 | thumbnail_file_id = _db_thumb \ 59 | if _db_thumb \ 60 | else (_f_thumb.file_id 61 | if _f_thumb 62 | else None) 63 | if thumbnail_file_id: 64 | await editable.edit("Fetching Thumbnail ...") 65 | thumb_path = await c.download_media(thumbnail_file_id, 66 | f"{Config.DOWNLOAD_DIR}/{m.from_user.id}/{m.message_id}/") 67 | if _db_thumb: 68 | thumb_path = await fix_thumbnail(thumb_path) 69 | thumb = await c.save_file(path=thumb_path) 70 | else: 71 | thumb = None 72 | 73 | media = raw.types.InputMediaUploadedDocument( 74 | mime_type=mime_type, 75 | file=file_id, 76 | ttl_seconds=ttl_seconds, 77 | thumb=thumb, 78 | attributes=[ 79 | raw.types.DocumentAttributeVideo( 80 | supports_streaming=supports_streaming, 81 | duration=duration, 82 | w=width, 83 | h=height 84 | ), 85 | raw.types.DocumentAttributeFilename(file_name=file_name) 86 | ] 87 | ) 88 | 89 | elif (upload_as_doc is False) and (file_type == "audio"): 90 | _f_thumb = m.reply_to_message.audio.thumbs[0] \ 91 | if m.reply_to_message.audio.thumbs \ 92 | else None 93 | _db_thumb = await db.get_thumbnail(m.from_user.id) 94 | thumbnail_file_id = _db_thumb \ 95 | if _db_thumb \ 96 | else (_f_thumb.file_id 97 | if _f_thumb 98 | else None) 99 | if thumbnail_file_id: 100 | await editable.edit("Fetching Thumbnail ...") 101 | thumb_path = await c.download_media(thumbnail_file_id, 102 | f"{Config.DOWNLOAD_DIR}/{m.from_user.id}/{m.message_id}/") 103 | if _db_thumb: 104 | thumb_path = await fix_thumbnail(thumb_path) 105 | thumb = await c.save_file(path=thumb_path) 106 | else: 107 | thumb = None 108 | mime_type = m.reply_to_message.audio.mime_type \ 109 | if m.reply_to_message.audio.mime_type \ 110 | else "audio/mpeg" 111 | duration = m.reply_to_message.audio.duration \ 112 | if m.reply_to_message.audio.duration \ 113 | else None 114 | performer = m.reply_to_message.audio.performer \ 115 | if m.reply_to_message.audio.performer \ 116 | else None 117 | title = m.reply_to_message.audio.title \ 118 | if m.reply_to_message.audio.title \ 119 | else None 120 | 121 | media = raw.types.InputMediaUploadedDocument( 122 | mime_type=mime_type, 123 | file=file_id, 124 | force_file=None, 125 | thumb=thumb, 126 | attributes=[ 127 | raw.types.DocumentAttributeAudio( 128 | duration=duration, 129 | performer=performer, 130 | title=title 131 | ), 132 | raw.types.DocumentAttributeFilename(file_name=file_name) 133 | ] 134 | ) 135 | 136 | elif (upload_as_doc is True) or (file_type == "document"): 137 | _f_thumb = get_thumb_file_id(m.reply_to_message) 138 | _db_thumb = await db.get_thumbnail(m.from_user.id) 139 | thumbnail_file_id = _db_thumb \ 140 | if _db_thumb \ 141 | else (_f_thumb 142 | if _f_thumb 143 | else None) 144 | if thumbnail_file_id: 145 | await editable.edit("Fetching Thumbnail ...") 146 | thumb_path = await c.download_media(thumbnail_file_id, 147 | f"{Config.DOWNLOAD_DIR}/{m.from_user.id}/{m.message_id}/") 148 | if _db_thumb: 149 | thumb_path = await fix_thumbnail(thumb_path) 150 | thumb = await c.save_file(path=thumb_path) 151 | else: 152 | thumb = None 153 | mime_type = get_media_mime_type(m.reply_to_message) or "application/zip" 154 | 155 | media = raw.types.InputMediaUploadedDocument( 156 | mime_type=mime_type, 157 | file=file_id, 158 | force_file=True, 159 | thumb=thumb, 160 | attributes=[ 161 | raw.types.DocumentAttributeFilename(file_name=file_name) 162 | ] 163 | ) 164 | else: 165 | return await editable.edit("I can't rename it!") 166 | 167 | reply_markup = m.reply_to_message.reply_markup \ 168 | if m.reply_to_message.reply_markup \ 169 | else None 170 | _db_caption = await db.get_caption(m.from_user.id) 171 | apply_caption = await db.get_apply_caption(m.from_user.id) 172 | if (not _db_caption) and (apply_caption is True): 173 | caption = m.reply_to_message.caption.markdown \ 174 | if m.reply_to_message.caption \ 175 | else "**Developer: @AbirHasan2005**" 176 | elif _db_caption and (apply_caption is True): 177 | caption = _db_caption 178 | else: 179 | caption = "" 180 | parse_mode = "Markdown" 181 | 182 | try: 183 | r = await c.send( 184 | raw.functions.messages.SendMedia( 185 | peer=await c.resolve_peer(m.chat.id), 186 | media=media, 187 | silent=None, 188 | reply_to_msg_id=None, 189 | random_id=c.rnd_id(), 190 | schedule_date=None, 191 | reply_markup=await reply_markup.write(c) if reply_markup else None, 192 | **await utils.parse_text_entities(c, caption, parse_mode, None) 193 | ) 194 | ) 195 | await rm_dir(f"{Config.DOWNLOAD_DIR}/{m.from_user.id}/{m.message_id}/") 196 | except Exception as _err: 197 | Config.LOGGER.getLogger(__name__).error(_err) 198 | Config.LOGGER.getLogger(__name__).info(f"{traceback.format_exc()}") 199 | else: 200 | await editable.edit("Uploaded Successfully!") 201 | -------------------------------------------------------------------------------- /bot/core/new/custom_uploader.py: -------------------------------------------------------------------------------- 1 | # (c) @AbirHasan2005 & @subinps 2 | # Bruh! 3 | # Took a huge time to make this script. 4 | # So Plis, 5 | # !! Don't Copy without credits !! 6 | 7 | # Some Parts Copied from: 8 | # https://github.com/pyrogram/pyrogram/blob/master/pyrogram/methods/advanced/save_file.py 9 | # https://github.com/pyrogram/pyrogram/blob/master/pyrogram/methods/messages/send_video.py 10 | # https://github.com/pyrogram/pyrogram/blob/master/pyrogram/methods/messages/send_audio.py 11 | # https://github.com/pyrogram/pyrogram/blob/master/pyrogram/methods/messages/send_document.py 12 | 13 | import os 14 | import io 15 | import math 16 | import inspect 17 | import asyncio 18 | import functools 19 | from configs import Config 20 | from hashlib import sha256, md5 21 | from pyrogram.crypto import aes 22 | from pyrogram import raw 23 | from pyrogram import utils 24 | from pyrogram import StopTransmission 25 | from pyrogram.errors import ( 26 | AuthBytesInvalid, 27 | VolumeLocNotFound 28 | ) 29 | from pyrogram.file_id import ( 30 | FileId, 31 | FileType, 32 | ThumbnailSource 33 | ) 34 | from pyrogram.session import ( 35 | Auth, 36 | Session 37 | ) 38 | from pyrogram.scaffold import Scaffold 39 | 40 | LOGGER = Config.LOGGER 41 | log = LOGGER.getLogger(__name__) 42 | 43 | 44 | class CustomUploader(Scaffold): 45 | async def custom_upload( 46 | self, 47 | file_id: FileId, 48 | file_size: int, 49 | file_name: str, 50 | progress: callable, 51 | progress_args: tuple = () 52 | ): 53 | dc_id = file_id.dc_id 54 | 55 | async with self.media_sessions_lock: 56 | session = self.media_sessions.get(dc_id, None) 57 | 58 | if session is None: 59 | if dc_id != await self.storage.dc_id(): 60 | session = Session( 61 | self, dc_id, await Auth(self, dc_id, await self.storage.test_mode()).create(), 62 | await self.storage.test_mode(), is_media=True 63 | ) 64 | await session.start() 65 | 66 | for _ in range(3): 67 | exported_auth = await self.send( 68 | raw.functions.auth.ExportAuthorization( 69 | dc_id=dc_id 70 | ) 71 | ) 72 | 73 | try: 74 | await session.send( 75 | raw.functions.auth.ImportAuthorization( 76 | id=exported_auth.id, 77 | bytes=exported_auth.bytes 78 | ) 79 | ) 80 | except AuthBytesInvalid: 81 | continue 82 | else: 83 | break 84 | else: 85 | await session.stop() 86 | raise AuthBytesInvalid 87 | else: 88 | session = Session( 89 | self, dc_id, await self.storage.auth_key(), 90 | await self.storage.test_mode(), is_media=True 91 | ) 92 | await session.start() 93 | 94 | self.media_sessions[dc_id] = session 95 | 96 | async def worker(session): 97 | while True: 98 | data = await queue.get() 99 | 100 | if data is None: 101 | return 102 | 103 | try: 104 | await self.loop.create_task(session.send(data)) 105 | except Exception as e: 106 | log.error(e) 107 | 108 | file_type = file_id.file_type 109 | 110 | if file_type == FileType.CHAT_PHOTO: 111 | if file_id.chat_id > 0: 112 | peer = raw.types.InputPeerUser( 113 | user_id=file_id.chat_id, 114 | access_hash=file_id.chat_access_hash 115 | ) 116 | else: 117 | if file_id.chat_access_hash == 0: 118 | peer = raw.types.InputPeerChat( 119 | chat_id=-file_id.chat_id 120 | ) 121 | else: 122 | peer = raw.types.InputPeerChannel( 123 | channel_id=utils.get_channel_id(file_id.chat_id), 124 | access_hash=file_id.chat_access_hash 125 | ) 126 | 127 | location = raw.types.InputPeerPhotoFileLocation( 128 | peer=peer, 129 | volume_id=file_id.volume_id, 130 | local_id=file_id.local_id, 131 | big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG 132 | ) 133 | elif file_type == FileType.PHOTO: 134 | location = raw.types.InputPhotoFileLocation( 135 | id=file_id.media_id, 136 | access_hash=file_id.access_hash, 137 | file_reference=file_id.file_reference, 138 | thumb_size=file_id.thumbnail_size 139 | ) 140 | else: 141 | location = raw.types.InputDocumentFileLocation( 142 | id=file_id.media_id, 143 | access_hash=file_id.access_hash, 144 | file_reference=file_id.file_reference, 145 | thumb_size=file_id.thumbnail_size 146 | ) 147 | part_size = 512 * 1024 148 | 149 | limit = 1024 * 1024 150 | _n_file_id = None 151 | file_id_ = None 152 | file_part = 0 153 | offset = 0 154 | file_total_parts = int(math.ceil(file_size / part_size)) 155 | is_big = file_size > 10 * 1024 * 1024 156 | pool_size = 3 if is_big else 1 157 | workers_count = 4 if is_big else 1 158 | 159 | pool = [ 160 | Session( 161 | self, await self.storage.dc_id(), await self.storage.auth_key(), 162 | await self.storage.test_mode(), is_media=True 163 | ) for _ in range(pool_size) 164 | ] 165 | workers = [self.loop.create_task(worker(session_)) for session_ in pool for _ in range(workers_count)] 166 | queue = asyncio.Queue(16) 167 | 168 | try: 169 | for session_ in pool: 170 | await session_.start() 171 | 172 | try: 173 | r = await session.send( 174 | raw.functions.upload.GetFile( 175 | location=location, 176 | offset=offset, 177 | limit=limit 178 | ), 179 | sleep_threshold=30 180 | ) 181 | 182 | if isinstance(r, raw.types.upload.File): 183 | while True: 184 | chunk = r.bytes 185 | fp_ = chunk 186 | fp = io.BytesIO(fp_) 187 | fp.seek(0, os.SEEK_END) 188 | file_size_ = fp.tell() 189 | fp.seek(0) 190 | 191 | if not chunk: 192 | break 193 | 194 | if file_size_ == 0: 195 | raise ValueError("File size equals to 0 B") 196 | 197 | if file_size_ > 2000 * 1024 * 1024: 198 | raise ValueError("Telegram doesn't support uploading files bigger than 2000 MiB") 199 | 200 | is_missing_part = _n_file_id is not None 201 | file_id_ = file_id_ or self.rnd_id() 202 | md5_sum = md5() if not is_big and not is_missing_part else None 203 | 204 | with fp: 205 | file_part_ = 0 206 | fp.seek(part_size * file_part_) 207 | 208 | while True: 209 | chunk_ = fp.read(part_size) 210 | 211 | if not chunk_: 212 | if not is_big and not is_missing_part: 213 | md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) 214 | break 215 | 216 | if is_big: 217 | rpc = raw.functions.upload.SaveBigFilePart( 218 | file_id=file_id_, 219 | file_part=file_part, 220 | file_total_parts=file_total_parts, 221 | bytes=chunk_ 222 | ) 223 | else: 224 | rpc = raw.functions.upload.SaveFilePart( 225 | file_id=file_id_, 226 | file_part=file_part, 227 | bytes=chunk_ 228 | ) 229 | # await session.send(rpc) 230 | 231 | await queue.put(rpc) 232 | 233 | if is_missing_part: 234 | return 235 | 236 | if not is_big and not is_missing_part: 237 | md5_sum.update(chunk_) 238 | 239 | file_part_ += 1 240 | file_part += 1 241 | 242 | offset += limit 243 | 244 | if progress: 245 | func = functools.partial( 246 | progress, 247 | min(offset, file_size) 248 | if file_size != 0 249 | else offset, 250 | file_size, 251 | *progress_args 252 | ) 253 | 254 | if inspect.iscoroutinefunction(progress): 255 | await func() 256 | else: 257 | await self.loop.run_in_executor(self.executor, func) 258 | r = await session.send( 259 | raw.functions.upload.GetFile( 260 | location=location, 261 | offset=offset, 262 | limit=limit 263 | ), 264 | sleep_threshold=30 265 | ) 266 | 267 | elif isinstance(r, raw.types.upload.FileCdnRedirect): 268 | async with self.media_sessions_lock: 269 | cdn_session = self.media_sessions.get(r.dc_id, None) 270 | 271 | if cdn_session is None: 272 | cdn_session = Session( 273 | self, r.dc_id, await Auth(self, r.dc_id, await self.storage.test_mode()).create(), 274 | await self.storage.test_mode(), is_media=True, is_cdn=True 275 | ) 276 | 277 | await cdn_session.start() 278 | 279 | self.media_sessions[r.dc_id] = cdn_session 280 | 281 | try: 282 | while True: 283 | r2 = await cdn_session.send( 284 | raw.functions.upload.GetCdnFile( 285 | file_token=r.file_token, 286 | offset=offset, 287 | limit=limit 288 | ) 289 | ) 290 | 291 | if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): 292 | try: 293 | await session.send( 294 | raw.functions.upload.ReuploadCdnFile( 295 | file_token=r.file_token, 296 | request_token=r2.request_token 297 | ) 298 | ) 299 | except VolumeLocNotFound: 300 | break 301 | else: 302 | continue 303 | 304 | chunk = r2.bytes 305 | 306 | # https://core.telegram.org/cdn#decrypting-files 307 | decrypted_chunk = aes.ctr256_decrypt( 308 | chunk, 309 | r.encryption_key, 310 | bytearray( 311 | r.encryption_iv[:-4] 312 | + (offset // 16).to_bytes(4, "big") 313 | ) 314 | ) 315 | 316 | hashes = await session.send( 317 | raw.functions.upload.GetCdnFileHashes( 318 | file_token=r.file_token, 319 | offset=offset 320 | ) 321 | ) 322 | 323 | # https://core.telegram.org/cdn#verifying-files 324 | for i, h in enumerate(hashes): 325 | cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] 326 | assert h.hash == sha256(cdn_chunk).digest(), f"Invalid CDN hash part {i}" 327 | 328 | fp_ = decrypted_chunk 329 | fp = io.BytesIO(fp_) 330 | fp.seek(0, os.SEEK_END) 331 | file_size_ = fp.tell() 332 | fp.seek(0) 333 | 334 | if not chunk: 335 | break 336 | 337 | if file_size_ == 0: 338 | raise ValueError("File size equals to 0 B") 339 | 340 | if file_size_ > 2000 * 1024 * 1024: 341 | raise ValueError("Telegram doesn't support uploading files bigger than 2000 MiB") 342 | 343 | is_missing_part = _n_file_id is not None 344 | file_id_ = file_id_ or self.rnd_id() 345 | md5_sum = md5() if not is_big and not is_missing_part else None 346 | 347 | with fp: 348 | file_part_ = 0 349 | fp.seek(part_size * file_part_) 350 | 351 | while True: 352 | chunk_ = fp.read(part_size) 353 | 354 | if not chunk_: 355 | if not is_big and not is_missing_part: 356 | md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) 357 | break 358 | 359 | if is_big: 360 | rpc = raw.functions.upload.SaveBigFilePart( 361 | file_id=file_id_, 362 | file_part=file_part, 363 | file_total_parts=file_total_parts, 364 | bytes=chunk_ 365 | ) 366 | else: 367 | rpc = raw.functions.upload.SaveFilePart( 368 | file_id=file_id_, 369 | file_part=file_part, 370 | bytes=chunk_ 371 | ) 372 | # await session.send(rpc) 373 | 374 | await queue.put(rpc) 375 | 376 | if is_missing_part: 377 | return 378 | 379 | if not is_big and not is_missing_part: 380 | md5_sum.update(chunk_) 381 | 382 | file_part_ += 1 383 | file_part += 1 384 | 385 | # f.write(decrypted_chunk) 386 | 387 | offset += limit 388 | 389 | if progress: 390 | func = functools.partial( 391 | progress, 392 | min(offset, file_size) 393 | if file_size != 0 394 | else offset, 395 | file_size, 396 | *progress_args 397 | ) 398 | 399 | if inspect.iscoroutinefunction(progress): 400 | await func() 401 | else: 402 | await self.loop.run_in_executor(self.executor, func) 403 | 404 | if len(chunk) < limit: 405 | break 406 | except Exception as e: 407 | log.error(e, exc_info=True) 408 | raise e 409 | except Exception as e: 410 | if not isinstance(e, StopTransmission): 411 | log.error(str(e), exc_info=True) 412 | try: 413 | os.remove(file_name) 414 | except OSError: 415 | pass 416 | log.error("Error") 417 | return None 418 | 419 | except StopTransmission: 420 | raise 421 | except Exception as e: 422 | log.error(e, exc_info=True) 423 | else: 424 | if is_big: 425 | return raw.types.InputFileBig( 426 | id=file_id_, 427 | parts=file_total_parts, 428 | name=file_name 429 | ) 430 | else: 431 | return raw.types.InputFile( 432 | id=file_id_, 433 | parts=file_total_parts, 434 | name=file_name, 435 | md5_checksum=md5_sum 436 | ) 437 | finally: 438 | for _ in workers: 439 | await queue.put(None) 440 | 441 | await asyncio.gather(*workers) 442 | 443 | for session_ in pool: 444 | await session_.stop() 445 | --------------------------------------------------------------------------------