modulerize & refactor routes for streaming provider

This commit is contained in:
mhdzumair
2024-02-16 01:45:30 +05:30
parent 4b7a81e975
commit 799abad5cd
10 changed files with 167 additions and 146 deletions
+14 -114
View File
@@ -14,25 +14,13 @@ from fastapi import (
HTTPException,
)
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import RedirectResponse, FileResponse, StreamingResponse
from fastapi.responses import FileResponse, StreamingResponse
from fastapi.templating import Jinja2Templates
from api import middleware
from db import database, crud, schemas
from db.config import settings
from streaming_providers.alldebrid.utils import get_direct_link_from_alldebrid
from streaming_providers.debridlink.api import router as debridlink_router
from streaming_providers.debridlink.utils import get_direct_link_from_debridlink
from streaming_providers.exceptions import ProviderException
from streaming_providers.offcloud.utils import get_direct_link_from_offcloud
from streaming_providers.pikpak.utils import get_direct_link_from_pikpak
from streaming_providers.premiumize.api import router as premiumize_router
from streaming_providers.premiumize.utils import get_direct_link_from_premiumize
from streaming_providers.realdebrid.api import router as realdebrid_router
from streaming_providers.realdebrid.utils import get_direct_link_from_realdebrid
from streaming_providers.seedr.api import router as seedr_router
from streaming_providers.seedr.utils import get_direct_link_from_seedr
from streaming_providers.torbox.utils import get_direct_link_from_torbox
from streaming_providers.routes import router as streaming_provider_router
from utils import crypto, torrent, poster, const, rate_limiter
from utils.parser import generate_manifest
@@ -58,16 +46,6 @@ app.add_middleware(middleware.SecureLoggingMiddleware)
app.add_middleware(middleware.UserDataMiddleware)
TEMPLATES = Jinja2Templates(directory="resources")
headers = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Cache-Control": "max-age=3600, stale-while-revalidate=3600, stale-if-error=604800, public",
}
no_cache_headers = {
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
"Pragma": "no-cache",
"Expires": "0",
}
def get_user_data(request: Request) -> schemas.UserData:
@@ -143,7 +121,7 @@ async def get_favicon():
@app.get("/static/{file_path:path}")
async def function(file_path: str):
response = FileResponse(f"resources/{file_path}")
response.headers.update(headers)
response.headers.update(const.DEFAULT_HEADERS)
return response
@@ -154,8 +132,7 @@ async def configure(
request: Request,
user_data: schemas.UserData = Depends(get_user_data),
):
response.headers.update(headers)
response.headers.update(no_cache_headers)
response.headers.update(const.NO_CACHE_HEADERS)
# Remove the password from the streaming provider
if user_data.streaming_provider:
@@ -177,7 +154,7 @@ async def configure(
async def get_manifest(
response: Response, user_data: schemas.UserData = Depends(get_user_data)
):
response.headers.update({**headers, **no_cache_headers})
response.headers.update(const.NO_CACHE_HEADERS)
with open("resources/manifest.json") as file:
manifest = json.load(file)
@@ -236,7 +213,7 @@ async def get_catalog(
genre: str = None,
user_data: schemas.UserData = Depends(get_user_data),
):
response.headers.update(headers)
response.headers.update(const.DEFAULT_HEADERS)
if genre and "&" in genre:
genre, skip = genre.split("&")
skip = skip.split("=")[1] if "=" in skip else "0"
@@ -246,7 +223,7 @@ async def get_catalog(
if user_data.streaming_provider and catalog_id.startswith(
user_data.streaming_provider.service
):
response.headers.update(no_cache_headers)
response.headers.update(const.NO_CACHE_HEADERS)
cache_key = None
# Try retrieving the cached data
@@ -295,7 +272,7 @@ async def search_meta(
],
search_query: str,
):
response.headers.update(headers)
response.headers.update(const.DEFAULT_HEADERS)
logging.debug("search for catalog_id: %s", catalog_id)
return await crud.process_search_query(search_query, catalog_type)
@@ -321,7 +298,7 @@ async def get_meta(
response: Response,
request: Request,
):
response.headers.update(headers)
response.headers.update(const.DEFAULT_HEADERS)
cache_key = f"{catalog_type}_{meta_id}_meta"
# Try retrieving the cached data
@@ -380,7 +357,7 @@ async def get_streams(
episode: int = None,
user_data: schemas.UserData = Depends(get_user_data),
):
response.headers.update(headers)
response.headers.update(const.DEFAULT_HEADERS)
if catalog_type == "movie":
fetched_streams = await crud.get_movie_streams(
@@ -402,82 +379,6 @@ async def encrypt_user_data(user_data: schemas.UserData):
return {"encrypted_str": encrypted_str}
@app.get("/{secret_str}/streaming_provider", tags=["streaming_provider"])
@rate_limiter.exclude
async def streaming_provider_endpoint(
secret_str: str,
info_hash: str,
response: Response,
request: Request,
season: int = None,
episode: int = None,
):
response.headers.update(headers)
response.headers.update(no_cache_headers)
user_data = request.scope.get("user", crypto.decrypt_user_data(secret_str))
if not user_data.streaming_provider:
raise HTTPException(status_code=400, detail="No streaming provider set.")
stream = await crud.get_stream_by_info_hash(info_hash)
if not stream:
raise HTTPException(status_code=400, detail="Stream not found.")
magnet_link = await torrent.convert_info_hash_to_magnet(
info_hash, stream.announce_list
)
episode_data = stream.get_episode(season, episode)
filename = episode_data.filename if episode_data else stream.filename
try:
if user_data.streaming_provider.service == "seedr":
video_url = await get_direct_link_from_seedr(
info_hash, magnet_link, user_data, stream, filename, 1, 0
)
elif user_data.streaming_provider.service == "realdebrid":
video_url = get_direct_link_from_realdebrid(
info_hash, magnet_link, user_data, filename, stream.file_index, 1, 0
)
elif user_data.streaming_provider.service == "alldebrid":
video_url = get_direct_link_from_alldebrid(
info_hash, magnet_link, user_data, filename, 1, 0
)
elif user_data.streaming_provider.service == "offcloud":
video_url = get_direct_link_from_offcloud(
info_hash, magnet_link, user_data, filename, 1, 0
)
elif user_data.streaming_provider.service == "pikpak":
video_url = await get_direct_link_from_pikpak(
info_hash, magnet_link, user_data, stream, filename, 1, 0
)
elif user_data.streaming_provider.service == "torbox":
video_url = get_direct_link_from_torbox(
info_hash, magnet_link, user_data, filename, 1, 0
)
elif user_data.streaming_provider.service == "premiumize":
video_url = get_direct_link_from_premiumize(
info_hash, magnet_link, user_data, stream.torrent_name, filename, 1, 0
)
else:
video_url = get_direct_link_from_debridlink(
info_hash, magnet_link, user_data, filename, stream.file_index, 1, 0
)
except ProviderException as error:
logging.error(
"Exception occurred for %s: %s",
info_hash,
error.message,
exc_info=True if error.video_file_name == "api_error.mp4" else False,
)
video_url = f"{settings.host_url}/static/exceptions/{error.video_file_name}"
except Exception as e:
logging.error("Exception occurred for %s: %s", info_hash, e, exc_info=True)
video_url = f"{settings.host_url}/static/exceptions/api_error.mp4"
return RedirectResponse(url=video_url, headers=response.headers)
@app.get("/poster/{catalog_type}/{mediafusion_id}.jpg", tags=["poster"])
@rate_limiter.exclude
async def get_poster(
@@ -516,7 +417,7 @@ async def get_poster(
image_byte_io.seek(0)
return StreamingResponse(
image_byte_io, media_type="image/jpeg", headers=headers
image_byte_io, media_type="image/jpeg", headers=const.DEFAULT_HEADERS
)
except Exception as e:
logging.error(f"Unexpected error while creating poster: {e}")
@@ -525,7 +426,6 @@ async def get_poster(
raise HTTPException(status_code=404, detail="Failed to create poster.")
app.include_router(seedr_router, prefix="/seedr", tags=["seedr"])
app.include_router(realdebrid_router, prefix="/realdebrid", tags=["realdebrid"])
app.include_router(debridlink_router, prefix="/debridlink", tags=["debridlink"])
app.include_router(premiumize_router, prefix="/premiumize", tags=["premiumize"])
app.include_router(
streaming_provider_router, prefix="/streaming_provider", tags=["streaming_provider"]
)
+2 -5
View File
@@ -10,7 +10,7 @@ from starlette.routing import Match
from db.config import settings
from db.schemas import UserData
from utils import crypto
from utils import crypto, const
def get_client_ip(request: Request) -> str | None:
@@ -107,10 +107,7 @@ class RateLimitMiddleware(BaseHTTPMiddleware):
return Response(
content="Rate limit exceeded",
status_code=429,
headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
},
headers=const.NO_CACHE_HEADERS,
)
return await call_next(request)
+4 -4
View File
@@ -206,13 +206,13 @@ oAuthBtn.addEventListener('click', async function () {
document.getElementById('provider_token').disabled = true;
if (provider === 'seedr') {
await initiateOAuthFlow('/seedr/get-device-code', '/seedr/authorize');
await initiateOAuthFlow('/streaming_provider/seedr/get-device-code', '/streaming_provider/seedr/authorize');
} else if (provider === 'realdebrid') {
await initiateOAuthFlow('/realdebrid/get-device-code', '/realdebrid/authorize');
await initiateOAuthFlow('/streaming_provider/realdebrid/get-device-code', '/streaming_provider/realdebrid/authorize');
} else if (provider === 'debridlink') {
await initiateOAuthFlow('/debridlink/get-device-code', '/debridlink/authorize')
await initiateOAuthFlow('/streaming_provider/debridlink/get-device-code', '/streaming_provider/debridlink/authorize')
} else if (provider === 'premiumize') {
return window.location.href = "/premiumize/authorize";
return window.location.href = "/streaming_provider/premiumize/authorize";
}
});
+5 -3
View File
@@ -3,19 +3,21 @@ from fastapi.responses import JSONResponse
from db.schemas import AuthorizeData
from streaming_providers.debridlink.client import DebridLink
from utils import const
router = APIRouter()
headers = {"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0", "Pragma": "no-cache", "Expires": "0"}
@router.get("/get-device-code")
async def get_device_code():
dl_client = DebridLink()
return JSONResponse(content=dl_client.get_device_code(), headers=headers)
return JSONResponse(
content=dl_client.get_device_code(), headers=const.NO_CACHE_HEADERS
)
@router.post("/authorize")
async def authorize(data: AuthorizeData):
dl_client = DebridLink()
response = dl_client.authorize(data.device_code)
return JSONResponse(content=response, headers=headers)
return JSONResponse(content=response, headers=const.NO_CACHE_HEADERS)
+7 -8
View File
@@ -3,14 +3,9 @@ from fastapi.responses import RedirectResponse
from db import schemas
from streaming_providers.premiumize.client import Premiumize
from utils import crypto
from utils import crypto, const
router = APIRouter()
headers = {
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
"Pragma": "no-cache",
"Expires": "0",
}
@router.get("/authorize")
@@ -19,7 +14,9 @@ async def authorize():
return {"error": "Premiumize OAuth not configured"}
premiumize_client = Premiumize()
return RedirectResponse(premiumize_client.get_authorization_url(), headers=headers)
return RedirectResponse(
premiumize_client.get_authorization_url(), headers=const.NO_CACHE_HEADERS
)
@router.get("/oauth2_redirect")
@@ -31,4 +28,6 @@ async def oauth2_redirect(code: str):
streaming_provider=schemas.StreamingProvider(service="premiumize", token=token)
)
encrypted_str = crypto.encrypt_user_data(user_data)
return RedirectResponse(f"/{encrypted_str}/configure", headers=headers)
return RedirectResponse(
f"/{encrypted_str}/configure", headers=const.NO_CACHE_HEADERS
)
+5 -3
View File
@@ -3,19 +3,21 @@ from fastapi.responses import JSONResponse
from db.schemas import AuthorizeData
from streaming_providers.realdebrid.client import RealDebrid
from utils import const
router = APIRouter()
headers = {"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0", "Pragma": "no-cache", "Expires": "0"}
@router.get("/get-device-code")
async def get_device_code():
rd_client = RealDebrid()
return JSONResponse(content=rd_client.get_device_code(), headers=headers)
return JSONResponse(
content=rd_client.get_device_code(), headers=const.NO_CACHE_HEADERS
)
@router.post("/authorize")
async def authorize(data: AuthorizeData):
rd_client = RealDebrid()
response = rd_client.authorize(data.device_code)
return JSONResponse(content=response, headers=headers)
return JSONResponse(content=response, headers=const.NO_CACHE_HEADERS)
+109
View File
@@ -0,0 +1,109 @@
import logging
from fastapi import (
Request,
Response,
HTTPException,
APIRouter,
)
from fastapi.responses import RedirectResponse
from db import crud
from db.config import settings
from streaming_providers.alldebrid.utils import get_direct_link_from_alldebrid
from streaming_providers.debridlink.api import router as debridlink_router
from streaming_providers.debridlink.utils import get_direct_link_from_debridlink
from streaming_providers.exceptions import ProviderException
from streaming_providers.offcloud.utils import get_direct_link_from_offcloud
from streaming_providers.pikpak.utils import get_direct_link_from_pikpak
from streaming_providers.premiumize.api import router as premiumize_router
from streaming_providers.premiumize.utils import get_direct_link_from_premiumize
from streaming_providers.realdebrid.api import router as realdebrid_router
from streaming_providers.realdebrid.utils import get_direct_link_from_realdebrid
from streaming_providers.seedr.api import router as seedr_router
from streaming_providers.seedr.utils import get_direct_link_from_seedr
from streaming_providers.torbox.utils import get_direct_link_from_torbox
from utils import crypto, torrent, rate_limiter, const
router = APIRouter()
@router.get("/{secret_str}/stream", tags=["streaming_provider"])
@rate_limiter.exclude
async def streaming_provider_endpoint(
secret_str: str,
info_hash: str,
response: Response,
request: Request,
season: int = None,
episode: int = None,
):
response.headers.update(const.NO_CACHE_HEADERS)
user_data = request.scope.get("user", crypto.decrypt_user_data(secret_str))
if not user_data.streaming_provider:
raise HTTPException(status_code=400, detail="No streaming provider set.")
stream = await crud.get_stream_by_info_hash(info_hash)
if not stream:
raise HTTPException(status_code=400, detail="Stream not found.")
magnet_link = await torrent.convert_info_hash_to_magnet(
info_hash, stream.announce_list
)
episode_data = stream.get_episode(season, episode)
filename = episode_data.filename if episode_data else stream.filename
try:
if user_data.streaming_provider.service == "seedr":
video_url = await get_direct_link_from_seedr(
info_hash, magnet_link, user_data, stream, filename, 1, 0
)
elif user_data.streaming_provider.service == "realdebrid":
video_url = get_direct_link_from_realdebrid(
info_hash, magnet_link, user_data, filename, stream.file_index, 1, 0
)
elif user_data.streaming_provider.service == "alldebrid":
video_url = get_direct_link_from_alldebrid(
info_hash, magnet_link, user_data, filename, 1, 0
)
elif user_data.streaming_provider.service == "offcloud":
video_url = get_direct_link_from_offcloud(
info_hash, magnet_link, user_data, filename, 1, 0
)
elif user_data.streaming_provider.service == "pikpak":
video_url = await get_direct_link_from_pikpak(
info_hash, magnet_link, user_data, stream, filename, 1, 0
)
elif user_data.streaming_provider.service == "torbox":
video_url = get_direct_link_from_torbox(
info_hash, magnet_link, user_data, filename, 1, 0
)
elif user_data.streaming_provider.service == "premiumize":
video_url = get_direct_link_from_premiumize(
info_hash, magnet_link, user_data, stream.torrent_name, filename, 1, 0
)
else:
video_url = get_direct_link_from_debridlink(
info_hash, magnet_link, user_data, filename, stream.file_index, 1, 0
)
except ProviderException as error:
logging.error(
"Exception occurred for %s: %s",
info_hash,
error.message,
exc_info=True if error.video_file_name == "api_error.mp4" else False,
)
video_url = f"{settings.host_url}/static/exceptions/{error.video_file_name}"
except Exception as e:
logging.error("Exception occurred for %s: %s", info_hash, e, exc_info=True)
video_url = f"{settings.host_url}/static/exceptions/api_error.mp4"
return RedirectResponse(url=video_url, headers=response.headers)
router.include_router(seedr_router, prefix="/seedr", tags=["seedr"])
router.include_router(realdebrid_router, prefix="/realdebrid", tags=["realdebrid"])
router.include_router(debridlink_router, prefix="/debridlink", tags=["debridlink"])
router.include_router(premiumize_router, prefix="/premiumize", tags=["premiumize"])
+6 -8
View File
@@ -3,20 +3,16 @@ from fastapi.responses import JSONResponse
from seedrcc import Login
from db.schemas import AuthorizeData
from utils import const
router = APIRouter()
headers = {
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
"Pragma": "no-cache",
"Expires": "0",
}
@router.get("/get-device-code")
async def get_device_code():
seedr = Login()
device_code = seedr.getDeviceCode()
return JSONResponse(content=device_code, headers=headers)
return JSONResponse(content=device_code, headers=const.NO_CACHE_HEADERS)
@router.post("/authorize")
@@ -25,6 +21,8 @@ async def authorize(data: AuthorizeData):
response = seedr.authorize(data.device_code)
if "access_token" in response:
return JSONResponse(content={"token": seedr.token}, headers=headers)
return JSONResponse(
content={"token": seedr.token}, headers=const.NO_CACHE_HEADERS
)
else:
return JSONResponse(content=response, headers=headers)
return JSONResponse(content=response, headers=const.NO_CACHE_HEADERS)
+14
View File
@@ -86,3 +86,17 @@ RESOLUTIONS = [
DEBRID_SERVER_TIMEOUT = 15
DEFAULT_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Cache-Control": "max-age=3600, stale-while-revalidate=3600, stale-if-error=604800, public",
}
NO_CACHE_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "*",
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
"Pragma": "no-cache",
"Expires": "0",
}
+1 -1
View File
@@ -152,7 +152,7 @@ async def parse_stream_data(
}
if user_data.streaming_provider:
base_proxy_url = f"{settings.host_url}/{secret_str}/streaming_provider?info_hash={stream_data.id}"
base_proxy_url = f"{settings.host_url}/streaming_provider/{secret_str}/stream?info_hash={stream_data.id}"
if episode_data:
base_proxy_url += f"&season={season}&episode={episode}"
stream_details["url"] = base_proxy_url