__version__ = (1, 0, 0) # ©️ Fixyres, 2026-2030 # 🌐 https://github.com/Fixyres/FModules # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # 🔑 http://www.apache.org/licenses/LICENSE-2.0 # meta banner: https://raw.githubusercontent.com/Fixyres/FModules/refs/heads/main/assets/SCD/banner.png # meta developer: @NFModules # meta fhsdesc: SoundCloud, Music, Music downloader, Downloader # requires: curl_cffi import io import re import json from telethon.tl.types import DocumentAttributeAudio from curl_cffi import requests from .. import loader, utils @loader.tds class SCD(loader.Module): '''Module for downloading songs from SoundCloud.''' _client_id = None strings = { "name": "SCD", "_cls_doc": "Module for downloading songs from SoundCloud.", "no_args": "✘ You didn't provide a link to the song, example of using the command: {prefix}sc (link)", "downloading": "↓ Downloading...", "not_found": "✘ Song not found." } strings_ru = { "_cls_doc": "Модуль для скачивания песен с SoundCloud.", "no_args": "✘ Вы не указали ссылку на песню, пример использования команды: {prefix}sc (ссылка)", "downloading": "↓ Скачивание...", "not_found": "✘ Песня не найдена." } strings_ua = { "_cls_doc": "Модуль для завантаження пісень із SoundCloud.", "no_args": "✘ Ви не вказали посилання на пісню, приклад використання команди: {prefix}sc (посилання)", "downloading": "↓ Завантаження...", "not_found": "✘ Пісню не знайдено." } strings_de = { "_cls_doc": "Modul zum Herunterladen von Liedern von SoundCloud.", "no_args": "✘ Sie haben keinen Link zum Lied angegeben, Anwendungsbeispiel des Befehls: {prefix}sc (Link)", "downloading": "↓ Wird heruntergeladen...", "not_found": "✘ Lied nicht gefunden." } strings_uz = { "_cls_doc": "SoundCloud-dan qo'shiqlarni yuklab olish uchun modul.", "no_args": "✘ Siz qo'shiq havolasini kiritmadingiz, buyruqdan foydalanish misoli: {prefix}sc (havola)", "downloading": "↓ Yuklab olinmoqda...", "not_found": "✘ Qo'shiq topilmadi." } strings_kz = { "_cls_doc": "SoundCloud-тан әндерді жүктеп алуға арналған модуль.", "no_args": "✘ Сіз әнге сілтеме көрсетпедіңіз, бұйрықты пайдалану мысалы: {prefix}sc (сілтеме)", "downloading": "↓ Жүктелуде...", "not_found": "✘ Ән табылмады." } strings_fr = { "_cls_doc": "Module pour télécharger des chansons depuis SoundCloud.", "no_args": "✘ Vous n'avez pas fourni de lien vers la chanson, exemple d'utilisation de la commande: {prefix}sc (lien)", "downloading": "↓ Téléchargement...", "not_found": "✘ Chanson non trouvée." } strings_jp = { "_cls_doc": "SoundCloudから曲をダウンロードするためのモジュール。", "no_args": "✘ 曲へのリンクが指定されていません。コマンドの使用例: {prefix}sc (リンク)", "downloading": "↓ ダウンロード中...", "not_found": "✘ 曲が見つかりません。" } async def _get_client_id(self, ses, html): if self._client_id: return self._client_id for scr in reversed(re.findall(r'src="(https://a-v2\.sndcdn\.com/assets/[^"]+\.js)"', html)): m = re.search(r'client_id:"([a-zA-Z0-9]{32})"', (await ses.get(scr)).text) if m: self._client_id = m.group(1) return self._client_id raise ValueError() @loader.command( ru_doc="(ссылка) - скачать песню с SoundCloud.", ua_doc="(посилання) - завантажити пісню з SoundCloud.", de_doc="(Link) - laden Sie ein Lied von SoundCloud herunter.", uz_doc="(havola) - SoundCloud-dan qo'shiq yuklab olish.", kz_doc="(сілтеме) - SoundCloud-тан әнді жүктеп алу.", fr_doc="(lien) - télécharger une chanson depuis SoundCloud.", jp_doc="(リンク) - SoundCloudから曲をダウンロードします。" ) async def scd(self, message): '''(link) - download a song from SoundCloud.''' args = utils.get_args_raw(message) if not args: await utils.answer(message, self.strings["no_args"].format(prefix=self.get_prefix())) return m = re.search(r"(https?://(?:[a-zA-Z0-9-]+\.)?soundcloud\.com/[^\s]+)", args) if not m: await utils.answer(message, self.strings["not_found"]) return msg = await utils.answer(message, self.strings["downloading"]) try: async with requests.AsyncSession(impersonate="chrome120") as ses: h_resp = await ses.get(m.group(1)) if h_resp.status_code != 200: raise ValueError() html = h_resp.text c_id = await self._get_client_id(ses, html) h_m = re.search(r'window\.__sc_hydration\s*=\s*(\[.*?\]);', html) if not h_m: raise ValueError() t_d = next((i.get("data") for i in json.loads(h_m.group(1)) if i.get("hydratable") == "sound"), None) if not t_d or t_d.get('kind') != 'track': raise ValueError() art = t_d.get("artwork_url") or t_d.get("user", {}).get("avatar_url") if art: art = art.replace("-large.jpg", "-t500x500.jpg") tr = t_d.get("media", {}).get("transcodings", []) if not tr: raise ValueError() s_info = next((t for t in tr if t.get("format", {}).get("protocol") == "progressive"), tr[0]) s_url = s_info.get("url") + f"?client_id={c_id}" + (f"&track_authorization={t_d.get('track_authorization')}" if t_d.get("track_authorization") else "") s_resp = await ses.get(s_url) if s_resp.status_code != 200 or not s_resp.json().get("url"): raise ValueError() a_buf = io.BytesIO() a_buf.name = "track.mp3" if s_info.get("format", {}).get("protocol") == "progressive": m_resp = await ses.get(s_resp.json().get("url")) if m_resp.status_code != 200: raise ValueError() a_buf.write(m_resp.content) else: m3_resp = await ses.get(s_resp.json().get("url")) if m3_resp.status_code != 200: raise ValueError() chk = [l for l in m3_resp.text.splitlines() if l and not l.startswith('#')] if not chk: raise ValueError() for c_u in chk: c_r = await ses.get(c_u) if c_r.status_code != 200: raise ValueError() a_buf.write(c_r.content) a_buf.seek(0) t_buf = None if art: try: a_r = await ses.get(art) if a_r.status_code == 200: t_buf = io.BytesIO(a_r.content) t_buf.name = "cover.jpg" except: pass await message.client.send_file( message.peer_id, a_buf, thumb=t_buf, attributes=[DocumentAttributeAudio( duration=t_d.get("duration", 0) // 1000, title=t_d.get("title", "Unknown"), performer=t_d.get("user", {}).get("username", "Unknown Artist") )], reply_to=message.reply_to_msg_id ) await msg.delete() except: await utils.answer(msg, self.strings["not_found"])