mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
260 lines
9.3 KiB
Python
260 lines
9.3 KiB
Python
from urllib.parse import quote
|
|
|
|
import json
|
|
import logging
|
|
from typing import Literal
|
|
|
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
from apscheduler.triggers.cron import CronTrigger
|
|
from fastapi import FastAPI, Request, Response, BackgroundTasks, Depends, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.staticfiles import StaticFiles
|
|
from fastapi.templating import Jinja2Templates
|
|
from fastapi.responses import RedirectResponse, FileResponse
|
|
|
|
from streaming_providers.exceptions import ProviderException
|
|
from streaming_providers.realdebrid.utils import get_direct_link_from_realdebrid
|
|
from streaming_providers.seedr.api import router as seedr_router
|
|
from streaming_providers.realdebrid.api import router as realdebrid_router
|
|
from db import database, crud, schemas
|
|
from db.config import settings
|
|
from streaming_providers.seedr.utils import get_direct_link_from_seedr
|
|
from utils import scrap, crypto, torrent
|
|
from utils.parser import generate_catalog_ids, clean_name
|
|
|
|
logging.basicConfig(
|
|
format="%(levelname)s::%(asctime)s - %(message)s", datefmt="%d-%b-%y %H:%M:%S", level=settings.logging_level
|
|
)
|
|
app = FastAPI()
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
app.mount("/static", StaticFiles(directory="resources"), name="static")
|
|
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",
|
|
}
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def init_db():
|
|
await database.init()
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def start_scheduler():
|
|
scheduler = AsyncIOScheduler()
|
|
scheduler.add_job(scrap.run_schedule_scrape, CronTrigger(hour="*/3"))
|
|
scheduler.start()
|
|
app.state.scheduler = scheduler
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
async def stop_scheduler():
|
|
app.state.scheduler.shutdown(wait=False)
|
|
|
|
|
|
@app.get("/")
|
|
async def get_home(request: Request):
|
|
with open("resources/manifest.json") as file:
|
|
manifest = json.load(file)
|
|
return TEMPLATES.TemplateResponse(
|
|
"home.html",
|
|
{
|
|
"request": request,
|
|
"name": manifest.get("name"),
|
|
"version": f"{manifest.get('version')}-{settings.git_rev[:7]}",
|
|
"description": manifest.get("description"),
|
|
"gives": [
|
|
"Tamil Movies & Series",
|
|
"Malayalam Movies & Series",
|
|
"Telugu Movies & Series",
|
|
"Hindi Movies & Series",
|
|
"Kannada Movies & Series",
|
|
"English Movies & Series",
|
|
"Dubbed Movies & Series",
|
|
],
|
|
"logo": "static/tamilblasters.png",
|
|
},
|
|
)
|
|
|
|
|
|
@app.get("/favicon.ico")
|
|
async def get_favicon():
|
|
return FileResponse("resources/tamilblasters.png", media_type="image/x-icon")
|
|
|
|
|
|
@app.get("/configure")
|
|
@app.get("/{secret_str}/configure")
|
|
async def configure(
|
|
response: Response, request: Request, user_data: schemas.UserData = Depends(crypto.decrypt_user_data)
|
|
):
|
|
response.headers.update(headers)
|
|
response.headers.update(no_cache_headers)
|
|
if user_data.streaming_provider:
|
|
user_data.streaming_provider.token = None
|
|
return TEMPLATES.TemplateResponse("configure.html", {"request": request, "user_data": user_data.model_dump()})
|
|
|
|
|
|
@app.get("/manifest.json")
|
|
@app.get("/{secret_str}/manifest.json")
|
|
async def get_manifest(response: Response, user_data: schemas.UserData = Depends(crypto.decrypt_user_data)):
|
|
response.headers.update(headers)
|
|
response.headers.update(no_cache_headers)
|
|
with open("resources/manifest.json") as file:
|
|
manifest = json.load(file)
|
|
|
|
user_catalog_ids = generate_catalog_ids(user_data.preferred_movie_languages, user_data.preferred_series_languages)
|
|
filtered_catalogs = [cat for cat in manifest["catalogs"] if cat["id"] in user_catalog_ids]
|
|
manifest["catalogs"] = filtered_catalogs
|
|
|
|
return manifest
|
|
|
|
|
|
@app.get("/{secret_str}/catalog/{catalog_type}/{catalog_id}.json", response_model=schemas.Movie)
|
|
@app.get("/catalog/{catalog_type}/{catalog_id}.json", response_model=schemas.Movie)
|
|
@app.get("/{secret_str}/catalog/{catalog_type}/{catalog_id}/skip={skip}.json", response_model=schemas.Movie)
|
|
@app.get("/catalog/{catalog_type}/{catalog_id}/skip={skip}.json", response_model=schemas.Movie)
|
|
async def get_catalog(
|
|
response: Response,
|
|
catalog_type: Literal["movie", "series"],
|
|
catalog_id: str,
|
|
skip: int = 0,
|
|
):
|
|
response.headers.update(headers)
|
|
movies = schemas.Movie()
|
|
movies.metas.extend(await crud.get_movies_and_series_meta(catalog_id, skip))
|
|
return movies
|
|
|
|
|
|
@app.get("/{secret_str}/catalog/{catalog_type}/tamil_blasters/search={search_query}.json", response_model=schemas.Movie)
|
|
@app.get("/catalog/{catalog_type}/tamil_blasters/search={search_query}.json", response_model=schemas.Movie)
|
|
async def search_movie(
|
|
response: Response,
|
|
catalog_type: Literal["movie", "series"],
|
|
search_query: str,
|
|
):
|
|
response.headers.update(headers)
|
|
logging.debug("Searching for %s", search_query)
|
|
|
|
return await crud.process_search_query(search_query, catalog_type)
|
|
|
|
|
|
@app.get("/{secret_str}/meta/{catalog_type}/{meta_id}.json")
|
|
@app.get("/meta/{catalog_type}/{meta_id}.json")
|
|
async def get_meta(catalog_type: Literal["movie", "series"], meta_id: str, response: Response):
|
|
response.headers.update(headers)
|
|
if catalog_type == "movie":
|
|
return await crud.get_movie_meta(meta_id)
|
|
return await crud.get_series_meta(meta_id)
|
|
|
|
|
|
@app.get(
|
|
"/{secret_str}/stream/{catalog_type}/{video_id}.json",
|
|
response_model=schemas.Streams,
|
|
response_model_exclude_none=True,
|
|
)
|
|
@app.get("/stream/{catalog_type}/{video_id}.json", response_model=schemas.Streams, response_model_exclude_none=True)
|
|
@app.get(
|
|
"/{secret_str}/stream/{catalog_type}/{video_id}:{season}:{episode}.json",
|
|
response_model=schemas.Streams,
|
|
response_model_exclude_none=True,
|
|
)
|
|
@app.get(
|
|
"/stream/{catalog_type}/{video_id}:{season}:{episode}.json",
|
|
response_model=schemas.Streams,
|
|
response_model_exclude_none=True,
|
|
)
|
|
async def get_streams(
|
|
catalog_type: Literal["movie", "series"],
|
|
video_id: str,
|
|
response: Response,
|
|
secret_str: str = None,
|
|
season: int = None,
|
|
episode: str = None,
|
|
user_data: schemas.UserData = Depends(crypto.decrypt_user_data),
|
|
):
|
|
response.headers.update(headers)
|
|
response.headers.update(no_cache_headers)
|
|
streams = schemas.Streams()
|
|
if catalog_type == "movie":
|
|
fetched_streams = await crud.get_movie_streams(user_data, video_id)
|
|
else:
|
|
fetched_streams = await crud.get_series_streams(user_data, video_id, season, episode)
|
|
|
|
if user_data.streaming_provider:
|
|
for stream in fetched_streams:
|
|
torrent_name = quote(clean_name(f"{stream.stream_name} {stream.description}"))
|
|
proxy_url = (
|
|
f"{settings.host_url}/{secret_str}/streaming_provider?info_hash={stream.infoHash}&name={torrent_name}"
|
|
)
|
|
stream.url = proxy_url
|
|
stream.infoHash = None
|
|
stream.behaviorHints = {"notWebReady": True}
|
|
|
|
fetched_streams.reverse()
|
|
streams.streams.extend(fetched_streams)
|
|
return streams
|
|
|
|
|
|
@app.post("/scraper")
|
|
def run_scraper(
|
|
background_tasks: BackgroundTasks,
|
|
language: Literal["tamil", "malayalam", "telugu", "hindi", "kannada", "english"] = "tamil",
|
|
video_type: Literal["hdrip", "tcrip", "dubbed", "series"] = "hdrip",
|
|
pages: int = 1,
|
|
start_page: int = 1,
|
|
is_scrape_home: bool = False,
|
|
):
|
|
background_tasks.add_task(scrap.run_scraper, language, video_type, pages, start_page, is_scrape_home)
|
|
return {"message": "Scraping in background..."}
|
|
|
|
|
|
@app.post("/encrypt-user-data")
|
|
async def encrypt_user_data(user_data: schemas.UserData):
|
|
encrypted_str = crypto.encrypt_user_data(user_data)
|
|
return {"encrypted_str": encrypted_str}
|
|
|
|
|
|
@app.get("/{secret_str}/streaming_provider")
|
|
async def streaming_provider_endpoint(secret_str: str, info_hash: str, name: str, response: Response, request: Request):
|
|
response.headers.update(headers)
|
|
response.headers.update(no_cache_headers)
|
|
|
|
user_data = crypto.decrypt_user_data(secret_str)
|
|
if not user_data.streaming_provider:
|
|
raise HTTPException(status_code=400, detail="No streaming provider set.")
|
|
|
|
magnet_link = torrent.convert_info_hash_to_magnet(info_hash, name)
|
|
|
|
try:
|
|
if user_data.streaming_provider.service == "seedr":
|
|
video_url = get_direct_link_from_seedr(
|
|
info_hash, magnet_link, user_data.streaming_provider.token, name, max_retries=3, retry_interval=1
|
|
)
|
|
else:
|
|
video_url = get_direct_link_from_realdebrid(
|
|
info_hash, magnet_link, user_data.streaming_provider.token, max_retries=3, retry_interval=1
|
|
)
|
|
except ProviderException as error:
|
|
logging.info("Exception occurred: %s", error.message)
|
|
video_url = f"{settings.host_url}/static/{error.video_file_name}"
|
|
|
|
return RedirectResponse(url=video_url, headers=response.headers)
|
|
|
|
|
|
app.include_router(seedr_router, prefix="/seedr", tags=["seedr"])
|
|
app.include_router(realdebrid_router, prefix="/realdebrid", tags=["realdebrid"])
|