diff --git a/.env-sample b/.env-sample index 83aa61f..862dba4 100644 --- a/.env-sample +++ b/.env-sample @@ -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 -# PUBLIC streams TTL (for /stream/movie/tt1234567.json without user config) -# These can be cached at edge and shared between all users. -# Higher = less origin traffic, lower = fresher results -HTTP_CACHE_PUBLIC_STREAMS_TTL=300 # 5 minutes (recommended: 120-600) - -# 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) +# STREAMS streams TTL (for /stream/movie/tt1234567.json) +# These results are cached at edge and shared between all users. +# Higher = less traffic to origin, Lower = faster updates for new content +HTTP_CACHE_STREAMS_TTL=300 # 5 minutes # Stale-While-Revalidate: serve stale content while fetching fresh in background # Improves perceived performance - users get instant response with cached data 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) diff --git a/comet/api/endpoints/chilllink.py b/comet/api/endpoints/chilllink.py index bd0c852..b6b75a3 100644 --- a/comet/api/endpoints/chilllink.py +++ b/comet/api/endpoints/chilllink.py @@ -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"}, diff --git a/comet/api/endpoints/manifest.py b/comet/api/endpoints/manifest.py index 5a0e3b6..e53a8a0 100644 --- a/comet/api/endpoints/manifest.py +++ b/comet/api/endpoints/manifest.py @@ -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) diff --git a/comet/api/endpoints/stream.py b/comet/api/endpoints/stream.py index eaef725..a53d328 100644 --- a/comet/api/endpoints/stream.py +++ b/comet/api/endpoints/stream.py @@ -30,7 +30,7 @@ streams = APIRouter() 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,12 +41,12 @@ def _build_stream_response( if check_etag_match(request, etag): return not_modified_response(etag) - if is_public: - cache_policy = CachePolicies.public_torrents() + if is_empty: + cache_policy = CachePolicies.empty_results() vary = ["Accept", "Accept-Encoding"] else: - cache_policy = CachePolicies.private_streams() - vary = ["Accept", "Accept-Encoding", "Authorization"] + cache_policy = CachePolicies.streams() + vary = ["Accept", "Accept-Encoding"] if vary_headers: vary.extend(vary_headers) @@ -173,17 +173,11 @@ async def stream( b64config: str = None, chilllink: bool = False, ): - is_public_request = b64config is None - if media_type not in ["movie", "series"]: - return _build_stream_response( - request, {"streams": []}, is_public=is_public_request - ) + return _build_stream_response(request, {"streams": []}, is_empty=True) if "tmdb:" in media_id: - return _build_stream_response( - request, {"streams": []}, is_public=is_public_request - ) + return _build_stream_response(request, {"streams": []}, is_empty=True) 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" if settings.DISABLE_TORRENT_STREAMS and is_torrent: @@ -209,9 +203,7 @@ async def stream( if settings.TORRENT_DISABLED_STREAM_URL: placeholder_stream["url"] = settings.TORRENT_DISABLED_STREAM_URL - return _build_stream_response( - request, {"streams": [placeholder_stream]}, is_public=is_public_request - ) + return _build_stream_response(request, {"streams": [placeholder_stream]}) connector = aiohttp.TCPConnector(limit=0) 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 @@ -335,15 +327,19 @@ 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 { - "streams": [ - { - "name": "[⚠️] Comet", - "description": "Unable to get metadata.", - "url": "https://comet.feels.legal", - } - ] - } + return _build_stream_response( + request, + { + "streams": [ + { + "name": "[⚠️] Comet", + "description": "Unable to get metadata.", + "url": "https://comet.feels.legal", + } + ] + }, + is_empty=True, + ) title = metadata["title"] year = metadata["year"] @@ -449,15 +445,19 @@ async def stream( ) if len(torrent_manager.torrents) == 0: - return { - "streams": [ - { - "name": "[🔄] Comet", - "description": "Scraping in progress by another instance, please try again in a few seconds...", - "url": "https://comet.feels.legal", - } - ] - } + return _build_stream_response( + request, + { + "streams": [ + { + "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: # Background scrape if first search OR if cache is stale (needs refresh) @@ -527,15 +527,19 @@ async def stream( episode, ) except DebridAuthError as e: - return { - "streams": [ - { - "name": "[❌] Comet", - "description": e.display_message, - "url": "https://comet.feels.legal", - } - ] - } + return _build_stream_response( + request, + { + "streams": [ + { + "name": "[❌] Comet", + "description": e.display_message, + "url": "https://comet.feels.legal", + } + ] + }, + is_empty=True, + ) if debrid_service != "torrent": cached_count = sum( @@ -641,14 +645,14 @@ 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_empty=not has_results, ) diff --git a/comet/core/logger.py b/comet/core/logger.py index 30be3d4..60f94c8 100644 --- a/comet/core/logger.py +++ b/comet/core/logger.py @@ -436,7 +436,7 @@ def log_startup_info(settings): logger.log("COMET", f"Custom Header HTML: {bool(settings.CUSTOM_HEADER_HTML)}") 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 else "" ) diff --git a/comet/core/models.py b/comet/core/models.py index 25abd80..a073fa6 100644 --- a/comet/core/models.py +++ b/comet/core/models.py @@ -138,9 +138,10 @@ class AppSettings(BaseSettings): RATELIMIT_RETRY_BASE_DELAY: Optional[float] = 1.0 RTN_FILTER_DEBUG: Optional[bool] = False HTTP_CACHE_ENABLED: Optional[bool] = False - HTTP_CACHE_PUBLIC_STREAMS_TTL: Optional[int] = 300 - HTTP_CACHE_PRIVATE_STREAMS_TTL: Optional[int] = 60 + HTTP_CACHE_STREAMS_TTL: Optional[int] = 300 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") def set_indexer_manager_type(cls, v, values): diff --git a/comet/services/filtering.py b/comet/services/filtering.py index 7aca56c..20b1deb 100644 --- a/comet/services/filtering.py +++ b/comet/services/filtering.py @@ -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) -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 = [] ez_aliases = aliases.get("ez", []) if 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: torrent_title = torrent["title"] torrent_title_lower = torrent_title.lower() @@ -53,18 +68,18 @@ def filter_worker(torrents, title, year, year_end, aliases, remove_adult_content continue if year and parsed.year: - if year_end is not None: - if not (year <= parsed.year <= year_end): - _log_exclusion( - f"📅 Rejected (Year Mismatch) | {torrent_title} | Year: {parsed.year} | Expected: {year}-{year_end}" - ) - continue - else: - if year < (parsed.year - 1) or year > (parsed.year + 1): - _log_exclusion( - f"📅 Rejected (Year Mismatch) | {torrent_title} | Year: {parsed.year} | Expected: ~{year}" - ) - continue + if not (min_year <= parsed.year <= max_year): + if year_end: + expected = f"{year}-{year_end}" + elif media_type == "series": + expected = f">{year}" + else: + expected = f"~{year}" + + _log_exclusion( + f"📅 Rejected (Year Mismatch) | {torrent_title} | Year: {parsed.year} | Expected: {expected}" + ) + continue torrent["parsed"] = parsed results.append(torrent) diff --git a/comet/services/orchestration.py b/comet/services/orchestration.py index 6c4bfe2..0e3452e 100644 --- a/comet/services/orchestration.py +++ b/comet/services/orchestration.py @@ -225,6 +225,7 @@ class TorrentManager: self.title, self.year, self.year_end, + self.media_type, self.aliases, self.remove_adult_content, ) diff --git a/comet/templates/admin_dashboard.html b/comet/templates/admin_dashboard.html index 6868eae..3299924 100644 --- a/comet/templates/admin_dashboard.html +++ b/comet/templates/admin_dashboard.html @@ -1,4 +1,4 @@ - +
@@ -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 @@
-
- Comet
-
- Comet is a passion project fueled by late nights and coffee. Your support helps keep the lights on and the - updates rolling! -
++ Comet is a passion project fueled by late nights and coffee. Your + support helps keep the lights on and the updates rolling! +
+ +