Merge pull request #467 from g0ldyy/feat/improve-http-caching

feat: enhance caching policies and improve manifest handling
This commit is contained in:
Goldy
2026-01-11 13:51:25 +01:00
committed by GitHub
8 changed files with 1280 additions and 895 deletions
+1 -3
View File
@@ -1,5 +1,3 @@
import random
import string
from typing import Optional
from fastapi import APIRouter, BackgroundTasks, Query, Request
@@ -28,7 +26,7 @@ async def chilllink_manifest(request: Request, b64config: str = None):
config = config_check(b64config)
manifest = {
"id": f"{settings.ADDON_ID}.{''.join(random.choice(string.ascii_letters) for _ in range(4))}",
"id": settings.ADDON_ID,
"version": "2.0.0",
"description": "Chillio's fastest debrid search add-on.",
"supported_endpoints": {"feeds": None, "streams": "/streams"},
+2 -7
View File
@@ -1,6 +1,3 @@
import random
import string
from fastapi import APIRouter, Request
from comet.core.config_validation import config_check
@@ -27,7 +24,7 @@ router = APIRouter()
)
async def manifest(request: Request, b64config: str = None):
base_manifest = {
"id": f"{settings.ADDON_ID}.{''.join(random.choice(string.ascii_letters) for _ in range(4))}",
"id": settings.ADDON_ID,
"description": "Stremio's fastest torrent/debrid search add-on.",
"version": "2.0.0",
"catalogs": [],
@@ -58,9 +55,7 @@ async def manifest(request: Request, b64config: str = None):
)
if settings.HTTP_CACHE_ENABLED:
base_manifest_etag_data = base_manifest.copy()
base_manifest_etag_data.pop("id", None)
etag = generate_etag(base_manifest_etag_data)
etag = generate_etag(base_manifest)
if check_etag_match(request, etag):
return not_modified_response(etag)
+45 -19
View File
@@ -5,7 +5,7 @@ from urllib.parse import quote
import aiohttp
from fastapi import APIRouter, BackgroundTasks, Request
from comet.core.config_validation import config_check
from comet.core.config_validation import config_check, is_default_config
from comet.core.logger import logger
from comet.core.models import database, settings, trackers
from comet.debrid.exceptions import DebridAuthError
@@ -31,6 +31,7 @@ def _build_stream_response(
request: Request,
content: dict,
is_public: bool = False,
is_empty: bool = False,
vary_headers: list = None,
):
if not settings.HTTP_CACHE_ENABLED:
@@ -41,7 +42,10 @@ def _build_stream_response(
if check_etag_match(request, etag):
return not_modified_response(etag)
if is_public:
if is_empty:
cache_policy = CachePolicies.empty_results()
vary = ["Accept", "Accept-Encoding"]
elif is_public:
cache_policy = CachePolicies.public_torrents()
vary = ["Accept", "Accept-Encoding"]
else:
@@ -177,12 +181,12 @@ async def stream(
if media_type not in ["movie", "series"]:
return _build_stream_response(
request, {"streams": []}, is_public=is_public_request
request, {"streams": []}, is_public=True, is_empty=True
)
if "tmdb:" in media_id:
return _build_stream_response(
request, {"streams": []}, is_public=is_public_request
request, {"streams": []}, is_public=True, is_empty=True
)
media_id = media_id.replace("imdb_id:", "")
@@ -198,7 +202,12 @@ async def stream(
}
]
}
return error_response
return _build_stream_response(
request, error_response, is_public=True, is_empty=True
)
if is_default_config(config):
is_public_request = True
is_torrent = config["debridService"] == "torrent"
if settings.DISABLE_TORRENT_STREAMS and is_torrent:
@@ -238,7 +247,8 @@ async def stream(
}
]
},
is_public=is_public_request,
is_public=True,
is_empty=True,
)
# Check if metadata is already cached
@@ -335,7 +345,9 @@ async def stream(
if lock_acquired and scrape_lock:
await scrape_lock.release()
logger.log("SCRAPER", f"❌ Failed to fetch metadata for {media_id}")
return {
return _build_stream_response(
request,
{
"streams": [
{
"name": "[⚠️] Comet",
@@ -343,7 +355,10 @@ async def stream(
"url": "https://comet.feels.legal",
}
]
}
},
is_public=True,
is_empty=True,
)
title = metadata["title"]
year = metadata["year"]
@@ -449,7 +464,9 @@ async def stream(
)
if len(torrent_manager.torrents) == 0:
return {
return _build_stream_response(
request,
{
"streams": [
{
"name": "[🔄] Comet",
@@ -457,7 +474,10 @@ async def stream(
"url": "https://comet.feels.legal",
}
]
}
},
is_public=True,
is_empty=True,
)
elif is_first or cache_is_stale:
# Background scrape if first search OR if cache is stale (needs refresh)
@@ -527,7 +547,9 @@ async def stream(
episode,
)
except DebridAuthError as e:
return {
return _build_stream_response(
request,
{
"streams": [
{
"name": "[❌] Comet",
@@ -535,7 +557,10 @@ async def stream(
"url": "https://comet.feels.legal",
}
]
}
},
is_public=False,
is_empty=True,
)
if debrid_service != "torrent":
cached_count = sum(
@@ -641,14 +666,15 @@ async def stream(
non_cached_results.append(the_stream)
if sort_mixed:
return _build_stream_response(
request,
{"streams": cached_results},
is_public=is_public_request,
)
final_streams = cached_results
else:
final_streams = cached_results + non_cached_results
has_results = len(final_streams) > 0
return _build_stream_response(
request,
{"streams": cached_results + non_cached_results},
is_public=is_public_request,
{"streams": final_streams},
is_public=is_public_request or not has_results,
is_empty=not has_results,
)
+55
View File
@@ -59,3 +59,58 @@ def config_check(b64config: str):
return validated_config
except Exception:
return default_config # if it doesn't pass, return default config
def is_default_config(config: dict) -> bool:
if config is None:
return True
ignored_fields = {
"debridApiKey",
"debridStreamProxyPassword",
"rtnSettings",
"rtnRanking",
"debridService",
}
if config.get("debridService") != "torrent":
return False
for field, default_value in default_config.items():
if field in ignored_fields:
continue
config_value = config.get(field)
if field == "resolutions":
if isinstance(config_value, dict):
for res, enabled in config_value.items():
if enabled is False:
return False
continue
if field == "languages":
config_lang = config_value or {}
default_lang = default_value or {}
if config_lang.get("exclude", []) != default_lang.get("exclude", []):
return False
if config_lang.get("preferred", []) != default_lang.get("preferred", []):
return False
continue
if field == "options":
config_opts = config_value or {}
default_opts = default_value or {}
for key in [
"remove_ranks_under",
"allow_english_in_languages",
"remove_unknown_languages",
]:
if config_opts.get(key) != default_opts.get(key):
return False
continue
if config_value != default_value:
return False
return True
+48 -25
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html class="sl-theme-dark">
<head>
<meta charset="UTF-8" />
@@ -44,9 +44,19 @@
#25292c 0%,
#0c0d13 100%
);
font-family: system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
"Helvetica Neue",
"Noto Sans",
"Liberation Sans",
Arial,
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
@@ -308,7 +318,8 @@
border-radius: 50%;
filter: drop-shadow(0 0 6px currentColor);
transform: translate3d(104em, 0, 0);
animation: fall var(--fall-duration) var(--fall-delay) linear infinite,
animation:
fall var(--fall-duration) var(--fall-delay) linear infinite,
tail-fade var(--tail-fade-duration) var(--fall-delay) ease-out
infinite;
}
@@ -780,7 +791,7 @@
star.style.setProperty("--top-offset", `${randomTopOffset}vh`);
star.style.setProperty(
"--star-tail-length",
`${randomTailLength}em`
`${randomTailLength}em`,
);
star.style.setProperty("--fall-duration", `${randomFallDuration}s`);
star.style.setProperty("--fall-delay", "0s");
@@ -956,9 +967,21 @@
<h3><sl-icon name="download"></sl-icon> Torrents Analysis</h3>
<div class="metric-content">
<div class="metric-subsection">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h4 style="margin: 0;">Top Trackers</h4>
<sl-select id="tracker-limit" size="small" value="5" style="width: 110px;">
<div
style="
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
"
>
<h4 style="margin: 0">Top Trackers</h4>
<sl-select
id="tracker-limit"
size="small"
value="5"
style="width: 110px"
>
<sl-option value="5">Top 5</sl-option>
<sl-option value="10">Top 10</sl-option>
<sl-option value="20">Top 20</sl-option>
@@ -1133,7 +1156,7 @@
// Logs refresh based on selected interval
const interval = parseInt(
document.getElementById("refresh-interval").value
document.getElementById("refresh-interval").value,
);
logsInterval = setInterval(loadLogs, interval);
}
@@ -1151,7 +1174,7 @@
if (document.getElementById("auto-refresh").checked) {
clearInterval(logsInterval);
const interval = parseInt(
document.getElementById("refresh-interval").value
document.getElementById("refresh-interval").value,
);
logsInterval = setInterval(loadLogs, interval);
}
@@ -1189,7 +1212,7 @@
connections.length > 0
? Math.round(
connections.reduce((sum, c) => sum + c.duration, 0) /
connections.length
connections.length,
)
: 0;
@@ -1239,7 +1262,7 @@
<td>
<span class="connection-id">${conn.id.substring(
0,
8
8,
)}...</span>
</td>
<td>
@@ -1247,11 +1270,11 @@
</td>
<td>
<span title="${escapeHtml(
conn.content
conn.content,
)}">${
conn.content.length > 25
? escapeHtml(
conn.content.substring(0, 25)
conn.content.substring(0, 25),
) + "..."
: escapeHtml(conn.content)
}</span>
@@ -1263,7 +1286,7 @@
</td>
<td>
<span class="duration-tag">${Math.round(
conn.duration
conn.duration,
)}s</span>
</td>
<td>
@@ -1284,7 +1307,7 @@
}</span>
</td>
</tr>
`
`,
)
.join("")}
</tbody>
@@ -1297,7 +1320,7 @@
async function loadLogs() {
try {
const response = await fetch(
`/admin/api/logs?since=${lastLogTimestamp}`
`/admin/api/logs?since=${lastLogTimestamp}`,
);
const data = await response.json();
@@ -1341,7 +1364,7 @@
} ${log.level}</span> <span class="log-module">${log.module}.${
log.function
}</span> - <span class="log-message">${escapeHtml(
log.message
log.message,
)}</span>`;
container.appendChild(logEntry);
@@ -1388,7 +1411,7 @@
const hideApiLogs = document.getElementById("hide-api-logs").checked;
const container = document.getElementById("logs-container");
const apiLogEntries = container.querySelectorAll(
'[data-api-log="true"]'
'[data-api-log="true"]',
);
apiLogEntries.forEach((entry) => {
@@ -1449,7 +1472,7 @@
(tracker) => `
<div class="tracker-item">
<div class="tracker-name">${escapeHtml(
tracker.tracker || "Unknown"
tracker.tracker || "Unknown",
)}</div>
<div class="tracker-stats">
<span class="tracker-count">${tracker.count.toLocaleString()} torrents</span>
@@ -1461,7 +1484,7 @@
} avg size</span>
</div>
</div>
`
`,
)
.join("");
}
@@ -1552,7 +1575,7 @@
function showMetricsError() {
const sections = document.querySelectorAll(
".metric-section .loading-state"
".metric-section .loading-state",
);
sections.forEach((section) => {
section.innerHTML =
@@ -1576,7 +1599,7 @@
(service) => `
<div class="debrid-service-item">
<div class="debrid-service-name">${escapeHtml(
service.service
service.service,
)}</div>
<div class="debrid-service-stats">
<span class="service-count">${service.count.toLocaleString()} files</span>
@@ -1588,7 +1611,7 @@
}</span>
</div>
</div>
`
`,
)
.join("");
}
+17 -6
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html class="sl-theme-dark">
<head>
<meta charset="UTF-8" />
@@ -44,9 +44,19 @@
#25292c 0%,
#0c0d13 100%
);
font-family: system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
"Helvetica Neue",
"Noto Sans",
"Liberation Sans",
Arial,
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
@@ -125,7 +135,8 @@
border-radius: 50%;
filter: drop-shadow(0 0 6px currentColor);
transform: translate3d(104em, 0, 0);
animation: fall var(--fall-duration) var(--fall-delay) linear infinite,
animation:
fall var(--fall-duration) var(--fall-delay) linear infinite,
tail-fade var(--tail-fade-duration) var(--fall-delay) ease-out
infinite;
}
@@ -235,7 +246,7 @@
star.style.setProperty("--top-offset", `${randomTopOffset}vh`);
star.style.setProperty(
"--star-tail-length",
`${randomTailLength}em`
`${randomTailLength}em`,
);
star.style.setProperty("--fall-duration", `${randomFallDuration}s`);
star.style.setProperty("--fall-delay", "0s");
+357 -94
View File
@@ -1,21 +1,39 @@
<!DOCTYPE html>
<!doctype html>
<html class="sl-theme-dark">
<head>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta content="Comet" property="og:title" />
<meta content="Stremio's fastest torrent/debrid search add-on." property="og:description" />
<meta
content="Stremio's fastest torrent/debrid search add-on."
property="og:description"
/>
<meta content="https://comet.feels.legal" property="og:url" />
<meta content="https://raw.githubusercontent.com/g0ldyy/comet/refs/heads/main/comet/assets/icon.png" property="og:image" />
<meta
content="https://raw.githubusercontent.com/g0ldyy/comet/refs/heads/main/comet/assets/icon.png"
property="og:image"
/>
<meta content="#6b6ef8" data-react-helmet="true" name="theme-color" />
<title>Comet - Stremio's fastest torrent/debrid search add-on.</title>
<link id="favicon" rel="icon" type="image/x-icon" href="https://raw.githubusercontent.com/g0ldyy/comet/refs/heads/main/comet/assets/icon.png" />
<link
id="favicon"
rel="icon"
type="image/x-icon"
href="https://raw.githubusercontent.com/g0ldyy/comet/refs/heads/main/comet/assets/icon.png"
/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/dark.css" />
<script type="module"
src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/shoelace-autoloader.js"></script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/dark.css"
/>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/shoelace-autoloader.js"
></script>
<style>
:not(:defined) {
@@ -29,12 +47,24 @@
align-items: center;
min-height: 100vh;
margin: 0;
background: radial-gradient(ellipse at bottom,
background: radial-gradient(
ellipse at bottom,
#25292c 0%,
#0c0d13 100%);
font-family: system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
#0c0d13 100%
);
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
"Helvetica Neue",
"Noto Sans",
"Liberation Sans",
Arial,
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
font-size: 1rem;
font-weight: 400;
@@ -117,8 +147,10 @@
border-radius: 50%;
filter: drop-shadow(0 0 6px currentColor);
transform: translate3d(104em, 0, 0);
animation: fall var(--fall-duration) var(--fall-delay) linear infinite,
tail-fade var(--tail-fade-duration) var(--fall-delay) ease-out infinite;
animation:
fall var(--fall-duration) var(--fall-delay) linear infinite,
tail-fade var(--tail-fade-duration) var(--fall-delay) ease-out
infinite;
}
@media screen and (max-width: 750px) {
@@ -135,10 +167,12 @@
left: calc(var(--star-width) / -2);
width: var(--star-width);
height: 100%;
background: linear-gradient(45deg,
background: linear-gradient(
45deg,
transparent,
currentColor,
transparent);
transparent
);
border-radius: inherit;
animation: blink 2s linear infinite;
}
@@ -158,7 +192,6 @@
}
@keyframes tail-fade {
0%,
50% {
width: var(--star-tail-length);
@@ -227,8 +260,8 @@
}
.discord-btn::part(base) {
background-color: #5865F2;
border-color: #5865F2;
background-color: #5865f2;
border-color: #5865f2;
color: white;
font-weight: 600;
display: flex;
@@ -236,8 +269,8 @@
}
.discord-btn::part(base):hover {
background-color: #4752C4;
border-color: #4752C4;
background-color: #4752c4;
border-color: #4752c4;
}
.heart-button {
@@ -292,9 +325,9 @@
}
}
</style>
</head>
</head>
<body>
<body>
<div class="stars"></div>
<script>
@@ -334,7 +367,7 @@
star.style.setProperty("--top-offset", `${randomTopOffset}vh`);
star.style.setProperty(
"--star-tail-length",
`${randomTailLength}em`
`${randomTailLength}em`,
);
star.style.setProperty("--fall-duration", `${randomFallDuration}s`);
star.style.setProperty("--fall-delay", "0s");
@@ -347,15 +380,39 @@
</script>
<div class="header">
<div
style="display: flex; align-items: center; justify-content: center; gap: 15px; margin-bottom: 10px; height: 50px;">
<p class="comet-text" style="margin: 0; display: flex; align-items: center; line-height: 1;">
<img class="emoji" src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f4ab/512.gif"
style="margin-right: 10px;" />
style="
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
margin-bottom: 10px;
height: 50px;
"
>
<p
class="comet-text"
style="margin: 0; display: flex; align-items: center; line-height: 1"
>
<img
class="emoji"
src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f4ab/512.gif"
style="margin-right: 10px"
/>
Comet
</p>
<sl-button href="https://discord.com/invite/UJEqpT42nb" target="_blank" size="small" pill class="discord-btn"
style="display: flex; align-items: center;">
<sl-icon slot="prefix" name="discord" style="font-size: 1rem;"></sl-icon>
<sl-button
href="https://discord.com/invite/UJEqpT42nb"
target="_blank"
size="small"
pill
class="discord-btn"
style="display: flex; align-items: center"
>
<sl-icon
slot="prefix"
name="discord"
style="font-size: 1rem"
></sl-icon>
Discord
</sl-button>
</div>
@@ -374,17 +431,33 @@
</div> -->
<div class="form-item">
<sl-select id="languages_preferred" multiple clearable label="Preferred Languages"
placeholder="Select preferred languages"></sl-select>
<sl-select
id="languages_preferred"
multiple
clearable
label="Preferred Languages"
placeholder="Select preferred languages"
></sl-select>
</div>
<div class="form-item">
<sl-select id="languages_excluded" multiple clearable label="Excluded Languages"
placeholder="Select excluded languages"></sl-select>
<sl-select
id="languages_excluded"
multiple
clearable
label="Excluded Languages"
placeholder="Select excluded languages"
></sl-select>
</div>
<div class="form-item">
<sl-select id="resolutions" multiple clearable label="Resolutions" placeholder="Select resolutions">
<sl-select
id="resolutions"
multiple
clearable
label="Resolutions"
placeholder="Select resolutions"
>
<sl-option value="r2160p">4K - 2160p</sl-option>
<sl-option value="r1080p">Full HD - 1080p</sl-option>
<sl-option value="r720p">HD - 720p</sl-option>
@@ -395,21 +468,48 @@
</div>
<div class="form-item">
<sl-input id="maxResultsPerResolution" type="number" min="0" value="0" label="Max Results Per Resolution"
placeholder="Enter max results per resolution"></sl-input>
<sl-input
id="maxResultsPerResolution"
type="number"
min="0"
value="0"
label="Max Results Per Resolution"
placeholder="Enter max results per resolution"
></sl-input>
</div>
<div class="form-item">
<sl-input id="maxSize" type="number" min="0" value="0" label="Max Size (GB)"
placeholder="Enter max size in gigabytes"></sl-input>
<sl-input
id="maxSize"
type="number"
min="0"
value="0"
label="Max Size (GB)"
placeholder="Enter max size in gigabytes"
></sl-input>
</div>
<div class="form-item" {% if not proxyDebridStream %}style="display: none" {% endif %}>
<sl-input id="debridStreamProxyPassword" label="Debrid Stream Proxy Password" placeholder="Enter password"
help-text="Debrid Stream Proxying allows you to use your Debrid Service from multiple IPs at same time!"></sl-input>
<div
class="form-item"
{%
if
not
proxyDebridStream
%}style="display: none"
{%
endif
%}
>
<sl-input
id="debridStreamProxyPassword"
label="Debrid Stream Proxy Password"
placeholder="Enter password"
help-text="Debrid Stream Proxying allows you to use your Debrid Service from multiple IPs at same time!"
></sl-input>
</div>
{% set default_debrid_service = 'realdebrid' if disableTorrentStreams else 'torrent' %}
{% set default_debrid_service = 'realdebrid' if disableTorrentStreams else
'torrent' %}
<div class="form-item">
<sl-select
id="debridService"
@@ -445,30 +545,58 @@
<sl-details summary="Advanced Settings">
<div class="form-item">
<sl-input id="rankThreshold" type="number" value="-10000000000" label="Minimum Rank Threshold"
<sl-input
id="rankThreshold"
type="number"
value="-10000000000"
label="Minimum Rank Threshold"
placeholder="Enter minimum rank threshold"
help-text="Torrents ranked below this value will be excluded from results."></sl-input>
<sl-checkbox id="cachedOnly" help-text="Show only content that has already been cached.">Show Cached
Only</sl-checkbox>
<sl-checkbox id="sortCachedUncachedTogether" help-text="Disable the default behavior of sorting cached results first, and instead mixes cached and uncached results together.">Sort Cached and Uncached Together</sl-checkbox>
<sl-checkbox id="allowEnglishInLanguages"
help-text="Allows English language torrents to be included even if you excluded English from your language preferences.">Allow
English in Languages</sl-checkbox>
<sl-checkbox id="removeUnknownLanguages"
help-text="Exclude torrents that don't have a language in the title.">Remove Unknown Languages</sl-checkbox>
<sl-checkbox checked id="removeTrash"
help-text="Remove all trash from results (if disabled, your filters will not be applied - Adult Content, CAM, Clean Audio, PDTV, R5, Screener, Size, Telecine and Telesync)">Remove
Trash</sl-checkbox>
<sl-select id="resultFormat" multiple label="Result Format" placeholder="Select what to show in result title"
hoist max-options-visible="10">
help-text="Torrents ranked below this value will be excluded from results."
></sl-input>
<sl-checkbox
id="cachedOnly"
help-text="Show only content that has already been cached."
>Show Cached Only</sl-checkbox
>
<sl-checkbox
id="sortCachedUncachedTogether"
help-text="Disable the default behavior of sorting cached results first, and instead mixes cached and uncached results together."
>Sort Cached and Uncached Together</sl-checkbox
>
<sl-checkbox
id="allowEnglishInLanguages"
help-text="Allows English language torrents to be included even if you excluded English from your language preferences."
>Allow English in Languages</sl-checkbox
>
<sl-checkbox
id="removeUnknownLanguages"
help-text="Exclude torrents that don't have a language in the title."
>Remove Unknown Languages</sl-checkbox
>
<sl-checkbox
checked
id="removeTrash"
help-text="Remove all trash from results (if disabled, your filters will not be applied - Adult Content, CAM, Clean Audio, PDTV, R5, Screener, Size, Telecine and Telesync)"
>Remove Trash</sl-checkbox
>
<sl-select
id="resultFormat"
multiple
label="Result Format"
placeholder="Select what to show in result title"
hoist
max-options-visible="10"
>
</sl-select>
</div>
</sl-details>
<script>
const debridServiceElement = document.getElementById("debridService");
const torrentDisabled = debridServiceElement.dataset.disableTorrent === "true";
const defaultDebridService = debridServiceElement.dataset.defaultService;
const torrentDisabled =
debridServiceElement.dataset.disableTorrent === "true";
const defaultDebridService =
debridServiceElement.dataset.defaultService;
window.disableTorrentStreams = torrentDisabled;
window.defaultDebridService = defaultDebridService;
@@ -509,7 +637,8 @@
apiKeyLink.textContent = "Get it here";
apiKeyLink.style.display = "inline-block";
referralLink.href = "https://torbox.app/subscription?referral=1ffb2238-1c5f-402e-a2ce-3d7a86c52d02";
referralLink.href =
"https://torbox.app/subscription?referral=1ffb2238-1c5f-402e-a2ce-3d7a86c52d02";
referralLink.textContent = "Sign up here";
referralLink.style.display = "inline-block";
} else if (selectedService === "debrider") {
@@ -538,10 +667,7 @@
apiKeyLink.style.display = "inline-block";
}
if (
selectedService === "offcloud" ||
selectedService === "pikpak"
) {
if (selectedService === "offcloud" || selectedService === "pikpak") {
apiKeyInput.helpText = "Format: `email:password`";
} else if (!torrentDisabled && selectedService === "torrent") {
apiKeyInput.helpText = "";
@@ -558,7 +684,9 @@
</script>
<div class="centered-item">
<sl-button id="install" variant="neutral" class="margin-left-install">Install</sl-button>
<sl-button id="install" variant="neutral" class="margin-left-install"
>Install</sl-button
>
<sl-alert id="installAlert" variant="neutral" duration="3000" closable>
<sl-icon slot="icon" name="clipboard2-check"></sl-icon>
<strong>Attempting to add the addon to Stremio...</strong>
@@ -634,8 +762,8 @@
customElements.whenDefined("sl-checkbox")
]);
const webConfig = {{ webConfig| tojson
}};
const webConfig = {{ webConfig| tojson }};
const resolutionsSelect = document.getElementById("resolutions");
resolutionsSelect.value = ["r2160p", "r1080p", "r720p", "r480p", "r360p", "unknown"];
@@ -795,28 +923,126 @@
};
}
// Check if config matches all default values
// If so, use /manifest.json instead of /{b64config}/manifest.json for better CDN caching
function isDefaultConfig(settings) {
// Must be torrent mode
if (settings.debridService !== "torrent") {
return false;
}
// Check all filtering fields match defaults
// maxResultsPerResolution default is 0
if (settings.maxResultsPerResolution !== 0) {
return false;
}
// maxSize default is 0
if (settings.maxSize !== 0) {
return false;
}
// cachedOnly default is false
if (settings.cachedOnly !== false) {
return false;
}
// sortCachedUncachedTogether default is false
if (settings.sortCachedUncachedTogether !== false) {
return false;
}
// removeTrash default is true
if (settings.removeTrash !== true) {
return false;
}
// resultFormat default is ["all"]
if (!settings.resultFormat ||
settings.resultFormat.length !== 1 ||
settings.resultFormat[0] !== "all") {
return false;
}
// resolutions: all should be enabled (no false values in the object)
// An empty object means all are enabled (default)
for (const [res, enabled] of Object.entries(settings.resolutions || {})) {
if (enabled === false) {
return false;
}
}
// languages: no excludes or preferences (empty arrays)
if (settings.languages) {
if (settings.languages.exclude && settings.languages.exclude.length > 0) {
return false;
}
if (settings.languages.preferred && settings.languages.preferred.length > 0) {
return false;
}
}
// options: check defaults
if (settings.options) {
// remove_ranks_under default is -10000000000
if (settings.options.remove_ranks_under !== -10000000000) {
return false;
}
// allow_english_in_languages default is false
if (settings.options.allow_english_in_languages !== false) {
return false;
}
// remove_unknown_languages default is false
if (settings.options.remove_unknown_languages !== false) {
return false;
}
}
return true;
}
// Generate the manifest URL - use /manifest.json for default configs
function getManifestUrl(settings, forInstall = false) {
const isDefault = isDefaultConfig(settings);
const protocol = forInstall ? "stremio:" : window.location.protocol;
const host = window.location.host;
if (isDefault) {
// Default config: use public manifest URL (better CDN caching)
return forInstall
? `stremio://${host}/manifest.json`
: `${window.location.origin}/manifest.json`;
} else {
// Custom config: use b64config in URL
const settingsString = btoa(JSON.stringify(settings));
return forInstall
? `stremio://${host}/${settingsString}/manifest.json`
: `${window.location.origin}/${settingsString}/manifest.json`;
}
}
copyLinkButton.addEventListener("click", () => {
const settings = getSettings();
const settingsString = btoa(JSON.stringify(settings))
const manifestUrl = getManifestUrl(settings, false);
const textArea = document.createElement("textarea");
textArea.value = `${window.location.origin}/${settingsString}/manifest.json`;
textArea.value = manifestUrl;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
console.log(settingsString);
console.log(isDefaultConfig(settings) ? "Using default manifest URL" : btoa(JSON.stringify(settings)));
copyAlert.toast();
});
installButton.addEventListener("click", () => {
const settings = getSettings();
const settingsString = btoa(JSON.stringify(settings))
window.location.href = `stremio://${window.location.host}/${settingsString}/manifest.json`;
console.log(settingsString);
const manifestUrl = getManifestUrl(settings, true);
window.location.href = manifestUrl;
console.log(isDefaultConfig(settings) ? "Installing with default manifest" : btoa(JSON.stringify(settings)));
installAlert.toast();
});
@@ -889,23 +1115,61 @@
<sl-icon name="heart-fill"></sl-icon>
</div>
<sl-dialog label="Support Comet" id="support-dialog" style="--width: 400px;">
<sl-dialog label="Support Comet" id="support-dialog" style="--width: 400px">
<div
style="display: flex; flex-direction: column; align-items: center; text-align: center; gap: 0.5rem; margin-top: -15px;">
<div style="font-size: 2.5rem; animation: heartbeat 1.5s ease-in-out infinite;">❤️</div>
<h3 style="margin: 0;">Donate to Me</h3>
<p style="color: var(--sl-color-neutral-500); margin: 0; font-size: 0.9rem;">
Comet is a passion project fueled by late nights and coffee. Your support helps keep the lights on and the
updates rolling!
style="
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 0.5rem;
margin-top: -15px;
"
>
<div
style="
font-size: 2.5rem;
animation: heartbeat 1.5s ease-in-out infinite;
"
>
❤️
</div>
<h3 style="margin: 0">Donate to Me</h3>
<p
style="
color: var(--sl-color-neutral-500);
margin: 0;
font-size: 0.9rem;
"
>
Comet is a passion project fueled by late nights and coffee. Your
support helps keep the lights on and the updates rolling!
</p>
<div style="display: flex; flex-direction: column; gap: 8px; width: 100%; margin-top: 10px;">
<sl-button href="https://github.com/sponsors/g0ldyy" target="_blank" outline style="width: 100%;">
<div
style="
display: flex;
flex-direction: column;
gap: 8px;
width: 100%;
margin-top: 10px;
"
>
<sl-button
href="https://github.com/sponsors/g0ldyy"
target="_blank"
outline
style="width: 100%"
>
<sl-icon slot="prefix" name="github"></sl-icon>
GitHub Sponsors
</sl-button>
<sl-button href="https://ko-fi.com/g0ldyy" target="_blank" outline
style="width: 100%; --sl-color-primary-600: #FF5E5B;">
<sl-button
href="https://ko-fi.com/g0ldyy"
target="_blank"
outline
style="width: 100%; --sl-color-primary-600: #ff5e5b"
>
<sl-icon slot="prefix" name="cup-hot"></sl-icon>
Ko-fi
</sl-button>
@@ -914,10 +1178,9 @@
</sl-dialog>
<script>
const dialog = document.querySelector('#support-dialog');
const openButton = document.querySelector('#support-trigger');
openButton.addEventListener('click', () => dialog.show());
const dialog = document.querySelector("#support-dialog");
const openButton = document.querySelector("#support-trigger");
openButton.addEventListener("click", () => dialog.show());
</script>
</body>
</body>
</html>
+14
View File
@@ -203,6 +203,20 @@ class CachePolicies:
return CacheControl().public().max_age(300).s_maxage(3600)
@staticmethod
def empty_results():
"""
For empty/temporary responses (no torrents found, processing, errors).
Short public cache to prevent spam while allowing quick retries.
"""
return (
CacheControl()
.public()
.max_age(15) # Browser cache 15 seconds
.s_maxage(30) # CDN cache 30 seconds
.stale_if_error(60) # Serve stale on error for 1 minute
)
@staticmethod
def no_cache():
"""