# █ █ ▀ █▄▀ ▄▀█ █▀█ ▀ # █▀█ █ █ █ █▀█ █▀▄ █ # © Copyright 2022 # # https://t.me/hikariatama # # 🔒 Licensed under the GNU AGPLv3 # 🌐 https://www.gnu.org/licenses/agpl-3.0.html # meta pic: https://static.dan.tatar/tidal_icon.png # meta banner: https://mods.hikariatama.ru/badges/tidal.jpg # meta developer: @hikarimods # scope: hikka_only # scope: hikka_min 1.2.10 # requires: tidalapi import asyncio import logging import tidalapi from telethon.tl.types import Message from .. import loader, utils from ..inline.types import InlineCall logger = logging.getLogger(__name__) @loader.tds class TidalMod(loader.Module): """API wrapper over TIDAL Hi-Fi music streaming service""" strings = { "name": "Tidal", "args": "🚫 Specify search query", "404": "🚫 No results found", "oauth": ( "🔑 Login to TIDAL\n\nThis link will expire in 5 minutes" ), "oauth_btn": "🔑 Login", "success": "✅ Successfully logged in!", "error": "🚫 Error logging in", "search": "🐈‍⬛ {}", "tidal_btn": "🐈‍⬛ Tidal", "searching": "🔍 Searching...", "tidal_like_btn": "🖤 Like", "tidal_dislike_btn": "💔 Dislike", "auth_first": "🚫 You need to login first", } strings_ru = { "args": "🚫 Укажите поисковый запрос", "404": "🚫 Ничего не найдено", "oauth": ( "🔑 Авторизуйтесь в TIDAL\n\nЭта ссылка будет действительна в" " течение 5 минут" ), "oauth_btn": "🔑 Авторизоваться", "success": "✅ Успешно авторизованы!", "error": "🚫 Ошибка авторизации", "search": "🐈‍⬛ {}", "tidal_btn": "🐈‍⬛ Tidal", "searching": "🔍 Ищем...", "tidal_like_btn": "🖤 Нравится", "tidal_dislike_btn": "💔 Не нравится", "auth_first": "🚫 Сначала нужно авторизоваться", } strings_de = { "args": "🚫 Gib einen Suchbegriff an", "404": "🚫 Nichts gefunden", "oauth": ( "🔑 Logge dich bei TIDAL ein\n\nDieser Link ist 5 Minuten lang" " gültig" ), "oauth_btn": "🔑 Einloggen", "success": "✅ Erfolgreich eingeloggt!", "error": "🚫 Fehler beim Einloggen", "search": "🐈‍⬛ {}", "tidal_btn": "🐈‍⬛ Tidal", "searching": "🔍 Suche...", "tidal_like_btn": "🖤 Gefällt mir", "tidal_dislike_btn": "💔 Gefällt mir nicht", "auth_first": "🚫 Du musst dich zuerst einloggen", } strings_tr = { "args": "🚫 Arama sorgusu belirtin", "404": "🚫 Sonuç bulunamadı", "oauth": ( "🔑 TIDAL'e giriş yapın\n\nBu bağlantı 5 dakika içinde sona" " erecek" ), "oauth_btn": "🔑 Giriş yap", "success": "✅ Başarıyla giriş yaptınız!", "error": "🚫 Giriş hatası", "search": "🐈‍⬛ {}", "tidal_btn": "🐈‍⬛ Tidal", "searching": "🔍 Aranıyor...", "tidal_like_btn": "🖤 Beğen", "tidal_dislike_btn": "💔 Beğenme", "auth_first": "🚫 Önce giriş yapmanız gerekir", } strings_hi = { "args": "🚫 खोज प्रश्न निर्दिष्ट करें", "404": "🚫 कोई परिणाम नहीं मिला", "oauth": "🔑 TIDAL में लॉगिन करें\n\nयह लिंक 5 मिनट के लिए सक्रिय होगा", "oauth_btn": "🔑 लॉगिन करें", "success": "✅ सफलतापूर्वक लॉगिन किया गया!", "error": "🚫 लॉगिन त्रुटि", "search": "🐈‍⬛ {}", "tidal_btn": "🐈‍⬛ Tidal", "searching": "🔍 खोज रहा है...", "tidal_like_btn": "🖤 पसंद", "tidal_dislike_btn": "💔 पसंद नहीं", "auth_first": "🚫 पहले लॉगिन करना आवश्यक है", } strings_uz = { "args": "🚫 Qidiruv so'rovi belgilang", "404": "🚫 Natija topilmadi", "oauth": ( "🔑 TIDAL'da kirishingiz kerak\n\nUshbu havola 5 daqiqaga aktiv" " bo'ladi" ), "oauth_btn": "🔑 Kirish", "success": "✅ Muvaffaqiyatli kirildi!", "error": "🚫 Kirishda xatolik", "search": "🐈‍⬛ {}", "tidal_btn": "🐈‍⬛ Tidal", "searching": "🔍 Izlanmoqda...", "tidal_like_btn": "🖤 Yoqadi", "tidal_dislike_btn": "💔 Yo'qadi", "auth_first": "🚫 Avval kirish kerak", } async def client_ready(self): self._faved = [] self.tidal = tidalapi.Session() login_credentials = ( self.get("session_id"), self.get("token_type"), self.get("access_token"), self.get("refresh_token"), ) if all(login_credentials): try: await utils.run_sync(self.tidal.load_oauth_session, *login_credentials) assert await utils.run_sync(self.tidal.check_login) except Exception: logger.exception("Error loading OAuth session") if not self.get("muted"): try: await utils.dnd(self._client, "@hikka_musicdl_bot", archive=True) await utils.dnd(self._client, "@DirectLinkGenerator_Bot", archive=True) except Exception: pass self.set("muted", True) self._obtain_faved.start() self.musicdl = await self.import_lib( "https://libs.hikariatama.ru/musicdl.py", suspend_on_error=True, ) @loader.loop(interval=60) async def _obtain_faved(self): if not await utils.run_sync(self.tidal.check_login): return self._faved = list( map( int, ( await utils.run_sync( self.tidal.request, "GET", f"users/{self.tidal.user.id}/favorites/ids", ) ).json()["TRACK"], ) ) def _save_session_info(self): self.set("token_type", self.tidal.token_type) self.set("session_id", self.tidal.session_id) self.set("access_token", self.tidal.access_token) self.set("refresh_token", self.tidal.refresh_token) @loader.command( ru_doc="Авторизация в TIDAL", de_doc="Authentifizierung in TIDAL", tr_doc="TIDAL'de oturum açma", hi_doc="TIDAL में प्रमाणीकरण", uz_doc="TIDAL'da avtorizatsiya", ) async def tlogincmd(self, message: Message): """Open OAuth window to login into TIDAL""" result, future = self.tidal.login_oauth() form = await self.inline.form( message=message, text=self.strings("oauth"), reply_markup={ "text": self.strings("oauth_btn"), "url": f"https://{result.verification_uri_complete}", }, gif="https://i.gifer.com/8Z2a.gif", ) outer_loop = asyncio.get_event_loop() def callback(*args, **kwargs): nonlocal form, outer_loop if self.tidal.check_login(): asyncio.ensure_future( form.edit( self.strings("success"), gif="https://c.tenor.com/IrKex2lXvR8AAAAC/sparkly-eyes-joy.gif", ), loop=outer_loop, ) self._save_session_info() else: asyncio.ensure_future(form.edit(self.strings("error")), loop=outer_loop) future.add_done_callback(callback) @loader.command( ru_doc="<запрос> - Поиск трека в TIDAL", de_doc=" - Suche nach einem Track in TIDAL", tr_doc=" - TIDAL'de bir parça arama", hi_doc="<अनुरोध> - TIDAL में एक ट्रैक खोजें", uz_doc=" - TIDAL'da parca qidirish", ) async def tidalcmd(self, message: Message): """ - Search TIDAL""" if not await utils.run_sync(self.tidal.check_login): await utils.answer(message, self.strings("auth_first")) return query = utils.get_args_raw(message) if not query: await utils.answer(message, self.strings("args")) return message = await utils.answer(message, self.strings("searching")) result = await utils.run_sync(self.tidal.search, "track", query, limit=1) if not result or not result.tracks: await utils.answer(message, self.strings("404")) return track = result.tracks[0] full_name = f"{track.artist.name} - {track.name}" meta = ( await utils.run_sync( self.tidal.request, "GET", f"tracks/{track.id}", ) ).json() tags = [] if meta.get("explicit"): tags += ["#explicit🤬"] if meta.get("audioQuality"): tags += [f"#{meta['audioQuality']}🔈"] if isinstance(meta.get("audioModes"), list): for tag in meta["audioModes"]: tags += [f"#{tag}🎧"] if track.id in self._faved: tags += ["#favorite🖤"] if tags: tags = "\n\n" + "\n".join( [" ".join(chunk) for chunk in utils.chunks(tags, 2)] ) text = self.strings("search").format(utils.escape_html(full_name)) + tags message = await utils.answer( message, text + "\n\nDownloading audio file..." ) url = await self.musicdl.dl(full_name) await self.inline.form( message=message, text=text, **( { "audio": { "url": url, "title": track.name, "performer": track.artist.name, } } if url else {} ), silent=True, reply_markup=[ [ { "text": self.strings("tidal_btn"), "url": f"https://listen.tidal.com/track/{track.id}", }, *( [ { "text": self.strings("tidal_like_btn"), "callback": self._like, "args": (track, text), } ] if track.id not in self._faved else [ { "text": self.strings("tidal_dislike_btn"), "callback": self._dislike, "args": (track, text), } ] ), ], ], ) async def _like(self, call: InlineCall, track: tidalapi.Track, text: str): try: await utils.run_sync(self.tidal.user.favorites.add_track, track.id) except Exception: logger.exception("Error liking track") await call.answer("🚫 Error!") else: await call.answer("💚 Liked!") await call.edit( text, reply_markup=[ [ { "text": self.strings("tidal_btn"), "url": f"https://listen.tidal.com/track/{track.id}", }, { "text": self.strings("tidal_dislike_btn"), "callback": self._dislike, "args": (track, text), }, ], ], ) async def _dislike(self, call: InlineCall, track: tidalapi.Track, text: str): try: await utils.run_sync(self.tidal.user.favorites.remove_track, track.id) except Exception: logger.exception("Error disliking track") await call.answer("🚫 Error!") else: await call.answer("💔 Disliked!") await call.edit( text, reply_markup=[ [ { "text": self.strings("tidal_btn"), "url": f"https://listen.tidal.com/track/{track.id}", }, { "text": self.strings("tidal_like_btn"), "callback": self._like, "args": (track, text), }, ], ], )