SpotifyNow.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. __version__ = (1, 0, 3)
  2. # █ █ ▀ █▄▀ ▄▀█ █▀█ ▀
  3. # █▀█ █ █ █ █▀█ █▀▄ █
  4. # © Copyright 2022
  5. # https://t.me/hikariatama
  6. #
  7. # 🔒 Licensed under the GNU AGPLv3
  8. # 🌐 https://www.gnu.org/licenses/agpl-3.0.html
  9. # meta pic: https://static.hikari.gay/spotify_icon.png
  10. # meta banner: https://mods.hikariatama.ru/badges/spotify.jpg
  11. # meta developer: @hikarimods
  12. # scope: hikka_only
  13. # scope: hikka_min 1.2.10
  14. # requires: spotipy Pillow
  15. import asyncio
  16. import contextlib
  17. import functools
  18. import io
  19. import logging
  20. import re
  21. import time
  22. import traceback
  23. from math import ceil
  24. from types import FunctionType
  25. import requests
  26. import spotipy
  27. from PIL import Image, ImageDraw, ImageFont
  28. from telethon.errors.rpcerrorlist import FloodWaitError
  29. from telethon.tl.functions.account import UpdateProfileRequest
  30. from telethon.tl.types import Message
  31. from .. import loader, utils
  32. logger = logging.getLogger(__name__)
  33. logging.getLogger("spotipy").setLevel(logging.CRITICAL)
  34. SIZE = (1200, 320)
  35. INNER_MARGIN = (16, 16)
  36. TRACK_FS = 48
  37. ARTIST_FS = 32
  38. @loader.tds
  39. class SpotifyMod(loader.Module):
  40. """Display beautiful spotify now bar. Idea: t.me/fuccsoc. Implementation: t.me/hikariatama"""
  41. strings = {
  42. "name": "SpotifyNow",
  43. "need_auth": (
  44. "<emoji document_id=5312526098750252863>🚫</emoji> <b>Call"
  45. " </b><code>.sauth</code><b> before using this action.</b>"
  46. ),
  47. "on-repeat": (
  48. "<emoji document_id=5469741319330996757>💫</emoji> <b>Set on-repeat.</b>"
  49. ),
  50. "off-repeat": (
  51. "<emoji document_id=5472354553527541051>✋</emoji> <b>Stopped track"
  52. " repeat.</b>"
  53. ),
  54. "skipped": (
  55. "<emoji document_id=5471978009449731768>👉</emoji> <b>Skipped track.</b>"
  56. ),
  57. "err": (
  58. "<emoji document_id=5312526098750252863>🚫</emoji> <b>Error occurred. Make"
  59. " sure the track is playing!</b>\n<code>{}</code>"
  60. ),
  61. "already_authed": (
  62. "<emoji document_id=5312526098750252863>🚫</emoji> <b>You are already"
  63. " authentificated</b>"
  64. ),
  65. "authed": (
  66. "<emoji document_id=6319076999105087378>🎧</emoji> <b>Auth successful</b>"
  67. ),
  68. "playing": "<emoji document_id=6319076999105087378>🎧</emoji> <b>Playing...</b>",
  69. "back": (
  70. "<emoji document_id=5469735272017043817>👈</emoji> <b>Switched to previous"
  71. " track</b>"
  72. ),
  73. "paused": "<emoji document_id=5469904794376217131>🤚</emoji> <b>Pause</b>",
  74. "deauth": (
  75. "<emoji document_id=6037460928423791421>🚪</emoji> <b>Unauthentificated</b>"
  76. ),
  77. "restarted": (
  78. "<emoji document_id=5469735272017043817>👈</emoji> <b>Playing track from the"
  79. " beginning</b>"
  80. ),
  81. "auth": (
  82. '<emoji document_id=5472308992514464048>🔐</emoji> <a href="{}">Proceed'
  83. " here</a>, approve request, then <code>.scode https://...</code> with"
  84. " redirected url"
  85. ),
  86. "liked": (
  87. "<emoji document_id=5199727145022134809>❤️</emoji> <b>Liked current"
  88. " playback</b>"
  89. ),
  90. "autobio": (
  91. "<emoji document_id=6319076999105087378>🎧</emoji> <b>Spotify autobio {}</b>"
  92. ),
  93. "404": "<emoji document_id=5312526098750252863>🚫</emoji> <b>No results</b>",
  94. "playing_track": (
  95. "<emoji document_id=5212941939053175244>🎧</emoji> <b>{} added to queue</b>"
  96. ),
  97. "no_music": (
  98. "<emoji document_id=5312526098750252863>🚫</emoji> <b>No music is"
  99. " playing!</b>"
  100. ),
  101. "searching": (
  102. "<emoji document_id=5188311512791393083>🔎</emoji> <b>Searching...</b>"
  103. ),
  104. "currently_on": "Currently listening on",
  105. "playlist": "Playlist",
  106. "owner": "Owner",
  107. "quality": "Quality",
  108. }
  109. strings_ru = {
  110. "need_auth": (
  111. "<emoji document_id=5312526098750252863>🚫</emoji> <b>Выполни"
  112. " </b><code>.sauth</code><b> перед выполнением этого действия.</b>"
  113. ),
  114. "on-repeat": (
  115. "<emoji document_id=5469741319330996757>💫</emoji> <b>Повторение"
  116. " включено.</b>"
  117. ),
  118. "off-repeat": (
  119. "<emoji document_id=5472354553527541051>✋</emoji> <b>Повторение"
  120. " выключено.</b>"
  121. ),
  122. "skipped": (
  123. "<emoji document_id=5471978009449731768>👉</emoji> <b>Трек переключен.</b>"
  124. ),
  125. "err": (
  126. "<emoji document_id=5312526098750252863>🚫</emoji> <b>Произошла ошибка."
  127. " Убедитесь, что музыка играет!</b>\n<code>{}</code>"
  128. ),
  129. "already_authed": (
  130. "<emoji document_id=5312526098750252863>🚫</emoji> <b>Уже авторизован</b>"
  131. ),
  132. "authed": (
  133. "<emoji document_id=6319076999105087378>🎧</emoji> <b>Успешная"
  134. " аутентификация</b>"
  135. ),
  136. "playing": "<emoji document_id=6319076999105087378>🎧</emoji> <b>Играю...</b>",
  137. "back": (
  138. "<emoji document_id=5469735272017043817>👈</emoji> <b>Переключил назад</b>"
  139. ),
  140. "paused": "<emoji document_id=5469904794376217131>🤚</emoji> <b>Пауза</b>",
  141. "deauth": (
  142. "<emoji document_id=6037460928423791421>🚪</emoji> <b>Авторизация"
  143. " отменена</b>"
  144. ),
  145. "restarted": (
  146. "<emoji document_id=5469735272017043817>👈</emoji> <b>Начал трек сначала</b>"
  147. ),
  148. "liked": (
  149. '<emoji document_id=5199727145022134809>❤️</emoji> <b>Поставил "Мне'
  150. ' нравится" текущему треку</b>'
  151. ),
  152. "autobio": (
  153. "<emoji document_id=6319076999105087378>🎧</emoji> <b>Обновление био"
  154. " включено {}</b>"
  155. ),
  156. "404": (
  157. "<emoji document_id=5312526098750252863>🚫</emoji> <b>Нет результатов</b>"
  158. ),
  159. "playing_track": (
  160. "<emoji document_id=5212941939053175244>🎧</emoji> <b>{} добавлен в"
  161. " очередь</b>"
  162. ),
  163. "no_music": (
  164. "<emoji document_id=5312526098750252863>🚫</emoji> <b>Музыка не играет!</b>"
  165. ),
  166. "_cmd_doc_sfind": "Найти информацию о треке",
  167. "_cmd_doc_sauth": "Первый этап аутентификации",
  168. "_cmd_doc_scode": "Второй этап аутентификации",
  169. "_cmd_doc_unauth": "Отменить аутентификацию",
  170. "_cmd_doc_sbio": "Включить автоматическое био",
  171. "_cmd_doc_stokrefresh": "Принудительное обновление токена",
  172. "_cmd_doc_snow": "Показать карточку текущего трека",
  173. "_cls_doc": (
  174. "Тулкит для Spotify. Автор идеи: @fuccsoc. Реализация: @hikariatama"
  175. ),
  176. "currently_on": "Сейчас слушаю на",
  177. "playlist": "Плейлист",
  178. "owner": "Владелец",
  179. "quality": "Качество",
  180. }
  181. def __init__(self):
  182. self._client_id = "e0708753ab60499c89ce263de9b4f57a"
  183. self._client_secret = "80c927166c664ee98a43a2c0e2981b4a"
  184. self.scope = (
  185. "user-read-playback-state playlist-read-private playlist-read-collaborative"
  186. " app-remote-control user-modify-playback-state user-library-modify"
  187. " user-library-read"
  188. )
  189. self.sp_auth = spotipy.oauth2.SpotifyOAuth(
  190. client_id=self._client_id,
  191. client_secret=self._client_secret,
  192. redirect_uri="https://fuccsoc.com/",
  193. scope=self.scope,
  194. )
  195. self.config = loader.ModuleConfig(
  196. loader.ConfigValue(
  197. "AutoBioTemplate",
  198. "🎧 {} ───○ 🔊 ᴴᴰ",
  199. lambda: "Template for Spotify AutoBio",
  200. )
  201. )
  202. def create_bar(self, current_playback: dict) -> str:
  203. try:
  204. percentage = ceil(
  205. current_playback["progress_ms"]
  206. / current_playback["item"]["duration_ms"]
  207. * 100
  208. )
  209. bar_filled = ceil(percentage / 10) - 1
  210. bar_empty = 10 - bar_filled - 1
  211. bar = "".join("─" for _ in range(bar_filled)) + "🞆"
  212. bar += "".join("─" for _ in range(bar_empty))
  213. bar += f' {current_playback["progress_ms"] // 1000 // 60:02}:{current_playback["progress_ms"] // 1000 % 60:02} /'
  214. bar += f' {current_playback["item"]["duration_ms"] // 1000 // 60:02}:{current_playback["item"]["duration_ms"] // 1000 % 60:02}'
  215. except Exception:
  216. bar = "──────🞆─── 0:00 / 0:00"
  217. return bar
  218. @staticmethod
  219. def create_vol(vol: int) -> str:
  220. volume = "─" * (vol * 4 // 100)
  221. volume += "○"
  222. volume += "─" * (4 - vol * 4 // 100)
  223. return volume
  224. async def create_badge(self, thumb_url: str, title: str, artist: str) -> bytes:
  225. thumb = Image.open(
  226. io.BytesIO((await utils.run_sync(requests.get, thumb_url)).content)
  227. )
  228. im = Image.new("RGB", SIZE, (30, 30, 30))
  229. draw = ImageDraw.Draw(im)
  230. thumb_size = SIZE[1] - INNER_MARGIN[1] * 2
  231. thumb = thumb.resize((thumb_size, thumb_size))
  232. im.paste(thumb, INNER_MARGIN)
  233. tpos = INNER_MARGIN
  234. tpos = (
  235. tpos[0] + thumb_size + INNER_MARGIN[0] + 8,
  236. thumb_size // 2 - (TRACK_FS + ARTIST_FS) // 2,
  237. )
  238. draw.text(tpos, title, (255, 255, 255), font=self.font)
  239. draw.text(
  240. (tpos[0], tpos[1] + TRACK_FS + 8),
  241. artist,
  242. (180, 180, 180),
  243. font=self.font_smaller,
  244. )
  245. img = io.BytesIO()
  246. im.save(img, format="PNG")
  247. return img.getvalue()
  248. @loader.loop(interval=90)
  249. async def autobio(self):
  250. try:
  251. current_playback = self.sp.current_playback()
  252. track = current_playback["item"]["name"]
  253. track = re.sub(r"([(].*?[)])", "", track).strip()
  254. except Exception:
  255. return
  256. bio = self.config["AutoBioTemplate"].format(f"{track}")
  257. try:
  258. await self._client(
  259. UpdateProfileRequest(about=bio[: 140 if self._premium else 70])
  260. )
  261. except FloodWaitError as e:
  262. logger.info(f"Sleeping {max(e.seconds, 60)} bc of floodwait")
  263. await asyncio.sleep(max(e.seconds, 60))
  264. return
  265. async def _dl_font(self):
  266. font = (
  267. await utils.run_sync(
  268. requests.get,
  269. "https://github.com/hikariatama/assets/raw/master/ARIALUNI.TTF",
  270. )
  271. ).content
  272. self.font_smaller = ImageFont.truetype(
  273. io.BytesIO(font), ARTIST_FS, encoding="UTF-8"
  274. )
  275. self.font = ImageFont.truetype(io.BytesIO(font), TRACK_FS, encoding="UTF-8")
  276. self.font_ready.set()
  277. async def client_ready(self, client, db):
  278. self.font_ready = asyncio.Event()
  279. asyncio.ensure_future(self._dl_font())
  280. self._premium = getattr(await client.get_me(), "premium", False)
  281. try:
  282. self.sp = spotipy.Spotify(auth=self.get("acs_tkn")["access_token"])
  283. except Exception:
  284. self.set("acs_tkn", None)
  285. self.sp = None
  286. if self.get("autobio", False):
  287. self.autobio.start()
  288. with contextlib.suppress(Exception):
  289. await utils.dnd(client, "@DirectLinkGenerator_Bot", archive=True)
  290. self.musicdl = await self.import_lib(
  291. "https://libs.hikariatama.ru/musicdl.py",
  292. suspend_on_error=True,
  293. )
  294. def tokenized(func) -> FunctionType:
  295. @functools.wraps(func)
  296. async def wrapped(*args, **kwargs):
  297. if not args[0].get("acs_tkn", False) or not args[0].sp:
  298. await utils.answer(args[1], args[0].strings("need_auth"))
  299. return
  300. return await func(*args, **kwargs)
  301. wrapped.__doc__ = func.__doc__
  302. wrapped.__module__ = func.__module__
  303. return wrapped
  304. def error_handler(func) -> FunctionType:
  305. @functools.wraps(func)
  306. async def wrapped(*args, **kwargs):
  307. try:
  308. return await func(*args, **kwargs)
  309. except Exception:
  310. logger.exception(traceback.format_exc())
  311. with contextlib.suppress(Exception):
  312. await utils.answer(
  313. args[1],
  314. args[0].strings("err").format(traceback.format_exc()),
  315. )
  316. wrapped.__doc__ = func.__doc__
  317. wrapped.__module__ = func.__module__
  318. return wrapped
  319. def autodelete(func) -> FunctionType:
  320. @functools.wraps(func)
  321. async def wrapped(*args, **kwargs):
  322. a = await func(*args, **kwargs)
  323. with contextlib.suppress(Exception):
  324. await asyncio.sleep(10)
  325. await args[1].delete()
  326. return a
  327. wrapped.__doc__ = func.__doc__
  328. wrapped.__module__ = func.__module__
  329. return wrapped
  330. @error_handler
  331. @tokenized
  332. @autodelete
  333. async def srepeatcmd(self, message: Message):
  334. """💫 Repeat"""
  335. self.sp.repeat("track")
  336. await utils.answer(message, self.strings("on-repeat"))
  337. @error_handler
  338. @tokenized
  339. @autodelete
  340. async def sderepeatcmd(self, message: Message):
  341. """✋ Stop repeat"""
  342. self.sp.repeat("context")
  343. await utils.answer(message, self.strings("off-repeat"))
  344. @error_handler
  345. @tokenized
  346. @autodelete
  347. async def snextcmd(self, message: Message):
  348. """👉 Skip"""
  349. self.sp.next_track()
  350. await utils.answer(message, self.strings("skipped"))
  351. @error_handler
  352. @tokenized
  353. @autodelete
  354. async def spausecmd(self, message: Message):
  355. """🤚 Pause"""
  356. self.sp.pause_playback()
  357. await utils.answer(message, self.strings("paused"))
  358. @error_handler
  359. @tokenized
  360. @autodelete
  361. async def splaycmd(self, message: Message, from_sq: bool = False):
  362. """▶️ Play"""
  363. args = utils.get_args_raw(message)
  364. reply = await message.get_reply_message()
  365. if not args:
  366. if not reply or "https://open.spotify.com/track/" not in reply.text:
  367. self.sp.start_playback()
  368. await utils.answer(message, self.strings("playing"))
  369. return
  370. else:
  371. args = re.search('https://open.spotify.com/track/(.+?)"', reply.text)[1]
  372. try:
  373. track = self.sp.track(args)
  374. except Exception:
  375. search = self.sp.search(q=args, type="track", limit=1)
  376. if not search:
  377. await utils.answer(message, self.strings("404"))
  378. try:
  379. track = search["tracks"]["items"][0]
  380. except Exception:
  381. await utils.answer(message, self.strings("404"))
  382. return
  383. self.sp.add_to_queue(track["id"])
  384. if not from_sq:
  385. self.sp.next_track()
  386. await message.delete()
  387. await self._client.send_file(
  388. message.peer_id,
  389. await self.create_badge(
  390. track["album"]["images"][0]["url"],
  391. track["name"],
  392. ", ".join([_["name"] for _ in track["artists"]]),
  393. ),
  394. caption=self.strings("playing_track").format(track["name"]),
  395. )
  396. @error_handler
  397. @tokenized
  398. @autodelete
  399. async def sfindcmd(self, message: Message):
  400. """Find info about track"""
  401. args = utils.get_args_raw(message)
  402. if not args:
  403. await utils.answer(message, self.strings("404"))
  404. message = await utils.answer(message, self.strings("searching"))
  405. try:
  406. track = self.sp.track(args)
  407. except Exception:
  408. search = self.sp.search(q=args, type="track", limit=1)
  409. if not search:
  410. await utils.answer(message, self.strings("404"))
  411. try:
  412. track = search["tracks"]["items"][0]
  413. assert track
  414. except Exception:
  415. await utils.answer(message, self.strings("404"))
  416. return
  417. await self._open_track(track, message)
  418. async def _open_track(
  419. self,
  420. track: dict,
  421. message: Message,
  422. override_text: str = None,
  423. ):
  424. reply = utils.get_topic(message) or None
  425. name = track.get("name")
  426. artists = [
  427. artist["name"] for artist in track.get("artists", []) if "name" in artist
  428. ]
  429. full_song_name = f"{name} - {', '.join(artists)}"
  430. music = await self.musicdl.dl(full_song_name, only_document=True)
  431. await self._client.send_file(
  432. message.peer_id,
  433. music,
  434. reply_to=reply,
  435. caption=(
  436. override_text
  437. or (
  438. (
  439. f"🗽 <b>{utils.escape_html(full_song_name)}</b>{{is_flac}}"
  440. if artists
  441. else f"🗽 <b>{utils.escape_html(track)}</b>{{is_flac}}"
  442. )
  443. if track
  444. else "{is_flac}"
  445. )
  446. ).format(
  447. is_flac=(
  448. "\n<emoji document_id=5359582743992737342>😎</emoji> <b>FLAC"
  449. f" {self.strings('quality')}</b>"
  450. )
  451. if getattr(music, "is_flac", False)
  452. else ""
  453. ),
  454. )
  455. if message.out:
  456. await message.delete()
  457. @error_handler
  458. @tokenized
  459. async def sqcmd(self, message: Message):
  460. """🔎"""
  461. await self.splaycmd(message, True)
  462. @error_handler
  463. @tokenized
  464. @autodelete
  465. async def sbackcmd(self, message: Message):
  466. """⏮"""
  467. self.sp.previous_track()
  468. await utils.answer(message, self.strings("back"))
  469. @error_handler
  470. @tokenized
  471. @autodelete
  472. async def sbegincmd(self, message: Message):
  473. """⏪"""
  474. self.sp.seek_track(0)
  475. await utils.answer(message, self.strings("restarted"))
  476. @error_handler
  477. @tokenized
  478. @autodelete
  479. async def slikecmd(self, message: Message):
  480. """❤️"""
  481. cupl = self.sp.current_playback()
  482. self.sp.current_user_saved_tracks_add([cupl["item"]["id"]])
  483. await utils.answer(message, self.strings("liked"))
  484. @error_handler
  485. async def sauthcmd(self, message: Message):
  486. """First stage of auth"""
  487. if self.get("acs_tkn", False) and not self.sp:
  488. await utils.answer(message, self.strings("already_authed"))
  489. else:
  490. self.sp_auth.get_authorize_url()
  491. await utils.answer(
  492. message,
  493. self.strings("auth").format(self.sp_auth.get_authorize_url()),
  494. )
  495. @error_handler
  496. @autodelete
  497. async def scodecmd(self, message: Message):
  498. """Second stage of auth"""
  499. url = message.message.split(" ")[1]
  500. code = self.sp_auth.parse_auth_response_url(url)
  501. self.set("acs_tkn", self.sp_auth.get_access_token(code, True, False))
  502. self.sp = spotipy.Spotify(auth=self.get("acs_tkn")["access_token"])
  503. await utils.answer(message, self.strings("authed"))
  504. @error_handler
  505. @autodelete
  506. async def unauthcmd(self, message: Message):
  507. """Deauth from Spotify API"""
  508. self.set("acs_tkn", None)
  509. del self.sp
  510. await utils.answer(message, self.strings("deauth"))
  511. @error_handler
  512. @tokenized
  513. @autodelete
  514. async def sbiocmd(self, message: Message):
  515. """Toggle bio playback streaming"""
  516. current = self.get("autobio", False)
  517. new = not current
  518. self.set("autobio", new)
  519. await utils.answer(
  520. message,
  521. self.strings("autobio").format("enabled" if new else "disabled"),
  522. )
  523. if new:
  524. self.autobio.start()
  525. else:
  526. self.autobio.stop()
  527. @error_handler
  528. @tokenized
  529. @autodelete
  530. async def stokrefreshcmd(self, message: Message):
  531. """Force refresh token"""
  532. self.set(
  533. "acs_tkn",
  534. self.sp_auth.refresh_access_token(self.get("acs_tkn")["refresh_token"]),
  535. )
  536. self.set("NextRefresh", time.time() + 45 * 60)
  537. self.sp = spotipy.Spotify(auth=self.get("acs_tkn")["access_token"])
  538. await utils.answer(message, self.strings("authed"))
  539. @error_handler
  540. async def snowcmd(self, message: Message):
  541. """Show current playback badge"""
  542. current_playback = self.sp.current_playback()
  543. try:
  544. device = (
  545. current_playback["device"]["name"]
  546. + " "
  547. + current_playback["device"]["type"].lower()
  548. )
  549. except Exception:
  550. device = None
  551. volume = current_playback.get("device", {}).get("volume_percent", 0)
  552. try:
  553. playlist_id = current_playback["context"]["uri"].split(":")[-1]
  554. playlist = self.sp.playlist(playlist_id)
  555. playlist_name = playlist.get("name", None)
  556. try:
  557. playlist_owner = f'<a href="https://open.spotify.com/user/{playlist["owner"]["id"]}">{playlist["owner"]["display_name"]}</a>'
  558. except KeyError:
  559. playlist_owner = None
  560. except Exception:
  561. playlist_name = None
  562. playlist_owner = None
  563. try:
  564. track = current_playback["item"]["name"]
  565. track_id = current_playback["item"]["id"]
  566. except Exception:
  567. await utils.answer(message, self.strings("no_music"))
  568. return
  569. track_url = (
  570. current_playback.get("item", {})
  571. .get("external_urls", {})
  572. .get("spotify", None)
  573. )
  574. artists = [
  575. artist["name"]
  576. for artist in current_playback.get("item", {}).get("artists", [])
  577. if "name" in artist
  578. ]
  579. try:
  580. result = (
  581. (
  582. "<emoji document_id=5188705588925702510>🎶</emoji>"
  583. f" <b>{utils.escape_html(track)} -"
  584. f" {utils.escape_html(' '.join(artists))}</b>"
  585. if artists
  586. else (
  587. "<emoji document_id=5188705588925702510>🎶</emoji>"
  588. f" <b>{utils.escape_html(track)}</b>"
  589. )
  590. )
  591. if track
  592. else ""
  593. )
  594. icon = (
  595. "<emoji document_id=5431376038628171216>💻</emoji>"
  596. if "computer" in str(device)
  597. else "<emoji document_id=5407025283456835913>📱</emoji>"
  598. )
  599. result += (
  600. f"{{is_flac}}\n\n{icon} <b>{self.strings('currently_on')}</b>"
  601. f" <code>{device}</code>"
  602. if device
  603. else ""
  604. )
  605. result += (
  606. "\n<emoji document_id=5431736674147114227>🗂</emoji>"
  607. f" <b>{self.strings('playlist')}</b>: <a"
  608. f' href="https://open.spotify.com/playlist/{playlist_id}">{playlist_name}</a>'
  609. if playlist_name and playlist_id
  610. else ""
  611. )
  612. result += (
  613. "\n<emoji document_id=5467406098367521267>👑</emoji>"
  614. f" <b>{self.strings('owner')}</b>: {playlist_owner}"
  615. if playlist_owner
  616. else ""
  617. )
  618. result += (
  619. "\n\n<emoji document_id=5359342878659191095>🎵</emoji> <b><a"
  620. f' href="{track_url}">Spotify</a></b>'
  621. )
  622. except Exception:
  623. result = self.strings("no_music")
  624. message = await utils.answer(
  625. message,
  626. result.format(is_flac="")
  627. + "\n\n<emoji document_id=5325617665874600234>🕔</emoji> <i>Loading audio"
  628. " file...</i>",
  629. )
  630. await self._open_track(current_playback["item"], message, result)
  631. async def watcher(self, message: Message):
  632. """Watcher is used to update token"""
  633. if not self.sp:
  634. return
  635. if self.get("NextRefresh", False):
  636. ttc = self.get("NextRefresh", 0)
  637. crnt = time.time()
  638. if ttc < crnt:
  639. self.set(
  640. "acs_tkn",
  641. self.sp_auth.refresh_access_token(
  642. self.get("acs_tkn")["refresh_token"]
  643. ),
  644. )
  645. self.set("NextRefresh", time.time() + 45 * 60)
  646. self.sp = spotipy.Spotify(auth=self.get("acs_tkn")["access_token"])
  647. else:
  648. self.set(
  649. "acs_tkn",
  650. self.sp_auth.refresh_access_token(self.get("acs_tkn")["refresh_token"]),
  651. )
  652. self.set("NextRefresh", time.time() + 45 * 60)
  653. self.sp = spotipy.Spotify(auth=self.get("acs_tkn")["access_token"])
  654. async def on_unload(self):
  655. with contextlib.suppress(Exception):
  656. self.autobio.stop()