From e5d03a147d332baf3968e298b986bc2b153142bb Mon Sep 17 00:00:00 2001 From: duongynhi000005-oss Date: Fri, 29 May 2026 04:21:22 +0000 Subject: [PATCH] fix: resolve #7 --- README.md | 29 +++++++++++++++++- udio_wrapper/__init__.py | 64 +++++++++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 61a8722..0bbb955 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,31 @@ pip install git+https://github.com/flowese/UdioWrapper.git 2. Once registered, open your browser's inspector: - In Chrome: `Ctrl+Shift+I` or `F12` on Windows, `Cmd+Option+I` on Mac. 3. Go to the `Application` tab. -4. On the left panel, locate and click on `Cookies`, then select the Ideogram website. +4. On the left panel, locate and click on `Cookies`, then select the Udio website. 5. Find the cookie named `sb-api-auth-token`. 6. Click on `sb-api-auth-token` and copy the value in the `Value` field. ![Udio Wrapper](screen_cookies.jpeg) +### hCaptcha, 500 responses, and stale sessions + +Udio can require a fresh browser session or an hCaptcha/anti-bot check before accepting +requests to `https://www.udio.com/api/generate-proxy`. When that happens, Udio may +return `500 Internal Server Error` even though the problem is authentication/session +state rather than a normal server outage. + +If generation fails with a 401, 403, 429, or 500 response: + +1. Open [Udio](https://www.udio.com/) in your browser. +2. Sign in and complete any hCaptcha or anti-bot challenge shown by Udio. +3. Refresh the page. +4. Copy either the refreshed `sb-api-auth-token` cookie value or the full `Cookie` + request header from your browser's network inspector. +5. Recreate `UdioWrapper` with the refreshed value. + +The wrapper does not bypass or automatically solve hCaptcha. It reuses the valid +browser session that you provide after completing Udio's challenge yourself. + ### Usage To use `udio_wrapper`, import the `UdioWrapper` class and provide the necessary parameters. @@ -75,6 +94,14 @@ auth_token = "your-auth-token-here" # Replace this with your actual authenticat udio_wrapper = UdioWrapper(auth_token) ``` +If Udio rejects token-only requests, pass the full browser Cookie header instead +with or without the leading `Cookie:` label: + +```python +cookies = "sb-api-auth-token=your-auth-token-here; other-cookie=value" +udio_wrapper = UdioWrapper(cookies=cookies) +``` + 1. Creating a Short Song You can specify the prompt, seed, custom lyrics. ```python diff --git a/udio_wrapper/__init__.py b/udio_wrapper/__init__.py index d4ed8fe..a1a15e5 100644 --- a/udio_wrapper/__init__.py +++ b/udio_wrapper/__init__.py @@ -10,30 +10,81 @@ import os import time + +class UdioWrapperError(Exception): + """Raised when Udio returns an error response.""" + + class UdioWrapper: API_BASE_URL = "https://www.udio.com/api" - def __init__(self, auth_token): + def __init__(self, auth_token=None, cookies=None, timeout=30): + if not auth_token and not cookies: + raise ValueError("auth_token or cookies is required") + self.auth_token = auth_token + self.cookies = cookies + self.timeout = timeout + self.session = requests.Session() self.all_track_ids = [] + if isinstance(cookies, dict): + self.session.cookies.update(cookies) + + def _get_cookie_header(self): + if isinstance(self.cookies, str): + cookie_header = self.cookies.strip() + if cookie_header.lower().startswith("cookie:"): + return cookie_header.split(":", 1)[1].strip() + return cookie_header + + if isinstance(self.cookies, dict): + cookie_header = "; ".join(f"{key}={value}" for key, value in self.cookies.items()) + if "sb-api-auth-token=" in cookie_header or not self.auth_token: + return cookie_header + return f"{cookie_header}; sb-api-auth-token={self.auth_token}" + + return f"sb-api-auth-token={self.auth_token}" + + def _format_error_message(self, response, method, url): + body = response.text.strip() + if len(body) > 1000: + body = f"{body[:1000]}..." + + message = ( + f"Error making {method} request to {url}: " + f"{response.status_code} {response.reason}" + ) + if body: + message = f"{message}\nResponse body: {body}" + + if url.endswith("/generate-proxy") and response.status_code in (401, 403, 429, 500): + message = ( + f"{message}\n\n" + "Udio may require a fresh browser session or an hCaptcha/anti-bot check. " + "Open udio.com in your browser, complete any challenge, then pass either " + "the refreshed sb-api-auth-token or the full Cookie header to UdioWrapper." + ) + return message + def make_request(self, url, method, data=None, headers=None): try: if method == 'POST': - response = requests.post(url, headers=headers, json=data) + response = self.session.post(url, headers=headers, json=data, timeout=self.timeout) else: - response = requests.get(url, headers=headers) + response = self.session.get(url, headers=headers, timeout=self.timeout) response.raise_for_status() return response + except requests.exceptions.HTTPError as e: + raise UdioWrapperError(self._format_error_message(e.response, method, url)) from e except requests.exceptions.RequestException as e: - print(f"Error making {method} request to {url}: {e}") - return None + raise UdioWrapperError(f"Error making {method} request to {url}: {e}") from e def get_headers(self, get_request=False): headers = { "Accept": "application/json, text/plain, */*" if get_request else "application/json", "Content-Type": "application/json", - "Cookie": f"; sb-api-auth-token={self.auth_token}", + "Cookie": self._get_cookie_header(), "Origin": "https://www.udio.com", "Referer": "https://www.udio.com/my-creations", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", @@ -219,4 +270,3 @@ def download_song(self, song_url, song_title, folder="downloaded_songs"): print(f"Downloaded {song_title} with url {song_url} to {file_path}") except requests.exceptions.RequestException as e: print(f"Failed to download the song. Error: {e}") -