Merge: promote dev to prod
This commit is contained in:
commit
9bd7e31463
3 changed files with 31 additions and 5 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.33.0
|
||||
0.33.1
|
||||
|
|
@ -55,6 +55,22 @@ class YouTubeClient:
|
|||
def __exit__(self, *exc) -> None:
|
||||
self._http.close()
|
||||
|
||||
# --- transport ---
|
||||
def _send(self, method: str, url: str, **kwargs) -> httpx.Response:
|
||||
"""Issue an HTTP request, mapping any transport/network failure (egress proxy down,
|
||||
DNS, timeout, connection reset) to YouTubeError. Every caller already handles
|
||||
YouTubeError — degrade gracefully, log, and move on — so wrapping here keeps a
|
||||
transient network fault (e.g. the fixed-IP egress proxy being unreachable) from
|
||||
escaping as a raw httpx error and turning into an uncaught 500. HTTP status errors
|
||||
are NOT touched here (we don't use raise_for_status): callers inspect resp.status_code
|
||||
themselves and raise their own YouTubeError with the response body."""
|
||||
try:
|
||||
return self._http.request(method, url, **kwargs)
|
||||
except httpx.HTTPError as exc:
|
||||
raise YouTubeError(
|
||||
f"YouTube API unreachable ({exc.__class__.__name__}: {exc})"
|
||||
) from exc
|
||||
|
||||
# --- auth ---
|
||||
def _access_token(self) -> str:
|
||||
tok = self.user.token
|
||||
|
|
@ -66,7 +82,8 @@ class YouTubeClient:
|
|||
refresh = decrypt(tok.refresh_token_enc)
|
||||
if not refresh:
|
||||
raise YouTubeError("No refresh token; user must re-authenticate")
|
||||
resp = self._http.post(
|
||||
resp = self._send(
|
||||
"POST",
|
||||
GOOGLE_TOKEN_URL,
|
||||
data={
|
||||
"client_id": sysconfig.get_str(self.db, "google_client_id"),
|
||||
|
|
@ -94,7 +111,7 @@ class YouTubeClient:
|
|||
p["key"] = api_key
|
||||
else:
|
||||
headers["Authorization"] = f"Bearer {self._access_token()}"
|
||||
resp = self._http.get(f"{API_BASE}/{path}", params=p, headers=headers)
|
||||
resp = self._send("GET", f"{API_BASE}/{path}", params=p, headers=headers)
|
||||
quota.record_usage(self.db, cost)
|
||||
if resp.status_code != 200:
|
||||
log.warning("YouTube API %s -> %s: %s", path, resp.status_code, resp.text[:200])
|
||||
|
|
@ -210,7 +227,8 @@ class YouTubeClient:
|
|||
def delete_subscription(self, subscription_id: str) -> None:
|
||||
"""Unsubscribe on YouTube. Requires the write scope and the user's OAuth token
|
||||
(never the public API key). subscriptions.delete costs 50 quota units."""
|
||||
resp = self._http.delete(
|
||||
resp = self._send(
|
||||
"DELETE",
|
||||
f"{API_BASE}/subscriptions",
|
||||
params={"id": subscription_id},
|
||||
headers={"Authorization": f"Bearer {self._access_token()}"},
|
||||
|
|
@ -268,7 +286,7 @@ class YouTubeClient:
|
|||
# --- write endpoints (OAuth only, never the API key; each costs 50 quota units) ---
|
||||
def _write(self, method: str, path: str, *, params: dict, json: dict | None = None) -> dict:
|
||||
headers = {"Authorization": f"Bearer {self._access_token()}"}
|
||||
resp = self._http.request(
|
||||
resp = self._send(
|
||||
method, f"{API_BASE}/{path}", params=params, json=json, headers=headers
|
||||
)
|
||||
quota.record_usage(self.db, 50)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,14 @@ export interface ReleaseEntry {
|
|||
}
|
||||
|
||||
export const RELEASE_NOTES: ReleaseEntry[] = [
|
||||
{
|
||||
version: "0.33.1",
|
||||
date: "2026-07-09",
|
||||
summary: "Fix: opening a channel no longer errors when YouTube is briefly unreachable.",
|
||||
fixes: [
|
||||
'Opening a channel page could pop a blocking "Server error" when the connection to YouTube was momentarily unavailable. The page now loads regardless — its latest channel details simply fill in once the connection is back — instead of failing.',
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.33.0",
|
||||
date: "2026-07-09",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue