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..fd33f1f 100644 --- a/comet/api/endpoints/stream.py +++ b/comet/api/endpoints/stream.py @@ -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,15 +345,20 @@ 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_public=True, + is_empty=True, + ) title = metadata["title"] year = metadata["year"] @@ -449,15 +464,20 @@ 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_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,15 +547,20 @@ 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_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, ) diff --git a/comet/core/config_validation.py b/comet/core/config_validation.py index dce9ca3..e2032a7 100644 --- a/comet/core/config_validation.py +++ b/comet/core/config_validation.py @@ -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 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 @@

Torrents Analysis

-
-

Top Trackers

- +
+

Top Trackers

+ Top 5 Top 10 Top 20 @@ -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 @@ ${conn.id.substring( 0, - 8 + 8, )}... @@ -1247,14 +1270,14 @@ ${ - conn.content.length > 25 - ? escapeHtml( - conn.content.substring(0, 25) - ) + "..." - : escapeHtml(conn.content) - } + conn.content.length > 25 + ? escapeHtml( + conn.content.substring(0, 25), + ) + "..." + : escapeHtml(conn.content) + } ${ @@ -1263,7 +1286,7 @@ ${Math.round( - conn.duration + conn.duration, )}s @@ -1284,7 +1307,7 @@ } - ` + `, ) .join("")} @@ -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} ${log.module}.${ log.function } - ${escapeHtml( - log.message + log.message, )}`; 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) => { @@ -1434,7 +1457,7 @@ const trackerContainer = document.getElementById("tracker-stats"); const limitSelect = document.getElementById("tracker-limit"); const limit = limitSelect ? limitSelect.value : "5"; - + let displayTrackers = torrentsData.by_tracker; if (limit !== "all") { displayTrackers = displayTrackers.slice(0, parseInt(limit)); @@ -1449,7 +1472,7 @@ (tracker) => `
${escapeHtml( - tracker.tracker || "Unknown" + tracker.tracker || "Unknown", )}
${tracker.count.toLocaleString()} torrents @@ -1461,7 +1484,7 @@ } avg size
- ` + `, ) .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) => `
${escapeHtml( - service.service + service.service, )}
${service.count.toLocaleString()} files @@ -1588,7 +1611,7 @@ }
- ` + `, ) .join(""); } diff --git a/comet/templates/admin_login.html b/comet/templates/admin_login.html index eeb12a2..f0c73b8 100644 --- a/comet/templates/admin_login.html +++ b/comet/templates/admin_login.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; @@ -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"); diff --git a/comet/templates/index.html b/comet/templates/index.html index d5c04ae..7d33ed4 100644 --- a/comet/templates/index.html +++ b/comet/templates/index.html @@ -1,369 +1,426 @@ - + + + + + + + + + - - - - - - - - + Comet - Stremio's fastest torrent/debrid search add-on. + - Comet - Stremio's fastest torrent/debrid search add-on. - + + - - - - - - - -
- - -
-
-

- - Comet -

- - - Discord - -
- {{CUSTOM_HEADER_HTML|safe}} -
+ } -
- -
- -
- -
- -
- -
- - 4K - 2160p - Full HD - 1080p - HD - 720p - SD - 480p - LD - 360p - Unknown - -
- -
- -
- -
- -
- -
- -
- - {% set default_debrid_service = 'realdebrid' if disableTorrentStreams else 'torrent' %} -
- - {% if not disableTorrentStreams %} - Torrent - {% endif %} - TorBox - Real-Debrid - All-Debrid - Debrid-Link - Premiumize - Debrider - EasyDebrid - Offcloud - PikPak - -
- -
- - -
- -
- - Show Cached - Only - Sort Cached and Uncached Together - Allow - English in Languages - Remove Unknown Languages - Remove - Trash - + +
+ +
+ +
+ +
+ + 4K - 2160p + Full HD - 1080p + HD - 720p + SD - 480p + LD - 360p + Unknown
-
- - -
- Install - - - Attempting to add the addon to Stremio... - - - Copy Link - - - The Stremio addon link has been automatically copied. - - - -
-
-
- -
+
+ Install + + + Attempting to add the addon to Stremio... + - -
-
❤️
-

Donate to Me

-

- Comet is a passion project fueled by late nights and coffee. Your support helps keep the lights on and the - updates rolling! -

+ Copy Link + + + The Stremio addon link has been automatically copied. + -
- - - GitHub Sponsors - - - - Ko-fi - +
-
- - +
+ +
- \ No newline at end of file + +
+
+ ❤️ +
+

Donate to Me

+

+ Comet is a passion project fueled by late nights and coffee. Your + support helps keep the lights on and the updates rolling! +

+ +
+ + + GitHub Sponsors + + + + Ko-fi + +
+
+
+ + + + diff --git a/comet/utils/cache.py b/comet/utils/cache.py index 2db9160..e3a03b2 100644 --- a/comet/utils/cache.py +++ b/comet/utils/cache.py @@ -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(): """