Skip to content

Commit

Permalink
feat(debrid): add support for stremthru
Browse files Browse the repository at this point in the history
  • Loading branch information
MunifTanjim committed Oct 30, 2024
1 parent 083efb6 commit 26882b9
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 33 deletions.
1 change: 1 addition & 0 deletions .env-sample
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ PROXY_DEBRID_STREAM_PASSWORD=CHANGE_ME # Secret password to enter on configurati
PROXY_DEBRID_STREAM_MAX_CONNECTIONS=100 # IP-Based connection limit for the Debrid Stream Proxy
PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE=realdebrid # 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
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
STREMTHRU_DEFAULT_URL=None # if you want your users to use StremThru without having to specify it
TITLE_MATCH_CHECK=True # disable if you only use Jackett / Prowlarr / Torrentio and are sure you're only scraping good titles, for example (keep it True if Zilean is enabled)
CUSTOM_HEADER_HTML=None # only set it if you know what it is
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Proxy support to bypass debrid restrictions
- Real-Debrid, All-Debrid, Premiumize, TorBox and Debrid-Link supported *(if you want other debrid services, please provide an account)*
- [Kitsu](https://kitsu.io/) support (anime)
- [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)!
Expand Down
1 change: 1 addition & 0 deletions comet/api/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,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 "",
},
)

Expand Down
47 changes: 15 additions & 32 deletions comet/api/stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
format_title,
get_client_ip,
)
from comet.utils.config import is_proxy_stream_authed, is_proxy_stream_enabled, prepare_debrid_config
from comet.utils.logger import logger
from comet.utils.models import database, rtn, settings

Expand Down Expand Up @@ -148,10 +149,8 @@ async def stream(request: Request, b64config: str, type: str, id: str):

results = []
if (
config["debridStreamProxyPassword"] != ""
and settings.PROXY_DEBRID_STREAM
and settings.PROXY_DEBRID_STREAM_PASSWORD
!= config["debridStreamProxyPassword"]
is_proxy_stream_enabled()
and not is_proxy_stream_authed(config)
):
results.append(
{
Expand Down Expand Up @@ -196,17 +195,7 @@ async def stream(request: Request, b64config: str, type: str, id: str):
else:
logger.info(f"No cache found for {log_name} with user configuration")

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)
debrid = getDebrid(session, config, get_client_ip(request))

check_premium = await debrid.check_premium()
Expand Down Expand Up @@ -400,10 +389,8 @@ async def stream(request: Request, b64config: str, type: str, id: str):

results = []
if (
config["debridStreamProxyPassword"] != ""
and settings.PROXY_DEBRID_STREAM
and settings.PROXY_DEBRID_STREAM_PASSWORD
!= config["debridStreamProxyPassword"]
is_proxy_stream_enabled()
and not is_proxy_stream_authed(config)
):
results.append(
{
Expand Down Expand Up @@ -468,13 +455,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() as session:
# Check for cached download link
Expand All @@ -498,8 +479,11 @@ async def playback(request: Request, b64config: str, hash: str, index: str):

ip = get_client_ip(request)

should_try_to_proxy_stream = True

if not download_link:
debrid = getDebrid(session, config, ip if (not settings.PROXY_DEBRID_STREAM or settings.PROXY_DEBRID_STREAM_PASSWORD != config["debridStreamProxyPassword"]) else "")
debrid = getDebrid(session, config, ip if (not is_proxy_stream_enabled() or not is_proxy_stream_authed(config)) else "")
should_try_to_proxy_stream = debrid.should_try_to_proxy_stream()
download_link = await debrid.generate_download_link(hash, index)
if not download_link:
return FileResponse("comet/assets/uncached.mp4")
Expand All @@ -516,11 +500,10 @@ async def playback(request: Request, b64config: str, hash: str, index: str):
},
)

if (
settings.PROXY_DEBRID_STREAM
and settings.PROXY_DEBRID_STREAM_PASSWORD
== config["debridStreamProxyPassword"]
):
if not should_try_to_proxy_stream:
return RedirectResponse(download_link, status_code=302)

if is_proxy_stream_enabled() and is_proxy_stream_authed(config):
active_ip_connections = await database.fetch_all(
"SELECT ip, COUNT(*) as connections FROM active_connections GROUP BY ip"
)
Expand Down
3 changes: 3 additions & 0 deletions comet/debrid/alldebrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def __init__(self, session: aiohttp.ClientSession, debrid_api_key: str):
self.api_url = "http://api.alldebrid.com/v4"
self.agent = "comet"

def should_try_to_proxy_stream(self):
return True

async def check_premium(self):
try:
check_premium = await self.session.get(
Expand Down
3 changes: 3 additions & 0 deletions comet/debrid/debridlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def __init__(self, session: aiohttp.ClientSession, debrid_api_key: str):

self.api_url = "https://debrid-link.com/api/v2"

def should_try_to_proxy_stream(self):
return True

async def check_premium(self):
try:
check_premium = await self.session.get(f"{self.api_url}/account/infos")
Expand Down
13 changes: 13 additions & 0 deletions comet/debrid/manager.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
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"],
store=debrid_service,
store_token=debrid_api_key,
credential=config["stremthruAuth"],
)

if debrid_service == "realdebrid":
return RealDebrid(session, debrid_api_key, ip)
elif debrid_service == "alldebrid":
Expand Down
3 changes: 3 additions & 0 deletions comet/debrid/premiumize.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ def __init__(self, session: aiohttp.ClientSession, debrid_api_key: str):
self.api_url = "https://premiumize.me/api"
self.debrid_api_key = debrid_api_key

def should_try_to_proxy_stream(self):
return True

async def check_premium(self):
try:
check_premium = await self.session.get(
Expand Down
3 changes: 3 additions & 0 deletions comet/debrid/realdebrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def __init__(self, session: aiohttp.ClientSession, debrid_api_key: str, ip: str)

self.api_url = "https://api.real-debrid.com/rest/1.0"

def should_try_to_proxy_stream(self):
return True

async def check_premium(self):
try:
check_premium = await self.session.get(f"{self.api_url}/user")
Expand Down
178 changes: 178 additions & 0 deletions comet/debrid/stremthru.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
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,
store: str,
store_token: Optional[str] = None,
credential: Optional[str] = None,
):
if not credential and not store_token:
raise TypeError("either stremthru credential or store token is required")

if not self.is_supported_store(store):
raise ValueError(f"unsupported store: {store}")

self.__is_proxy_authorized = False

session.headers["X-StremThru-Store-Name"] = store

if credential:
session.headers["Proxy-Authorization"] = f"Basic {credential}"
self.__is_proxy_authorized = True
elif store_token:
session.headers["X-StremThru-Store-Authorization"] = f"Bearer {store_token}"

self.session = session
self.base_url = f"{url}/v0/store"
self.name = f"StremThru[{store}]"

def should_try_to_proxy_stream(self):
return not self.__is_proxy_authorized

@staticmethod
def is_supported_store(store: str):
if store == "alldebrid":
return True
return False

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}/magnet?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}/magnet",
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}"
)
3 changes: 3 additions & 0 deletions comet/debrid/torbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ def __init__(self, session: aiohttp.ClientSession, debrid_api_key: str):
self.api_url = "https://api.torbox.app/v1/api"
self.debrid_api_key = debrid_api_key

def should_try_to_proxy_stream(self):
return True

async def check_premium(self):
try:
check_premium = await self.session.get(
Expand Down
Loading

0 comments on commit 26882b9

Please sign in to comment.