commit dfaf61dc53839a211df8f6447e9815841736518c
parent 9223d451e2839f3d7949ceb0fd7c2608c3a6a361
Author: Amit Dutta <mail@amit.is-a.dev>
Date: Wed, 16 Sep 2026 22:22:50 +0530
Update Cobalt API integration and error handling
Diffstat:
| M | app/main.py | | | 41 | ++++++++++++++++++++++++++++++----------- |
1 file changed, 30 insertions(+), 11 deletions(-)
diff --git a/app/main.py b/app/main.py
@@ -319,44 +319,64 @@ async def yt_download_cobalt(request: Request, body: YtRequest):
if not url:
raise HTTPException(status_code=400, detail="Missing YouTube URL.")
- # Defaults to public instance. Can be overridden in Render Environment Variables later if needed.
- cobalt_api = os.getenv("COBALT_API_URL", "https://api.cobalt.tools/api/json")
+ # Note: The official api.cobalt.tools now requires authentication (API keys/Turnstile).
+ # It is recommended to self-host or supply your instance URL here.
+ cobalt_api = os.getenv("COBALT_API_URL", "https://api.cobalt.tools/")
+ if not cobalt_api.endswith("/"):
+ cobalt_api += "/"
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
- # Spoofing a standard browser to ensure the public API accepts the request
"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"
}
+ # Optionally bind an authorization header if an instance token is set
+ cobalt_api_token = os.getenv("COBALT_API_TOKEN")
+ if cobalt_api_token:
+ # Standardize Api-Key auth if not strictly provided as Bearer
+ if " " not in cobalt_api_token:
+ headers["Authorization"] = f"Api-Key {cobalt_api_token}"
+ else:
+ headers["Authorization"] = cobalt_api_token
+
# Cobalt v11 API Payload
payload = {
"url": url,
- "isAudioOnly": True,
- "audioFormat": "mp3"
+ "downloadMode": "audio",
+ "audioFormat": "mp3",
+ "filenameStyle": "basic"
}
async with httpx.AsyncClient(timeout=60.0) as client:
try:
res = await client.post(cobalt_api, json=payload, headers=headers)
+ # Non-200 could be bot protection, ratelimits, or API validation errors.
if res.status_code != 200:
err_text = res.text
try:
- err_text = res.json().get("text", err_text)
+ data = res.json()
+ if "error" in data and "code" in data["error"]:
+ err_text = data["error"]["code"]
+ else:
+ err_text = data.get("text", err_text)
except Exception:
- pass
+ # Truncate raw HTML (like Turnstile challenge pages) so it doesn't flood logs
+ if len(err_text) > 200:
+ err_text = err_text[:200] + "..."
raise HTTPException(status_code=400, detail=f"Cobalt API failed: {err_text}")
data = res.json()
status = data.get("status")
if status == "error":
- raise HTTPException(status_code=400, detail=data.get("text", "Unknown Cobalt error"))
+ err_code = data.get("error", {}).get("code", "Unknown Cobalt error")
+ raise HTTPException(status_code=400, detail=f"Cobalt error: {err_code}")
dl_url = data.get("url")
if not dl_url:
- raise HTTPException(status_code=500, detail="Cobalt succeeded but returned no download link.")
+ raise HTTPException(status_code=500, detail=f"Cobalt succeeded but returned no download link. (Status: {status})")
return {"url": dl_url, "title": "Audio"}
@@ -692,4 +712,4 @@ async def serve(request: Request, path: str):
extra_ctx["recent_activity"] = await get_recent_activity()
ctx = await render_context(extra_ctx)
- return templates.TemplateResponse(request, "index.html", ctx)
-\ No newline at end of file
+ return templates.TemplateResponse(request, "index.html", ctx)