commit dc913441637fe302bdb2932c506b3b93cd339acd
parent e0d4085636eca17b389b865506703c126b5e925e
Author: Amit Dutta <mail@amit.is-a.dev>
Date: Thu, 17 Sep 2026 10:21:06 +0530
Merge pull request #22 from notamitgamer/remove-broken-cobalt-ytmusic
Remove broken YouTube/Cobalt mp3 download feature
Diffstat:
6 files changed, 5 insertions(+), 910 deletions(-)
diff --git a/Dockerfile b/Dockerfile
@@ -1,5 +1,3 @@
-FROM ghcr.io/imputnet/cobalt:11.5-47d8ccb AS cobalt
-
FROM node:24-bookworm-slim
WORKDIR /app
@@ -11,9 +9,6 @@ RUN apt-get update \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
-# Copy the already-built Cobalt API from the official Cobalt image
-COPY --from=cobalt /app /opt/cobalt-api
-
# Install FastAPI dependencies
COPY requirements.txt .
diff --git a/app/main.py b/app/main.py
@@ -368,333 +368,6 @@ async def gh_sync(
}
-@app.get("/ytmusic")
-async def url_ytmusic_page(request: Request):
- ctx = await render_context({"page": "ytmusic"})
- return templates.TemplateResponse(request, "ytmusic.html", ctx)
-
-
-# ---------------------------------------------------------------------------------
-# YTMusic routes: Cobalt API Proxy
-# ---------------------------------------------------------------------------------
-
-class YtRequest(BaseModel):
- url: str
-
-
-COBALT_INTERNAL_URL = os.getenv(
- "COBALT_INTERNAL_URL",
- "http://127.0.0.1:9000",
-).rstrip("/")
-
-COBALT_PUBLIC_PREFIX = "/cobalt"
-
-
-@app.api_route(
- "/cobalt/{path:path}",
- methods=["GET", "HEAD"],
-)
-async def cobalt_tunnel_proxy(path: str, request: Request):
- cobalt_url = f"{COBALT_INTERNAL_URL}/{path}"
-
- query_string = request.url.query
- if query_string:
- cobalt_url += f"?{query_string}"
-
- headers = {}
-
- for header in [
- "Range",
- "If-Range",
- "If-None-Match",
- "If-Modified-Since",
- ]:
- value = request.headers.get(header)
- if value:
- headers[header] = value
-
- client = httpx.AsyncClient(
- follow_redirects=False,
- timeout=60.0,
- )
-
- try:
- req = client.build_request(
- request.method,
- cobalt_url,
- headers=headers,
- )
-
- res = await client.send(req, stream=True)
-
- response_headers = {}
-
- for header in [
- "Content-Type",
- "Content-Length",
- "Content-Range",
- "Accept-Ranges",
- "Content-Disposition",
- "Cache-Control",
- "ETag",
- "Last-Modified",
- ]:
- value = res.headers.get(header)
- if value:
- response_headers[header] = value
-
- return StreamingResponse(
- _proxy_stream(client, res),
- status_code=res.status_code,
- headers=response_headers,
- )
-
- except httpx.RequestError as exc:
- await client.aclose()
- raise HTTPException(
- status_code=502,
- detail=f"Error connecting to Cobalt tunnel: {exc}",
- )
-
-
-@app.post("/api/yt/download")
-async def yt_download_cobalt(request: Request, body: YtRequest):
- url = body.url.strip()
-
- if not url:
- raise HTTPException(
- status_code=400,
- detail="Missing YouTube URL.",
- )
-
- cobalt_api = f"{COBALT_INTERNAL_URL}/"
-
- headers = {
- "Accept": "application/json",
- "Content-Type": "application/json",
- "User-Agent": (
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
- "AppleWebKit/537.36 (KHTML, like Gecko) "
- "Chrome/122.0.0.0 Safari/537.36"
- ),
- }
-
- cobalt_api_token = os.getenv("COBALT_API_TOKEN")
-
- if cobalt_api_token:
- if " " not in cobalt_api_token:
- headers["Authorization"] = f"Api-Key {cobalt_api_token}"
- else:
- headers["Authorization"] = cobalt_api_token
-
- payload = {
- "url": url,
- "downloadMode": "audio",
- "audioFormat": "mp3",
- "audioBitrate": "128",
- "filenameStyle": "basic",
- "youtubeHLS": True,
- }
-
- async with httpx.AsyncClient(
- timeout=60.0,
- follow_redirects=False,
- ) as client:
- try:
- res = await client.post(
- cobalt_api,
- json=payload,
- headers=headers,
- )
-
- if res.status_code != 200:
- err_text = res.text
-
- try:
- data = res.json()
-
- if "error" in data and isinstance(data["error"], dict):
- err_text = data["error"].get(
- "code",
- err_text,
- )
- elif data.get("text"):
- err_text = data["text"]
-
- except Exception:
- pass
-
- if len(err_text) > 300:
- err_text = err_text[:300] + "..."
-
- raise HTTPException(
- status_code=400,
- detail=f"Cobalt API failed: {err_text}",
- )
-
- data = res.json()
- status = data.get("status")
-
- if status == "error":
- error = data.get("error", {})
-
- print("========== COBALT ERROR ==========")
- print(f"Full response: {data}")
-
- if isinstance(error, dict):
- print(f"Error code: {error.get('code')}")
- print(f"Error context: {error.get('context')}")
- else:
- print(f"Error: {error}")
-
- print("==================================")
-
- if isinstance(error, dict):
- raise HTTPException(
- status_code=400,
- detail={
- "message": "Cobalt API failed",
- "code": error.get("code"),
- "context": error.get("context"),
- "raw": data,
- },
- )
- else:
- raise HTTPException(
- status_code=400,
- detail={
- "message": "Cobalt API failed",
- "error": str(error),
- "raw": data,
- },
- )
-
- dl_url = data.get("url")
-
- if not dl_url:
- raise HTTPException(
- status_code=500,
- detail=(
- "Cobalt succeeded but returned no "
- f"download link. (Status: {status})"
- ),
- )
-
- if status == "tunnel":
- parsed = urlparse(dl_url)
-
- if parsed.path.startswith("/tunnel"):
- public_url = (
- f"{CDN_BASE_URL}{COBALT_PUBLIC_PREFIX}"
- f"{parsed.path}"
- )
-
- if parsed.query:
- public_url += f"?{parsed.query}"
-
- dl_url = public_url
-
- return {
- "url": dl_url,
- "title": data.get("filename", "Audio"),
- }
-
- except httpx.RequestError as exc:
- raise HTTPException(
- status_code=502,
- detail=f"Error connecting to Cobalt: {exc}",
- )
-
-
-# ---------------------------------------------------------------------------------
-# TEMPORARY COBALT DIAGNOSTIC ENDPOINT
-# ---------------------------------------------------------------------------------
-
-@app.get("/api/yt/debug")
-async def yt_debug(url: str):
- url = url.strip()
-
- if not url:
- raise HTTPException(
- status_code=400,
- detail="Missing YouTube URL.",
- )
-
- cobalt_api = f"{COBALT_INTERNAL_URL}/"
-
- headers = {
- "Accept": "application/json",
- "Content-Type": "application/json",
- "User-Agent": (
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
- "AppleWebKit/537.36 (KHTML, like Gecko) "
- "Chrome/122.0.0.0 Safari/537.36"
- ),
- }
-
- cobalt_api_token = os.getenv("COBALT_API_TOKEN")
-
- if cobalt_api_token:
- if " " not in cobalt_api_token:
- headers["Authorization"] = f"Api-Key {cobalt_api_token}"
- else:
- headers["Authorization"] = cobalt_api_token
-
- payload = {
- "url": url,
- "downloadMode": "audio",
- "audioFormat": "mp3",
- "audioBitrate": "128",
- "filenameStyle": "basic",
- }
-
- async with httpx.AsyncClient(
- timeout=60.0,
- follow_redirects=False,
- ) as client:
- try:
- res = await client.post(
- cobalt_api,
- json=payload,
- headers=headers,
- )
-
- raw_text = res.text
-
- try:
- response_body = res.json()
- except Exception:
- response_body = raw_text
-
- print("========== COBALT DEBUG ==========")
- print(f"HTTP status: {res.status_code}")
- print(f"Response headers: {dict(res.headers)}")
- print(f"Response body: {response_body}")
- print("===================================")
-
- return {
- "cobalt_url": cobalt_api,
- "http_status": res.status_code,
- "response_headers": dict(res.headers),
- "response": response_body,
- }
-
- except httpx.RequestError as exc:
- print("========== COBALT DEBUG ERROR ==========")
- print(f"Request error: {repr(exc)}")
- print("========================================")
-
- raise HTTPException(
- status_code=502,
- detail={
- "message": "Error connecting to Cobalt",
- "error": repr(exc),
- },
- )
-
-
-# ---------------------------------------------------------------------------------
-
@app.get("/shorten")
async def url_shortener_page(request: Request):
ctx = await render_context({"page": "shorten"})
diff --git a/app/templates/_navbar.html b/app/templates/_navbar.html
@@ -179,9 +179,6 @@
<a href="/shorten" class="{{ 'active' if page == 'shorten' }}" title="URL Shortener" aria-label="URL Shortener"{{ ' aria-current="page"'|safe if page == 'shorten' }}>
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
</a>
- <a href="/ytmusic" class="{{ 'active' if page == 'ytmusic' }}" title="Youtube Music Download" aria-label="Youtube Music Download"{{ ' aria-current="page"'|safe if page == 'ytmusic' }}>
- <svg xmlns="http://www.w3.org/2000/svg" width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 10v3"/><path d="M6 6v11"/><path d="M10 3v18"/><path d="M14 8v7"/><path d="M18 5v13"/><path d="M22 10v3"/></svg>
- </a>
</nav>
</header>
{% if not (page == 'listing' and not path) %}
@@ -209,8 +206,6 @@
{% set ns.items = ns.items + [{'name': 'gh-sync', 'href': '/gh-sync'}] %}
{% elif page == 'shorten' %}
{% set ns.items = ns.items + [{'name': 'shorten', 'href': '/shorten'}] %}
- {% elif page == 'ytmusic' %}
- {% set ns.items = ns.items + [{'name': 'ytmusic', 'href': '/ytmusic'}] %}
{% elif page == 'file' %}
{% set ns.items = ns.items + [{'name': filename, 'href': none}] %}
{% endif %}
diff --git a/app/templates/ytmusic.html b/app/templates/ytmusic.html
@@ -1,461 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>YouTube Music Downloader — amit/cdn</title>
- <meta name="description" content="Get a direct download link for YouTube music.">
- <link rel="icon" href="/favicon.ico" sizes="any">
- <link rel="icon" type="image/png" sizes="32x32" href="/static/icons/favicon-32x32.png">
- <link rel="icon" type="image/png" sizes="16x16" href="/static/icons/favicon-16x16.png">
- <link rel="icon" type="image/svg+xml" href="/static/icons/logo.svg">
- <link rel="apple-touch-icon" sizes="180x180" href="/static/icons/apple-touch-icon.png">
- <link rel="manifest" href="/static/manifest.json">
- <meta name="theme-color" content="#09090b">
- <meta name="color-scheme" content="dark">
- <script type="module" src="https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.4/dist/turbo.es2017-esm.js"></script>
- <link rel="preconnect" href="https://fonts.googleapis.com">
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
- <link href="https://fonts.googleapis.com/css2?family=Zilla+Slab:wght@500;600;700&family=Work+Sans:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
- <style>
- :root {
- --bg: #09090b;
- --surface: #121214;
- --surface-2: #1e1e22;
- --surface-3: #27272a;
- --text-main: #f4f4f5;
- --text-dim: #b8b8c0;
- --accent-primary: #818cf8;
- --accent-primary-hover: #a5b4fc;
- --success: #10b981;
- --danger: #ef4444;
- --border: rgba(255, 255, 255, 0.08);
- --border-hover: rgba(255, 255, 255, 0.15);
- --font-heading: 'Zilla Slab', Georgia, 'Times New Roman', serif;
- --font-body: 'Work Sans', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
- --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
- --radius-md: 8px;
- --radius-lg: 12px;
- --radius-xl: 16px;
- }
- * { box-sizing: border-box; }
- body {
- font-family: var(--font-body);
- background: var(--bg);
- color: var(--text-main);
- margin: 0;
- padding: 0 0 4rem 0;
- line-height: 1.6;
- font-size: 1rem;
- -webkit-font-smoothing: antialiased;
- }
- @media (max-width: 480px) {
- body { font-size: 0.95rem; }
- }
- .container {
- width: 90%;
- max-width: 1000px;
- margin: 0 auto;
- }
- h1 {
- font-family: var(--font-heading);
- font-size: clamp(2.2rem, 5vw, 3rem);
- letter-spacing: -0.02em;
- color: #ffffff;
- font-weight: 600;
- margin: 0 0 1.25rem 0;
- line-height: 1.1;
- max-width: 20ch;
- }
- blockquote {
- background: var(--surface);
- border: 1px solid var(--border);
- border-left: 3px solid var(--accent-primary);
- border-radius: var(--radius-md);
- margin: 0 0 2.25rem 0;
- padding: 1rem 1.25rem;
- color: var(--text-dim);
- max-width: 68ch;
- font-size: 0.95rem;
- }
- a { color: var(--accent-primary); text-decoration: none; transition: color 0.15s; }
- a:hover { color: var(--accent-primary-hover); text-decoration: underline; }
- .input-group {
- position: relative;
- display: flex;
- align-items: center;
- width: 100%;
- }
- .url-input {
- width: 100%;
- padding: 0.85rem 2.5rem 0.85rem 1.25rem;
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius-md);
- color: var(--text-main);
- font-family: var(--font-mono);
- font-size: 0.9rem;
- transition: all 0.2s ease;
- box-shadow: 0 2px 10px rgba(0,0,0,0.1) inset;
- }
- .url-input:focus {
- outline: none;
- background: var(--surface-2);
- border-color: var(--accent-primary);
- box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.15);
- }
- .url-input::placeholder { color: var(--text-dim); }
- .input-clear-btn {
- position: absolute;
- right: 0.75rem;
- top: 50%;
- transform: translateY(-50%);
- background: transparent;
- border: none;
- color: var(--text-dim);
- cursor: pointer;
- padding: 0.25rem;
- display: none;
- align-items: center;
- justify-content: center;
- border-radius: 50%;
- transition: color 0.2s, background 0.2s;
- }
- .input-group[data-has-text="true"] .input-clear-btn { display: flex; }
- .input-clear-btn:hover { color: var(--text-main); background: var(--surface-3); }
- .btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- gap: 0.5rem;
- background: var(--surface-2);
- color: var(--text-main);
- padding: 0.65rem 1.25rem;
- border: 1px solid var(--border);
- border-radius: var(--radius-md);
- cursor: pointer;
- font-family: var(--font-body);
- font-size: 0.9rem;
- font-weight: 500;
- transition: all 0.2s ease, transform 0.1s ease;
- text-decoration: none;
- }
- .btn:hover:not(:disabled) { background: var(--surface-3); border-color: var(--border-hover); text-decoration: none; color: var(--text-main); }
- .btn:active:not(:disabled) { transform: scale(0.97); }
- .btn:disabled { opacity: 0.6; cursor: not-allowed; }
- .btn-primary { background: var(--accent-primary) !important; color: #ffffff !important; border-color: var(--accent-primary) !important; font-weight: 600; transition: none !important; }
- .btn-primary:hover:not(:disabled), .btn-primary:focus:not(:disabled), .btn-primary:active:not(:disabled) { background: var(--accent-primary) !important; border-color: var(--accent-primary) !important; color: #ffffff !important; }
- .btn-primary svg { stroke: #ffffff !important; }
- .result-box {
- margin-top: 2rem;
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius-lg);
- padding: 1.5rem;
- }
- .result-box h3 {
- font-family: var(--font-heading);
- font-size: 1.2rem;
- margin: 0 0 1rem 0;
- color: #fff;
- }
- .result-url {
- font-family: var(--font-mono);
- font-size: 0.95rem;
- word-break: break-all;
- color: var(--accent-primary);
- background: var(--surface-2);
- padding: 0.75rem 1rem;
- border-radius: var(--radius-md);
- border: 1px solid var(--border);
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 1rem;
- flex-wrap: wrap;
- }
- .result-url a { color: var(--accent-primary); text-decoration: none; }
- .result-url a:hover { text-decoration: underline; }
- .error-box {
- display: none;
- color: var(--danger);
- margin-top: 1rem;
- padding: 1rem 1.25rem;
- border: 1px solid rgba(239, 68, 68, 0.5);
- background: rgba(239, 68, 68, 0.1);
- border-radius: var(--radius-md);
- }
- .info-text {
- color: var(--text-dim);
- font-size: 0.9rem;
- margin-top: 1rem;
- }
- .spin-anim { animation: spin 0.7s linear infinite; display: inline-flex; }
- @keyframes spin { 100% { transform: rotate(360deg); } }
- .btn-primary .btn-icon { display: inline-flex; }
- .btn-primary.is-loading .btn-icon.btn-icon-default { display: none; }
- .btn-primary .btn-icon-spinner { display: none; }
- .btn-primary.is-loading .btn-icon-spinner { display: inline-flex; }
- footer {
- width: 90%;
- max-width: 1000px;
- margin: 4rem auto 0;
- padding-top: 1.5rem;
- border-top: 1px solid var(--border);
- font-size: .85rem;
- color: var(--muted);
- }
- @media (max-width: 480px) {
- .container { width: 100%; padding: 0 1.25rem; }
- .result-url { flex-direction: column; align-items: flex-start; }
- }
- </style>
-</head>
-<body>
- {% include "_navbar.html" %}
- <main class="container">
- <h1>YouTube Music Downloader</h1>
- <blockquote>
- Paste a YouTube or YouTube Music URL and get a 128 KBPS audio download link.
- </blockquote>
-
- <div class="input-group" id="youtube-group">
- <input
- type="url"
- id="youtube-input"
- class="url-input"
- placeholder="Paste a YouTube URL (e.g. https://music.youtube.com/...)"
- autocomplete="off"
- spellcheck="false"
- required
- >
- <button type="button" class="input-clear-btn" id="youtube-clear" aria-label="Clear URL">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
- <line x1="18" y1="6" x2="6" y2="18"/>
- <line x1="6" y1="6" x2="18" y2="18"/>
- </svg>
- </button>
- </div>
-
- <div style="display:flex; gap:0.75rem; margin-top:1rem; align-items:center;">
- <button type="button" class="btn btn-primary" id="download-btn">
- <span class="btn-icon btn-icon-default">
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <path d="M12 3v12"/>
- <path d="m7 10 5 5 5-5"/>
- <path d="M5 21h14"/>
- </svg>
- </span>
- <span class="btn-icon btn-icon-spinner spin-anim">
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
- <path d="M21 12a9 9 0 1 1-6.219-8.56"/>
- </svg>
- </span>
- <span id="download-btn-label">Get Download Link</span>
- </button>
- </div>
-
- <div id="status-box" class="info-text" style="display:none;" aria-live="polite"></div>
- <div id="error-box" class="error-box" role="alert" aria-live="polite"></div>
-
- <div id="result" style="display:none;" class="result-box">
- <h3>Your download link</h3>
- <div class="result-url">
- <a href="#" id="masked-link" target="_blank" rel="noreferrer" referrerpolicy="no-referrer"></a>
- <button type="button" class="btn" id="copy-btn">Copy</button>
- </div>
- <p class="info-text">This link redirects to the official VidsSave download stream.</p>
- </div>
- </main>
-
- <footer>
- <p>© 2025 - 2026 notamitgamer. All rights reserved.</p>
- </footer>
-
- <script>
- (function() {
- const AUTH = '20250901majwlqo';
- const DOMAIN = 'api-ak.vidssave.com';
-
- function isYouTubeUrl(rawUrl) {
- try {
- const parsed = new URL(rawUrl);
- if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return false;
- const host = parsed.hostname.toLowerCase().replace(/^www\./, '');
- return (
- host === 'youtube.com' ||
- host === 'm.youtube.com' ||
- host === 'music.youtube.com' ||
- host === 'youtu.be'
- );
- } catch {
- return false;
- }
- }
-
- function setupYtMusic() {
- const youtubeInput = document.getElementById('youtube-input');
- const youtubeClear = document.getElementById('youtube-clear');
- const downloadBtn = document.getElementById('download-btn');
- const downloadBtnLabel = document.getElementById('download-btn-label');
- const statusBox = document.getElementById('status-box');
- const errorBox = document.getElementById('error-box');
- const resultDiv = document.getElementById('result');
- const maskedLink = document.getElementById('masked-link');
- const copyBtn = document.getElementById('copy-btn');
-
- if (!youtubeInput || !downloadBtn || downloadBtn.dataset.initialized) return;
- downloadBtn.dataset.initialized = 'true';
-
- function setStatus(message) {
- statusBox.textContent = message;
- statusBox.style.display = message ? 'block' : 'none';
- }
-
- function showError(message) {
- errorBox.textContent = message;
- errorBox.style.display = 'block';
- }
-
- function clearMessages() {
- errorBox.style.display = 'none';
- resultDiv.style.display = 'none';
- setStatus('');
- }
-
- function setLoading(loading) {
- downloadBtn.disabled = loading;
- downloadBtn.classList.toggle('is-loading', loading);
- downloadBtnLabel.textContent = loading ? 'Working...' : 'Get Download Link';
- }
-
- async function getDownloadLink(url) {
- const response = await fetch('/api/yt/download', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ url })
- });
-
- const data = await response.json();
- if (!response.ok || !data.url) {
- throw new Error(data.detail || 'Failed to extract download link.');
- }
- return data.url;
- }
-
- async function shortenUrl(url) {
- const response = await fetch('/api/shorten', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ url })
- });
-
- const data = await response.json().catch(() => ({}));
- if (!response.ok || !data.id || !data.masked_url) {
- throw new Error(data.detail || 'The download link could not be shortened.');
- }
- return data.masked_url;
- }
-
- downloadBtn.addEventListener('click', async () => {
- const url = youtubeInput.value.trim();
- clearMessages();
-
- if (!url) {
- showError('Please enter a YouTube URL.');
- return;
- }
- if (!isYouTubeUrl(url)) {
- showError('Please enter a valid YouTube or YouTube Music URL.');
- return;
- }
-
- setLoading(true);
-
- try {
- setStatus('Extracting direct audio link natively...');
- const downloadUrl = await getDownloadLink(url);
-
- setStatus('Shortening link...');
- const maskedUrl = await shortenUrl(downloadUrl);
-
- maskedLink.href = maskedUrl;
- maskedLink.textContent = maskedUrl;
- copyBtn.setAttribute('data-url', maskedUrl);
-
- resultDiv.style.display = 'block';
- setStatus('Ready.');
- } catch (error) {
- console.error(error);
- showError(error.message || 'Something went wrong.');
- } finally {
- setLoading(false);
- }
- });
-
- youtubeClear.addEventListener('click', () => {
- youtubeInput.value = '';
- youtubeInput.focus();
- youtubeClear.parentElement.removeAttribute('data-has-text');
- clearMessages();
- });
-
- youtubeInput.addEventListener('input', () => {
- if (youtubeInput.value.length > 0) {
- youtubeClear.parentElement.setAttribute('data-has-text', 'true');
- } else {
- youtubeClear.parentElement.removeAttribute('data-has-text');
- }
- });
-
- youtubeInput.addEventListener('keydown', event => {
- if (event.key === 'Enter') {
- event.preventDefault();
- downloadBtn.click();
- }
- });
-
- copyBtn.addEventListener('click', async () => {
- const url = copyBtn.getAttribute('data-url');
- if (!url) return;
-
- try {
- await navigator.clipboard.writeText(url);
- const originalText = copyBtn.textContent;
- const originalStyle = copyBtn.style.cssText;
-
- copyBtn.textContent = 'Copied!';
- copyBtn.style.borderColor = 'var(--success)';
- copyBtn.style.color = 'var(--success)';
-
- setTimeout(() => {
- copyBtn.style.cssText = originalStyle;
- copyBtn.textContent = originalText;
- }, 1000);
- } catch (error) {
- console.error('Copy failed:', error);
- }
- });
- }
-
- document.addEventListener('turbo:load', setupYtMusic);
- document.addEventListener('DOMContentLoaded', setupYtMusic);
-
- if (!window.__ytmusicKbdBound) {
- window.__ytmusicKbdBound = true;
- document.addEventListener('keydown', event => {
- if (event.key !== '/') return;
- const tag = document.activeElement?.tagName || '';
- if (tag === 'INPUT' || tag === 'TEXTAREA') return;
-
- const currentYtInput = document.getElementById('youtube-input');
- if (currentYtInput) {
- event.preventDefault();
- currentYtInput.focus();
- }
- });
- }
- })();
- </script>
-</body>
-</html>
-\ No newline at end of file
diff --git a/render.yaml b/render.yaml
@@ -14,10 +14,4 @@ services:
value: raw.cdn.amit.is-a.dev
- key: RAW_BASE_URL
- value: https://raw.cdn.amit.is-a.dev
-
- - key: COBALT_API_URL
- value: https://cdn.amit.is-a.dev/cobalt/
-
- - key: COBALT_PORT
- value: "9000"
-\ No newline at end of file
+ value: https://raw.cdn.amit.is-a.dev
+\ No newline at end of file
diff --git a/start.sh b/start.sh
@@ -3,109 +3,9 @@
set -e
echo "========================================"
-echo "Starting CDN + Cobalt"
+echo "Starting CDN"
echo "========================================"
-echo ""
-echo "========== PROXY ENV =========="
-echo "HTTP_PROXY=${HTTP_PROXY:+SET}"
-echo "HTTPS_PROXY=${HTTPS_PROXY:+SET}"
-echo "ALL_PROXY=${ALL_PROXY:+SET}"
-echo "NO_PROXY=${NO_PROXY:+SET}"
-echo "http_proxy=${http_proxy:+SET}"
-echo "https_proxy=${https_proxy:+SET}"
-echo "all_proxy=${all_proxy:+SET}"
-echo "no_proxy=${no_proxy:+SET}"
-echo "==============================="
-echo ""
-
-echo "========== COBALT ENV =========="
-echo "API_URL=${API_URL:-https://cdn.amit.is-a.dev/cobalt/}"
-echo "API_PORT=9000"
-echo "COOKIE_PATH=${COOKIE_PATH:-NOT_SET}"
-echo "YOUTUBE_SESSION_SERVER=${YOUTUBE_SESSION_SERVER:-NOT_SET}"
-echo "YOUTUBE_SESSION_INNERTUBE_CLIENT=${YOUTUBE_SESSION_INNERTUBE_CLIENT:-NOT_SET}"
-echo "YOUTUBE_PLAYER_ID=${YOUTUBE_PLAYER_ID:-NOT_SET}"
-echo "ENABLE_DEPRECATED_YOUTUBE_HLS=${ENABLE_DEPRECATED_YOUTUBE_HLS:-NOT_SET}"
-echo "================================"
-echo ""
-
-echo "Checking Cobalt installation..."
-
-if [ ! -f /opt/cobalt-api/src/cobalt ]; then
- echo "ERROR: Cobalt executable not found:"
- echo "/opt/cobalt-api/src/cobalt"
- exit 1
-fi
-
-echo "Cobalt executable found."
-
-echo ""
-echo "Starting Cobalt..."
-
-cd /opt/cobalt-api
-
-API_URL="https://cdn.amit.is-a.dev/cobalt/" \
-API_PORT=9000 \
-node src/cobalt &
-
-COBALT_PID=$!
-
-echo "Cobalt PID: $COBALT_PID"
-
-echo ""
-echo "Waiting for Cobalt to start..."
-
-COBALT_READY=0
-
-for i in $(seq 1 30); do
- if python3 -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:9000/', timeout=1)" >/dev/null 2>&1; then
- COBALT_READY=1
- echo "Cobalt is ready."
- break
- fi
-
- if ! kill -0 "$COBALT_PID" 2>/dev/null; then
- echo "ERROR: Cobalt process stopped unexpectedly."
- wait "$COBALT_PID"
- exit 1
- fi
-
- sleep 1
-done
-
-if [ "$COBALT_READY" -ne 1 ]; then
- echo "ERROR: Cobalt did not become ready within 30 seconds."
- kill "$COBALT_PID" 2>/dev/null || true
- exit 1
-fi
-
-echo ""
-echo "Starting FastAPI..."
-
-cd /app
-
-uvicorn app.main:app \
+exec uvicorn app.main:app \
--host 0.0.0.0 \
- --port "${PORT:-8000}" &
-
-FASTAPI_PID=$!
-
-echo "FastAPI PID: $FASTAPI_PID"
-
-echo ""
-echo "========================================"
-echo "Both services started"
-echo "========================================"
-echo "Cobalt: http://127.0.0.1:9000"
-echo "FastAPI: http://0.0.0.0:${PORT:-8000}"
-echo "========================================"
-
-wait -n "$COBALT_PID" "$FASTAPI_PID"
-
-echo ""
-echo "ERROR: One of the services stopped."
-echo "Cobalt PID: $COBALT_PID"
-echo "FastAPI PID: $FASTAPI_PID"
-
-exit 1
+ --port "${PORT:-8000}"