From 06c327933f618bf0df49165884f6ff480e919471 Mon Sep 17 00:00:00 2001 From: BarsTiger Date: Tue, 7 Nov 2023 14:13:14 +0200 Subject: [PATCH] Add YouTube recoding --- bot/callbacks/__init__.py | 3 +- bot/callbacks/settings.py | 18 ++-- bot/handlers/__init__.py | 3 +- bot/handlers/on_chosen/__init__.py | 4 +- bot/handlers/on_chosen/recode_cached.py | 101 ++++++++++++++++++++++ bot/handlers/on_chosen/spotify.py | 46 +++++++++- bot/handlers/on_chosen/suppress_verify.py | 16 ++++ bot/handlers/on_chosen/youtube.py | 36 +++++++- bot/middlewares/__init__.py | 1 + bot/middlewares/inject_settings.py | 22 +++++ bot/modules/database/db.py | 1 + bot/modules/youtube/downloader.py | 4 +- bot/results/common/search.py | 5 ++ 13 files changed, 245 insertions(+), 15 deletions(-) create mode 100644 bot/handlers/on_chosen/recode_cached.py create mode 100644 bot/handlers/on_chosen/suppress_verify.py create mode 100644 bot/middlewares/inject_settings.py diff --git a/bot/callbacks/__init__.py b/bot/callbacks/__init__.py index f6e0108..6ad7d02 100644 --- a/bot/callbacks/__init__.py +++ b/bot/callbacks/__init__.py @@ -4,12 +4,13 @@ from . import ( on_home, settings, ) -from bot.middlewares import PrivateButtonMiddleware +from bot.middlewares import PrivateButtonMiddleware, SettingsInjectorMiddleware router = Router() router.callback_query.middleware(PrivateButtonMiddleware()) +router.callback_query.middleware(SettingsInjectorMiddleware()) router.include_routers( full_menu.router, diff --git a/bot/callbacks/settings.py b/bot/callbacks/settings.py index 97a4b67..f940d1a 100644 --- a/bot/callbacks/settings.py +++ b/bot/callbacks/settings.py @@ -2,6 +2,7 @@ from aiogram import Router, Bot from aiogram.types import ( CallbackQuery ) +from aiogram.exceptions import TelegramBadRequest from bot.factories.open_setting import OpenSettingCallback, SettingChoiceCallback @@ -34,11 +35,14 @@ async def on_change_setting( bot: Bot ): UserSettings(callback_query.from_user.id)[callback_data.s_id] = callback_data.choice - await bot.edit_message_text( - inline_message_id=callback_query.inline_message_id, - text=settings_strings[callback_data.s_id].description, - reply_markup=get_setting_kb( - callback_data.s_id, - str(callback_query.from_user.id) + try: + await bot.edit_message_text( + inline_message_id=callback_query.inline_message_id, + text=settings_strings[callback_data.s_id].description, + reply_markup=get_setting_kb( + callback_data.s_id, + str(callback_query.from_user.id) + ) ) - ) + except TelegramBadRequest: + pass diff --git a/bot/handlers/__init__.py b/bot/handlers/__init__.py index c23c359..2d2b194 100644 --- a/bot/handlers/__init__.py +++ b/bot/handlers/__init__.py @@ -9,11 +9,12 @@ from . import ( on_chosen, ) -from bot.middlewares import SaveChosenMiddleware +from bot.middlewares import SaveChosenMiddleware, SettingsInjectorMiddleware router = Router() router.chosen_inline_result.outer_middleware(SaveChosenMiddleware()) +router.chosen_inline_result.middleware(SettingsInjectorMiddleware()) router.include_routers( initialize.router, diff --git a/bot/handlers/on_chosen/__init__.py b/bot/handlers/on_chosen/__init__.py index ba2899d..ce4b25b 100644 --- a/bot/handlers/on_chosen/__init__.py +++ b/bot/handlers/on_chosen/__init__.py @@ -1,5 +1,5 @@ from aiogram import Router -from . import spotify, deezer, youtube +from . import spotify, deezer, youtube, recode_cached, suppress_verify router = Router() @@ -7,6 +7,8 @@ router.include_routers( spotify.router, deezer.router, youtube.router, + recode_cached.router, + suppress_verify.router, ) __all__ = ['router'] diff --git a/bot/handlers/on_chosen/recode_cached.py b/bot/handlers/on_chosen/recode_cached.py new file mode 100644 index 0000000..ff3f23f --- /dev/null +++ b/bot/handlers/on_chosen/recode_cached.py @@ -0,0 +1,101 @@ +from aiogram import Router, Bot, F +from aiogram.types import ( + BufferedInputFile, InputMediaAudio, + ChosenInlineResult, +) + +from bot.modules.youtube.downloader import YouTubeBytestream + +from bot.utils.config import config +from bot.modules.database import db +from bot.modules.settings import UserSettings + +from io import BytesIO + +router = Router() + + +@router.chosen_inline_result( + F.result_id.startswith('spotc::') | F.result_id.startswith('ytc::') +) +async def on_cached_chosen(chosen_result: ChosenInlineResult, bot: Bot, + settings: UserSettings): + if settings['recode_youtube'].value != 'yes': + await bot.edit_message_reply_markup( + inline_message_id=chosen_result.inline_message_id, + reply_markup=None + ) + return + + if ( + type( + db.recoded.get( + song_id := chosen_result.result_id + .removeprefix('spotc::') + .removeprefix('ytc::') + ) + ) in [bool, type(None)] + ): + await bot.edit_message_reply_markup( + inline_message_id=chosen_result.inline_message_id, + reply_markup=None + ) + return + + await bot.edit_message_caption( + inline_message_id=chosen_result.inline_message_id, + caption='🔄 Recoding...', + reply_markup=None + ) + + message = await bot.forward_message( + config.telegram.files_chat, + config.telegram.files_chat, + db.recoded[song_id] + ) + + song_io: BytesIO = await bot.download( # type: ignore + destination=BytesIO(), + file=message.audio.file_id, + ) + await message.delete() + + bytestream = YouTubeBytestream.from_bytestream( + bytestream=song_io, + filename=message.audio.file_name, + duration=message.audio.duration, + ) + + await bytestream.rerender() + + audio = await bot.send_audio( + chat_id=config.telegram.files_chat, + audio=BufferedInputFile( + file=bytestream.file, + filename=bytestream.filename, + ), + thumbnail=BufferedInputFile( + file=(await bot.download(message.audio.thumbnail.file_id)).read(), + filename='thumbnail.jpg' + ), + performer=message.audio.performer, + title=message.audio.title, + duration=bytestream.duration, + ) + + await bot.edit_message_caption( + inline_message_id=chosen_result.inline_message_id, + caption='', + reply_markup=None, + ) + await bot.edit_message_media( + inline_message_id=chosen_result.inline_message_id, + media=InputMediaAudio(media=audio.audio.file_id) + ) + + if chosen_result.result_id.startswith('spotc::'): + db.spotify[song_id] = audio.audio.file_id + else: + db.youtube[song_id] = audio.audio.file_id + db.recoded[song_id] = True + await db.occasionally_write() diff --git a/bot/handlers/on_chosen/spotify.py b/bot/handlers/on_chosen/spotify.py index ceb7027..f801b84 100644 --- a/bot/handlers/on_chosen/spotify.py +++ b/bot/handlers/on_chosen/spotify.py @@ -10,6 +10,7 @@ from bot.modules.youtube.song import SongItem from bot.modules.deezer import deezer from bot.utils.config import config from bot.modules.database import db +from bot.modules.settings import UserSettings router = Router() @@ -22,13 +23,14 @@ def not_strict_name(song, yt_song): @router.chosen_inline_result(F.result_id.startswith('spot::')) -async def on_new_chosen(chosen_result: ChosenInlineResult, bot: Bot): +async def on_new_chosen(chosen_result: ChosenInlineResult, bot: Bot, + settings: UserSettings): song = spotify.songs.from_id(chosen_result.result_id.removeprefix('spot::')) bytestream = None audio = None - yt_song: SongItem = youtube.songs.search_one( + yt_song: SongItem | None = youtube.songs.search_one( song.full_name, exact_match=True, ) @@ -40,6 +42,7 @@ async def on_new_chosen(chosen_result: ChosenInlineResult, bot: Bot): reply_markup=None, parse_mode='HTML', ) + yt_song = None bytestream = False try: @@ -66,6 +69,7 @@ async def on_new_chosen(chosen_result: ChosenInlineResult, bot: Bot): reply_markup=None, parse_mode='HTML', ) + yt_song = None if not bytestream: try: @@ -110,4 +114,42 @@ async def on_new_chosen(chosen_result: ChosenInlineResult, bot: Bot): parse_mode='HTML', ) + if yt_song and settings['recode_youtube'].value == 'yes': + await bot.edit_message_caption( + inline_message_id=chosen_result.inline_message_id, + caption='🔄 Recoding...', + reply_markup=None, + parse_mode='HTML', + ) + await bytestream.rerender() + + audio = await bot.send_audio( + chat_id=config.telegram.files_chat, + audio=BufferedInputFile( + file=bytestream.file, + filename=bytestream.filename, + ), + thumbnail=URLInputFile(song.thumbnail), + performer=song.all_artists, + title=song.name, + duration=bytestream.duration, + ) + db.youtube[yt_song.id] = audio.audio.file_id + db.spotify[song.id] = audio.audio.file_id + db.recoded[yt_song.id] = True + db.recoded[song.id] = True + + await bot.edit_message_caption( + inline_message_id=chosen_result.inline_message_id, + caption='', + reply_markup=None, + ) + await bot.edit_message_media( + inline_message_id=chosen_result.inline_message_id, + media=InputMediaAudio(media=audio.audio.file_id) + ) + elif yt_song and settings['recode_youtube'].value == 'no': + db.recoded[yt_song.id] = audio.message_id + db.recoded[song.id] = audio.message_id + await db.occasionally_write() diff --git a/bot/handlers/on_chosen/suppress_verify.py b/bot/handlers/on_chosen/suppress_verify.py new file mode 100644 index 0000000..71ef9c0 --- /dev/null +++ b/bot/handlers/on_chosen/suppress_verify.py @@ -0,0 +1,16 @@ +from aiogram import Router, Bot, F +from aiogram.types import ( + ChosenInlineResult, +) + +router = Router() + + +@router.chosen_inline_result( + F.result_id.startswith('deezc::') +) +async def on_unneeded_cached_chosen(chosen_result: ChosenInlineResult, bot: Bot): + await bot.edit_message_reply_markup( + inline_message_id=chosen_result.inline_message_id, + reply_markup=None + ) diff --git a/bot/handlers/on_chosen/youtube.py b/bot/handlers/on_chosen/youtube.py index 5b5400d..0d22a28 100644 --- a/bot/handlers/on_chosen/youtube.py +++ b/bot/handlers/on_chosen/youtube.py @@ -7,12 +7,14 @@ from aiogram.types import ( from bot.modules.youtube import youtube, AgeRestrictedError from bot.utils.config import config from bot.modules.database import db +from bot.modules.settings import UserSettings router = Router() @router.chosen_inline_result(F.result_id.startswith('yt::')) -async def on_new_chosen(chosen_result: ChosenInlineResult, bot: Bot): +async def on_new_chosen(chosen_result: ChosenInlineResult, bot: Bot, + settings: UserSettings): song = youtube.songs.from_id(chosen_result.result_id.removeprefix('yt::')) try: @@ -46,4 +48,36 @@ async def on_new_chosen(chosen_result: ChosenInlineResult, bot: Bot): reply_markup=None ) + if settings['recode_youtube'].value == 'yes': + await bot.edit_message_caption( + inline_message_id=chosen_result.inline_message_id, + caption='🔄 Recoding...', + reply_markup=None + ) + + await bytestream.rerender() + + audio = await bot.send_audio( + chat_id=config.telegram.files_chat, + audio=BufferedInputFile( + file=bytestream.file, + filename=bytestream.filename, + ), + thumbnail=URLInputFile(song.thumbnail), + performer=song.all_artists, + title=song.name, + duration=bytestream.duration, + ) + + db.youtube[song.id] = audio.audio.file_id + db.recoded[song.id] = True + + await bot.edit_message_media( + inline_message_id=chosen_result.inline_message_id, + media=InputMediaAudio(media=audio.audio.file_id), + reply_markup=None + ) + else: + db.recoded[song.id] = audio.message_id + await db.occasionally_write() diff --git a/bot/middlewares/__init__.py b/bot/middlewares/__init__.py index e4cf1ae..ac8128d 100644 --- a/bot/middlewares/__init__.py +++ b/bot/middlewares/__init__.py @@ -1,2 +1,3 @@ from .private_button import PrivateButtonMiddleware from .save_chosen import SaveChosenMiddleware +from .inject_settings import SettingsInjectorMiddleware diff --git a/bot/middlewares/inject_settings.py b/bot/middlewares/inject_settings.py new file mode 100644 index 0000000..49a7e39 --- /dev/null +++ b/bot/middlewares/inject_settings.py @@ -0,0 +1,22 @@ +from aiogram.dispatcher.middlewares.base import BaseMiddleware +from aiogram.types import TelegramObject + +from typing import Any, Awaitable, Callable, Dict + +from bot.modules.settings import UserSettings + + +class SettingsInjectorMiddleware(BaseMiddleware): + async def __call__( + self, + handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]], + event: TelegramObject, + data: Dict[str, Any], + ): + if not hasattr(event, 'from_user'): + return await handler(event, data) + + settings = UserSettings(event.from_user.id) + data['settings'] = settings + + return await handler(event, data) diff --git a/bot/modules/database/db.py b/bot/modules/database/db.py index 49245be..4e8ee7e 100644 --- a/bot/modules/database/db.py +++ b/bot/modules/database/db.py @@ -20,6 +20,7 @@ class Db(object): self.spotify = DBDict('spotify') self.deezer = DBDict('deezer') self.youtube = DBDict('youtube') + self.recoded = DBDict('recoded') async def write(self): await self.config.write() diff --git a/bot/modules/youtube/downloader.py b/bot/modules/youtube/downloader.py index 76bf63f..12c3bad 100644 --- a/bot/modules/youtube/downloader.py +++ b/bot/modules/youtube/downloader.py @@ -29,10 +29,10 @@ class YouTubeBytestream: async def rerender(self): segment = AudioSegment.from_file( - file=self.file + file=BytesIO(self.file) ) - self.file = segment.export(BytesIO(), format='mp3', codec='libmp3lame') + self.file = segment.export(BytesIO(), format='mp3', codec='libmp3lame').read() return self diff --git a/bot/results/common/search.py b/bot/results/common/search.py index 00a8f7b..0d79895 100644 --- a/bot/results/common/search.py +++ b/bot/results/common/search.py @@ -35,5 +35,10 @@ async def get_common_search_result( InlineQueryResultCachedAudio( id=f'{service_id}c::' + audio.id, audio_file_id=db_table[audio.id], + reply_markup=InlineKeyboardMarkup( + inline_keyboard=[ + [InlineKeyboardButton(text='Verifying...', callback_data='.')] + ] + ), ) )