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:
@@ -144,3 +144,4 @@ mediafusion.local*
|
|||||||
kodi/build/
|
kodi/build/
|
||||||
kodi/*.zip
|
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 db.schemas import SortingOption
|
||||||
from kodi.routes import kodi_router
|
from kodi.routes import kodi_router
|
||||||
from metrics.routes import metrics_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.routes import router as scrapers_router
|
||||||
from scrapers.rpdb import update_rpdb_posters, update_rpdb_poster
|
from scrapers.rpdb import update_rpdb_posters, update_rpdb_poster
|
||||||
from streaming_providers import mapper
|
from streaming_providers import mapper
|
||||||
@@ -123,6 +124,7 @@ app.add_middleware(middleware.SecureLoggingMiddleware)
|
|||||||
app.mount("/static", StaticFiles(directory="resources"), name="static")
|
app.mount("/static", StaticFiles(directory="resources"), name="static")
|
||||||
|
|
||||||
|
|
||||||
|
# Keep the existing template-based routes for backward compatibility
|
||||||
@app.get("/", tags=["home"])
|
@app.get("/", tags=["home"])
|
||||||
async def get_home(request: Request):
|
async def get_home(request: Request):
|
||||||
return TEMPLATES.TemplateResponse(
|
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(metrics_router, prefix="/metrics", tags=["metrics"])
|
||||||
|
|
||||||
app.include_router(kodi_router, prefix="/kodi", tags=["kodi"])
|
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:
|
if encoded_user_data:
|
||||||
user_data = crypto_utils.decode_user_data(encoded_user_data)
|
user_data = crypto_utils.decode_user_data(encoded_user_data)
|
||||||
else:
|
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)
|
user_data = await crypto_utils.decrypt_user_data(secret_str)
|
||||||
except (ValueError, ValidationError):
|
except (ValueError, ValidationError):
|
||||||
# check if the endpoint is for /streams
|
# check if the endpoint is for /streams
|
||||||
|
|||||||
Reference in New Issue
Block a user