mirror of
https://github.com/g0ldyy/comet.git
synced 2026-01-12 01:16:12 +01:00
feat(debrid): add support for stremthru
This commit is contained in:
@@ -27,4 +27,5 @@ PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE=realdebrid # if you want your users w
|
||||
PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY=CHANGE_ME # if you want your users who use the Debrid Stream Proxy not to have to specify Debrid information, but to use the default one instead
|
||||
TITLE_MATCH_CHECK=True # disable if you only use Torrentio / MediaFusion and are sure you're only scraping good titles, for example (keep it True if Zilean is enabled)
|
||||
REMOVE_ADULT_CONTENT=False # detect and remove adult content
|
||||
STREMTHRU_DEFAULT_URL=None # if you want your users to use StremThru without having to specify it
|
||||
CUSTOM_HEADER_HTML=None # only set it if you know what it is
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
- Direct Torrent supported (do not specify a Debrid API Key on the configuration page (webui) to activate it - it will use the cached results of other users using debrid service)
|
||||
- [Kitsu](https://kitsu.io/) support (anime)
|
||||
- Adult Content Filter
|
||||
- [StremThru](https://github.com/MunifTanjim/stremthru) support
|
||||
|
||||
# Installation
|
||||
To customize your Comet experience to suit your needs, please first take a look at all the [environment variables](https://github.com/g0ldyy/comet/blob/main/.env-sample)!
|
||||
|
||||
@@ -47,6 +47,7 @@ async def configure(request: Request):
|
||||
"webConfig": web_config,
|
||||
"indexerManager": settings.INDEXER_MANAGER_TYPE,
|
||||
"proxyDebridStream": settings.PROXY_DEBRID_STREAM,
|
||||
"stremthruDefaultUrl": settings.STREMTHRU_DEFAULT_URL or "",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
+15
-32
@@ -32,6 +32,7 @@ from comet.utils.general import (
|
||||
get_aliases,
|
||||
add_torrent_to_cache,
|
||||
)
|
||||
from comet.utils.config import is_proxy_stream_authed, is_proxy_stream_enabled, prepare_debrid_config, should_skip_proxy_stream
|
||||
from comet.utils.logger import logger
|
||||
from comet.utils.models import database, rtn, settings, trackers
|
||||
|
||||
@@ -135,19 +136,10 @@ async def stream(
|
||||
if type == "series":
|
||||
log_name = f"{name} S{season:02d}E{episode:02d}"
|
||||
|
||||
if (
|
||||
settings.PROXY_DEBRID_STREAM
|
||||
and settings.PROXY_DEBRID_STREAM_PASSWORD
|
||||
== config["debridStreamProxyPassword"]
|
||||
and config["debridApiKey"] == ""
|
||||
):
|
||||
config["debridService"] = (
|
||||
settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE
|
||||
)
|
||||
config["debridApiKey"] = settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY
|
||||
prepare_debrid_config(config)
|
||||
|
||||
if config["debridApiKey"] == "":
|
||||
services = ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink"]
|
||||
services = ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink", "stremthru"]
|
||||
debrid_emoji = "⬇️"
|
||||
else:
|
||||
services = [config["debridService"]]
|
||||
@@ -155,10 +147,8 @@ async def stream(
|
||||
|
||||
results = []
|
||||
if (
|
||||
config["debridStreamProxyPassword"] != ""
|
||||
and settings.PROXY_DEBRID_STREAM
|
||||
and settings.PROXY_DEBRID_STREAM_PASSWORD
|
||||
!= config["debridStreamProxyPassword"]
|
||||
is_proxy_stream_enabled(config)
|
||||
and not is_proxy_stream_authed(config)
|
||||
):
|
||||
results.append(
|
||||
{
|
||||
@@ -480,10 +470,8 @@ async def stream(
|
||||
|
||||
results = []
|
||||
if (
|
||||
config["debridStreamProxyPassword"] != ""
|
||||
and settings.PROXY_DEBRID_STREAM
|
||||
and settings.PROXY_DEBRID_STREAM_PASSWORD
|
||||
!= config["debridStreamProxyPassword"]
|
||||
is_proxy_stream_enabled(config)
|
||||
and not is_proxy_stream_authed(config)
|
||||
):
|
||||
results.append(
|
||||
{
|
||||
@@ -545,13 +533,7 @@ async def playback(request: Request, b64config: str, hash: str, index: str):
|
||||
if not config:
|
||||
return FileResponse("comet/assets/invalidconfig.mp4")
|
||||
|
||||
if (
|
||||
settings.PROXY_DEBRID_STREAM
|
||||
and settings.PROXY_DEBRID_STREAM_PASSWORD == config["debridStreamProxyPassword"]
|
||||
and config["debridApiKey"] == ""
|
||||
):
|
||||
config["debridService"] = settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE
|
||||
config["debridApiKey"] = settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY
|
||||
prepare_debrid_config(config)
|
||||
|
||||
async with aiohttp.ClientSession(raise_for_status=True) as session:
|
||||
# Check for cached download link
|
||||
@@ -581,9 +563,8 @@ async def playback(request: Request, b64config: str, hash: str, index: str):
|
||||
config,
|
||||
ip
|
||||
if (
|
||||
not settings.PROXY_DEBRID_STREAM
|
||||
or settings.PROXY_DEBRID_STREAM_PASSWORD
|
||||
!= config["debridStreamProxyPassword"]
|
||||
not is_proxy_stream_enabled(config)
|
||||
or not is_proxy_stream_authed(config)
|
||||
)
|
||||
else "",
|
||||
)
|
||||
@@ -603,10 +584,12 @@ async def playback(request: Request, b64config: str, hash: str, index: str):
|
||||
},
|
||||
)
|
||||
|
||||
if should_skip_proxy_stream(config):
|
||||
return RedirectResponse(download_link, status_code=302)
|
||||
|
||||
if (
|
||||
settings.PROXY_DEBRID_STREAM
|
||||
and settings.PROXY_DEBRID_STREAM_PASSWORD
|
||||
== config["debridStreamProxyPassword"]
|
||||
is_proxy_stream_enabled(config)
|
||||
and is_proxy_stream_authed(config)
|
||||
):
|
||||
if settings.PROXY_DEBRID_STREAM_MAX_CONNECTIONS != -1:
|
||||
active_ip_connections = await database.fetch_all(
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
import aiohttp
|
||||
|
||||
from comet.utils.config import should_use_stremthru
|
||||
|
||||
from .realdebrid import RealDebrid
|
||||
from .alldebrid import AllDebrid
|
||||
from .premiumize import Premiumize
|
||||
from .torbox import TorBox
|
||||
from .debridlink import DebridLink
|
||||
from .stremthru import StremThru
|
||||
|
||||
|
||||
def getDebrid(session: aiohttp.ClientSession, config: dict, ip: str):
|
||||
debrid_service = config["debridService"]
|
||||
debrid_api_key = config["debridApiKey"]
|
||||
|
||||
if should_use_stremthru(config):
|
||||
return StremThru(
|
||||
session=session,
|
||||
url=config["stremthruUrl"],
|
||||
debrid_service=debrid_service,
|
||||
token=debrid_api_key,
|
||||
)
|
||||
|
||||
if debrid_service == "realdebrid":
|
||||
return RealDebrid(session, debrid_api_key, ip)
|
||||
elif debrid_service == "alldebrid":
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
|
||||
import aiohttp
|
||||
from RTN import parse
|
||||
|
||||
from comet.utils.general import is_video
|
||||
from comet.utils.logger import logger
|
||||
|
||||
|
||||
class StremThru:
|
||||
def __init__(
|
||||
self,
|
||||
session: aiohttp.ClientSession,
|
||||
url: str,
|
||||
token: str,
|
||||
debrid_service: str,
|
||||
):
|
||||
if not self.is_supported_store(debrid_service):
|
||||
raise ValueError(f"unsupported store: {debrid_service}")
|
||||
|
||||
if debrid_service == "stremthru":
|
||||
session.headers["Proxy-Authorization"] = f"Basic {token}"
|
||||
else:
|
||||
session.headers["X-StremThru-Store-Name"] = debrid_service
|
||||
session.headers["X-StremThru-Store-Authorization"] = f"Bearer {token}"
|
||||
|
||||
session.headers["User-Agent"] = "comet"
|
||||
|
||||
self.session = session
|
||||
self.base_url = f"{url}/v0/store"
|
||||
self.name = f"StremThru[{debrid_service}]" if debrid_service else "StremThru"
|
||||
|
||||
@staticmethod
|
||||
def is_supported_store(name: Optional[str]):
|
||||
return (
|
||||
name == "stremthru"
|
||||
or name == "alldebrid"
|
||||
or name == "debridlink"
|
||||
or name == "premiumize"
|
||||
or name == "realdebrid"
|
||||
or name == "torbox"
|
||||
)
|
||||
|
||||
async def check_premium(self):
|
||||
try:
|
||||
user = await self.session.get(f"{self.base_url}/user")
|
||||
user = await user.json()
|
||||
return user["data"]["subscription_status"] == "premium"
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Exception while checking premium status on {self.name}: {e}"
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
async def get_instant(self, magnets: list):
|
||||
try:
|
||||
magnet = await self.session.get(
|
||||
f"{self.base_url}/magnets/check?magnet={','.join(magnets)}"
|
||||
)
|
||||
return await magnet.json()
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Exception while checking hash instant availability on {self.name}: {e}"
|
||||
)
|
||||
|
||||
async def get_files(
|
||||
self, torrent_hashes: list, type: str, season: str, episode: str, kitsu: bool
|
||||
):
|
||||
chunk_size = 25
|
||||
chunks = [
|
||||
torrent_hashes[i : i + chunk_size]
|
||||
for i in range(0, len(torrent_hashes), chunk_size)
|
||||
]
|
||||
|
||||
tasks = []
|
||||
for chunk in chunks:
|
||||
tasks.append(self.get_instant(chunk))
|
||||
|
||||
responses = await asyncio.gather(*tasks)
|
||||
|
||||
availability = [
|
||||
response["data"]["items"]
|
||||
for response in responses
|
||||
if response and "data" in response
|
||||
]
|
||||
|
||||
files = {}
|
||||
|
||||
if type == "series":
|
||||
for magnets in availability:
|
||||
for magnet in magnets:
|
||||
if magnet["status"] != "cached":
|
||||
continue
|
||||
|
||||
for file in magnet["files"]:
|
||||
filename = file["name"]
|
||||
|
||||
if not is_video(filename) or "sample" in filename:
|
||||
continue
|
||||
|
||||
filename_parsed = parse(filename)
|
||||
|
||||
if episode not in filename_parsed.episodes:
|
||||
continue
|
||||
|
||||
if kitsu:
|
||||
if filename_parsed.seasons:
|
||||
continue
|
||||
else:
|
||||
if season not in filename_parsed.seasons:
|
||||
continue
|
||||
|
||||
files[magnet["hash"]] = {
|
||||
"index": file["index"],
|
||||
"title": filename,
|
||||
"size": file["size"],
|
||||
}
|
||||
|
||||
break
|
||||
else:
|
||||
for magnets in availability:
|
||||
for magnet in magnets:
|
||||
if magnet["status"] != "cached":
|
||||
continue
|
||||
|
||||
for file in magnet["files"]:
|
||||
filename = file["name"]
|
||||
|
||||
if not is_video(filename) or "sample" in filename:
|
||||
continue
|
||||
|
||||
files[magnet["hash"]] = {
|
||||
"index": file["index"],
|
||||
"title": filename,
|
||||
"size": file["size"],
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
return files
|
||||
|
||||
async def generate_download_link(self, hash: str, index: str):
|
||||
try:
|
||||
magnet = await self.session.post(
|
||||
f"{self.base_url}/magnets",
|
||||
json={"magnet": f"magnet:?xt=urn:btih:{hash}"},
|
||||
)
|
||||
magnet = await magnet.json()
|
||||
|
||||
file = next(
|
||||
(
|
||||
file
|
||||
for file in magnet["data"]["files"]
|
||||
if file["index"] == int(index)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if not file:
|
||||
return
|
||||
|
||||
link = await self.session.post(
|
||||
f"{self.base_url}/link/generate",
|
||||
json={"link": file["link"]},
|
||||
)
|
||||
link = await link.json()
|
||||
|
||||
return link["data"]["link"]
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Exception while getting download link from {self.name} for {hash}|{index}: {e}"
|
||||
)
|
||||
@@ -156,6 +156,8 @@ def start_log():
|
||||
"COMET",
|
||||
f"Debrid Stream Proxy: {bool(settings.PROXY_DEBRID_STREAM)} - Password: {settings.PROXY_DEBRID_STREAM_PASSWORD} - Max Connections: {settings.PROXY_DEBRID_STREAM_MAX_CONNECTIONS} - Default Debrid Service: {settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE} - Default Debrid API Key: {settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY}",
|
||||
)
|
||||
if settings.STREMTHRU_DEFAULT_URL:
|
||||
logger.log("COMET", f"Default StremThru URL: {settings.STREMTHRU_DEFAULT_URL}")
|
||||
logger.log("COMET", f"Title Match Check: {bool(settings.TITLE_MATCH_CHECK)}")
|
||||
logger.log("COMET", f"Remove Adult Content: {bool(settings.REMOVE_ADULT_CONTENT)}")
|
||||
logger.log("COMET", f"Custom Header HTML: {bool(settings.CUSTOM_HEADER_HTML)}")
|
||||
|
||||
@@ -546,6 +546,7 @@
|
||||
<sl-option value="alldebrid">All-Debrid</sl-option>
|
||||
<sl-option value="premiumize">Premiumize</sl-option>
|
||||
<sl-option value="realdebrid">Real-Debrid</sl-option>
|
||||
<sl-option value="stremthru">StremThru</sl-option>
|
||||
</sl-select>
|
||||
</div>
|
||||
|
||||
@@ -557,6 +558,10 @@
|
||||
<sl-input id="debridApiKey" placeholder="Enter API key" help-text="If no key is specified, you'll get direct torrents!"></sl-input>
|
||||
</div>
|
||||
|
||||
<div class="form-item">
|
||||
<sl-input id="stremthruUrl" label="StremThru URL" placeholder="{{stremthruDefaultUrl}}" help-text="URL for StremThru."></sl-input>
|
||||
</div>
|
||||
|
||||
<sl-details summary="Advanced Settings">
|
||||
<div class="form-item">
|
||||
<sl-checkbox checked id="removeTrash" help-text="Remove all trash from results (Adult Content, CAM, Clean Audio, PDTV, R5, Screener, Size, Telecine and Telesync)">Remove Trash</sl-checkbox>
|
||||
@@ -581,6 +586,8 @@
|
||||
apiKeyLink.href = "https://torbox.app/settings";
|
||||
} else if (selectedService === "debridlink") {
|
||||
apiKeyLink.href = "https://debrid-link.com/webapp/apikey";
|
||||
} else if (selectedService === "stremthru") {
|
||||
apiKeyLink.href = "https://github.com/MunifTanjim/stremthru?tab=readme-ov-file#configuration";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -725,6 +732,7 @@
|
||||
const resultFormat = Array.from(document.getElementById("resultFormat").selectedOptions).map(option => option.value);
|
||||
const debridService = document.getElementById("debridService").value;
|
||||
const debridApiKey = document.getElementById("debridApiKey").value;
|
||||
const stremthruUrl = document.getElementById("stremthruUrl").value;
|
||||
const debridStreamProxyPassword = document.getElementById("debridStreamProxyPassword").value;
|
||||
const selectedLanguages = languages.length === defaultLanguages.length && languages.every((val, index) => val === defaultLanguages[index]) ? ["All"] : languages;
|
||||
const selectedResolutions = resolutions.length === defaultResolutions.length && resolutions.every((val, index) => val === defaultResolutions[index]) ? ["All"] : resolutions;
|
||||
@@ -742,6 +750,7 @@
|
||||
languages: selectedLanguages,
|
||||
debridService: debridService,
|
||||
debridApiKey: debridApiKey,
|
||||
stremthruUrl: stremthruUrl,
|
||||
debridStreamProxyPassword: debridStreamProxyPassword,
|
||||
};
|
||||
}
|
||||
@@ -807,6 +816,8 @@
|
||||
document.getElementById("indexers").value = settings.indexers;
|
||||
if (settings.languages !== null && settings.languages != "All")
|
||||
document.getElementById("languages").value = settings.languages;
|
||||
if (settings.stremthruUrl !== null)
|
||||
document.getElementById("stremthruUrl").value = settings.stremthruUrl;
|
||||
if (settings.resolutions !== null && settings.resolutions != "All")
|
||||
document.getElementById("resolutions").value = settings.resolutions;
|
||||
if (settings.resultFormat !== null && settings.resultFormat != "All")
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from typing import Any
|
||||
|
||||
from comet.debrid.stremthru import StremThru
|
||||
from comet.utils.models import settings
|
||||
|
||||
|
||||
def is_proxy_stream_enabled(config: dict[str, Any]):
|
||||
return (
|
||||
bool(settings.PROXY_DEBRID_STREAM) and config["debridStreamProxyPassword"] != ""
|
||||
)
|
||||
|
||||
|
||||
def is_proxy_stream_authed(config: dict[str, Any]):
|
||||
return settings.PROXY_DEBRID_STREAM_PASSWORD == config["debridStreamProxyPassword"]
|
||||
|
||||
|
||||
def should_use_stremthru(config: dict[str, Any]):
|
||||
return config["stremthruUrl"] and StremThru.is_supported_store(
|
||||
config["debridService"]
|
||||
)
|
||||
|
||||
|
||||
def should_skip_proxy_stream(config: dict[str, Any]):
|
||||
return config["stremthruUrl"] and config["debridService"] == "stremthru"
|
||||
|
||||
|
||||
def should_use_fallback_debrid_config(config: dict[str, Any]):
|
||||
if is_proxy_stream_authed(config) and config["debridApiKey"] == "":
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def prepare_debrid_config(config: dict[str, Any]):
|
||||
if should_use_fallback_debrid_config(config):
|
||||
config["debridService"] = settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE
|
||||
config["debridApiKey"] = settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY
|
||||
|
||||
if not config["stremthruUrl"]:
|
||||
config["stremthruUrl"] = settings.STREMTHRU_DEFAULT_URL
|
||||
@@ -269,6 +269,7 @@ def get_debrid_extension(debridService: str, debridApiKey: str = None):
|
||||
"premiumize": "PM",
|
||||
"torbox": "TB",
|
||||
"debridlink": "DL",
|
||||
"stremthru": "ST",
|
||||
}
|
||||
|
||||
return debrid_extensions.get(debridService, None)
|
||||
|
||||
@@ -42,6 +42,7 @@ class AppSettings(BaseSettings):
|
||||
PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY: Optional[str] = None
|
||||
TITLE_MATCH_CHECK: Optional[bool] = True
|
||||
REMOVE_ADULT_CONTENT: Optional[bool] = False
|
||||
STREMTHRU_DEFAULT_URL: Optional[str] = None
|
||||
|
||||
@field_validator("DASHBOARD_ADMIN_PASSWORD")
|
||||
def set_dashboard_admin_password(cls, v, values):
|
||||
@@ -78,6 +79,7 @@ class ConfigModel(BaseModel):
|
||||
debridService: str
|
||||
debridApiKey: str
|
||||
debridStreamProxyPassword: Optional[str] = ""
|
||||
stremthruUrl: Optional[str] = None
|
||||
|
||||
@field_validator("indexers")
|
||||
def check_indexers(cls, v, values):
|
||||
@@ -123,7 +125,7 @@ class ConfigModel(BaseModel):
|
||||
|
||||
@field_validator("debridService")
|
||||
def check_debrid_service(cls, v):
|
||||
if v not in ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink"]:
|
||||
if v not in ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink", "stremthru"]:
|
||||
raise ValueError("Invalid debridService")
|
||||
return v
|
||||
|
||||
|
||||
Reference in New Issue
Block a user