feat: add ChillLink API endpoints and refactor stream description formatting logic

This commit is contained in:
g0ldyy
2025-12-27 19:18:16 +01:00
parent 783b7b8ed4
commit ec2aa2bb17
6 changed files with 217 additions and 78 deletions
+9 -3
View File
@@ -9,7 +9,8 @@ from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from comet.api.endpoints import admin, base, config, manifest, playback
from comet.api.endpoints import (admin, base, chilllink, config, manifest,
playback)
from comet.api.endpoints import stream as streams_router
from comet.background_scraper.worker import background_scraper
from comet.core.database import (cleanup_expired_locks,
@@ -118,8 +119,12 @@ tags_metadata = [
"description": "Endpoints for configuring Comet.",
},
{
"name": "Stremio Add-on",
"description": "Standard Stremio add-on endpoints.",
"name": "Stremio",
"description": "Standard Stremio endpoints.",
},
{
"name": "ChillLink",
"description": "Chillio specific endpoints.",
},
{
"name": "Admin",
@@ -153,3 +158,4 @@ app.include_router(manifest.router)
app.include_router(admin.router)
app.include_router(playback.router)
app.include_router(streams_router.streams)
app.include_router(chilllink.router)
+108
View File
@@ -0,0 +1,108 @@
import random
import string
from typing import Optional
from fastapi import APIRouter, BackgroundTasks, Query, Request
from comet.api.endpoints.stream import stream as get_streams
from comet.core.config_validation import config_check
from comet.core.models import settings
from comet.debrid.manager import get_debrid_extension
router = APIRouter()
@router.get(
"/manifest",
tags=["ChillLink"],
summary="Add-on Manifest",
description="Returns the add-on manifest.",
)
@router.get(
"/{b64config}/manifest",
tags=["ChillLink"],
summary="Add-on Manifest",
description="Returns the add-on manifest with existing configuration.",
)
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))}",
"version": "2.0.0",
"description": "Chillio's fastest debrid search add-on.",
"supported_endpoints": {"feeds": None, "streams": "/streams"},
}
debrid_extension = get_debrid_extension(config["debridService"])
manifest["name"] = (
f"{settings.ADDON_NAME}{(' | ' + debrid_extension) if debrid_extension != 'TORRENT' else ''}"
)
return manifest
@router.get(
"/streams",
tags=["ChillLink"],
summary="Stream Provider",
description="Returns a list of streams for the specified media.",
)
@router.get(
"/{b64config}/streams",
tags=["ChillLink"],
summary="Stream Provider",
description="Returns a list of streams for the specified media with existing configuration.",
)
async def chilllink_streams(
request: Request,
background_tasks: BackgroundTasks,
imdbID: str = Query(...),
type: str = Query(...),
season: Optional[int] = Query(None),
episode: Optional[int] = Query(None),
b64config: Optional[str] = None,
):
config = config_check(b64config)
if config["debridService"] == "torrent":
return {
"sources": [
{
"id": "comet.fast",
"title": "You need to configure a debrid service to use Comet in Chillio.",
"url": "https://comet.fast",
"metadata": [],
}
]
}
if type == "movie":
media_id = imdbID
elif type == "series":
media_id = f"{imdbID}:{season}:{episode}"
else:
return {"sources": []}
stremio_response = await get_streams(
request=request,
media_type=type,
media_id=media_id,
background_tasks=background_tasks,
b64config=b64config,
chilllink=True,
)
stremio_streams = stremio_response.get("streams", [])
sources = []
for stream in stremio_streams:
sources.append(
{
"id": stream["behaviorHints"]["bingeGroup"],
"title": stream["behaviorHints"]["filename"],
"url": stream["url"],
"metadata": stream["_chilllink"],
}
)
return {"sources": sources}
+2 -2
View File
@@ -12,13 +12,13 @@ router = APIRouter()
@router.get(
"/manifest.json",
tags=["Stremio Add-on"],
tags=["Stremio"],
summary="Add-on Manifest",
description="Returns the add-on manifest.",
)
@router.get(
"/{b64config}/manifest.json",
tags=["Stremio Add-on"],
tags=["Stremio"],
summary="Add-on Manifest",
description="Returns the add-on manifest with existing configuration.",
)
+1 -1
View File
@@ -18,7 +18,7 @@ router = APIRouter()
@router.get(
"/{b64config}/playback/{hash}/{index}/{season}/{episode}/{torrent_name:path}",
tags=["Stremio Add-on"],
tags=["Stremio"],
summary="Playback Proxy",
description="Proxies the playback request to the Debrid service or returns a cached link.",
)
+20 -11
View File
@@ -13,7 +13,8 @@ from comet.metadata.manager import MetadataScraper
from comet.services.debrid import DebridService
from comet.services.lock import DistributedLock, is_scrape_in_progress
from comet.services.orchestration import TorrentManager
from comet.utils.formatting import format_title
from comet.utils.formatting import (format_chilllink, format_title,
get_formatted_components)
from comet.utils.network import get_client_ip
from comet.utils.parsing import parse_media_id
@@ -116,13 +117,13 @@ async def wait_for_scrape_completion(media_id: str, context: str = ""):
@streams.get(
"/stream/{media_type}/{media_id}.json",
tags=["Stremio Add-on"],
tags=["Stremio"],
summary="Stream Provider",
description="Returns a list of streams for the specified media.",
)
@streams.get(
"/{b64config}/stream/{media_type}/{media_id}.json",
tags=["Stremio Add-on"],
tags=["Stremio"],
summary="Stream Provider",
description="Returns a list of streams for the specified media with existing configuration.",
)
@@ -132,6 +133,7 @@ async def stream(
media_id: str,
background_tasks: BackgroundTasks,
b64config: str = None,
chilllink: bool = False,
):
if "tmdb:" in media_id:
return {"streams": []}
@@ -453,16 +455,18 @@ async def stream(
)
torrent_title = torrent["title"]
formatted_components = get_formatted_components(
rtn_data,
torrent_title,
torrent["seeders"],
torrent["size"],
torrent["tracker"],
config["resultFormat"],
)
the_stream = {
"name": f"[{debrid_extension}{debrid_emoji}] Comet {rtn_data.resolution}",
"description": format_title(
rtn_data,
torrent_title,
torrent["seeders"],
torrent["size"],
torrent["tracker"],
config["resultFormat"],
),
"description": format_title(formatted_components),
"behaviorHints": {
"bingeGroup": "comet|" + info_hash,
"videoSize": torrent["size"],
@@ -470,6 +474,11 @@ async def stream(
},
}
if chilllink:
the_stream["_chilllink"] = format_chilllink(
formatted_components, torrent["cached"]
)
if debrid_service == "torrent":
the_stream["infoHash"] = info_hash
+77 -61
View File
@@ -180,7 +180,7 @@ def format_group_info(data: ParsedData):
return "".join(group_parts) if group_parts else ""
def format_title(
def get_formatted_components(
data: ParsedData,
ttitle: str,
seeders: int,
@@ -189,74 +189,90 @@ def format_title(
result_format: list,
):
has_all = "all" in result_format
components = {}
if has_all or "title" in result_format:
components["title"] = f"📄 {ttitle}"
if has_all or "video_info" in result_format:
info = format_video_info(data)
if info:
components["video"] = f"📹 {info}"
if has_all or "audio_info" in result_format:
info = format_audio_info(data)
if info:
components["audio"] = f"🔊 {info}"
if has_all or "quality_info" in result_format:
info = format_quality_info(data)
if info:
components["quality"] = f"{info}"
if has_all or "release_group" in result_format:
info = format_group_info(data)
if info:
components["group"] = f"🏷️ {info}"
if (has_all or "seeders" in result_format) and seeders is not None:
components["seeders"] = f"👤 {seeders}"
if has_all or "size" in result_format:
components["size"] = f"💾 {format_bytes(size)}"
if has_all or "tracker" in result_format:
components["tracker"] = f"🔎 {tracker}"
if (
(has_all or "languages" in result_format)
and hasattr(data, "languages")
and data.languages
):
formatted_languages = "/".join(
get_language_emoji(language) for language in data.languages
)
components["languages"] = formatted_languages
return components
def format_title(components: dict):
lines = []
show_title = has_all or "title" in result_format
if show_title:
lines.append(f"📄 {ttitle}")
if "title" in components:
lines.append(components["title"])
show_video = has_all or "video_info" in result_format
show_audio = has_all or "audio_info" in result_format
show_quality = has_all or "quality_info" in result_format
show_group = has_all or "release_group" in result_format
video_audio = [components[k] for k in ["video", "audio"] if k in components]
if video_audio:
lines.append(" | ".join(video_audio))
video_audio_parts = []
quality_group = [components[k] for k in ["quality", "group"] if k in components]
if quality_group:
lines.append(" | ".join(quality_group))
if show_video:
video_info = format_video_info(data)
if video_info:
video_audio_parts.append(f"📹 {video_info}")
info = [components[k] for k in ["seeders", "size", "tracker"] if k in components]
if info:
lines.append(" ".join(info))
if show_audio:
audio_info = format_audio_info(data)
if audio_info:
video_audio_parts.append(f"🔊 {audio_info}")
if video_audio_parts:
lines.append(" | ".join(video_audio_parts))
quality_parts = []
if show_quality:
quality_info = format_quality_info(data)
if quality_info:
quality_parts.append(f"{quality_info}")
if show_group:
groups = format_group_info(data)
if groups:
quality_parts.append(f"🏷️ {groups}")
if quality_parts:
lines.append(" | ".join(quality_parts))
show_seeders = has_all or "seeders" in result_format
show_size = has_all or "size" in result_format
show_tracker = has_all or "tracker" in result_format
info_parts = []
if show_seeders and seeders is not None:
info_parts.append(f"👤 {seeders}")
if show_size:
info_parts.append(f"💾 {format_bytes(size)}")
if show_tracker:
info_parts.append(f"🔎 {tracker}")
if info_parts:
lines.append(" ".join(info_parts))
show_languages = has_all or "languages" in result_format
if show_languages:
if hasattr(data, "languages") and data.languages:
formatted_languages = "/".join(
get_language_emoji(language) for language in data.languages
)
lines.append(f"{formatted_languages}")
if "languages" in components:
lines.append(components["languages"])
if not lines:
return "Empty result format configuration"
return "\n".join(lines)
def format_chilllink(components: dict, cached: bool):
metadata = []
if cached:
metadata.append("⚡ Instant")
else:
metadata.append("⬇️ Not Cached")
for key, value in components.items():
if key != "title":
metadata.append(value)
return metadata