Skip to content

chore(release): release v1.10.0#126

Open
TimilsinaBimal wants to merge 23 commits intomainfrom
dev
Open

chore(release): release v1.10.0#126
TimilsinaBimal wants to merge 23 commits intomainfrom
dev

Conversation

@TimilsinaBimal
Copy link
Copy Markdown
Owner

No description provided.

TimilsinaBimal and others added 17 commits March 1, 2026 13:14
…ction for improved clarity and maintainability
Cherry-pick features from main: TMDB language-aware image fetching for
posters/logos/backgrounds, and translation fix that preserves item titles
in catalog names instead of translating them word-by-word.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
watchly Ready Ready Preview, Comment Apr 5, 2026 7:43am

@TimilsinaBimal TimilsinaBimal changed the title Dev chore(release): release v1.10.0 Apr 2, 2026
e.preventDefault();
e.stopPropagation();
const url = document.getElementById('addonUrl').textContent;
window.location.href = `stremio://${url.replace(/^https?:\/\//, '')}`;

Check failure

Code scanning / CodeQL

DOM text reinterpreted as HTML High

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 4 days ago

General fix: When reading data from the DOM and reusing it in another sensitive context (here, as a URL for navigation), validate and normalize it rather than trusting the raw string. Ensure that only expected schemes, hostnames, and path characters are allowed, and avoid interpreting arbitrary text as a protocol URL.

Best specific fix here: Validate the url read from addonUrl before using it to build the stremio:// URL. A robust approach is:

  1. Trim the string and ensure it is non-empty.
  2. Normalize it as an HTTP(S) URL first via the URL constructor to ensure it has the expected scheme and structure.
  3. Extract the host and path from the normalized URL, and reconstruct the stremio:// URL from those parts, rather than doing a naive string replace.
  4. If parsing fails or the scheme is not http or https, abort (or show an error) instead of navigating.

This preserves functionality (it still converts an HTTP(S) addon URL into a stremio:// URL) but prevents arbitrary text from becoming an unchecked custom-protocol URL. All changes are confined to app/static/js/modules/form-success.js, specifically around lines 25–27. No new imports are needed.

Concretely, we will replace:

const url = document.getElementById('addonUrl').textContent;
window.location.href = `stremio://${url.replace(/^https?:\/\//, '')}`;

with logic that:

  • Reads and trims the text;
  • Uses new URL(...) to parse it (falling back to https:// if no scheme is provided);
  • Verifies the protocol is http: or https:;
  • Builds stremio:// using urlObj.host + urlObj.pathname + urlObj.search + urlObj.hash;
  • Handles parsing errors by not redirecting (optionally using showError if we want minimal behavior change; to avoid new behavior we can just silently return).

Suggested changeset 1
app/static/js/modules/form-success.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/static/js/modules/form-success.js b/app/static/js/modules/form-success.js
--- a/app/static/js/modules/form-success.js
+++ b/app/static/js/modules/form-success.js
@@ -22,8 +22,31 @@
         installDesktopBtn.addEventListener('click', (e) => {
             e.preventDefault();
             e.stopPropagation();
-            const url = document.getElementById('addonUrl').textContent;
-            window.location.href = `stremio://${url.replace(/^https?:\/\//, '')}`;
+            const rawText = document.getElementById('addonUrl').textContent || '';
+            const trimmed = rawText.trim();
+            if (!trimmed) {
+                return;
+            }
+
+            let normalizedUrl;
+            try {
+                // Ensure we have an absolute HTTP(S) URL before converting to the stremio:// protocol
+                if (/^https?:\/\//i.test(trimmed)) {
+                    normalizedUrl = new URL(trimmed);
+                } else {
+                    normalizedUrl = new URL(`https://${trimmed}`);
+                }
+            } catch (_) {
+                // If the URL is not valid, do not attempt to navigate
+                return;
+            }
+
+            if (normalizedUrl.protocol !== 'http:' && normalizedUrl.protocol !== 'https:') {
+                return;
+            }
+
+            const addonTarget = `${normalizedUrl.host}${normalizedUrl.pathname}${normalizedUrl.search}${normalizedUrl.hash}`;
+            window.location.href = `stremio://${addonTarget}`;
         });
     }
 
EOF
@@ -22,8 +22,31 @@
installDesktopBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const url = document.getElementById('addonUrl').textContent;
window.location.href = `stremio://${url.replace(/^https?:\/\//, '')}`;
const rawText = document.getElementById('addonUrl').textContent || '';
const trimmed = rawText.trim();
if (!trimmed) {
return;
}

let normalizedUrl;
try {
// Ensure we have an absolute HTTP(S) URL before converting to the stremio:// protocol
if (/^https?:\/\//i.test(trimmed)) {
normalizedUrl = new URL(trimmed);
} else {
normalizedUrl = new URL(`https://${trimmed}`);
}
} catch (_) {
// If the URL is not valid, do not attempt to navigate
return;
}

if (normalizedUrl.protocol !== 'http:' && normalizedUrl.protocol !== 'https:') {
return;
}

const addonTarget = `${normalizedUrl.host}${normalizedUrl.pathname}${normalizedUrl.search}${normalizedUrl.hash}`;
window.location.href = `stremio://${addonTarget}`;
});
}

Copilot is powered by AI and may make mistakes. Always verify output.
username = user_info.get("user", {}).get("username") or user_info.get("username", "Unknown")
except Exception as e:
logger.error(f"Trakt OAuth callback failed: {e}")
return HTMLResponse(_oauth_error_page("Trakt", str(e)))

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 5 days ago

In general, the fix is to stop exposing raw exception messages (str(e)) to the user and instead return a generic, non-sensitive error message while logging the detailed error server-side. The logging preserves debuggability; the user sees only a high-level explanation like “Trakt authorization failed. Please try again.”

Concretely for this file:

  • Keep logging the exception using logger.error(...) (possibly with exc_info=True if desired), so developers still get full details.
  • Change _oauth_error_page so it no longer accepts or renders an arbitrary error string. Instead, have it generate a static, generic error page for the given provider.
  • Update the call at line 52 from _oauth_error_page("Trakt", str(e)) to simply _oauth_error_page("Trakt").
  • Adjust _oauth_error_page’s signature and body accordingly: remove the error parameter and the <p>{error}</p> line, perhaps replace it with a generic message like “An error occurred while communicating with Trakt.”

All needed functionality is already available; no new imports or helpers are required. All changes are within app/api/endpoints/oauth.py at the shown locations.

Suggested changeset 1
app/api/endpoints/oauth.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/api/endpoints/oauth.py b/app/api/endpoints/oauth.py
--- a/app/api/endpoints/oauth.py
+++ b/app/api/endpoints/oauth.py
@@ -49,7 +49,7 @@
         username = user_info.get("user", {}).get("username") or user_info.get("username", "Unknown")
     except Exception as e:
         logger.error(f"Trakt OAuth callback failed: {e}")
-        return HTMLResponse(_oauth_error_page("Trakt", str(e)))
+        return HTMLResponse(_oauth_error_page("Trakt"))
 
     return HTMLResponse(
         _oauth_success_page(
@@ -159,11 +159,11 @@
         </body></html>"""
 
 
-def _oauth_error_page(provider: str, error: str) -> str:
+def _oauth_error_page(provider: str) -> str:
     return f"""<!DOCTYPE html>
 <html><head><title>{provider.title()} Error</title></head>
 <body>
 <h2>{provider.title()} login failed</h2>
-<p>{error}</p>
+<p>An error occurred while communicating with {provider.title()}. Please try again.</p>
 <p>Please close this window and try again.</p>
 </body></html>"""
EOF
@@ -49,7 +49,7 @@
username = user_info.get("user", {}).get("username") or user_info.get("username", "Unknown")
except Exception as e:
logger.error(f"Trakt OAuth callback failed: {e}")
return HTMLResponse(_oauth_error_page("Trakt", str(e)))
return HTMLResponse(_oauth_error_page("Trakt"))

return HTMLResponse(
_oauth_success_page(
@@ -159,11 +159,11 @@
</body></html>"""


def _oauth_error_page(provider: str, error: str) -> str:
def _oauth_error_page(provider: str) -> str:
return f"""<!DOCTYPE html>
<html><head><title>{provider.title()} Error</title></head>
<body>
<h2>{provider.title()} login failed</h2>
<p>{error}</p>
<p>An error occurred while communicating with {provider.title()}. Please try again.</p>
<p>Please close this window and try again.</p>
</body></html>"""
Copilot is powered by AI and may make mistakes. Always verify output.
username = user_info.get("user", {}).get("name") or user_info.get("account", {}).get("id", "Unknown")
except Exception as e:
logger.error(f"Simkl OAuth callback failed: {e}")
return HTMLResponse(_oauth_error_page("Simkl", str(e)))

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 5 days ago

In general, to fix information exposure through exceptions, you should log full exception details on the server and return only a high-level, user-friendly, generic error message to the client. Do not interpolate raw exception messages, stack traces, or internal configuration values into responses.

For this specific code, the best minimal change is to stop passing str(e) into _oauth_error_page and instead pass a generic message such as "An internal error occurred while connecting to Simkl." (or similar). This avoids changing the function signature or overall behavior of the endpoint: the user still gets an error page, but without internal details. The server-side logging with logger.error remains, so you still have all diagnostic detail in logs.

Concretely:

  • In app/api/endpoints/oauth.py, within the simkl_callback function’s except block, replace return HTMLResponse(_oauth_error_page("Simkl", str(e))) with a call that uses a static, non-sensitive message string.
  • No additional imports or helper methods are required; we reuse _oauth_error_page as-is.
  • _oauth_error_page itself remains unchanged; it will receive the safe message string and render it.
Suggested changeset 1
app/api/endpoints/oauth.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/app/api/endpoints/oauth.py b/app/api/endpoints/oauth.py
--- a/app/api/endpoints/oauth.py
+++ b/app/api/endpoints/oauth.py
@@ -126,7 +126,7 @@
             username = user_info.get("user", {}).get("name") or user_info.get("account", {}).get("id", "Unknown")
     except Exception as e:
         logger.error(f"Simkl OAuth callback failed: {e}")
-        return HTMLResponse(_oauth_error_page("Simkl", str(e)))
+        return HTMLResponse(_oauth_error_page("Simkl", "An internal error occurred while connecting to Simkl."))
 
     return HTMLResponse(
         _oauth_success_page(
EOF
@@ -126,7 +126,7 @@
username = user_info.get("user", {}).get("name") or user_info.get("account", {}).get("id", "Unknown")
except Exception as e:
logger.error(f"Simkl OAuth callback failed: {e}")
return HTMLResponse(_oauth_error_page("Simkl", str(e)))
return HTMLResponse(_oauth_error_page("Simkl", "An internal error occurred while connecting to Simkl."))

return HTMLResponse(
_oauth_success_page(
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces significant architectural changes, including the addition of OAuth support for Trakt and Simkl, a refactored token management system, and a new unified library/history model. While these changes expand functionality, several critical issues were identified: an AttributeError in the profile service due to incorrect attribute access on Pydantic models, a security vulnerability in the OAuth callback using a wildcard origin for postMessage, a missing import for the caching decorator in the TMDB service, and a breaking change to the health check endpoint's response format.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Integrate Trakt and Simkl Watch history recommendations

2 participants