681 lines
18 KiB
Python
681 lines
18 KiB
Python
from typing import Any, List
|
|
|
|
from fastapi import APIRouter, HTTPException, Depends
|
|
from requests import Session
|
|
|
|
from app import schemas
|
|
from app.chain.media import MediaChain
|
|
from app.chain.subscribe import SubscribeChain
|
|
from app.core.config import settings
|
|
from app.core.metainfo import MetaInfo
|
|
from app.db import get_db
|
|
from app.db.models.subscribe import Subscribe
|
|
from app.schemas import RadarrMovie, SonarrSeries
|
|
from app.schemas.types import MediaType
|
|
from version import APP_VERSION
|
|
|
|
arr_router = APIRouter(tags=['servarr'])
|
|
|
|
|
|
@arr_router.get("/system/status", summary="系统状态")
|
|
def arr_system_status(apikey: str) -> Any:
|
|
"""
|
|
模拟Radarr、Sonarr系统状态
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
return {
|
|
"appName": "MoviePilot",
|
|
"instanceName": "moviepilot",
|
|
"version": APP_VERSION,
|
|
"urlBase": ""
|
|
}
|
|
|
|
|
|
@arr_router.get("/qualityProfile", summary="质量配置")
|
|
def arr_qualityProfile(apikey: str) -> Any:
|
|
"""
|
|
模拟Radarr、Sonarr质量配置
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
return [
|
|
{
|
|
"id": 1,
|
|
"name": "默认"
|
|
}
|
|
]
|
|
|
|
|
|
@arr_router.get("/rootfolder", summary="根目录")
|
|
def arr_rootfolder(apikey: str) -> Any:
|
|
"""
|
|
模拟Radarr、Sonarr根目录
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
library_path = "/"
|
|
if settings.LIBRARY_PATH:
|
|
library_path = settings.LIBRARY_PATH.split(",")[0]
|
|
return [
|
|
{
|
|
"id": 1,
|
|
"path": library_path,
|
|
"accessible": True,
|
|
"freeSpace": 0,
|
|
"unmappedFolders": []
|
|
}
|
|
]
|
|
|
|
|
|
@arr_router.get("/tag", summary="标签")
|
|
def arr_tag(apikey: str) -> Any:
|
|
"""
|
|
模拟Radarr、Sonarr标签
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
return [
|
|
{
|
|
"id": 1,
|
|
"label": "默认"
|
|
}
|
|
]
|
|
|
|
|
|
@arr_router.get("/languageprofile", summary="语言")
|
|
def arr_languageprofile(apikey: str) -> Any:
|
|
"""
|
|
模拟Radarr、Sonarr语言
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
return [{
|
|
"id": 1,
|
|
"name": "默认",
|
|
"upgradeAllowed": True,
|
|
"cutoff": {
|
|
"id": 1,
|
|
"name": "默认"
|
|
},
|
|
"languages": [
|
|
{
|
|
"id": 1,
|
|
"language": {
|
|
"id": 1,
|
|
"name": "默认"
|
|
},
|
|
"allowed": True
|
|
}
|
|
]
|
|
}]
|
|
|
|
|
|
@arr_router.get("/movie", summary="所有订阅电影", response_model=List[schemas.RadarrMovie])
|
|
def arr_movies(apikey: str, db: Session = Depends(get_db)) -> Any:
|
|
"""
|
|
查询Rardar电影
|
|
"""
|
|
"""
|
|
[
|
|
{
|
|
"id": 0,
|
|
"title": "string",
|
|
"originalTitle": "string",
|
|
"originalLanguage": {
|
|
"id": 0,
|
|
"name": "string"
|
|
},
|
|
"secondaryYear": 0,
|
|
"secondaryYearSourceId": 0,
|
|
"sortTitle": "string",
|
|
"sizeOnDisk": 0,
|
|
"status": "tba",
|
|
"overview": "string",
|
|
"inCinemas": "2023-06-13T09:23:41.494Z",
|
|
"physicalRelease": "2023-06-13T09:23:41.494Z",
|
|
"digitalRelease": "2023-06-13T09:23:41.494Z",
|
|
"physicalReleaseNote": "string",
|
|
"images": [
|
|
{
|
|
"coverType": "unknown",
|
|
"url": "string",
|
|
"remoteUrl": "string"
|
|
}
|
|
],
|
|
"website": "string",
|
|
"remotePoster": "string",
|
|
"year": 0,
|
|
"hasFile": true,
|
|
"youTubeTrailerId": "string",
|
|
"studio": "string",
|
|
"path": "string",
|
|
"qualityProfileId": 0,
|
|
"monitored": true,
|
|
"minimumAvailability": "tba",
|
|
"isAvailable": true,
|
|
"folderName": "string",
|
|
"runtime": 0,
|
|
"cleanTitle": "string",
|
|
"imdbId": "string",
|
|
"tmdbId": 0,
|
|
"titleSlug": "string",
|
|
"rootFolderPath": "string",
|
|
"folder": "string",
|
|
"certification": "string",
|
|
"genres": [
|
|
"string"
|
|
],
|
|
"tags": [
|
|
0
|
|
],
|
|
"added": "2023-06-13T09:23:41.494Z",
|
|
"addOptions": {
|
|
"ignoreEpisodesWithFiles": true,
|
|
"ignoreEpisodesWithoutFiles": true,
|
|
"monitor": "movieOnly",
|
|
"searchForMovie": true,
|
|
"addMethod": "manual"
|
|
},
|
|
"popularity": 0
|
|
}
|
|
]
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
# 查询所有电影订阅
|
|
result = []
|
|
subscribes = Subscribe.list(db)
|
|
for subscribe in subscribes:
|
|
if subscribe.type != MediaType.MOVIE.value:
|
|
continue
|
|
result.append(RadarrMovie(
|
|
id=subscribe.id,
|
|
title=subscribe.name,
|
|
year=subscribe.year,
|
|
isAvailable=True,
|
|
monitored=True,
|
|
tmdbId=subscribe.tmdbid,
|
|
imdbId=subscribe.imdbid,
|
|
profileId=1,
|
|
qualityProfileId=1,
|
|
hasFile=False
|
|
))
|
|
return result
|
|
|
|
|
|
@arr_router.get("/movie/lookup", summary="查询电影", response_model=List[schemas.RadarrMovie])
|
|
def arr_movie_lookup(apikey: str, term: str, db: Session = Depends(get_db)) -> Any:
|
|
"""
|
|
查询Rardar电影 term: `tmdb:${id}`
|
|
存在和不存在均不能返回错误
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
tmdbid = term.replace("tmdb:", "")
|
|
# 查询媒体信息
|
|
mediainfo = MediaChain().recognize_media(mtype=MediaType.MOVIE, tmdbid=int(tmdbid))
|
|
if not mediainfo:
|
|
return [RadarrMovie()]
|
|
# 查询是否已存在
|
|
exists = MediaChain().media_exists(mediainfo=mediainfo)
|
|
if not exists:
|
|
# 文件不存在
|
|
hasfile = False
|
|
else:
|
|
# 文件存在
|
|
hasfile = True
|
|
# 查询是否已订阅
|
|
subscribes = Subscribe.get_by_tmdbid(db, int(tmdbid))
|
|
if subscribes:
|
|
# 订阅ID
|
|
subid = subscribes[0].id
|
|
# 已订阅
|
|
monitored = True
|
|
else:
|
|
subid = None
|
|
monitored = False
|
|
|
|
return [RadarrMovie(
|
|
id=subid,
|
|
title=mediainfo.title,
|
|
year=mediainfo.year,
|
|
isAvailable=True,
|
|
monitored=monitored,
|
|
tmdbId=mediainfo.tmdb_id,
|
|
imdbId=mediainfo.imdb_id,
|
|
titleSlug=mediainfo.original_title,
|
|
folderName=mediainfo.title_year,
|
|
profileId=1,
|
|
qualityProfileId=1,
|
|
hasFile=hasfile
|
|
)]
|
|
|
|
|
|
@arr_router.get("/movie/{mid}", summary="电影订阅详情", response_model=schemas.RadarrMovie)
|
|
def arr_movie(apikey: str, mid: int, db: Session = Depends(get_db)) -> Any:
|
|
"""
|
|
查询Rardar电影订阅
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
subscribe = Subscribe.get(db, mid)
|
|
if subscribe:
|
|
return RadarrMovie(
|
|
id=subscribe.id,
|
|
title=subscribe.name,
|
|
year=subscribe.year,
|
|
isAvailable=True,
|
|
monitored=True,
|
|
tmdbId=subscribe.tmdbid,
|
|
imdbId=subscribe.imdbid,
|
|
profileId=1,
|
|
qualityProfileId=1,
|
|
hasFile=False
|
|
)
|
|
else:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="未找到该电影!"
|
|
)
|
|
|
|
|
|
@arr_router.post("/movie", summary="新增电影订阅")
|
|
def arr_add_movie(apikey: str, movie: RadarrMovie) -> Any:
|
|
"""
|
|
新增Rardar电影订阅
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
sid, message = SubscribeChain().add(title=movie.title,
|
|
year=movie.year,
|
|
mtype=MediaType.MOVIE,
|
|
tmdbid=movie.tmdbId,
|
|
userid="Seerr")
|
|
if sid:
|
|
return {
|
|
"id": sid
|
|
}
|
|
else:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"添加订阅失败:{message}"
|
|
)
|
|
|
|
|
|
@arr_router.delete("/movie/{mid}", summary="删除电影订阅", response_model=schemas.Response)
|
|
def arr_remove_movie(apikey: str, mid: int, db: Session = Depends(get_db)) -> Any:
|
|
"""
|
|
删除Rardar电影订阅
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
subscribe = Subscribe.get(db, mid)
|
|
if subscribe:
|
|
subscribe.delete(db, mid)
|
|
return schemas.Response(success=True)
|
|
else:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="未找到该电影!"
|
|
)
|
|
|
|
|
|
@arr_router.get("/series", summary="所有剧集", response_model=List[schemas.SonarrSeries])
|
|
def arr_series(apikey: str, db: Session = Depends(get_db)) -> Any:
|
|
"""
|
|
查询Sonarr剧集
|
|
"""
|
|
"""
|
|
[
|
|
{
|
|
"id": 0,
|
|
"title": "string",
|
|
"sortTitle": "string",
|
|
"status": "continuing",
|
|
"ended": true,
|
|
"profileName": "string",
|
|
"overview": "string",
|
|
"nextAiring": "2023-06-13T09:08:17.624Z",
|
|
"previousAiring": "2023-06-13T09:08:17.624Z",
|
|
"network": "string",
|
|
"airTime": "string",
|
|
"images": [
|
|
{
|
|
"coverType": "unknown",
|
|
"url": "string",
|
|
"remoteUrl": "string"
|
|
}
|
|
],
|
|
"originalLanguage": {
|
|
"id": 0,
|
|
"name": "string"
|
|
},
|
|
"remotePoster": "string",
|
|
"seasons": [
|
|
{
|
|
"seasonNumber": 0,
|
|
"monitored": true,
|
|
"statistics": {
|
|
"nextAiring": "2023-06-13T09:08:17.624Z",
|
|
"previousAiring": "2023-06-13T09:08:17.624Z",
|
|
"episodeFileCount": 0,
|
|
"episodeCount": 0,
|
|
"totalEpisodeCount": 0,
|
|
"sizeOnDisk": 0,
|
|
"releaseGroups": [
|
|
"string"
|
|
],
|
|
"percentOfEpisodes": 0
|
|
},
|
|
"images": [
|
|
{
|
|
"coverType": "unknown",
|
|
"url": "string",
|
|
"remoteUrl": "string"
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"year": 0,
|
|
"path": "string",
|
|
"qualityProfileId": 0,
|
|
"seasonFolder": true,
|
|
"monitored": true,
|
|
"useSceneNumbering": true,
|
|
"runtime": 0,
|
|
"tvdbId": 0,
|
|
"tvRageId": 0,
|
|
"tvMazeId": 0,
|
|
"firstAired": "2023-06-13T09:08:17.624Z",
|
|
"seriesType": "standard",
|
|
"cleanTitle": "string",
|
|
"imdbId": "string",
|
|
"titleSlug": "string",
|
|
"rootFolderPath": "string",
|
|
"folder": "string",
|
|
"certification": "string",
|
|
"genres": [
|
|
"string"
|
|
],
|
|
"tags": [
|
|
0
|
|
],
|
|
"added": "2023-06-13T09:08:17.624Z",
|
|
"addOptions": {
|
|
"ignoreEpisodesWithFiles": true,
|
|
"ignoreEpisodesWithoutFiles": true,
|
|
"monitor": "unknown",
|
|
"searchForMissingEpisodes": true,
|
|
"searchForCutoffUnmetEpisodes": true
|
|
},
|
|
"ratings": {
|
|
"votes": 0,
|
|
"value": 0
|
|
},
|
|
"statistics": {
|
|
"seasonCount": 0,
|
|
"episodeFileCount": 0,
|
|
"episodeCount": 0,
|
|
"totalEpisodeCount": 0,
|
|
"sizeOnDisk": 0,
|
|
"releaseGroups": [
|
|
"string"
|
|
],
|
|
"percentOfEpisodes": 0
|
|
},
|
|
"episodesChanged": true
|
|
}
|
|
]
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
# 查询所有电视剧订阅
|
|
result = []
|
|
subscribes = Subscribe.list(db)
|
|
for subscribe in subscribes:
|
|
if subscribe.type != MediaType.TV.value:
|
|
continue
|
|
result.append(SonarrSeries(
|
|
id=subscribe.id,
|
|
title=subscribe.name,
|
|
seasonCount=1,
|
|
seasons=[{
|
|
"seasonNumber": subscribe.season,
|
|
"monitored": True,
|
|
}],
|
|
remotePoster=subscribe.image,
|
|
year=subscribe.year,
|
|
tmdbId=subscribe.tmdbid,
|
|
tvdbId=subscribe.tvdbid,
|
|
imdbId=subscribe.imdbid,
|
|
profileId=1,
|
|
languageProfileId=1,
|
|
qualityProfileId=1,
|
|
isAvailable=True,
|
|
monitored=True,
|
|
hasFile=False
|
|
))
|
|
return result
|
|
|
|
|
|
@arr_router.get("/series/lookup", summary="查询剧集")
|
|
def arr_series_lookup(apikey: str, term: str, db: Session = Depends(get_db)) -> Any:
|
|
"""
|
|
查询Sonarr剧集 term: `tvdb:${id}` title
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
|
|
# 获取TVDBID
|
|
if not term.startswith("tvdb:"):
|
|
mediainfo = MediaChain().recognize_media(meta=MetaInfo(term),
|
|
mtype=MediaType.TV)
|
|
if not mediainfo:
|
|
return [SonarrSeries()]
|
|
tvdbid = mediainfo.tvdb_id
|
|
if not tvdbid:
|
|
return [SonarrSeries()]
|
|
else:
|
|
mediainfo = None
|
|
tvdbid = int(term.replace("tvdb:", ""))
|
|
|
|
# 查询TVDB信息
|
|
tvdbinfo = MediaChain().tvdb_info(tvdbid=tvdbid)
|
|
if not tvdbinfo:
|
|
return [SonarrSeries()]
|
|
|
|
# 季信息
|
|
seas: List[int] = []
|
|
sea_num = tvdbinfo.get('season')
|
|
if sea_num:
|
|
seas = list(range(1, int(sea_num) + 1))
|
|
|
|
# 根据TVDB查询媒体信息
|
|
if not mediainfo:
|
|
mediainfo = MediaChain().recognize_media(meta=MetaInfo(tvdbinfo.get('seriesName')),
|
|
mtype=MediaType.TV)
|
|
|
|
# 查询是否存在
|
|
exists = MediaChain().media_exists(mediainfo)
|
|
if exists:
|
|
hasfile = True
|
|
else:
|
|
hasfile = False
|
|
|
|
# 查询订阅信息
|
|
seasons: List[dict] = []
|
|
subscribes = Subscribe.get_by_tmdbid(db, mediainfo.tmdb_id)
|
|
if subscribes:
|
|
# 已监控
|
|
monitored = True
|
|
# 已监控季
|
|
sub_seas = [sub.season for sub in subscribes]
|
|
for sea in seas:
|
|
if sea in sub_seas:
|
|
seasons.append({
|
|
"seasonNumber": sea,
|
|
"monitored": True,
|
|
})
|
|
else:
|
|
seasons.append({
|
|
"seasonNumber": sea,
|
|
"monitored": False,
|
|
})
|
|
subid = subscribes[-1].id
|
|
else:
|
|
subid = None
|
|
monitored = False
|
|
for sea in seas:
|
|
seasons.append({
|
|
"seasonNumber": sea,
|
|
"monitored": False,
|
|
})
|
|
|
|
return [SonarrSeries(
|
|
id=subid,
|
|
title=mediainfo.title,
|
|
seasonCount=len(seasons),
|
|
seasons=seasons,
|
|
remotePoster=mediainfo.get_poster_image(),
|
|
year=mediainfo.year,
|
|
tmdbId=mediainfo.tmdb_id,
|
|
tvdbId=mediainfo.tvdb_id,
|
|
imdbId=mediainfo.imdb_id,
|
|
profileId=1,
|
|
languageProfileId=1,
|
|
qualityProfileId=1,
|
|
isAvailable=True,
|
|
monitored=monitored,
|
|
hasFile=hasfile
|
|
)]
|
|
|
|
|
|
@arr_router.get("/series/{tid}", summary="剧集详情")
|
|
def arr_serie(apikey: str, tid: int, db: Session = Depends(get_db)) -> Any:
|
|
"""
|
|
查询Sonarr剧集
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
subscribe = Subscribe.get(db, tid)
|
|
if subscribe:
|
|
return SonarrSeries(
|
|
id=subscribe.id,
|
|
title=subscribe.name,
|
|
seasonCount=1,
|
|
seasons=[{
|
|
"seasonNumber": subscribe.season,
|
|
"monitored": True,
|
|
}],
|
|
year=subscribe.year,
|
|
remotePoster=subscribe.image,
|
|
tmdbId=subscribe.tmdbid,
|
|
tvdbId=subscribe.tvdbid,
|
|
imdbId=subscribe.imdbid,
|
|
profileId=1,
|
|
languageProfileId=1,
|
|
qualityProfileId=1,
|
|
isAvailable=True,
|
|
monitored=True,
|
|
hasFile=False
|
|
)
|
|
else:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="未找到该电视剧!"
|
|
)
|
|
|
|
|
|
@arr_router.post("/series", summary="新增剧集订阅")
|
|
def arr_add_series(apikey: str, tv: schemas.SonarrSeries) -> Any:
|
|
"""
|
|
新增Sonarr剧集订阅
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
sid = 0
|
|
message = ""
|
|
for season in tv.seasons:
|
|
if not season.get("monitored"):
|
|
continue
|
|
sid, message = SubscribeChain().add(title=tv.title,
|
|
year=tv.year,
|
|
season=season.get("seasonNumber"),
|
|
tmdbid=tv.tmdbId,
|
|
mtype=MediaType.TV,
|
|
userid="Seerr")
|
|
|
|
if sid:
|
|
return {
|
|
"id": sid
|
|
}
|
|
else:
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail=f"添加订阅失败:{message}"
|
|
)
|
|
|
|
|
|
@arr_router.delete("/series/{tid}", summary="删除剧集订阅")
|
|
def arr_remove_series(apikey: str, tid: int, db: Session = Depends(get_db)) -> Any:
|
|
"""
|
|
删除Sonarr剧集订阅
|
|
"""
|
|
if not apikey or apikey != settings.API_TOKEN:
|
|
raise HTTPException(
|
|
status_code=403,
|
|
detail="认证失败!",
|
|
)
|
|
subscribe = Subscribe.get(db, tid)
|
|
if subscribe:
|
|
subscribe.delete(db, tid)
|
|
return schemas.Response(success=True)
|
|
else:
|
|
raise HTTPException(
|
|
status_code=404,
|
|
detail="未找到该电视剧!"
|
|
)
|