mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
Add support for frontend API for configuration and app related data (#481)
This commit is contained in:
+2
-1
@@ -143,4 +143,5 @@ mediafusion.local*
|
||||
.lock
|
||||
kodi/build/
|
||||
kodi/*.zip
|
||||
!kodi/**/lib/
|
||||
!kodi/**/lib/
|
||||
.vscode/
|
||||
@@ -0,0 +1,216 @@
|
||||
import math
|
||||
from typing import Dict, List, Optional, Any, Literal
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, HTTPException, BackgroundTasks
|
||||
from pydantic import BaseModel
|
||||
|
||||
from db import crud, schemas
|
||||
from db.config import settings
|
||||
from utils import const
|
||||
from utils.network import get_user_data, get_user_public_ip
|
||||
|
||||
# Create a router with more appropriate naming for the frontend
|
||||
router = APIRouter(prefix="/api/v1", tags=["frontend"])
|
||||
|
||||
|
||||
# ---- Data Models ----
|
||||
|
||||
class AppConfig(BaseModel):
|
||||
"""Base configuration data for the application"""
|
||||
addon_name: str
|
||||
logo_url: str
|
||||
host_url: str
|
||||
poster_host_url: Optional[str]
|
||||
version: str
|
||||
description: str
|
||||
branding_description: str
|
||||
is_public_instance: bool
|
||||
disabled_providers: List[str]
|
||||
authentication_required: bool
|
||||
|
||||
|
||||
class UserConfig(BaseModel):
|
||||
"""User-specific configuration data"""
|
||||
user_data: Dict[str, Any]
|
||||
configured_fields: List[str]
|
||||
|
||||
|
||||
class ValidationResult(BaseModel):
|
||||
"""Data model for validation results"""
|
||||
status: str
|
||||
message: Optional[str] = None
|
||||
|
||||
|
||||
class StreamData(BaseModel):
|
||||
"""Data model for stream information"""
|
||||
title: str
|
||||
year: int
|
||||
poster: str
|
||||
background: str
|
||||
description: str
|
||||
streams: List[Dict[str, Any]]
|
||||
catalog_type: str
|
||||
season: Optional[int] = None
|
||||
episode: Optional[int] = None
|
||||
video_id: str
|
||||
secret_str: str
|
||||
series_data: Optional[str] = None
|
||||
|
||||
|
||||
# ---- API Endpoints ----
|
||||
|
||||
@router.get("/app-config", response_model=AppConfig)
|
||||
async def get_app_config():
|
||||
"""
|
||||
Get basic application configuration.
|
||||
Replaces the former home endpoint with more specific naming.
|
||||
"""
|
||||
return {
|
||||
"addon_name": settings.addon_name,
|
||||
"logo_url": settings.logo_url,
|
||||
"host_url": settings.host_url,
|
||||
"poster_host_url": settings.poster_host_url,
|
||||
"version": settings.version,
|
||||
"description": settings.description,
|
||||
"branding_description": settings.branding_description,
|
||||
"is_public_instance": settings.is_public_instance,
|
||||
"disabled_providers": settings.disabled_providers,
|
||||
"authentication_required": settings.api_password is not None and not settings.is_public_instance,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/user-config", response_model=UserConfig)
|
||||
async def get_user_config(
|
||||
user_data: schemas.UserData = Depends(get_user_data),
|
||||
secret_str: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
Get user-specific configuration.
|
||||
Returns only user configuration without default values from constants.
|
||||
"""
|
||||
configured_fields = []
|
||||
|
||||
# Handle sensitive data masking
|
||||
if user_data.streaming_provider:
|
||||
user_data.streaming_provider.password = "••••••••"
|
||||
user_data.streaming_provider.token = "••••••••"
|
||||
configured_fields.extend(["provider_token", "password"])
|
||||
|
||||
if user_data.streaming_provider.qbittorrent_config:
|
||||
user_data.streaming_provider.qbittorrent_config.qbittorrent_password = "••••••••"
|
||||
user_data.streaming_provider.qbittorrent_config.webdav_password = "••••••••"
|
||||
configured_fields.extend(["qbittorrent_password", "webdav_password"])
|
||||
|
||||
if user_data.mediaflow_config:
|
||||
user_data.mediaflow_config.api_password = "••••••••"
|
||||
configured_fields.append("mediaflow_api_password")
|
||||
|
||||
if user_data.rpdb_config:
|
||||
user_data.rpdb_config.api_key = "••••••••"
|
||||
configured_fields.append("rpdb_api_key")
|
||||
|
||||
user_data.api_password = None
|
||||
|
||||
return {
|
||||
"user_data": user_data.model_dump(),
|
||||
"configured_fields": configured_fields,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/system-constants")
|
||||
async def get_system_constants():
|
||||
"""
|
||||
Get all system constants needed by the frontend.
|
||||
Renamed from 'constants' to be more descriptive.
|
||||
"""
|
||||
return {
|
||||
"CATALOG_DATA": const.CATALOG_DATA,
|
||||
"RESOLUTIONS": const.RESOLUTIONS,
|
||||
"TORRENT_SORTING_PRIORITY_OPTIONS": const.TORRENT_SORTING_PRIORITY_OPTIONS,
|
||||
"SUPPORTED_LANGUAGES": const.SUPPORTED_LANGUAGES,
|
||||
"QUALITY_GROUPS": const.QUALITY_GROUPS,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/download/{secret_str}/{catalog_type}/{video_id}", response_model=StreamData)
|
||||
@router.get("/download/{secret_str}/{catalog_type}/{video_id}/{season}/{episode}", response_model=StreamData)
|
||||
async def get_download_info(
|
||||
request: Request,
|
||||
secret_str: str,
|
||||
catalog_type: Literal["movie", "series"],
|
||||
video_id: str,
|
||||
user_data: schemas.UserData = Depends(get_user_data),
|
||||
background_tasks: BackgroundTasks = BackgroundTasks(),
|
||||
season: int = None,
|
||||
episode: int = None,
|
||||
):
|
||||
"""
|
||||
Get download information for the React frontend.
|
||||
Replaces the Jinja template rendering for the download page.
|
||||
"""
|
||||
if (
|
||||
not user_data.streaming_provider
|
||||
or not user_data.streaming_provider.download_via_browser
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Download option is not enabled or no streaming provider configured",
|
||||
)
|
||||
|
||||
metadata = (
|
||||
await crud.get_movie_data_by_id(video_id)
|
||||
if catalog_type == "movie"
|
||||
else await crud.get_series_data_by_id(video_id)
|
||||
)
|
||||
if not metadata:
|
||||
raise HTTPException(status_code=404, detail="Metadata not found")
|
||||
|
||||
user_ip = await get_user_public_ip(request, user_data)
|
||||
|
||||
if catalog_type == "movie":
|
||||
streams = await crud.get_movie_streams(
|
||||
user_data, secret_str, video_id, user_ip, background_tasks
|
||||
)
|
||||
else:
|
||||
streams = await crud.get_series_streams(
|
||||
user_data, secret_str, video_id, season, episode, user_ip, background_tasks
|
||||
)
|
||||
|
||||
streaming_provider_path = f"{settings.host_url}/streaming_provider/"
|
||||
downloadable_streams = [
|
||||
stream.model_dump()
|
||||
for stream in streams
|
||||
if stream.url and stream.url.startswith(streaming_provider_path)
|
||||
]
|
||||
|
||||
if video_id.startswith("tt") and user_data.rpdb_config:
|
||||
_poster = f"https://api.ratingposterdb.com/{user_data.rpdb_config.api_key}/imdb/poster-default/{video_id}.jpg?fallback=true"
|
||||
else:
|
||||
_poster = f"{settings.poster_host_url}/poster/{catalog_type}/{video_id}.jpg"
|
||||
|
||||
background = (
|
||||
metadata.background
|
||||
if metadata.background
|
||||
else f"{settings.host_url}/static/images/background.jpg"
|
||||
)
|
||||
|
||||
return {
|
||||
"title": metadata.title,
|
||||
"year": metadata.year,
|
||||
"poster": _poster,
|
||||
"background": background,
|
||||
"description": metadata.description,
|
||||
"streams": downloadable_streams,
|
||||
"catalog_type": catalog_type,
|
||||
"season": season,
|
||||
"episode": episode,
|
||||
"video_id": video_id,
|
||||
"secret_str": secret_str,
|
||||
"series_data": (
|
||||
metadata.model_dump_json(
|
||||
include={"episodes": {"__all__": {"season_number", "episode_number"}}}
|
||||
)
|
||||
if catalog_type == "series"
|
||||
else None
|
||||
),
|
||||
}
|
||||
@@ -29,6 +29,7 @@ from db.redis_database import REDIS_ASYNC_CLIENT
|
||||
from db.schemas import SortingOption
|
||||
from kodi.routes import kodi_router
|
||||
from metrics.routes import metrics_router
|
||||
from api.frontend_api import router as frontend_api_router
|
||||
from scrapers.routes import router as scrapers_router
|
||||
from scrapers.rpdb import update_rpdb_posters, update_rpdb_poster
|
||||
from streaming_providers import mapper
|
||||
@@ -123,6 +124,7 @@ app.add_middleware(middleware.SecureLoggingMiddleware)
|
||||
app.mount("/static", StaticFiles(directory="resources"), name="static")
|
||||
|
||||
|
||||
# Keep the existing template-based routes for backward compatibility
|
||||
@app.get("/", tags=["home"])
|
||||
async def get_home(request: Request):
|
||||
return TEMPLATES.TemplateResponse(
|
||||
@@ -957,3 +959,6 @@ app.include_router(scrapers_router, prefix="/scraper", tags=["scraper"])
|
||||
app.include_router(metrics_router, prefix="/metrics", tags=["metrics"])
|
||||
|
||||
app.include_router(kodi_router, prefix="/kodi", tags=["kodi"])
|
||||
|
||||
# Add the frontend API router
|
||||
app.include_router(frontend_api_router)
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@ class UserDataMiddleware(BaseHTTPMiddleware):
|
||||
if encoded_user_data:
|
||||
user_data = crypto_utils.decode_user_data(encoded_user_data)
|
||||
else:
|
||||
secret_str = request.path_params.get("secret_str")
|
||||
secret_str = request.path_params.get("secret_str") or request.query_params.get("secret_str")
|
||||
user_data = await crypto_utils.decrypt_user_data(secret_str)
|
||||
except (ValueError, ValidationError):
|
||||
# check if the endpoint is for /streams
|
||||
|
||||
Reference in New Issue
Block a user