__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: @FModules
# 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"))