mirror of
https://github.com/g0ldyy/comet.git
synced 2026-01-12 01:16:12 +01:00
feat: enhance caching policies and improve manifest handling
This commit is contained in:
@@ -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"},
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,14 +1270,14 @@
|
||||
</td>
|
||||
<td>
|
||||
<span title="${escapeHtml(
|
||||
conn.content
|
||||
conn.content,
|
||||
)}">${
|
||||
conn.content.length > 25
|
||||
? escapeHtml(
|
||||
conn.content.substring(0, 25)
|
||||
) + "..."
|
||||
: escapeHtml(conn.content)
|
||||
}</span>
|
||||
conn.content.length > 25
|
||||
? escapeHtml(
|
||||
conn.content.substring(0, 25),
|
||||
) + "..."
|
||||
: escapeHtml(conn.content)
|
||||
}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span style="font-size: 0.875rem; color: #9ca3af;">${
|
||||
@@ -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("");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
+1070
-807
File diff suppressed because it is too large
Load Diff
@@ -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():
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user