diff --git a/README.md b/README.md index 20ec5624..42048c0e 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ In each single file there is a major change from base code, it's almost totaly d - Custom default video quality for each user - Fix download progress - Embed original thumbnail and add it for leech +- All supported audio formats ### Database - Mongo Database support - Store bot settings diff --git a/bot/helper/ext_utils/help_messages.py b/bot/helper/ext_utils/help_messages.py index 63d1330f..6ca4e025 100644 --- a/bot/helper/ext_utils/help_messages.py +++ b/bot/helper/ext_utils/help_messages.py @@ -44,6 +44,7 @@ Check here all RcloneFlags. 2. Options (s, m: and multi) should be added randomly before link and before any other option. 3. Options (n:, pswd: and opt:) should be added randomly after the link if link along with the cmd or after cmd if by reply. 4. You can always add video quality from yt-dlp api options. +5. Don't add file extension while rename using `n:` Check all yt-dlp api options from this FILE. """ diff --git a/bot/helper/mirror_utils/download_utils/yt_dlp_download.py b/bot/helper/mirror_utils/download_utils/yt_dlp_download.py index 60fa235e..6b28a51e 100644 --- a/bot/helper/mirror_utils/download_utils/yt_dlp_download.py +++ b/bot/helper/mirror_utils/download_utils/yt_dlp_download.py @@ -191,12 +191,18 @@ class YoutubeDLHelper: {'add_chapters': True, 'add_infojson': 'if_exists', 'add_metadata': True, 'key': 'FFmpegMetadata'}] if qual.startswith('ba/b-'): - mp3_info = qual.split('-') - qual = mp3_info[0] - rate = mp3_info[1] + audio_info = qual.split('-') + qual = audio_info[0] + audio_format = audio_info[1] + rate = audio_info[2] self.opts['postprocessors'].append( - {'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': rate}) - self.__ext = '.mp3' + {'key': 'FFmpegExtractAudio', 'preferredcodec': audio_format, 'preferredquality': rate}) + if audio_format == 'vorbis': + self.__ext = '.ogg' + elif audio_format == 'alac': + self.__ext = '.m4a' + else: + self.__ext = f'.{audio_format}' self.opts['format'] = qual @@ -226,7 +232,6 @@ class YoutubeDLHelper: elif not self.__listener.isLeech: self.opts['writethumbnail'] = False - msg, button = await stop_duplicate_check(name, self.__listener) if msg: await self.__listener.onDownloadError(msg, button) diff --git a/bot/helper/mirror_utils/rclone_utils/list.py b/bot/helper/mirror_utils/rclone_utils/list.py index cf3c7225..f0baa7b7 100644 --- a/bot/helper/mirror_utils/rclone_utils/list.py +++ b/bot/helper/mirror_utils/rclone_utils/list.py @@ -15,7 +15,6 @@ from bot.helper.telegram_helper.message_utils import sendMessage, editMessage from bot.helper.ext_utils.bot_utils import cmd_exec, new_thread, get_readable_file_size, new_task, get_readable_time LIST_LIMIT = 6 -TIMEOUT = 240 @new_task @@ -92,6 +91,7 @@ class RcloneList: self.__sections = [] self.__reply_to = None self.__time = time() + self.__timeout = 240 self.remote = '' self.is_cancelled = False self.query_proc = False @@ -111,7 +111,7 @@ class RcloneList: handler = self.__client.add_handler(CallbackQueryHandler( pfunc, filters=regex('^rcq') & user(self.__user_id)), group=-1) try: - await wait_for(self.event.wait(), timeout=240) + await wait_for(self.event.wait(), timeout=self.__timeout) except: self.path = '' self.remote = 'Timed Out. Task has been cancelled!' @@ -173,7 +173,7 @@ class RcloneList: msg += f' | Page: {int(page)}/{pages} | Page Step: {self.page_step}' msg += f'\n\nItem Type: {self.item_type}\nConfig Path: {self.config_path}' msg += f'\nCurrent Path: {self.remote}{self.path}' - msg += f'\nTimeout: {get_readable_time(TIMEOUT-(time()-self.__time))}' + msg += f'\nTimeout: {get_readable_time(self.__timeout-(time()-self.__time))}' await self.__send_list_message(msg, button) async def get_path(self, itype=''): @@ -218,7 +218,7 @@ class RcloneList: ('\nTransfer Type: Download' if self.list_status == 'rcd' else '\nTransfer Type: Upload') msg += f'\nConfig Path: {self.config_path}' - msg += f'\nTimeout: {get_readable_time(TIMEOUT-(time()-self.__time))}' + msg += f'\nTimeout: {get_readable_time(self.__timeout-(time()-self.__time))}' buttons = ButtonMaker() for remote in self.__sections: buttons.ibutton(remote, f'rcq re {remote}:') @@ -233,7 +233,7 @@ class RcloneList: msg = 'Choose Rclone config:' + \ ('\nTransfer Type: Download' if self.list_status == 'rcd' else '\nTransfer Type: Upload') - msg += f'\nTimeout: {get_readable_time(TIMEOUT-(time()-self.__time))}' + msg += f'\nTimeout: {get_readable_time(self.__timeout-(time()-self.__time))}' buttons = ButtonMaker() buttons.ibutton('Owner Config', 'rcq owner') buttons.ibutton('My Config', 'rcq user') diff --git a/bot/helper/mirror_utils/upload_utils/pyrogramEngine.py b/bot/helper/mirror_utils/upload_utils/pyrogramEngine.py index 6ac99e54..ee434cc0 100644 --- a/bot/helper/mirror_utils/upload_utils/pyrogramEngine.py +++ b/bot/helper/mirror_utils/upload_utils/pyrogramEngine.py @@ -229,8 +229,9 @@ class TgUploader: if not is_image and thumb is None: file_name = ospath.splitext(file)[0] - if await aiopath.isfile(f"{self.__path}/yt-dlp-thumb/{file_name}.jpg"): - thumb = f"{self.__path}/yt-dlp-thumb/{file_name}.jpg" + thumb_path = f"{self.__path}/yt-dlp-thumb/{file_name}.jpg" + if await aiopath.isfile(thumb_path): + thumb = thumb_path if self.__as_doc or force_document or (not is_video and not is_audio and not is_image): key = 'documents' diff --git a/bot/modules/bot_settings.py b/bot/modules/bot_settings.py index b68c4c8c..400445e8 100644 --- a/bot/modules/bot_settings.py +++ b/bot/modules/bot_settings.py @@ -806,7 +806,7 @@ async def edit_bot_settings(client, query): await event_handler(client, query, pfunc, rfunc) elif data[1] == 'editaria' and STATE == 'view': value = aria2_options[data[2]] - if len(value) > 200: + if len(str(value)) > 200: await query.answer() with BytesIO(str.encode(value)) as out_file: out_file.name = f"{data[2]}.txt" diff --git a/bot/modules/rss.py b/bot/modules/rss.py index 57827cd5..bcbe2e1e 100644 --- a/bot/modules/rss.py +++ b/bot/modules/rss.py @@ -9,9 +9,10 @@ from functools import partial from aiohttp import ClientSession from apscheduler.triggers.interval import IntervalTrigger from re import split as re_split +from io import BytesIO from bot import scheduler, rss_dict, LOGGER, DATABASE_URL, config_dict, bot -from bot.helper.telegram_helper.message_utils import sendMessage, editMessage, sendRss +from bot.helper.telegram_helper.message_utils import sendMessage, editMessage, sendRss, sendFile from bot.helper.telegram_helper.filters import CustomFilters from bot.helper.telegram_helper.bot_commands import BotCommands from bot.helper.ext_utils.db_handler import DbManger @@ -273,7 +274,14 @@ async def rssGet(client, message, pre_event): link = rss_d.entries[item_num]['link'] item_info += f"Name: {rss_d.entries[item_num]['title'].replace('>', '').replace('<', '')}\n" item_info += f"Link: {link}\n\n" - await editMessage(msg, item_info) + item_info_ecd = item_info.encode() + if len(item_info_ecd) > 4000: + with BytesIO(item_info_ecd) as out_file: + out_file.name = f"rssGet {title} items_no. {count}.txt" + await sendFile(message, out_file) + await msg.delete() + else: + await editMessage(msg, item_info) except IndexError as e: LOGGER.error(str(e)) await editMessage(msg, "Parse depth exceeded. Try again with a lower value.") @@ -282,7 +290,7 @@ async def rssGet(client, message, pre_event): await editMessage(msg, str(e)) except Exception as e: LOGGER.error(str(e)) - await sendMessage(message, f"Enter a vaild value!. {e}") + await sendMessage(message, f"Enter a valid value!. {e}") await updateRssMenu(pre_event) @@ -297,7 +305,7 @@ async def rssEdit(client, message, pre_event): await sendMessage(message, f'{item}. Wrong Input format. Read help message before editing!') continue elif not rss_dict[user_id].get(title, False): - await sendMessage(message, "Enter a vaild title. Title not found!") + await sendMessage(message, "Enter a valid title. Title not found!") continue inf_lists = [] exf_lists = [] @@ -415,7 +423,7 @@ async def rssListener(client, query): buttons.ibutton("Back", f"rss back {user_id}") buttons.ibutton("Close", f"rss close {user_id}") button = buttons.build_menu(2) - await editMessage(message, 'Send one title with vlaue seperated by space get last X items.\nTitle Value\nTimeout: 60 sec.', button) + await editMessage(message, 'Send one title with vlaue separated by space get last X items.\nTitle Value\nTimeout: 60 sec.', button) pfunc = partial(rssGet, pre_event=query) await event_handler(client, query, pfunc) elif data[1] in ['unsubscribe', 'pause', 'resume']: @@ -435,7 +443,7 @@ async def rssListener(client, query): buttons.ibutton("Unsub AllMyFeeds", f"rss uallunsub {user_id}") buttons.ibutton("Close", f"rss close {user_id}") button = buttons.build_menu(2) - await editMessage(message, f'Send one or more rss titles seperated by space to {data[1]}.\nTimeout: 60 sec.', button) + await editMessage(message, f'Send one or more rss titles separated by space to {data[1]}.\nTimeout: 60 sec.', button) pfunc = partial(rssUpdate, pre_event=query, state=data[1]) await event_handler(client, query, pfunc) elif data[1] == 'edit': @@ -448,7 +456,7 @@ async def rssListener(client, query): buttons.ibutton("Back", f"rss back {user_id}") buttons.ibutton("Close", f"rss close {user_id}") button = buttons.build_menu(2) - msg = '''Send one or more rss titles with new filters or command seperated by new line. + msg = '''Send one or more rss titles with new filters or command separated by new line. Examples: Title1 c: mirror exf: none inf: 1080 or 720 opt: up: remote:path/subdir Title2 c: none inf: none opt: none @@ -526,7 +534,7 @@ Timeout: 60 sec. buttons.ibutton("Back", f"rss back {user_id}") buttons.ibutton("Close", f"rss close {user_id}") button = buttons.build_menu(2) - msg = 'Send one or more user_id seperated by space to delete their resources.\nTimeout: 60 sec.' + msg = 'Send one or more user_id separated by space to delete their resources.\nTimeout: 60 sec.' await editMessage(message, msg, button) pfunc = partial(rssDelete, pre_event=query) await event_handler(client, query, pfunc) diff --git a/bot/modules/users_settings.py b/bot/modules/users_settings.py index fad13663..17da46cf 100644 --- a/bot/modules/users_settings.py +++ b/bot/modules/users_settings.py @@ -317,8 +317,9 @@ Check all available qualities options {b_name}:\nTimeout: {get_readable_time(self.__timeout-(time()-self.__time))}' + await editMessage(self.__reply_to, msg, subbuttons) + + async def mp3_subbuttons(self): + i = 's' if self.__is_playlist else '' + buttons = ButtonMaker() + audio_qualities = [64, 128, 320] + for q in audio_qualities: + audio_format = f'ba/b-mp3-{q}' + buttons.ibutton(f'{q}K-mp3', f'ytq {audio_format}') + buttons.ibutton('Back', 'ytq back') + buttons.ibutton('Cancel', 'ytq cancel') + subbuttons = buttons.build_menu(3) + msg = f'Choose mp3 Audio{i} Bitrate:\nTimeout: {get_readable_time(self.__timeout-(time()-self.__time))}' + await editMessage(self.__reply_to, msg, subbuttons) + + async def audio_format(self): + i = 's' if self.__is_playlist else '' + buttons = ButtonMaker() + for frmt in ['aac', 'alac', 'flac', 'm4a', 'opus', 'vorbis', 'wav']: + audio_format = f'ba/b-{frmt}-' + buttons.ibutton(frmt, f'ytq aq {audio_format}') + buttons.ibutton('Back', 'ytq back', 'footer') + buttons.ibutton('Cancel', 'ytq cancel', 'footer') + subbuttons = buttons.build_menu(3) + msg = f'Choose Audio{i} Format:\nTimeout: {get_readable_time(self.__timeout-(time()-self.__time))}' + await editMessage(self.__reply_to, msg, subbuttons) + + async def audio_quality(self, format): + i = 's' if self.__is_playlist else '' + buttons = ButtonMaker() + for qual in range(11): + audio_format = f'{format}{qual}' + buttons.ibutton(qual, f'ytq {audio_format}') + buttons.ibutton('Back', 'ytq aq back') + buttons.ibutton('Cancel', 'ytq aq cancel') + subbuttons = buttons.build_menu(5) + msg = f'Choose Audio{i} Qaulity:\n0 is best and 10 is worst\nTimeout: {get_readable_time(self.__timeout-(time()-self.__time))}' + await editMessage(self.__reply_to, msg, subbuttons) def extract_info(link): @@ -41,20 +237,10 @@ async def _mdisk(link, name): return name, link -async def _auto_cancel(msg, task_id): - await sleep(120) - try: - del listener_dict[task_id] - await editMessage(msg, 'Timed out! Task has been cancelled.') - except: - pass - - @new_task async def _ytdl(client, message, isZip=False, isLeech=False, sameDir={}): mssg = message.text user_id = message.from_user.id - msg_id = message.id qual = '' select = False multi = 0 @@ -76,7 +262,7 @@ async def _ytdl(client, message, isZip=False, isLeech=False, sameDir={}): elif x.startswith('m:'): marg = x.split('m:', 1) if len(marg) > 1: - folder_name = f"/{marg[1]}" + folder_name = f'/{marg[1]}' if not sameDir: sameDir = set() sameDir.add(message.id) @@ -97,8 +283,8 @@ async def _ytdl(client, message, isZip=False, isLeech=False, sameDir={}): await sleep(4) nextmsg = await client.get_messages(chat_id=message.chat.id, message_ids=message.reply_to_message_id + 1) ymsg = mssg.split(maxsplit=mi+1) - ymsg[mi] = f"{multi - 1}" - nextmsg = await sendMessage(nextmsg, " ".join(ymsg)) + ymsg[mi] = f'{multi - 1}' + nextmsg = await sendMessage(nextmsg, ' '.join(ymsg)) nextmsg = await client.get_messages(chat_id=message.chat.id, message_ids=nextmsg.id) if len(folder_name) > 0: sameDir.add(nextmsg.id) @@ -129,7 +315,7 @@ async def _ytdl(client, message, isZip=False, isLeech=False, sameDir={}): 0].strip() if len(up) > 1 else None if username := message.from_user.username: - tag = f"@{username}" + tag = f'@{username}' else: tag = message.from_user.mention @@ -138,7 +324,7 @@ async def _ytdl(client, message, isZip=False, isLeech=False, sameDir={}): link = reply_to.text.split('\n', 1)[0].strip() if not reply_to.from_user.is_bot: if username := reply_to.from_user.username: - tag = f"@{username}" + tag = f'@{username}' else: tag = reply_to.from_user.mention @@ -163,7 +349,7 @@ async def _ytdl(client, message, isZip=False, isLeech=False, sameDir={}): else: config_path = 'rclone.conf' if not await aiopath.exists(config_path): - await sendMessage(message, f"Rclone Config: {config_path} not Exists!") + await sendMessage(message, f'Rclone Config: {config_path} not Exists!') return if up == 'rcl' and not isLeech: @@ -180,7 +366,7 @@ async def _ytdl(client, message, isZip=False, isLeech=False, sameDir={}): result = await sync_to_async(extract_info, link) except Exception as e: msg = str(e).replace('<', ' ').replace('>', ' ') - await sendMessage(message, f"{tag} {msg}") + await sendMessage(message, f'{tag} {msg}') __run_multi() return @@ -199,170 +385,14 @@ async def _ytdl(client, message, isZip=False, isLeech=False, sameDir={}): else: qual = config_dict.get('YT_DLP_QUALITY') - if qual: - playlist = 'entries' in result - LOGGER.info(f"Downloading with YT-DLP: {link}") - ydl = YoutubeDLHelper(listener) - await ydl.add_download(link, path, name, qual, playlist, opt) - else: - buttons = ButtonMaker() - best_video = "bv*+ba/b" - best_audio = "ba/b" - formats_dict = {} - if 'entries' in result: - for i in ['144', '240', '360', '480', '720', '1080', '1440', '2160']: - video_format = f"bv*[height<=?{i}][ext=mp4]+ba[ext=m4a]/b[height<=?{i}]" - b_data = f"{i}|mp4" - formats_dict[b_data] = video_format - buttons.ibutton(f"{i}-mp4", f"qu {msg_id} {b_data} t") - video_format = f"bv*[height<=?{i}][ext=webm]+ba/b[height<=?{i}]" - b_data = f"{i}|webm" - formats_dict[b_data] = video_format - buttons.ibutton(f"{i}-webm", f"qu {msg_id} {b_data} t") - buttons.ibutton("MP3", f"qu {msg_id} mp3 t") - buttons.ibutton("Best Videos", f"qu {msg_id} {best_video} t") - buttons.ibutton("Best Audios", f"qu {msg_id} {best_audio} t") - buttons.ibutton("Cancel", f"qu {msg_id} cancel") - mbuttons = buttons.build_menu(3) - bmsg = await sendMessage(message, 'Choose Playlist Videos Quality:', mbuttons) - else: - formats = result.get('formats') - is_m4a = False - if formats is not None: - for frmt in formats: - if frmt.get('tbr'): - - format_id = frmt['format_id'] - - if frmt.get('filesize'): - size = frmt['filesize'] - elif frmt.get('filesize_approx'): - size = frmt['filesize_approx'] - else: - size = 0 - - if frmt.get('video_ext') == 'none' and frmt.get('acodec') != 'none': - if frmt.get('audio_ext') == 'm4a': - is_m4a = True - b_name = f"{frmt['acodec']}-{frmt['ext']}" - v_format = f"ba[format_id={format_id}]" - elif frmt.get('height'): - height = frmt['height'] - ext = frmt['ext'] - fps = frmt['fps'] if frmt.get('fps') else '' - b_name = f"{height}p{fps}-{ext}" - ba_ext = '[ext=m4a]' if is_m4a and ext == 'mp4' else '' - v_format = f"bv*[format_id={format_id}]+ba{ba_ext}/b[height=?{height}]" - else: - continue - - formats_dict.setdefault(b_name, {})[str(frmt['tbr'])] = [ - size, v_format] - - for b_name, tbr_dict in formats_dict.items(): - if len(tbr_dict) == 1: - tbr, v_list = next(iter(tbr_dict.items())) - buttonName = f"{b_name} ({get_readable_file_size(v_list[0])})" - buttons.ibutton( - buttonName, f"qu {msg_id} {b_name}|{tbr}") - else: - buttons.ibutton(b_name, f"qu {msg_id} dict {b_name}") - buttons.ibutton("MP3", f"qu {msg_id} mp3") - buttons.ibutton("Best Video", f"qu {msg_id} {best_video}") - buttons.ibutton("Best Audio", f"qu {msg_id} {best_audio}") - buttons.ibutton("Cancel", f"qu {msg_id} cancel") - mbuttons = buttons.build_menu(2) - bmsg = await sendMessage(message, 'Choose Video Quality:', mbuttons) - - listener_dict[msg_id] = [listener, user_id, link, - name, mbuttons, opt, formats_dict, path] - await _auto_cancel(bmsg, msg_id) - - -async def _qual_subbuttons(task_id, b_name, msg): - buttons = ButtonMaker() - tbr_dict = listener_dict[task_id][6][b_name] - for tbr, d_data in tbr_dict.items(): - button_name = f"{tbr}K ({get_readable_file_size(d_data[0])})" - buttons.ibutton(button_name, f"qu {task_id} {b_name}|{tbr}") - buttons.ibutton("Back", f"qu {task_id} back") - buttons.ibutton("Cancel", f"qu {task_id} cancel") - subbuttons = buttons.build_menu(2) - await editMessage(msg, f"Choose Bit rate for {b_name}:", subbuttons) - - -async def _mp3_subbuttons(task_id, msg, playlist=False): - buttons = ButtonMaker() - audio_qualities = [64, 128, 320] - for q in audio_qualities: - if playlist: - i = 's' - audio_format = f"ba/b-{q} t" - else: - i = '' - audio_format = f"ba/b-{q}" - buttons.ibutton(f"{q}K-mp3", f"qu {task_id} {audio_format}") - buttons.ibutton("Back", f"qu {task_id} back") - buttons.ibutton("Cancel", f"qu {task_id} cancel") - subbuttons = buttons.build_menu(2) - await editMessage(msg, f"Choose Audio{i} Bitrate:", subbuttons) - - -@new_task -async def select_format(client, query): - user_id = query.from_user.id - data = query.data.split() - message = query.message - task_id = int(data[1]) - try: - task_info = listener_dict[task_id] - except: - await editMessage(message, "This is an old task") - return - uid = task_info[1] - if user_id != uid and not await CustomFilters.sudo(client, query): - await query.answer(text="This task is not for you!", show_alert=True) - return - elif data[2] == "dict": - await query.answer() - b_name = data[3] - await _qual_subbuttons(task_id, b_name, message) - return - elif data[2] == "back": - await query.answer() - await editMessage(message, 'Choose Video Quality:', task_info[4]) - return - elif data[2] == "mp3": - await query.answer() - playlist = len(data) == 4 - await _mp3_subbuttons(task_id, message, playlist) - return - elif data[2] == "cancel": - await query.answer() - await editMessage(message, 'Task has been cancelled.') - del listener_dict[task_id] - else: - await query.answer() - listener = task_info[0] - link = task_info[2] - name = task_info[3] - opt = task_info[5] - qual = data[2] - path = task_info[7] - if len(data) == 4: - playlist = True - if '|' in qual: - qual = task_info[6][qual] - else: - playlist = False - if '|' in qual: - b_name, tbr = qual.split('|') - qual = task_info[6][b_name][tbr][1] - LOGGER.info(f"Downloading with YT-DLP: {link}") - await message.delete() - del listener_dict[task_id] - ydl = YoutubeDLHelper(listener) - await ydl.add_download(link, path, name, qual, playlist, opt) + if not qual: + qual = await YtSelection(client, message).get_quality(result) + if qual is None: + return + LOGGER.info(f'Downloading with YT-DLP: {link}') + playlist = 'entries' in result + ydl = YoutubeDLHelper(listener) + await ydl.add_download(link, path, name, qual, playlist, opt) async def ytdl(client, message): @@ -388,4 +418,3 @@ bot.add_handler(MessageHandler(ytdlleech, filters=command( BotCommands.YtdlLeechCommand) & CustomFilters.authorized)) bot.add_handler(MessageHandler(ytdlZipleech, filters=command( BotCommands.YtdlZipLeechCommand) & CustomFilters.authorized)) -bot.add_handler(CallbackQueryHandler(select_format, filters=regex("^qu")))