diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..cc0612c61 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# PyCharm +.idea/ +*.iml +*.ipr +*.iws + +# VSCode +.vscode/ +.vscode/settings.json +.vscode/tasks.json +.vscode/launch.json +.vscode/extensions.json +.vscode/*.code-workspace + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Pyrogram/Telethon specific +*.session +*.session-journal + +# Local environment variables +.env.local +.env.dev +.env.test +.env.prod + +# Log files +*.log + +# test files +*.test + diff --git a/Dockerfile b/Dockerfile index 0bd91ea89..649864692 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM python:3.9.2-slim-buster +FROM python:3.10-slim RUN mkdir /app && chmod 777 /app WORKDIR /app ENV DEBIAN_FRONTEND=noninteractive -RUN apt -qq update && apt -qq install -y git python3 python3-pip ffmpeg +RUN apt -qq update && apt -qq install -y git python3 python3-pip ffmpeg p7zip-full COPY . . RUN pip3 install --no-cache-dir -r requirements.txt CMD ["bash","bash.sh"] diff --git a/docker-compose.yml b/docker-compose.yml index 51b41620d..f24e261f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,3 +12,6 @@ services: SESSION: # Pyrogram string session AUTH: # User ID of Bot owner FORCESUB: # Username name of public channel without using '@' + # optionals + MAX_SPLIT_SIZE: # max file split size in Mb, if you don't have telegram premium - default is 2048 (for 2GB), if you have premium skip this option + BATCH_SIZE: # batch size - default is 100 \ No newline at end of file diff --git a/main/__init__.py b/main/__init__.py index f4f60cf34..1c076275e 100644 --- a/main/__init__.py +++ b/main/__init__.py @@ -8,6 +8,8 @@ from decouple import config import logging, time, sys +from main.utils import logger + logging.basicConfig(format='[%(levelname) 5s/%(asctime)s] %(name)s: %(message)s', level=logging.WARNING) @@ -19,6 +21,7 @@ FORCESUB = config("FORCESUB", default=None) AUTH = config("AUTH", default=None, cast=int) + bot = TelegramClient('bot', API_ID, API_HASH).start(bot_token=BOT_TOKEN) userbot = Client("saverestricted", session_string=SESSION, api_hash=API_HASH, api_id=API_ID) @@ -26,7 +29,7 @@ try: userbot.start() except BaseException: - print("Userbot Error ! Have you added SESSION while deploying??") + logger.error("Userbot Error ! Have you added SESSION while deploying??") sys.exit(1) Bot = Client( @@ -39,5 +42,5 @@ try: Bot.start() except Exception as e: - print(e) + logger.error(e) sys.exit(1) diff --git a/main/__main__.py b/main/__main__.py index fda9da21e..1f8a4a0ca 100644 --- a/main/__main__.py +++ b/main/__main__.py @@ -4,6 +4,8 @@ import logging from . import bot +from main.utils import logger + logging.basicConfig(format='[%(levelname) 5s/%(asctime)s] %(name)s: %(message)s', level=logging.WARNING) @@ -16,8 +18,8 @@ load_plugins(plugin_name.replace(".py", "")) #Don't be a thief -print("Successfully deployed!") -print("By MaheshChauhan • DroneBots") +logger.info("Successfully deployed!") +logger.info("By MaheshChauhan • DroneBots") if __name__ == "__main__": bot.run_until_disconnected() diff --git a/main/plugins/batch.py b/main/plugins/batch.py index 2bb76d965..be2e4f21c 100644 --- a/main/plugins/batch.py +++ b/main/plugins/batch.py @@ -5,13 +5,15 @@ Plugin for both public & private channels! """ -import time, os, asyncio +import time, os, asyncio, decouple from .. import bot as Drone from .. import userbot, Bot, AUTH from .. import FORCESUB as fs from main.plugins.pyroplug import get_bulk_msg from main.plugins.helpers import get_link, screenshot +from main.utils import logger + from telethon import events, Button, errors from telethon.tl.types import DocumentAttributeVideo @@ -26,6 +28,8 @@ batch = [] +BATCH_SIZE = decouple.config("BATCH_SIZE", 100) + @Drone.on(events.NewMessage(incoming=True, from_users=AUTH, pattern='/cancel')) async def cancel(event): if not event.sender_id in batch: @@ -37,7 +41,7 @@ async def cancel(event): async def _batch(event): if not event.is_private: return - s, r = await force_sub(event.client, fs, event.sender_id, ft) + s, r = await force_sub(event.client, fs, event.sender_id, ft) if s == True: await event.reply(r) return @@ -54,19 +58,19 @@ async def _batch(event): await conv.send_message("No link found.") return conv.cancel() except Exception as e: - print(e) + logger.error(e) await conv.send_message("Cannot wait more longer for your response!") return conv.cancel() await conv.send_message("Send me the number of files/range you want to save from the given message, as a reply to this message.", buttons=Button.force_reply()) try: _range = await conv.get_reply() except Exception as e: - print(e) + logger.error(e) await conv.send_message("Cannot wait more longer for your response!") return conv.cancel() try: value = int(_range.text) - if value > 100: + if value > BATCH_SIZE: await conv.send_message("You can only get upto 100 files in a single batch.") return conv.cancel() except ValueError: @@ -96,7 +100,7 @@ async def run_batch(userbot, client, sender, link, _range): await client.send_message(sender, "Batch completed.") break except Exception as e: - print(e) + logger.error(e) await client.send_message(sender, "Batch completed.") break try: diff --git a/main/plugins/frontend.py b/main/plugins/frontend.py index b35c84ea0..85fdc2d64 100644 --- a/main/plugins/frontend.py +++ b/main/plugins/frontend.py @@ -7,6 +7,7 @@ from .. import FORCESUB as fs from main.plugins.pyroplug import get_msg from main.plugins.helpers import get_link, join +from main.utils import logger from telethon import events from pyrogram.errors import FloodWait @@ -44,6 +45,6 @@ async def clone(event): except FloodWait as fw: return await Drone.send_message(event.sender_id, f'Try again after {fw.x} seconds due to floodwait from telegram.') except Exception as e: - print(e) + logger.error(e) await Drone.send_message(event.sender_id, f"An error occurred during cloning of `{link}`\n\n**Error:** {str(e)}") diff --git a/main/plugins/helpers.py b/main/plugins/helpers.py index bc3ed2a47..22609f99e 100644 --- a/main/plugins/helpers.py +++ b/main/plugins/helpers.py @@ -1,5 +1,7 @@ #Github.com/Vasusen-code +from main.utils import logger + from pyrogram.errors import FloodWait, InviteHashInvalid, InviteHashExpired, UserAlreadyParticipant from telethon import errors, events @@ -20,7 +22,7 @@ async def join(client, invite_link): except FloodWait: return "Too many requests, try again later." except Exception as e: - print(e) + logger.error(e) return "Could not join, try joining manually." #Regex--------------------------------------------------------------------------------------------------------------- diff --git a/main/plugins/pyroplug.py b/main/plugins/pyroplug.py index 1e608e86a..68db7b639 100644 --- a/main/plugins/pyroplug.py +++ b/main/plugins/pyroplug.py @@ -5,6 +5,9 @@ from .. import bot as Drone from main.plugins.progress import progress_for_pyrogram from main.plugins.helpers import screenshot +from main.utils import isPremium +from main.plugins.splitter import split_video, do_file_split +from main.utils import logger from pyrogram import Client, filters from pyrogram.errors import ChannelBanned, ChannelInvalid, ChannelPrivate, ChatIdInvalid, ChatInvalid, PeerIdInvalid @@ -64,17 +67,16 @@ async def get_msg(userbot, client, bot, sender, edit_id, msg_link, i): time.time() ) ) - print(file) + logger.info(file) await edit.edit('Preparing to Upload!') caption = None if msg.caption is not None: caption = msg.caption if msg.media==MessageMediaType.VIDEO_NOTE: round_message = True - print("Trying to get metadata") + logger.info("Trying to get metadata") data = video_metadata(file) height, width, duration = data["height"], data["width"], data["duration"] - print(f'd: {duration}, w: {width}, h:{height}') try: thumb_path = await screenshot(file, duration, sender) except Exception: @@ -93,48 +95,133 @@ async def get_msg(userbot, client, bot, sender, edit_id, msg_link, i): ) ) elif msg.media==MessageMediaType.VIDEO and msg.video.mime_type in ["video/mp4", "video/x-matroska"]: - print("Trying to get metadata") - data = video_metadata(file) - height, width, duration = data["height"], data["width"], data["duration"] - print(f'd: {duration}, w: {width}, h:{height}') - try: - thumb_path = await screenshot(file, duration, sender) - except Exception: - thumb_path = None - await client.send_video( - chat_id=sender, - video=file, - caption=caption, - supports_streaming=True, - height=height, width=width, duration=duration, - thumb=thumb_path, - progress=progress_for_pyrogram, - progress_args=( - client, - '**UPLOADING:**\n', - edit, - time.time() + logger.info("Trying to get metadata") + if (not (await isPremium(userbot)) and os.path.getsize(file) > 2 * 1024 * 1024 * 1024 ): + try: + splitted_files = split_video(file) + if not splitted_files: + logger.info(file) + raise Exception("The input files are corrupt") + for file in splitted_files: + data = video_metadata(file) + height, width, duration = data["height"], data["width"], data["duration"] + try: + thumb_path = await screenshot(file, duration, sender) + except Exception: + thumb_path = None + await client.send_video( + chat_id=sender, + video=file, + caption=caption, + supports_streaming=True, + height=height, width=width, duration=duration, + thumb=thumb_path, + progress=progress_for_pyrogram, + progress_args=( + client, "**UPLOADING**\n", + edit, + time.time() + ) + ) + + # clean up + try: + os.remove(file) + except: + try: + os.remove(file) + except: + pass + + # clean up + try: + os.remove(file) + except: + try: + os.remove(file) + except: + pass + except Exception as e: + await client.send_message(sender, f"An error occured while splitting the file\n\n{e}") + else: + data = video_metadata(file) + height, width, duration = data["height"], data["width"], data["duration"] + logger.info(f'd: {duration}, w: {width}, h:{height}') + try: + thumb_path = await screenshot(file, duration, sender) + except Exception: + thumb_path = None + + await client.send_video( + chat_id=sender, + video=file, + caption=caption, + supports_streaming=True, + height=height, width=width, duration=duration, + thumb=thumb_path, + progress=progress_for_pyrogram, + progress_args=( + client, + '**UPLOADING:**\n', + edit, + time.time() + ) ) - ) elif msg.media==MessageMediaType.PHOTO: await edit.edit("Uploading photo.") await bot.send_file(sender, file, caption=caption) else: - thumb_path=thumbnail(sender) - await client.send_document( - sender, - file, - caption=caption, - thumb=thumb_path, - progress=progress_for_pyrogram, - progress_args=( - client, - '**UPLOADING:**\n', - edit, - time.time() + thumb_path = thumbnail(sender) + if (not (await isPremium(client)) and os.path.getsize(file) > 2 * 1024 * 1024 * 1024): + # splitting the files + splitted_files = do_file_split(file) + for file in splitted_files: + await client.send_document( + sender, + file, + caption=caption, + thumb=thumb_path, + progress=progress_for_pyrogram, + progress_args=( + client, + '**UPLOADING:**\n', + edit, + time.time() + ) + ) + + # clean up + try: + os.remove(file) + except: + try: + os.remove(file) + except: + pass + + # clean up + try: + os.remove(file) + except: + try: + os.remove(file) + except: + pass + else: + await client.send_document( + sender, + file, + caption=caption, + thumb=thumb_path, + progress=progress_for_pyrogram, + progress_args=( + client, + '**UPLOADING:**\n', + edit, + time.time() + ) ) - ) try: os.remove(file) if os.path.isfile(file) == True: @@ -154,7 +241,7 @@ async def get_msg(userbot, client, bot, sender, edit_id, msg_link, i): new_link = f"t.me/b/{chat}/{msg_id}" return await get_msg(userbot, client, bot, sender, edit_id, msg_link, i) except Exception as e: - print(e) + logger.error(e) if "messages.SendMedia" in str(e) \ or "SaveBigFilePartRequest" in str(e) \ or "SendMediaRequest" in str(e) \ @@ -176,7 +263,7 @@ async def get_msg(userbot, client, bot, sender, edit_id, msg_link, i): if os.path.isfile(file) == True: os.remove(file) except Exception as e: - print(e) + logger.error(e) await client.edit_message_text(sender, edit_id, f'Failed to save: `{msg_link}`\n\nError: {str(e)}') try: os.remove(file) @@ -208,7 +295,7 @@ async def get_msg(userbot, client, bot, sender, edit_id, msg_link, i): return await get_msg(userbot, client, bot, sender, edit_id, new_link, i) await client.copy_message(sender, chat, msg_id) except Exception as e: - print(e) + logger.error(e) return await client.edit_message_text(sender, edit_id, f'Failed to save: `{msg_link}`\n\nError: {str(e)}') await edit.delete() diff --git a/main/plugins/splitter.py b/main/plugins/splitter.py new file mode 100644 index 000000000..4d2c16e32 --- /dev/null +++ b/main/plugins/splitter.py @@ -0,0 +1,82 @@ +import subprocess +import os +import math +from decouple import config +from time import sleep +from moviepy.editor import VideoFileClip + +from main.utils import logger + +MAX_SPLIT_SIZE = config("MAX_SPLIT_SIZE", default=2*1024) + +def split_video(video_path:str): + sleep(3) + clip = VideoFileClip(video_path) + duration = clip.duration + half_duration = duration / 2 + + base_path, ext = os.path.splitext(video_path) + base_path = "_".join(base_path.split()) + output_path_template = f"{base_path}_part%2d{ext}" + + file_name = video_path.split("/")[-1] + directory = video_path.split("/")[0:-1] + directory = "/".join(directory) + video_path = f"{directory}/'{file_name}'" + + + try: + split_command = f"ffmpeg -i {video_path} -loglevel fatal -c copy -segment_time {half_duration} -f segment -reset_timestamps 1 {output_path_template}" + return_code = subprocess.call(split_command, shell=True) + if return_code != 0: + logger.error(f"Error: ffmpeg command failed with return code {return_code}") + return [] + + sleep(3) + + video_paths = [f"{base_path}_part0{i}{ext}" for i in range(5) if os.path.exists(f"{base_path}_part0{i}{ext}")] + return video_paths + except: + logger.error("Error") + return [] + + + +def file_split_7z(file_path, split_size=MAX_SPLIT_SIZE): + file_path_7z_list = [] + + origin_file_path = "" + if os.path.splitext(file_path)[1] == ".7z": + origin_file_path = file_path + file_path = os.path.splitext(origin_file_path)[0] + ".7zo" + os.rename(origin_file_path, file_path) + + fz = os.path.getsize(file_path) / 1024 / 1024 + pa = math.ceil(fz / split_size) + head, ext = os.path.splitext(os.path.abspath(file_path)) + archive_head = "".join((head, ext.replace(".", "_"))) + ".7z" + for i in range(pa): + check_file_name = "{}.{:03d}".format(archive_head, i + 1) + if os.path.isfile(check_file_name): + logger.debug("remove exists file | {}".format(check_file_name)) + os.remove(check_file_name) + cmd_7z = ["7z", "a", "-v{}m".format(split_size), "-y", "-mx0", archive_head, file_path] + proc = subprocess.Popen(cmd_7z, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + if b"Everything is Ok" not in out: + logger.error("7z output | {}".format(out.decode("utf-8"))) + logger.error("7z error | {}".format(err.decode("utf-8"))) + return file_path_7z_list + + for i in range(pa): + file_path_7z_list.append("{}.{:03d}".format(archive_head, i + 1)) + + if origin_file_path: + os.rename(file_path, origin_file_path) + return file_path_7z_list + + +def do_file_split(file_path): + file_path_7z_list = file_split_7z(file_path) + return file_path_7z_list + diff --git a/main/utils.py b/main/utils.py index ba63ac827..f4fafba2b 100644 --- a/main/utils.py +++ b/main/utils.py @@ -2,6 +2,11 @@ import logging import importlib from pathlib import Path +from pyrogram import Client + +import logzero + +logger = logzero.logger def load_plugins(plugin_name): path = Path(f"main/plugins/{plugin_name}.py") @@ -12,3 +17,10 @@ def load_plugins(plugin_name): spec.loader.exec_module(load) sys.modules["main.plugins." + plugin_name] = load print("main has Imported " + plugin_name) + +async def isPremium(client:Client ) -> bool: + my_info = await client.get_me() + return my_info.is_premium + + + diff --git a/requirements.txt b/requirements.txt index 48fcbc6ff..3523572e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,5 @@ cryptg tgcrypto pyrogram python-decouple +logzero +moviepy \ No newline at end of file