mirror of
https://github.com/g0ldyy/comet.git
synced 2026-01-12 01:16:12 +01:00
Merge pull request #473 from g0ldyy/development
Consolidate HTTP cache settings and restructure web UI
This commit is contained in:
+12
-9
@@ -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,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"},
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 ""
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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) => {
|
||||||
@@ -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("");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
+1070
-807
File diff suppressed because it is too large
Load Diff
+25
-20
@@ -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():
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user