├── .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 | [](https://youtu.be/edcOa_cZWg4)
36 |
37 |
38 | Press Below Button to Deploy!
39 |
40 | [](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 |
--------------------------------------------------------------------------------