Merge pull request #473 from g0ldyy/development

Consolidate HTTP cache settings and restructure web UI
This commit is contained in:
Goldy
2026-01-11 16:03:26 +01:00
committed by GitHub
13 changed files with 1285 additions and 955 deletions
+12 -9
View File
@@ -277,16 +277,19 @@ STREMTHRU_URL=https://stremthru.13377001.xyz # needed to use debrid services
HTTP_CACHE_ENABLED=False # Master switch for HTTP cache headers HTTP_CACHE_ENABLED=False # Master switch for HTTP cache headers
# PUBLIC streams TTL (for /stream/movie/tt1234567.json without user config) # STREAMS streams TTL (for /stream/movie/tt1234567.json)
# These can be cached at edge and shared between all users. # These results are cached at edge and shared between all users.
# Higher = less origin traffic, lower = fresher results # Higher = less traffic to origin, Lower = faster updates for new content
HTTP_CACHE_PUBLIC_STREAMS_TTL=300 # 5 minutes (recommended: 120-600) HTTP_CACHE_STREAMS_TTL=300 # 5 minutes
# PRIVATE streams TTL (for /{config}/stream/... with user config)
# Only cached in user's browser (private cache).
# These contain user-specific debrid availability info.
HTTP_CACHE_PRIVATE_STREAMS_TTL=60 # 1 minute (recommended: 30-120)
# Stale-While-Revalidate: serve stale content while fetching fresh in background # Stale-While-Revalidate: serve stale content while fetching fresh in background
# Improves perceived performance - users get instant response with cached data # Improves perceived performance - users get instant response with cached data
HTTP_CACHE_STALE_WHILE_REVALIDATE=60 # 1 minute HTTP_CACHE_STALE_WHILE_REVALIDATE=60 # 1 minute
# MANIFEST TTL (for /manifest.json and /{config}/manifest.json)
# Manifest rarely changes, can be cached longer
HTTP_CACHE_MANIFEST_TTL=86400 # 24 hours (recommended: 3600-86400)
# CONFIGURE page TTL (for /configure and /{config}/configure)
# Static page, can be cached for a long time
HTTP_CACHE_CONFIGURE_TTL=86400 # 24 hours (recommended: 3600-86400)
+1 -3
View File
@@ -1,5 +1,3 @@
import random
import string
from typing import Optional from typing import Optional
from fastapi import APIRouter, BackgroundTasks, Query, Request from fastapi import APIRouter, BackgroundTasks, Query, Request
@@ -28,7 +26,7 @@ async def chilllink_manifest(request: Request, b64config: str = None):
config = config_check(b64config) config = config_check(b64config)
manifest = { manifest = {
"id": f"{settings.ADDON_ID}.{''.join(random.choice(string.ascii_letters) for _ in range(4))}", "id": settings.ADDON_ID,
"version": "2.0.0", "version": "2.0.0",
"description": "Chillio's fastest debrid search add-on.", "description": "Chillio's fastest debrid search add-on.",
"supported_endpoints": {"feeds": None, "streams": "/streams"}, "supported_endpoints": {"feeds": None, "streams": "/streams"},
+2 -7
View File
@@ -1,6 +1,3 @@
import random
import string
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from comet.core.config_validation import config_check from comet.core.config_validation import config_check
@@ -27,7 +24,7 @@ router = APIRouter()
) )
async def manifest(request: Request, b64config: str = None): async def manifest(request: Request, b64config: str = None):
base_manifest = { 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.", "description": "Stremio's fastest torrent/debrid search add-on.",
"version": "2.0.0", "version": "2.0.0",
"catalogs": [], "catalogs": [],
@@ -58,9 +55,7 @@ async def manifest(request: Request, b64config: str = None):
) )
if settings.HTTP_CACHE_ENABLED: if settings.HTTP_CACHE_ENABLED:
base_manifest_etag_data = base_manifest.copy() etag = generate_etag(base_manifest)
base_manifest_etag_data.pop("id", None)
etag = generate_etag(base_manifest_etag_data)
if check_etag_match(request, etag): if check_etag_match(request, etag):
return not_modified_response(etag) return not_modified_response(etag)
+56 -52
View File
@@ -30,7 +30,7 @@ streams = APIRouter()
def _build_stream_response( def _build_stream_response(
request: Request, request: Request,
content: dict, content: dict,
is_public: bool = False, is_empty: bool = False,
vary_headers: list = None, vary_headers: list = None,
): ):
if not settings.HTTP_CACHE_ENABLED: if not settings.HTTP_CACHE_ENABLED:
@@ -41,12 +41,12 @@ def _build_stream_response(
if check_etag_match(request, etag): if check_etag_match(request, etag):
return not_modified_response(etag) return not_modified_response(etag)
if is_public: if is_empty:
cache_policy = CachePolicies.public_torrents() cache_policy = CachePolicies.empty_results()
vary = ["Accept", "Accept-Encoding"] vary = ["Accept", "Accept-Encoding"]
else: else:
cache_policy = CachePolicies.private_streams() cache_policy = CachePolicies.streams()
vary = ["Accept", "Accept-Encoding", "Authorization"] vary = ["Accept", "Accept-Encoding"]
if vary_headers: if vary_headers:
vary.extend(vary_headers) vary.extend(vary_headers)
@@ -173,17 +173,11 @@ async def stream(
b64config: str = None, b64config: str = None,
chilllink: bool = False, chilllink: bool = False,
): ):
is_public_request = b64config is None
if media_type not in ["movie", "series"]: if media_type not in ["movie", "series"]:
return _build_stream_response( return _build_stream_response(request, {"streams": []}, is_empty=True)
request, {"streams": []}, is_public=is_public_request
)
if "tmdb:" in media_id: if "tmdb:" in media_id:
return _build_stream_response( return _build_stream_response(request, {"streams": []}, is_empty=True)
request, {"streams": []}, is_public=is_public_request
)
media_id = media_id.replace("imdb_id:", "") media_id = media_id.replace("imdb_id:", "")
@@ -198,7 +192,7 @@ async def stream(
} }
] ]
} }
return error_response return _build_stream_response(request, error_response, is_empty=True)
is_torrent = config["debridService"] == "torrent" is_torrent = config["debridService"] == "torrent"
if settings.DISABLE_TORRENT_STREAMS and is_torrent: if settings.DISABLE_TORRENT_STREAMS and is_torrent:
@@ -209,9 +203,7 @@ async def stream(
if settings.TORRENT_DISABLED_STREAM_URL: if settings.TORRENT_DISABLED_STREAM_URL:
placeholder_stream["url"] = settings.TORRENT_DISABLED_STREAM_URL placeholder_stream["url"] = settings.TORRENT_DISABLED_STREAM_URL
return _build_stream_response( return _build_stream_response(request, {"streams": [placeholder_stream]})
request, {"streams": [placeholder_stream]}, is_public=is_public_request
)
connector = aiohttp.TCPConnector(limit=0) connector = aiohttp.TCPConnector(limit=0)
async with aiohttp.ClientSession(connector=connector) as session: async with aiohttp.ClientSession(connector=connector) as session:
@@ -238,7 +230,7 @@ async def stream(
} }
] ]
}, },
is_public=is_public_request, is_empty=True,
) )
# Check if metadata is already cached # Check if metadata is already cached
@@ -335,15 +327,19 @@ async def stream(
if lock_acquired and scrape_lock: if lock_acquired and scrape_lock:
await scrape_lock.release() await scrape_lock.release()
logger.log("SCRAPER", f"❌ Failed to fetch metadata for {media_id}") logger.log("SCRAPER", f"❌ Failed to fetch metadata for {media_id}")
return { return _build_stream_response(
"streams": [ request,
{ {
"name": "[⚠️] Comet", "streams": [
"description": "Unable to get metadata.", {
"url": "https://comet.feels.legal", "name": "[⚠️] Comet",
} "description": "Unable to get metadata.",
] "url": "https://comet.feels.legal",
} }
]
},
is_empty=True,
)
title = metadata["title"] title = metadata["title"]
year = metadata["year"] year = metadata["year"]
@@ -449,15 +445,19 @@ async def stream(
) )
if len(torrent_manager.torrents) == 0: if len(torrent_manager.torrents) == 0:
return { return _build_stream_response(
"streams": [ request,
{ {
"name": "[🔄] Comet", "streams": [
"description": "Scraping in progress by another instance, please try again in a few seconds...", {
"url": "https://comet.feels.legal", "name": "[🔄] Comet",
} "description": "Scraping in progress by another instance, please try again in a few seconds...",
] "url": "https://comet.feels.legal",
} }
]
},
is_empty=True,
)
elif is_first or cache_is_stale: elif is_first or cache_is_stale:
# Background scrape if first search OR if cache is stale (needs refresh) # Background scrape if first search OR if cache is stale (needs refresh)
@@ -527,15 +527,19 @@ async def stream(
episode, episode,
) )
except DebridAuthError as e: except DebridAuthError as e:
return { return _build_stream_response(
"streams": [ request,
{ {
"name": "[❌] Comet", "streams": [
"description": e.display_message, {
"url": "https://comet.feels.legal", "name": "[❌] Comet",
} "description": e.display_message,
] "url": "https://comet.feels.legal",
} }
]
},
is_empty=True,
)
if debrid_service != "torrent": if debrid_service != "torrent":
cached_count = sum( cached_count = sum(
@@ -641,14 +645,14 @@ async def stream(
non_cached_results.append(the_stream) non_cached_results.append(the_stream)
if sort_mixed: if sort_mixed:
return _build_stream_response( final_streams = cached_results
request, else:
{"streams": cached_results}, final_streams = cached_results + non_cached_results
is_public=is_public_request,
) has_results = len(final_streams) > 0
return _build_stream_response( return _build_stream_response(
request, request,
{"streams": cached_results + non_cached_results}, {"streams": final_streams},
is_public=is_public_request, is_empty=not has_results,
) )
+1 -1
View File
@@ -436,7 +436,7 @@ def log_startup_info(settings):
logger.log("COMET", f"Custom Header HTML: {bool(settings.CUSTOM_HEADER_HTML)}") logger.log("COMET", f"Custom Header HTML: {bool(settings.CUSTOM_HEADER_HTML)}")
http_cache_info = ( http_cache_info = (
f" - Public Streams TTL: {settings.HTTP_CACHE_PUBLIC_STREAMS_TTL}s - Private Streams TTL: {settings.HTTP_CACHE_PRIVATE_STREAMS_TTL}s - Stale While Revalidate: {settings.HTTP_CACHE_STALE_WHILE_REVALIDATE}s" f" - Streams TTL: {settings.HTTP_CACHE_STREAMS_TTL}s - Manifest TTL: {settings.HTTP_CACHE_MANIFEST_TTL}s - Configure TTL: {settings.HTTP_CACHE_CONFIGURE_TTL}s - SWR: {settings.HTTP_CACHE_STALE_WHILE_REVALIDATE}s"
if settings.HTTP_CACHE_ENABLED if settings.HTTP_CACHE_ENABLED
else "" else ""
) )
+3 -2
View File
@@ -138,9 +138,10 @@ class AppSettings(BaseSettings):
RATELIMIT_RETRY_BASE_DELAY: Optional[float] = 1.0 RATELIMIT_RETRY_BASE_DELAY: Optional[float] = 1.0
RTN_FILTER_DEBUG: Optional[bool] = False RTN_FILTER_DEBUG: Optional[bool] = False
HTTP_CACHE_ENABLED: Optional[bool] = False HTTP_CACHE_ENABLED: Optional[bool] = False
HTTP_CACHE_PUBLIC_STREAMS_TTL: Optional[int] = 300 HTTP_CACHE_STREAMS_TTL: Optional[int] = 300
HTTP_CACHE_PRIVATE_STREAMS_TTL: Optional[int] = 60
HTTP_CACHE_STALE_WHILE_REVALIDATE: Optional[int] = 60 HTTP_CACHE_STALE_WHILE_REVALIDATE: Optional[int] = 60
HTTP_CACHE_MANIFEST_TTL: Optional[int] = 86400
HTTP_CACHE_CONFIGURE_TTL: Optional[int] = 86400
@field_validator("INDEXER_MANAGER_TYPE") @field_validator("INDEXER_MANAGER_TYPE")
def set_indexer_manager_type(cls, v, values): def set_indexer_manager_type(cls, v, values):
+28 -13
View File
@@ -17,13 +17,28 @@ def quick_alias_match(text_normalized: str, ez_aliases_normalized: list[str]):
return any(alias in text_normalized for alias in ez_aliases_normalized) return any(alias in text_normalized for alias in ez_aliases_normalized)
def filter_worker(torrents, title, year, year_end, aliases, remove_adult_content): def filter_worker(
torrents, title, year, year_end, media_type, aliases, remove_adult_content
):
results = [] results = []
ez_aliases = aliases.get("ez", []) ez_aliases = aliases.get("ez", [])
if ez_aliases: if ez_aliases:
ez_aliases_normalized = [normalize_title(a) for a in ez_aliases] ez_aliases_normalized = [normalize_title(a) for a in ez_aliases]
min_year = 0
max_year = float("inf")
if year:
if year_end:
min_year = year
max_year = year_end
elif media_type == "series":
min_year = year - 1
else:
min_year = year - 1
max_year = year + 1
for torrent in torrents: for torrent in torrents:
torrent_title = torrent["title"] torrent_title = torrent["title"]
torrent_title_lower = torrent_title.lower() torrent_title_lower = torrent_title.lower()
@@ -53,18 +68,18 @@ def filter_worker(torrents, title, year, year_end, aliases, remove_adult_content
continue continue
if year and parsed.year: if year and parsed.year:
if year_end is not None: if not (min_year <= parsed.year <= max_year):
if not (year <= parsed.year <= year_end): if year_end:
_log_exclusion( expected = f"{year}-{year_end}"
f"📅 Rejected (Year Mismatch) | {torrent_title} | Year: {parsed.year} | Expected: {year}-{year_end}" elif media_type == "series":
) expected = f">{year}"
continue else:
else: expected = f"~{year}"
if year < (parsed.year - 1) or year > (parsed.year + 1):
_log_exclusion( _log_exclusion(
f"📅 Rejected (Year Mismatch) | {torrent_title} | Year: {parsed.year} | Expected: ~{year}" f"📅 Rejected (Year Mismatch) | {torrent_title} | Year: {parsed.year} | Expected: {expected}"
) )
continue continue
torrent["parsed"] = parsed torrent["parsed"] = parsed
results.append(torrent) results.append(torrent)
+1
View File
@@ -225,6 +225,7 @@ class TorrentManager:
self.title, self.title,
self.year, self.year,
self.year_end, self.year_end,
self.media_type,
self.aliases, self.aliases,
self.remove_adult_content, self.remove_adult_content,
) )
+54 -31
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html class="sl-theme-dark"> <html class="sl-theme-dark">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -44,9 +44,19 @@
#25292c 0%, #25292c 0%,
#0c0d13 100% #0c0d13 100%
); );
font-family: system-ui, -apple-system, "Segoe UI", Roboto, font-family:
"Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, system-ui,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", -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"; "Noto Color Emoji";
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
@@ -308,7 +318,8 @@
border-radius: 50%; border-radius: 50%;
filter: drop-shadow(0 0 6px currentColor); filter: drop-shadow(0 0 6px currentColor);
transform: translate3d(104em, 0, 0); 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 tail-fade var(--tail-fade-duration) var(--fall-delay) ease-out
infinite; infinite;
} }
@@ -780,7 +791,7 @@
star.style.setProperty("--top-offset", `${randomTopOffset}vh`); star.style.setProperty("--top-offset", `${randomTopOffset}vh`);
star.style.setProperty( star.style.setProperty(
"--star-tail-length", "--star-tail-length",
`${randomTailLength}em` `${randomTailLength}em`,
); );
star.style.setProperty("--fall-duration", `${randomFallDuration}s`); star.style.setProperty("--fall-duration", `${randomFallDuration}s`);
star.style.setProperty("--fall-delay", "0s"); star.style.setProperty("--fall-delay", "0s");
@@ -956,9 +967,21 @@
<h3><sl-icon name="download"></sl-icon> Torrents Analysis</h3> <h3><sl-icon name="download"></sl-icon> Torrents Analysis</h3>
<div class="metric-content"> <div class="metric-content">
<div class="metric-subsection"> <div class="metric-subsection">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;"> <div
<h4 style="margin: 0;">Top Trackers</h4> style="
<sl-select id="tracker-limit" size="small" value="5" style="width: 110px;"> 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="5">Top 5</sl-option>
<sl-option value="10">Top 10</sl-option> <sl-option value="10">Top 10</sl-option>
<sl-option value="20">Top 20</sl-option> <sl-option value="20">Top 20</sl-option>
@@ -1133,7 +1156,7 @@
// Logs refresh based on selected interval // Logs refresh based on selected interval
const interval = parseInt( const interval = parseInt(
document.getElementById("refresh-interval").value document.getElementById("refresh-interval").value,
); );
logsInterval = setInterval(loadLogs, interval); logsInterval = setInterval(loadLogs, interval);
} }
@@ -1151,7 +1174,7 @@
if (document.getElementById("auto-refresh").checked) { if (document.getElementById("auto-refresh").checked) {
clearInterval(logsInterval); clearInterval(logsInterval);
const interval = parseInt( const interval = parseInt(
document.getElementById("refresh-interval").value document.getElementById("refresh-interval").value,
); );
logsInterval = setInterval(loadLogs, interval); logsInterval = setInterval(loadLogs, interval);
} }
@@ -1189,7 +1212,7 @@
connections.length > 0 connections.length > 0
? Math.round( ? Math.round(
connections.reduce((sum, c) => sum + c.duration, 0) / connections.reduce((sum, c) => sum + c.duration, 0) /
connections.length connections.length,
) )
: 0; : 0;
@@ -1239,7 +1262,7 @@
<td> <td>
<span class="connection-id">${conn.id.substring( <span class="connection-id">${conn.id.substring(
0, 0,
8 8,
)}...</span> )}...</span>
</td> </td>
<td> <td>
@@ -1247,14 +1270,14 @@
</td> </td>
<td> <td>
<span title="${escapeHtml( <span title="${escapeHtml(
conn.content conn.content,
)}">${ )}">${
conn.content.length > 25 conn.content.length > 25
? escapeHtml( ? escapeHtml(
conn.content.substring(0, 25) conn.content.substring(0, 25),
) + "..." ) + "..."
: escapeHtml(conn.content) : escapeHtml(conn.content)
}</span> }</span>
</td> </td>
<td> <td>
<span style="font-size: 0.875rem; color: #9ca3af;">${ <span style="font-size: 0.875rem; color: #9ca3af;">${
@@ -1263,7 +1286,7 @@
</td> </td>
<td> <td>
<span class="duration-tag">${Math.round( <span class="duration-tag">${Math.round(
conn.duration conn.duration,
)}s</span> )}s</span>
</td> </td>
<td> <td>
@@ -1284,7 +1307,7 @@
}</span> }</span>
</td> </td>
</tr> </tr>
` `,
) )
.join("")} .join("")}
</tbody> </tbody>
@@ -1297,7 +1320,7 @@
async function loadLogs() { async function loadLogs() {
try { try {
const response = await fetch( const response = await fetch(
`/admin/api/logs?since=${lastLogTimestamp}` `/admin/api/logs?since=${lastLogTimestamp}`,
); );
const data = await response.json(); const data = await response.json();
@@ -1341,7 +1364,7 @@
} ${log.level}</span> <span class="log-module">${log.module}.${ } ${log.level}</span> <span class="log-module">${log.module}.${
log.function log.function
}</span> - <span class="log-message">${escapeHtml( }</span> - <span class="log-message">${escapeHtml(
log.message log.message,
)}</span>`; )}</span>`;
container.appendChild(logEntry); container.appendChild(logEntry);
@@ -1388,7 +1411,7 @@
const hideApiLogs = document.getElementById("hide-api-logs").checked; const hideApiLogs = document.getElementById("hide-api-logs").checked;
const container = document.getElementById("logs-container"); const container = document.getElementById("logs-container");
const apiLogEntries = container.querySelectorAll( const apiLogEntries = container.querySelectorAll(
'[data-api-log="true"]' '[data-api-log="true"]',
); );
apiLogEntries.forEach((entry) => { apiLogEntries.forEach((entry) => {
@@ -1434,7 +1457,7 @@
const trackerContainer = document.getElementById("tracker-stats"); const trackerContainer = document.getElementById("tracker-stats");
const limitSelect = document.getElementById("tracker-limit"); const limitSelect = document.getElementById("tracker-limit");
const limit = limitSelect ? limitSelect.value : "5"; const limit = limitSelect ? limitSelect.value : "5";
let displayTrackers = torrentsData.by_tracker; let displayTrackers = torrentsData.by_tracker;
if (limit !== "all") { if (limit !== "all") {
displayTrackers = displayTrackers.slice(0, parseInt(limit)); displayTrackers = displayTrackers.slice(0, parseInt(limit));
@@ -1449,7 +1472,7 @@
(tracker) => ` (tracker) => `
<div class="tracker-item"> <div class="tracker-item">
<div class="tracker-name">${escapeHtml( <div class="tracker-name">${escapeHtml(
tracker.tracker || "Unknown" tracker.tracker || "Unknown",
)}</div> )}</div>
<div class="tracker-stats"> <div class="tracker-stats">
<span class="tracker-count">${tracker.count.toLocaleString()} torrents</span> <span class="tracker-count">${tracker.count.toLocaleString()} torrents</span>
@@ -1461,7 +1484,7 @@
} avg size</span> } avg size</span>
</div> </div>
</div> </div>
` `,
) )
.join(""); .join("");
} }
@@ -1552,7 +1575,7 @@
function showMetricsError() { function showMetricsError() {
const sections = document.querySelectorAll( const sections = document.querySelectorAll(
".metric-section .loading-state" ".metric-section .loading-state",
); );
sections.forEach((section) => { sections.forEach((section) => {
section.innerHTML = section.innerHTML =
@@ -1576,7 +1599,7 @@
(service) => ` (service) => `
<div class="debrid-service-item"> <div class="debrid-service-item">
<div class="debrid-service-name">${escapeHtml( <div class="debrid-service-name">${escapeHtml(
service.service service.service,
)}</div> )}</div>
<div class="debrid-service-stats"> <div class="debrid-service-stats">
<span class="service-count">${service.count.toLocaleString()} files</span> <span class="service-count">${service.count.toLocaleString()} files</span>
@@ -1588,7 +1611,7 @@
}</span> }</span>
</div> </div>
</div> </div>
` `,
) )
.join(""); .join("");
} }
+17 -6
View File
@@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html class="sl-theme-dark"> <html class="sl-theme-dark">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -44,9 +44,19 @@
#25292c 0%, #25292c 0%,
#0c0d13 100% #0c0d13 100%
); );
font-family: system-ui, -apple-system, "Segoe UI", Roboto, font-family:
"Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, system-ui,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", -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"; "Noto Color Emoji";
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
@@ -125,7 +135,8 @@
border-radius: 50%; border-radius: 50%;
filter: drop-shadow(0 0 6px currentColor); filter: drop-shadow(0 0 6px currentColor);
transform: translate3d(104em, 0, 0); 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 tail-fade var(--tail-fade-duration) var(--fall-delay) ease-out
infinite; infinite;
} }
@@ -235,7 +246,7 @@
star.style.setProperty("--top-offset", `${randomTopOffset}vh`); star.style.setProperty("--top-offset", `${randomTopOffset}vh`);
star.style.setProperty( star.style.setProperty(
"--star-tail-length", "--star-tail-length",
`${randomTailLength}em` `${randomTailLength}em`,
); );
star.style.setProperty("--fall-duration", `${randomFallDuration}s`); star.style.setProperty("--fall-duration", `${randomFallDuration}s`);
star.style.setProperty("--fall-delay", "0s"); star.style.setProperty("--fall-delay", "0s");
+1071 -808
View File
File diff suppressed because it is too large Load Diff
+25 -20
View File
@@ -154,13 +154,13 @@ def not_modified_response(etag: str):
class CachePolicies: class CachePolicies:
@staticmethod @staticmethod
def public_torrents(): def streams():
""" """
For public torrent lists (without user config). For all stream results.
Cache for a short time at CDN, revalidate often. Cache for a short time at CDN, revalidate often.
""" """
ttl = settings.HTTP_CACHE_PUBLIC_STREAMS_TTL ttl = settings.HTTP_CACHE_STREAMS_TTL
swr = settings.HTTP_CACHE_STALE_WHILE_REVALIDATE swr = settings.HTTP_CACHE_STALE_WHILE_REVALIDATE
return ( return (
@@ -172,36 +172,41 @@ class CachePolicies:
.stale_if_error(300) .stale_if_error(300)
) )
@staticmethod
def private_streams():
"""
For user-specific stream results (with b64config).
Private cache only, short TTL.
"""
ttl = settings.HTTP_CACHE_PRIVATE_STREAMS_TTL
return CacheControl().private().max_age(ttl).must_revalidate()
@staticmethod @staticmethod
def manifest(): def manifest():
""" """
For manifest.json responses. For manifest.json responses.
Very short cache as it can change based on config. Long cache as manifest rarely changes.
""" """
return CacheControl().private().max_age(60).must_revalidate() ttl = settings.HTTP_CACHE_MANIFEST_TTL
swr = settings.HTTP_CACHE_STALE_WHILE_REVALIDATE
return CacheControl().public().max_age(ttl).stale_while_revalidate(swr)
@staticmethod @staticmethod
def configure_page(): def configure_page():
""" """
For the /configure page. For the /configure page.
Cacheable if no custom HTML, otherwise private. Long cache as the page is mostly static.
""" """
ttl = settings.HTTP_CACHE_CONFIGURE_TTL
swr = settings.HTTP_CACHE_STALE_WHILE_REVALIDATE
if settings.CUSTOM_HEADER_HTML: return CacheControl().public().max_age(ttl).stale_while_revalidate(swr)
return CacheControl().private().max_age(300)
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 @staticmethod
def no_cache(): def no_cache():
+14 -3
View File
@@ -3,7 +3,7 @@
To optimize performance, configure Cloudflare to offload traffic from your server. We use **Cache Rules** to cache responses while respecting the application's cache headers. To optimize performance, configure Cloudflare to offload traffic from your server. We use **Cache Rules** to cache responses while respecting the application's cache headers.
## 1. Streams (Cache Rule) ## 1. Streams (Cache Rule)
Cache all stream results (public and private). Comet controls the TTL via headers. Cache all stream results. Comet controls the TTL via headers.
* **Rule Name**: Streams * **Rule Name**: Streams
* **Expression**: `(http.request.uri.path contains "/stream/")` * **Expression**: `(http.request.uri.path contains "/stream/")`
@@ -20,12 +20,23 @@ Cache the configuration page.
* **Action**: Eligible for Cache * **Action**: Eligible for Cache
* **Edge TTL**: Use cache-control header if present (first option) * **Edge TTL**: Use cache-control header if present (first option)
* **Browser TTL**: Respect origin * **Browser TTL**: Respect origin
* **Serve stale content while revalidating**: On
## 3. Tiered Cache ## 3. Manifest (Cache Rule)
Cache the add-on manifest.
* **Rule Name**: Manifest
* **Expression**: `(http.request.uri.path contains "/manifest.json")`
* **Action**: Eligible for Cache
* **Edge TTL**: Use cache-control header if present (first option)
* **Browser TTL**: Respect origin
* **Serve stale content while revalidating**: On
## 4. Tiered Cache
Enable **Tiered Cache** in **Caching > Tiered Cache**. Enable **Tiered Cache** in **Caching > Tiered Cache**.
This minimizes requests to your origin by checking other Cloudflare datacenters first. This minimizes requests to your origin by checking other Cloudflare datacenters first.
## 4. Network Optimizations ## 5. Network Optimizations
In **Speed > Protocol**: In **Speed > Protocol**:
* **HTTP/3 (QUIC)**: On (faster connections, especially on mobile) * **HTTP/3 (QUIC)**: On (faster connections, especially on mobile)