Compare commits

...

2 Commits

Author SHA1 Message Date
github-actions[bot]
71ed0c604c Updated modules.json after parse 2026-06-12 02:52:17 2026-06-12 02:52:17 +00:00
github-actions[bot]
f335145976 Added and updated repositories 2026-06-12 02:51:44 2026-06-12 02:51:44 +00:00
3 changed files with 68237 additions and 67916 deletions

View File

@@ -26,7 +26,6 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Banners: class Banners:
def __init__( def __init__(
self, self,
@@ -324,6 +323,16 @@ class YaMusicMod(loader.Module):
"name": "YaMusic" "name": "YaMusic"
} }
duration_placeholder = {
"start_duration": "<tg-emoji emoji-id=5262663495538742892>☀️</tg-emoji><tg-emoji emoji-id=5260381609479153468>☀️</tg-emoji>",
"start_full_duration": "<tg-emoji emoji-id=5262663495538742892>☀️</tg-emoji><tg-emoji emoji-id=5260609582048254485>☀️</tg-emoji>",
"closed_duration": "<tg-emoji emoji-id=5260467667738859177>☀️</tg-emoji>",
"empty_mid": "<tg-emoji emoji-id=5260415715814448198>☀️</tg-emoji>",
"empty_closed_duration_duration": "<tg-emoji emoji-id=5260239235608255208>☀️</tg-emoji>",
"end_duration_full": "<tg-emoji emoji-id=5260467667738859177>☀️</tg-emoji>",
"empty_closed_duration": "<tg-emoji emoji-id=5260239235608255208>☀️</tg-emoji>",
}
def __init__(self): def __init__(self):
self.config = loader.ModuleConfig( self.config = loader.ModuleConfig(
loader.ConfigValue( loader.ConfigValue(
@@ -553,67 +562,19 @@ class YaMusicMod(loader.Module):
return "0%" return "0%"
percent = (progress / duration) * 100 percent = (progress / duration) * 100
fill_logic = int(percent // 16.66)
s_less_10 = ( bar = self.duration_placeholder["start_full_duration"] if fill_logic >= 1 else self.duration_placeholder["start_duration"]
"<emoji document_id=5454137780454067986></emoji>" for i in range(2, 6):
"<emoji document_id=6158923355173949539>⭐</emoji>" if fill_logic >= i:
"<emoji document_id=6159012102083188132>⭐</emoji>" bar += self.duration_placeholder["closed_duration"]
"<emoji document_id=6159012102083188132>⭐</emoji>" else:
"<emoji document_id=6158753257289158944>⭐</emoji>" bar += self.duration_placeholder["empty_mid"]
"<emoji document_id=6156700344526049665>⭐</emoji>" if fill_logic >= 6:
) bar += self.duration_placeholder["end_duration_full"]
s_10_to_20 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=6159095673556840262>⭐</emoji>"
"<emoji document_id=6159012102083188132>⭐</emoji>"
"<emoji document_id=6156933677214341691>⭐</emoji>"
"<emoji document_id=6158753257289158944>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_30_to_40 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6158923355173949539>⭐</emoji>"
"<emoji document_id=6159012102083188132>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_over_50 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6156933677214341691>⭐</emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
s_over_80 = (
"<emoji document_id=5454137780454067986></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=5454397458471750662></emoji>"
"<emoji document_id=6156700344526049665>⭐</emoji>"
)
if percent < 10:
return s_less_10
elif percent < 20:
return s_10_to_20
elif percent < 30:
return s_10_to_20
elif percent < 40:
return s_30_to_40
elif percent < 50:
return s_30_to_40
elif percent < 80:
return s_over_50
else: else:
return s_over_80 bar += self.duration_placeholder["empty_closed_duration"]
return bar
except Exception as e: except Exception as e:
return f"Error: {e}" return f"Error: {e}"

135856
modules.json

File diff suppressed because it is too large Load Diff

View File

@@ -17,10 +17,10 @@
# ======================================= # =======================================
# #
# meta developer: @ke_mods # meta developer: @ke_mods
# requires: telethon spotipy pillow requests yt-dlp curl_cffi # requires: telethon spotipy pillow requests httpx
# scope: ffmpeg # scope: ffmpeg
__version__ = (1, 0) __version__ = (1, 0, 2)
import asyncio import asyncio
import contextlib import contextlib
@@ -35,6 +35,7 @@ import os
from types import FunctionType from types import FunctionType
import random import random
import httpx
import requests import requests
import spotipy import spotipy
from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont, ImageOps from PIL import Image, ImageDraw, ImageEnhance, ImageFilter, ImageFont, ImageOps
@@ -48,6 +49,14 @@ from .. import loader, utils
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logging.getLogger("spotipy").setLevel(logging.CRITICAL) logging.getLogger("spotipy").setLevel(logging.CRITICAL)
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",
}
class Banners: class Banners:
def __init__( def __init__(
self, self,
@@ -517,7 +526,6 @@ class SpotifyMod(loader.Module):
"autobio": ( "autobio": (
"<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Spotify autobio {}</b>" "<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Spotify autobio {}</b>"
), ),
"no_ytdlp": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>yt-dlp not found... Check config or install yt-dlp (<code>{}terminal pip install yt-dlp</code>)</b>",
"snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Download failed</b>", "snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Download failed</b>",
"uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Uploading banner...</i>", "uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Uploading banner...</i>",
"downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Downloading track...</i>", "downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Downloading track...</i>",
@@ -531,6 +539,12 @@ class SpotifyMod(loader.Module):
"playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Playlist {} deleted.</b>", "playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Playlist {} deleted.</b>",
"no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Please specify a playlist name.</b>", "no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Please specify a playlist name.</b>",
"device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Select playback device:</b>", "device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Select playback device:</b>",
"on-shuffle": (
"<tg-emoji emoji-id=5267246517701352801>🔀</tg-emoji> <b>Shuffle enabled.</b>"
),
"off-shuffle": (
"<tg-emoji emoji-id=5265105218806259720>🔀</tg-emoji> <b>Shuffle disabled.</b>"
),
} }
strings_ru = { strings_ru = {
@@ -641,7 +655,6 @@ class SpotifyMod(loader.Module):
"<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Обновление био" "<tg-emoji emoji-id=6319076999105087378>🎧</tg-emoji> <b>Обновление био"
" включено {}</b>" " включено {}</b>"
), ),
"no_ytdlp": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>yt-dlp не найден... Проверьте конфиг или установите yt-dlp (<code>{}terminal pip install yt-dlp</code>)</b>",
"snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Ошибка скачивания.</b>", "snowt_failed": "\n\n<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Ошибка скачивания.</b>",
"uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Загрузка баннера...</i>", "uploading_banner": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Загрузка баннера...</i>",
"downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Скачивание трека...</i>", "downloading_track": "\n\n<tg-emoji emoji-id=5841359499146825803>🕔</tg-emoji> <i>Скачивание трека...</i>",
@@ -655,6 +668,12 @@ class SpotifyMod(loader.Module):
"playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Плейлист {} удален.</b>", "playlist_deleted": "<tg-emoji emoji-id=5776375003280838798>✅</tg-emoji> <b>Плейлист {} удален.</b>",
"no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Пожалуйста, укажите название плейлиста.</b>", "no_playlist_name": "<tg-emoji emoji-id=5778527486270770928>❌</tg-emoji> <b>Пожалуйста, укажите название плейлиста.</b>",
"device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Выберите устройство для воспроизведения:</b>", "device_select": "<tg-emoji emoji-id=5956561916573782596>📄</tg-emoji> <b>Выберите устройство для воспроизведения:</b>",
"on-shuffle": (
"<tg-emoji emoji-id=5267246517701352801>🔀</tg-emoji> <b>Перемешивание включено.</b>"
),
"off-shuffle": (
"<tg-emoji emoji-id=5265105218806259720>🔀</tg-emoji> <b>Перемешивание отключено.</b>"
),
} }
def __init__(self): def __init__(self):
@@ -700,16 +719,10 @@ class SpotifyMod(loader.Module):
lambda: "Template for Spotify AutoBio, supports {artist}, {title}", lambda: "Template for Spotify AutoBio, supports {artist}, {title}",
), ),
loader.ConfigValue( loader.ConfigValue(
"ytdlp_path", "TimeOut",
"", 60,
"Path to ytdlp binary", "Response timeout in seconds | Время ожидания ответа в секундах",
validator=loader.validators.String(), validator=loader.validators.Integer(minimum=30),
),
loader.ConfigValue(
"cookies_path",
"",
"Path to your cookies for yt-dlp",
validator=loader.validators.String(),
), ),
loader.ConfigValue( loader.ConfigValue(
"banner_version", "banner_version",
@@ -944,36 +957,102 @@ class SpotifyMod(loader.Module):
success = False success = False
try: try:
squery = query.replace('"', '').replace("'", "") track_url = (query or "").strip().split("?")[0]
cookies = self.config["cookies_path"] if "spotify:track:" in track_url:
ytdlp_flags = '-x --audio-format mp3 --audio-quality 0 --add-metadata --format "bestaudio/best" --no-playlist' track_url = f"https://open.spotify.com/track/{track_url.split(':')[-1]}"
cookies_flag = f"--cookies {cookies} " if cookies else ""
cmd = (
f'{self.config["ytdlp_path"]} {ytdlp_flags} {cookies_flag}'
f'-o "{dl_dir}/%(title)s [%(id)s].%(ext)s" '
f'"ytsearch1:{squery}"'
)
proc = await asyncio.create_subprocess_shell( if "track/" not in track_url:
cmd, results = await asyncio.to_thread(
stdout=asyncio.subprocess.PIPE, self.sp.search,
stderr=asyncio.subprocess.PIPE, q=query,
) limit=1,
_, stderr = await proc.communicate() type="track",
)
items = (results or {}).get("tracks", {}).get("items", [])
if not items:
logger.error("SpotifyMod: Spotify track not found for %r", log_context or query)
await send_text(self.strings["snowt_failed"])
return False
if proc.returncode: track_data = items[0]
err_text = stderr.decode(errors="ignore").strip() if stderr else "yt-dlp failed" track_url = track_data.get("external_urls", {}).get("spotify") or f"https://open.spotify.com/track/{track_data['id']}"
logger.error("SpotifyMod: yt-dlp code %s for %r: %s", proc.returncode, log_context or query, err_text[-400:])
files = [f for f in os.listdir(dl_dir) if f.endswith(".mp3")] async with httpx.AsyncClient(follow_redirects=True) as client:
if files: csrf = await self.get_session(client)
success = await send_file(os.path.join(dl_dir, files[0])) 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":
logger.error("SpotifyMod: spotmate returned no track for %r", log_context or query)
await send_text(self.strings["snowt_failed"])
return False
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"):
logger.error("SpotifyMod: task error for %r", log_context or query)
await send_text(self.strings["dl_err"])
return False
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"):
logger.error("SpotifyMod: task failed for %r", log_context or query)
await send_text(self.strings["dl_err"])
return False
if not download_url:
logger.error("SpotifyMod: download timeout for %r", log_context or query)
await send_text(self.strings["snowt_failed"])
return False
file_res = await client.get(
download_url,
headers={"User-Agent": headers["User-Agent"], "Referer": "https://spotmate.online/en1"},
timeout=self.config["TimeOut"],
)
file_path = os.path.join(dl_dir, f"{track_id}.mp3")
with open(file_path, "wb") as f:
f.write(file_res.content)
success = await send_file(file_path)
if not success: if not success:
logger.error("SpotifyMod: failed to send %r (target=%s)", log_context or query, type(target).__name__) logger.error("SpotifyMod: failed to send %r (target=%s)", log_context or query, type(target).__name__)
await send_text(self.strings["dl_err"]) await send_text(self.strings["dl_err"])
else:
logger.error("SpotifyMod: yt-dlp produced no mp3 for %r", log_context or query)
await send_text(self.strings["snowt_failed"])
except Exception as e: except Exception as e:
logger.error("Download track error (%s): %s", log_context or "no context", e, exc_info=True) logger.error("Download track error (%s): %s", log_context or "no context", e, exc_info=True)
@@ -986,6 +1065,20 @@ class SpotifyMod(loader.Module):
return success return success
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)
def _short_text(self, text: str, limit: int = 60) -> str: def _short_text(self, text: str, limit: int = 60) -> str:
text = " ".join(text.split()) text = " ".join(text.split())
if len(text) <= limit: if len(text) <= limit:
@@ -1126,7 +1219,13 @@ class SpotifyMod(loader.Module):
tracks = results["tracks"]["items"] tracks = results["tracks"]["items"]
store_id = id(tracks) store_id = id(tracks)
self._sp_store[store_id] = [(t.get("name", "Unknown"), ", ".join(a.get("name", "") for a in t.get("artists", []) if a.get("name")) or "Unknown Artist") for t in tracks] self._sp_store[store_id] = [
(
t.get("name", "Unknown"),
", ".join(a.get("name", "") for a in t.get("artists", []) if a.get("name")) or "Unknown Artist",
)
for t in tracks
]
entries = [] entries = []
for i, track in enumerate(tracks): for i, track in enumerate(tracks):
@@ -1154,7 +1253,7 @@ class SpotifyMod(loader.Module):
async def ssearch(self, query): async def ssearch(self, query):
"""<query> - search Spotify track""" """<query> - search Spotify track"""
return await self._inline_search_tracks(query) return await self._inline_search_tracks(query)
@error_handler @error_handler
@tokenized @tokenized
@loader.command( @loader.command(
@@ -1427,6 +1526,26 @@ class SpotifyMod(loader.Module):
self.sp.repeat("context") self.sp.repeat("context")
await utils.answer(message, self.strings["off-repeat"]) await utils.answer(message, self.strings["off-repeat"])
@error_handler
@tokenized
@loader.command(
ru_doc="- 🔀 Включить перемешивание"
)
async def sshufflecmd(self, message: Message):
"""- 🔀 Enable shuffle"""
self.sp.shuffle(True)
await utils.answer(message, self.strings["on-shuffle"])
@error_handler
@tokenized
@loader.command(
ru_doc="- 🔀 Отключить перемешивание"
)
async def sdeshufflecmd(self, message: Message):
"""- 🔀 Disable shuffle"""
self.sp.shuffle(False)
await utils.answer(message, self.strings["off-shuffle"])
@error_handler @error_handler
@tokenized @tokenized
@loader.command( @loader.command(
@@ -1730,11 +1849,10 @@ class SpotifyMod(loader.Module):
@error_handler @error_handler
@tokenized @tokenized
@loader.command( @loader.command(
ru_doc="| .sq - 🔍 Поиск треков.", ru_doc="- 🔍 Поиск треков."
alias="sq"
) )
async def ssearchcmd(self, message: Message): async def sqcmd(self, message: Message):
"""| .sq - 🔍 Search for tracks.""" """- 🔍 Search for tracks."""
args = utils.get_args_raw(message) args = utils.get_args_raw(message)
if not args: if not args:
await utils.answer(message, self.strings["no_search_query"]) await utils.answer(message, self.strings["no_search_query"])
@@ -1800,6 +1918,13 @@ class SpotifyMod(loader.Module):
), ),
) )
@error_handler
@tokenized
@loader.command(ru_doc="- 🔍 Поиск треков.")
async def ssearchcmd(self, message: Message):
"""- 🔍 Search for tracks."""
await self.sqcmd(message)
async def watcher(self, message: Message): async def watcher(self, message: Message):
"""Watcher is used to update token""" """Watcher is used to update token"""
if not self.sp: if not self.sp:
@@ -1855,4 +1980,5 @@ class SpotifyMod(loader.Module):
refresh_token = await self.invoke("stokrefresh", "", self.inline.bot.id) refresh_token = await self.invoke("stokrefresh", "", self.inline.bot.id)
await refresh_token.delete() await refresh_token.delete()
else: else:
self.set("NextRefresh", time.time() + 300) self.set("NextRefresh", time.time() + 300)
# слендермен