Files
limoka/SunnexGB/Heroku-Modules/SpotiSaver.py
2026-06-12 08:36:40 +00:00

191 lines
9.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Спасибо: snfsx, кезу, а так же Gemini
# requires: httpx
# meta developer: @SunnexGB
# meta repo: https://raw.githubusercontent.com/SunnexGB/Heroku-Modules/refs/heads/main/spotisaver.py
# meta pic: https://r2.fakecrime.bio/uploads/ddf03169-09fe-4eb1-8eea-bad1a4cc4ada.jpg
# meta banner: https://r2.fakecrime.bio/uploads/ddf03169-09fe-4eb1-8eea-bad1a4cc4ada.jpg
# meta fhsdesc: Spotify, downloader, music, музыка, спотифай,скачать музыку
# это не должно было быть в релизе,но ладно я потом пофикшу все и вся в говнокоде.
__version__ = (1, 1, 1)
import asyncio
import httpx
import os
import re
import logging
from .. import loader, utils
from herokutl.types import Message
logger = logging.getLogger(__name__)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36",
"Accept": "application/json",
"Content-Type": "application/json",
"Origin": "https://spotmate.online",
"Referer": "https://spotmate.online/en1",
}
@loader.tds
class SpotiSaver(loader.Module):
"""Downloading music from Spotify"""
strings = {
"name": "SpotiSaver",
# "args": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> link to song is not specified</b>",
"downloading": "<b><tg-emoji emoji-id=5443127283898405358>📥</tg-emoji> Downloading:</b> <code>{}</code>",
"error": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> Error, see logs!</b>",
"done": "<b><tg-emoji emoji-id=5206607081334906820>✔️</tg-emoji> Done!</b>",
"no_spotifymod": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>SpotifyMod not found.</b>",
"no_spotify": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>Nothing is playing on Spotify.</b>",
"nf_id": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> ID key not found!</b>",
"nf_track": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> Song not found.</b>",
"timeout": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> timeout! Try again.</b>",
}
strings_ru = {
"name": "SpotiSaver",
"_cls_doc": "Скачивание музыки из Spotify",
# "args": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> Ссылка на песню не указана</b>",
"downloading": "<b><tg-emoji emoji-id=5443127283898405358>📥</tg-emoji> Скачиваю:</b> <code>{}</code>",
"error": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> Ерорь, смотри логи!</b>",
"done": "<b><tg-emoji emoji-id=5206607081334906820>✔️</tg-emoji> Готово!</b>",
"no_spotifymod": "<tg-emoji emoji-id=5431402435497181911>💢</tg-emoji> <b>SpotifyMod не найден.</b>",
"no_spotify": "<tg-emoji emoji-id=5429164207780152924>😅</tg-emoji> <b>В Spotify ничего не играет.</b>",
"nf_id": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> ID песни не найден</b>",
"nf_track": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> Песня не найдена</b>",
"timeout": "<b><tg-emoji emoji-id=5210952531676504517>❌</tg-emoji> Таймаут! Попробуй ещё раз.</b>",
}
def __init__(self):
self.config = loader.ModuleConfig(
loader.ConfigValue(
"TimeOut",
60,
"Response timeout in seconds | Время ожидания ответа в секундах",
validator=loader.validators.Integer(minimum=30),
)
)
async def get_session(self, client: httpx.AsyncClient) -> str:
res = await client.get("https://spotmate.online/en1", headers={
"User-Agent": headers["User-Agent"],
"Accept": "text/html",
},
timeout=self.config["TimeOut"])
match = re.search(r'csrf-token[^>]*content="([^"]+)"', res.text)
if not match:
raise ValueError("CSRF token not found")
return match.group(1)
async def get_current_spotify_url(self) -> str | None:
spotifymod = self.lookup("SpotifyMod")
if not spotifymod or not spotifymod.sp:
return None
current_playback = await asyncio.to_thread(spotifymod.sp.current_playback)
if not current_playback or not current_playback.get("is_playing"):
return None
track_id = current_playback["item"]["id"]
return f"https://open.spotify.com/track/{track_id}"
@loader.command(ru_doc="<ссылка> — Скачать трек из Spotify")
async def spotsave(self, message: Message):
"""<link> - Download track from Spotify"""
args = utils.get_args_raw(message)
if not args:
spotifymod = self.lookup("SpotifyMod")
if not spotifymod or not spotifymod.sp:
return await utils.answer(message, self.strings["no_spotifymod"])
args = await self.get_current_spotify_url()
if not args:
return await utils.answer(message, self.strings["no_spotify"])
if "track/" not in args:
return await utils.answer(message, self.strings["nf_id"])
track_url = args.split("?")[0]
try:
async with httpx.AsyncClient(follow_redirects=True) as client:
csrf = await self.get_session(client)
hdrs = {**headers, "X-CSRF-TOKEN": csrf}
info_res = await client.post(
"https://spotmate.online/getTrackData",
headers=hdrs,
json={"spotify_url": track_url},
timeout=self.config["TimeOut"],
)
info = info_res.json()
if info.get("type") != "track":
return await utils.answer(message, self.strings["nf_track"])
track_name = info.get("name", "Unknown")
artists = ", ".join(a["name"] for a in info.get("artists", []))
full_name = f"{artists} - {track_name}"
track_id = info.get("id", track_url.split("/")[-1])
conv_res = await client.post(
"https://spotmate.online/convert",
headers=hdrs,
json={"urls": track_url},
timeout=self.config["TimeOut"],
)
conv = conv_res.json()
download_url = conv.get("url") or conv.get("download_url")
task_id = conv.get("task_id") or conv.get("taskId")
if not download_url and task_id:
for _ in range(40):
await asyncio.sleep(4.5)
task_res = await client.get(
f"https://spotmate.online/tasks/{task_id}",
headers={**hdrs, "Accept": "application/json"},
timeout=self.config["TimeOut"],
)
task = task_res.json()
if task.get("error"):
return await utils.answer(message, self.strings["error"])
data = task.get("data") or task.get("result") or {}
status = str(data.get("status") or data.get("state") or "").lower()
if status == "finished":
download_url = (
data.get("url") or data.get("download_url")
or (data.get("result") or {}).get("url")
or (data.get("result") or {}).get("download_url")
)
break
if status in ("failed", "error", "expired", "cancelled"):
return await utils.answer(message, self.strings["error"])
if not download_url:
return await utils.answer(message, self.strings["timeout"])
await utils.answer(
message,
self.strings["downloading"].format(utils.escape_html(full_name)),
)
file_res = await client.get(
download_url,
headers={"User-Agent": headers["User-Agent"], "Referer": "https://spotmate.online/en1"},
timeout=self.config["TimeOut"],
)
filename = f"{track_id}.mp3"
with open(filename, "wb") as f:
f.write(file_res.content)
await self.client.send_file(
message.chat_id,
filename,
caption=self.strings["done"],
reply_to=message.id,
attributes=(
[utils.get_audio_tag(filename, title=track_name, performer=artists)]
if hasattr(utils, "get_audio_tag")
else []
),
)
await message.delete()
if os.path.exists(filename):
os.remove(filename)
except Exception:
logger.exception("Download failed")
await utils.answer(message, self.strings["error"])