mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-03-20 03:57:30 +08:00
Merge remote-tracking branch 'upstream/dev' into feature/db
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -14,6 +14,9 @@ app/plugins/**
|
||||
config/cookies/**
|
||||
config/user.db
|
||||
config/sites/**
|
||||
config/logs/
|
||||
config/temp/
|
||||
config/cache/
|
||||
*.pyc
|
||||
*.log
|
||||
.vscode
|
||||
|
||||
@@ -398,7 +398,7 @@ def user_subscribes(
|
||||
return Subscribe.list_by_username(db, username)
|
||||
|
||||
|
||||
@router.get("/files/{subscribe_id}", summary="订阅相关文件信息", response_model=List[schemas.SubscrbieInfo])
|
||||
@router.get("/files/{subscribe_id}", summary="订阅相关文件信息", response_model=schemas.SubscrbieInfo)
|
||||
def subscribe_files(
|
||||
subscribe_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
@@ -30,6 +30,10 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
# 临时识别结果 {title, name, year, season, episode}
|
||||
recognize_temp: Optional[dict] = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.storagechain = StorageChain()
|
||||
|
||||
def metadata_nfo(self, meta: MetaBase, mediainfo: MediaInfo,
|
||||
season: int = None, episode: int = None) -> Optional[str]:
|
||||
"""
|
||||
@@ -348,37 +352,35 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
|
||||
def scrape_metadata(self, fileitem: schemas.FileItem,
|
||||
meta: MetaBase = None, mediainfo: MediaInfo = None,
|
||||
init_folder: bool = True, parent: schemas.FileItem = None):
|
||||
init_folder: bool = True, parent: schemas.FileItem = None,
|
||||
overwrite: bool = False):
|
||||
"""
|
||||
手动刮削媒体信息
|
||||
:param fileitem: 刮削目录或文件
|
||||
:param meta: 元数据
|
||||
:param mediainfo: 媒体信息
|
||||
:param init_folder: 是否刮削根目录
|
||||
:param parent: 上级目录
|
||||
:param overwrite: 是否覆盖已有文件
|
||||
"""
|
||||
|
||||
def __list_files(_fileitem: schemas.FileItem):
|
||||
"""
|
||||
列出下级文件
|
||||
"""
|
||||
return StorageChain().list_files(fileitem=_fileitem)
|
||||
return self.storagechain.list_files(fileitem=_fileitem)
|
||||
|
||||
def __save_file(_fileitem: schemas.FileItem, _path: Path, _content: Union[bytes, str],
|
||||
overwrite: bool = False):
|
||||
def __save_file(_fileitem: schemas.FileItem, _path: Path, _content: Union[bytes, str]):
|
||||
"""
|
||||
保存或上传文件
|
||||
:param _fileitem: 关联的媒体文件项
|
||||
:param _path: 元数据文件路径
|
||||
:param _content: 文件内容
|
||||
:param overwrite: 是否覆盖
|
||||
"""
|
||||
if not overwrite and _path.exists():
|
||||
return
|
||||
tmp_file = settings.TEMP_PATH / _path.name
|
||||
tmp_file.write_bytes(_content)
|
||||
upload_item = copy.deepcopy(_fileitem)
|
||||
upload_item.path = str(_path)
|
||||
upload_item.name = _path.name
|
||||
upload_item.basename = _path.stem
|
||||
upload_item.extension = _path.suffix
|
||||
logger.info(f"保存文件:【{_fileitem.storage}】{_path}")
|
||||
StorageChain().upload_file(fileitem=upload_item, path=tmp_file)
|
||||
self.storagechain.upload_file(fileitem=_fileitem, path=tmp_file)
|
||||
if tmp_file.exists():
|
||||
tmp_file.unlink()
|
||||
|
||||
@@ -419,7 +421,11 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
logger.warn(f"{filepath.name} nfo文件生成失败!")
|
||||
return
|
||||
# 保存或上传nfo文件到上级目录
|
||||
__save_file(_fileitem=parent, _path=filepath.with_suffix(".nfo"), _content=movie_nfo)
|
||||
nfo_path = filepath.with_suffix(".nfo")
|
||||
if not overwrite and self.storagechain.get_file_item(storage=fileitem.storage, path=nfo_path):
|
||||
logger.info(f"已存在nfo文件:{nfo_path}")
|
||||
return
|
||||
__save_file(_fileitem=parent, _path=nfo_path, _content=movie_nfo)
|
||||
else:
|
||||
# 电影目录
|
||||
files = __list_files(_fileitem=fileitem)
|
||||
@@ -438,6 +444,9 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
and attr_value.startswith("http"):
|
||||
image_name = attr_name.replace("_path", "") + Path(attr_value).suffix
|
||||
image_path = filepath / image_name
|
||||
if not overwrite and self.storagechain.get_file_item(storage=fileitem.storage, path=image_path):
|
||||
logger.info(f"已存在图片文件:{image_path}")
|
||||
continue
|
||||
# 下载图片
|
||||
content = __download_image(_url=attr_value)
|
||||
# 写入图片到当前目录
|
||||
@@ -461,17 +470,24 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
logger.warn(f"{filepath.name} nfo生成失败!")
|
||||
return
|
||||
# 保存或上传nfo文件到上级目录
|
||||
__save_file(_fileitem=parent, _path=filepath.with_suffix(".nfo"), _content=episode_nfo)
|
||||
nfo_path = filepath.with_suffix(".nfo")
|
||||
if not overwrite and self.storagechain.get_file_item(storage=fileitem.storage, path=nfo_path):
|
||||
logger.info(f"已存在nfo文件:{nfo_path}")
|
||||
return
|
||||
__save_file(_fileitem=parent, _path=nfo_path, _content=episode_nfo)
|
||||
# 获取集的图片
|
||||
image_dict = self.metadata_img(mediainfo=file_mediainfo,
|
||||
season=file_meta.begin_season, episode=file_meta.begin_episode)
|
||||
if image_dict:
|
||||
for episode, image_url in image_dict.items():
|
||||
image_path = filepath.with_suffix(Path(image_url).suffix)
|
||||
if not overwrite and self.storagechain.get_file_item(storage=fileitem.storage, path=image_path):
|
||||
logger.info(f"已存在图片文件:{image_path}")
|
||||
continue
|
||||
# 下载图片
|
||||
content = __download_image(image_url)
|
||||
# 保存图片文件到当前目录
|
||||
__save_file(_fileitem=fileitem, _path=image_path, _content=content)
|
||||
__save_file(_fileitem=parent, _path=image_path, _content=content)
|
||||
|
||||
else:
|
||||
# 当前为目录,处理目录内的文件
|
||||
@@ -493,12 +509,18 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
return
|
||||
# 写入nfo到根目录
|
||||
nfo_path = filepath / "season.nfo"
|
||||
if not overwrite and self.storagechain.get_file_item(storage=fileitem.storage, path=nfo_path):
|
||||
logger.info(f"已存在nfo文件:{nfo_path}")
|
||||
return
|
||||
__save_file(_fileitem=fileitem, _path=nfo_path, _content=season_nfo)
|
||||
# TMDB季poster图片
|
||||
image_dict = self.metadata_img(mediainfo=mediainfo, season=season_meta.begin_season)
|
||||
if image_dict:
|
||||
for image_name, image_url in image_dict.items():
|
||||
image_path = filepath.with_name(image_name)
|
||||
if not overwrite and self.storagechain.get_file_item(storage=fileitem.storage, path=image_path):
|
||||
logger.info(f"已存在图片文件:{image_path}")
|
||||
continue
|
||||
# 下载图片
|
||||
content = __download_image(image_url)
|
||||
# 保存图片文件到当前目录
|
||||
@@ -511,12 +533,18 @@ class MediaChain(ChainBase, metaclass=Singleton):
|
||||
return
|
||||
# 写入tvshow nfo到根目录
|
||||
nfo_path = filepath / "tvshow.nfo"
|
||||
if not overwrite and self.storagechain.get_file_item(storage=fileitem.storage, path=nfo_path):
|
||||
logger.info(f"已存在nfo文件:{nfo_path}")
|
||||
return
|
||||
__save_file(_fileitem=fileitem, _path=nfo_path, _content=tv_nfo)
|
||||
# 生成目录图片
|
||||
image_dict = self.metadata_img(mediainfo=mediainfo)
|
||||
if image_dict:
|
||||
for image_name, image_url in image_dict.items():
|
||||
image_path = filepath / image_name
|
||||
if not overwrite and self.storagechain.get_file_item(storage=fileitem.storage, path=image_path):
|
||||
logger.info(f"已存在图片文件:{image_path}")
|
||||
continue
|
||||
# 下载图片
|
||||
content = __download_image(image_url)
|
||||
# 保存图片文件到当前目录
|
||||
|
||||
@@ -64,6 +64,7 @@ class SiteChain(ChainBase):
|
||||
userdata: SiteUserData = self.run_module("refresh_userdata", site=site)
|
||||
if userdata:
|
||||
self.siteoper.update_userdata(domain=StringUtils.get_url_domain(site.get("domain")),
|
||||
name=site.get("name"),
|
||||
payload=userdata.dict())
|
||||
return userdata
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ from app.helper.message import MessageHelper
|
||||
from app.helper.subscribe import SubscribeHelper
|
||||
from app.helper.torrent import TorrentHelper
|
||||
from app.log import logger
|
||||
from app.schemas import NotExistMediaInfo, Notification, SubscrbieInfo, SubscribeEpisodeInfo
|
||||
from app.schemas import NotExistMediaInfo, Notification, SubscrbieInfo, SubscribeEpisodeInfo, SubscribeDownloadFileInfo, \
|
||||
SubscribeLibraryFileInfo
|
||||
from app.schemas.types import MediaType, SystemConfigKey, MessageChannel, NotificationType, EventType
|
||||
|
||||
|
||||
@@ -1167,47 +1168,34 @@ class SubscribeChain(ChainBase):
|
||||
return
|
||||
|
||||
# 返回订阅数据
|
||||
subscribe_info = SubscrbieInfo(
|
||||
id=subscribe.id,
|
||||
name=subscribe.name,
|
||||
year=subscribe.year,
|
||||
type=subscribe.type,
|
||||
tmdbid=subscribe.tmdbid,
|
||||
doubanid=subscribe.doubanid,
|
||||
season=subscribe.season,
|
||||
poster=subscribe.poster,
|
||||
backdrop=subscribe.backdrop,
|
||||
vote=subscribe.vote,
|
||||
description=subscribe.description,
|
||||
episodes_info={}
|
||||
)
|
||||
subscribe_info = SubscrbieInfo()
|
||||
|
||||
# 所有集的数据
|
||||
episodes_info = {}
|
||||
episodes: Dict[int, SubscribeEpisodeInfo] = {}
|
||||
if subscribe.tmdbid and subscribe.type == MediaType.TV.value:
|
||||
# 查询TMDB中的集信息
|
||||
tmdb_episodes = self.tmdbchain.tmdb_episodes(
|
||||
tmdbid=subscribe.tmdb_id,
|
||||
tmdbid=subscribe.tmdbid,
|
||||
season=subscribe.season
|
||||
)
|
||||
if tmdb_episodes:
|
||||
for episode in tmdb_episodes:
|
||||
episode_info = SubscribeEpisodeInfo()
|
||||
episodes_info.title = episode.name
|
||||
episodes_info.description = episode.overview
|
||||
episodes_info.backdrop = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/w500${episode.still_path}"
|
||||
episodes_info[episode.episode_number] = episode_info
|
||||
info = SubscribeEpisodeInfo()
|
||||
info.title = episode.name
|
||||
info.description = episode.overview
|
||||
info.backdrop = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/w500${episode.still_path}"
|
||||
episodes[episode.episode_number] = info
|
||||
elif subscribe.type == MediaType.TV.value:
|
||||
# 根据开始结束集计算集信息
|
||||
for i in range(subscribe.start_episode or 1, subscribe.total_episode + 1):
|
||||
episode_info = SubscribeEpisodeInfo()
|
||||
episode_info.title = f'第 {i} 集'
|
||||
episodes_info[i] = episode_info
|
||||
info = SubscribeEpisodeInfo()
|
||||
info.title = f'第 {i} 集'
|
||||
episodes[i] = info
|
||||
else:
|
||||
# 电影
|
||||
episode_info = SubscribeEpisodeInfo()
|
||||
episode_info.title = subscribe.name
|
||||
episodes_info[0] = episode_info
|
||||
info = SubscribeEpisodeInfo()
|
||||
info.title = subscribe.name
|
||||
episodes[0] = info
|
||||
|
||||
# 所有下载记录
|
||||
download_his = self.downloadhis.get_by_mediaid(tmdbid=subscribe.tmdbid, doubanid=subscribe.doubanid)
|
||||
@@ -1221,14 +1209,20 @@ class SubscribeChain(ChainBase):
|
||||
for file in files:
|
||||
# 识别文件名
|
||||
file_meta = MetaInfo(file.filepath)
|
||||
# 下载文件信息
|
||||
file_info = SubscribeDownloadFileInfo(
|
||||
torrent_title=his.torrent_name,
|
||||
site_name=his.torrent_site,
|
||||
downloader=file.downloader,
|
||||
hash=his.download_hash,
|
||||
file_path=file.fullpath,
|
||||
)
|
||||
if subscribe.type == MediaType.TV.value:
|
||||
episode_number = file_meta.begin_episode
|
||||
if episode_number and episodes_info.get(episode_number):
|
||||
episodes_info[episode_number].download_file = file.fullpath
|
||||
episodes_info[episode_number].torrent = torrent_url
|
||||
if episode_number and episodes.get(episode_number):
|
||||
episodes[episode_number].download.append(file_info)
|
||||
else:
|
||||
episodes_info[0].download_file = file.fullpath
|
||||
episodes_info[0].torrent = torrent_url
|
||||
episodes[0].download.append(file_info)
|
||||
|
||||
# 生成元数据
|
||||
meta = MetaInfo(subscribe.name)
|
||||
@@ -1255,13 +1249,19 @@ class SubscribeChain(ChainBase):
|
||||
for fileitem in library_fileitems:
|
||||
# 识别文件名
|
||||
file_meta = MetaInfo(fileitem.path)
|
||||
# 媒体库文件信息
|
||||
file_info = SubscribeLibraryFileInfo(
|
||||
storage=fileitem.storage,
|
||||
file_path=fileitem.path,
|
||||
)
|
||||
if subscribe.type == MediaType.TV.value:
|
||||
episode_number = file_meta.begin_episode
|
||||
if episode_number and episodes_info.get(episode_number):
|
||||
episodes_info[episode_number].library_file = fileitem.path
|
||||
if episode_number and episodes.get(episode_number):
|
||||
episodes[episode_number].library.append(file_info)
|
||||
else:
|
||||
episodes_info[0].library_file = fileitem.path
|
||||
episodes[0].library.append(file_info)
|
||||
|
||||
# 更新订阅信息
|
||||
subscribe_info.episodes_info = episodes_info
|
||||
subscribe_info.subscribe = Subscribe(**subscribe.to_dict())
|
||||
subscribe_info.episodes = episodes
|
||||
return subscribe_info
|
||||
|
||||
@@ -13,6 +13,8 @@ class SiteUserData(Base):
|
||||
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
|
||||
# 站点域名
|
||||
domain = Column(String, index=True)
|
||||
# 站点名称
|
||||
name = Column(String)
|
||||
# 用户名
|
||||
username = Column(String)
|
||||
# 用户ID
|
||||
|
||||
@@ -104,7 +104,7 @@ class SiteOper(DbOper):
|
||||
})
|
||||
return True, "更新站点RSS地址成功"
|
||||
|
||||
def update_userdata(self, domain: str, payload: dict) -> Tuple[bool, str]:
|
||||
def update_userdata(self, domain: str, name: str, payload: dict) -> Tuple[bool, str]:
|
||||
"""
|
||||
更新站点用户数据
|
||||
"""
|
||||
@@ -113,6 +113,7 @@ class SiteOper(DbOper):
|
||||
current_time = datetime.now().strftime('%H:%M:%S')
|
||||
payload.update({
|
||||
"domain": domain,
|
||||
"name": name,
|
||||
"updated_day": current_day,
|
||||
"updated_time": current_time
|
||||
})
|
||||
@@ -127,6 +128,12 @@ class SiteOper(DbOper):
|
||||
SiteUserData(**payload).create(self._db)
|
||||
return True, "更新站点用户数据成功"
|
||||
|
||||
def get_userdata(self) -> List[SiteUserData]:
|
||||
"""
|
||||
获取站点用户数据
|
||||
"""
|
||||
return SiteUserData.list(self._db)
|
||||
|
||||
def get_userdata_by_domain(self, domain: str, workdate: str = None) -> List[SiteUserData]:
|
||||
"""
|
||||
获取站点用户数据
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
class DownloaderHelper:
|
||||
from app.helper.servicebase import ServiceBaseHelper
|
||||
from app.schemas import DownloaderConf
|
||||
from app.schemas.types import SystemConfigKey
|
||||
|
||||
|
||||
class DownloaderHelper(ServiceBaseHelper[DownloaderConf]):
|
||||
"""
|
||||
下载器帮助类
|
||||
"""
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
config_key=SystemConfigKey.Downloaders,
|
||||
conf_type=DownloaderConf,
|
||||
modules=["QbittorrentModule", "TransmissionModule"]
|
||||
)
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
class MediaServerHelper:
|
||||
from app.helper.servicebase import ServiceBaseHelper
|
||||
from app.schemas import MediaServerConf
|
||||
from app.schemas.types import SystemConfigKey
|
||||
|
||||
|
||||
class MediaServerHelper(ServiceBaseHelper[MediaServerConf]):
|
||||
"""
|
||||
媒体服务器帮助类
|
||||
"""
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
config_key=SystemConfigKey.MediaServers,
|
||||
conf_type=MediaServerConf,
|
||||
modules=["PlexModule", "EmbyModule", "JellyfinModule"]
|
||||
)
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
class NotificationHelper:
|
||||
from app.helper.servicebase import ServiceBaseHelper
|
||||
from app.schemas import NotificationConf
|
||||
from app.schemas.types import SystemConfigKey
|
||||
|
||||
|
||||
class NotificationHelper(ServiceBaseHelper[NotificationConf]):
|
||||
"""
|
||||
消息通知渠道帮助类
|
||||
消息通知帮助类
|
||||
"""
|
||||
pass
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
config_key=SystemConfigKey.Notifications,
|
||||
conf_type=NotificationConf,
|
||||
modules=["WechatModule", "WebPushModule", "VoceChatModule", "TelegramModule", "SynologyChatModule",
|
||||
"SlackModule"]
|
||||
)
|
||||
|
||||
97
app/helper/servicebase.py
Normal file
97
app/helper/servicebase.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from typing import Dict, List, Optional, Type, TypeVar, Generic, Iterator
|
||||
|
||||
from app.core.module import ModuleManager
|
||||
from app.helper.serviceconfig import ServiceConfigHelper
|
||||
from app.schemas import ServiceInfo
|
||||
from app.schemas.types import SystemConfigKey
|
||||
|
||||
TConf = TypeVar("TConf")
|
||||
|
||||
|
||||
class ServiceBaseHelper(Generic[TConf]):
|
||||
"""
|
||||
通用服务帮助类,抽象获取配置和服务实例的通用逻辑
|
||||
"""
|
||||
|
||||
def __init__(self, config_key: SystemConfigKey, conf_type: Type[TConf], modules: List[str]):
|
||||
self.modulemanager = ModuleManager()
|
||||
self.config_key = config_key
|
||||
self.conf_type = conf_type
|
||||
self.modules = modules
|
||||
|
||||
def get_configs(self, include_disabled: bool = False) -> Dict[str, TConf]:
|
||||
"""
|
||||
获取配置列表
|
||||
|
||||
:param include_disabled: 是否包含禁用的配置,默认 False(仅返回启用的配置)
|
||||
:return: 配置字典
|
||||
"""
|
||||
configs: List[TConf] = ServiceConfigHelper.get_configs(self.config_key, self.conf_type)
|
||||
return {
|
||||
config.name: config
|
||||
for config in configs
|
||||
if (config.name and config.type and config.enabled) or include_disabled
|
||||
} if configs else {}
|
||||
|
||||
def get_config(self, name: str) -> Optional[TConf]:
|
||||
"""
|
||||
获取指定名称配置
|
||||
"""
|
||||
if not name:
|
||||
return None
|
||||
configs = self.get_configs()
|
||||
return configs.get(name)
|
||||
|
||||
def iterate_module_instances(self) -> Iterator[ServiceInfo]:
|
||||
"""
|
||||
迭代所有模块的实例及其对应的配置,返回 ServiceInfo 实例
|
||||
"""
|
||||
configs = self.get_configs()
|
||||
for module_name in self.modules:
|
||||
module = self.modulemanager.get_running_module(module_name)
|
||||
if not module:
|
||||
continue
|
||||
module_instances = module.get_instances()
|
||||
if not isinstance(module_instances, dict):
|
||||
continue
|
||||
for name, instance in module_instances.items():
|
||||
if not instance:
|
||||
continue
|
||||
config = configs.get(name)
|
||||
service_info = ServiceInfo(
|
||||
name=name,
|
||||
instance=instance,
|
||||
module=module,
|
||||
type=config.type if config else None,
|
||||
config=config
|
||||
)
|
||||
yield service_info
|
||||
|
||||
def get_services(self, type_filter: Optional[str] = None) -> Dict[str, ServiceInfo]:
|
||||
"""
|
||||
获取服务信息列表,并根据类型过滤
|
||||
|
||||
:param type_filter: 需要过滤的服务类型
|
||||
:return: 过滤后的服务信息字典
|
||||
"""
|
||||
return {
|
||||
service_info.name: service_info
|
||||
for service_info in self.iterate_module_instances()
|
||||
if service_info.config and (type_filter is None or service_info.type == type_filter)
|
||||
}
|
||||
|
||||
def get_service(self, name: str, type_filter: Optional[str] = None) -> Optional[ServiceInfo]:
|
||||
"""
|
||||
获取指定名称的服务信息,并根据类型过滤
|
||||
|
||||
:param name: 服务名称
|
||||
:param type_filter: 需要过滤的服务类型
|
||||
:return: 对应的服务信息,若不存在或类型不匹配则返回 None
|
||||
"""
|
||||
if not name:
|
||||
return None
|
||||
for service_info in self.iterate_module_instances():
|
||||
if service_info.name == name:
|
||||
if service_info.config and (type_filter is None or service_info.type == type_filter):
|
||||
return service_info
|
||||
return None
|
||||
@@ -56,7 +56,7 @@ class ServiceConfigHelper:
|
||||
@staticmethod
|
||||
def get_notification_switch(mtype: NotificationType) -> Optional[str]:
|
||||
"""
|
||||
获取消息通知场景开关
|
||||
获取指定类型的消息通知场景的开关
|
||||
"""
|
||||
switchs = ServiceConfigHelper.get_notification_switches()
|
||||
for switch in switchs:
|
||||
|
||||
@@ -90,6 +90,14 @@ class ServiceBase(Generic[TService, TConf], metaclass=ABCMeta):
|
||||
# 如果传入的是工厂函数,直接调用工厂函数
|
||||
self._instances[conf.name] = service_type(conf)
|
||||
|
||||
def get_instances(self) -> Dict[str, TService]:
|
||||
"""
|
||||
获取服务实例列表
|
||||
|
||||
:return: 返回服务实例列表
|
||||
"""
|
||||
return self._instances
|
||||
|
||||
def get_instance(self, name: str) -> Optional[TService]:
|
||||
"""
|
||||
获取服务实例
|
||||
|
||||
@@ -187,7 +187,7 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
|
||||
"""
|
||||
server: Emby = self.get_instance(server)
|
||||
if server:
|
||||
yield from server.get_items(library_id, start_index, limit)
|
||||
return server.get_items(library_id, start_index, limit)
|
||||
return None
|
||||
|
||||
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
|
||||
|
||||
@@ -1093,9 +1093,6 @@ class FileManagerModule(_ModuleBase):
|
||||
media_path = dir_path / rel_path.parts[0]
|
||||
else:
|
||||
continue
|
||||
# 检查媒体文件夹是否存在
|
||||
if not media_path.exists():
|
||||
continue
|
||||
# 检索媒体文件
|
||||
fileitem = storage_oper.get_item(media_path)
|
||||
if not fileitem:
|
||||
|
||||
@@ -185,7 +185,7 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
|
||||
"""
|
||||
server: Jellyfin = self.get_instance(server)
|
||||
if server:
|
||||
yield from server.get_items(library_id, start_index, limit)
|
||||
return server.get_items(library_id, start_index, limit)
|
||||
return None
|
||||
|
||||
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
|
||||
|
||||
@@ -173,7 +173,7 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
|
||||
"""
|
||||
server: Plex = self.get_instance(server)
|
||||
if server:
|
||||
yield from server.get_items(library_id, start_index, limit)
|
||||
return server.get_items(library_id, start_index, limit)
|
||||
return None
|
||||
|
||||
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
|
||||
|
||||
@@ -81,43 +81,41 @@ class Subscribe(BaseModel):
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class SubscribeDownloadFileInfo(BaseModel):
|
||||
# 种子名称
|
||||
torrent_title: Optional[str] = None
|
||||
# 站点名称
|
||||
site_name: Optional[str] = None
|
||||
# 下载器
|
||||
downloader: Optional[str] = None
|
||||
# hash
|
||||
hash: Optional[str] = None
|
||||
# 文件路径
|
||||
file_path: Optional[str] = None
|
||||
|
||||
|
||||
class SubscribeLibraryFileInfo(BaseModel):
|
||||
# 存储
|
||||
storage: Optional[str] = "local"
|
||||
# 文件路径
|
||||
file_path: Optional[str] = None
|
||||
|
||||
|
||||
class SubscribeEpisodeInfo(BaseModel):
|
||||
# 种子地址
|
||||
torrent: Optional[str] = None
|
||||
# 下载文件路程
|
||||
download_file: Optional[str] = None
|
||||
# 媒体库文件路径
|
||||
library_file: Optional[str] = None
|
||||
# 标题
|
||||
title: Optional[str] = None
|
||||
# 描述
|
||||
description: Optional[str] = None
|
||||
# 背景图
|
||||
backdrop: Optional[str] = None
|
||||
# 下载文件信息
|
||||
download: Optional[List[SubscribeDownloadFileInfo]] = []
|
||||
# 媒体库文件信息
|
||||
library: Optional[List[SubscribeLibraryFileInfo]] = []
|
||||
|
||||
|
||||
class SubscrbieInfo(BaseModel):
|
||||
# 订阅ID
|
||||
id: Optional[int] = None
|
||||
# 订阅名称
|
||||
name: Optional[str] = None
|
||||
# 订阅年份
|
||||
year: Optional[str] = None
|
||||
# 订阅类型 电影/电视剧
|
||||
type: Optional[str] = None
|
||||
# 媒体ID
|
||||
tmdbid: Optional[int] = None
|
||||
doubanid: Optional[str] = None
|
||||
bangumiid: Optional[int] = None
|
||||
# 季号
|
||||
season: Optional[int] = None
|
||||
# 海报
|
||||
poster: Optional[str] = None
|
||||
# 背景图
|
||||
backdrop: Optional[str] = None
|
||||
# 评分
|
||||
vote: Optional[int] = 0
|
||||
# 描述
|
||||
description: Optional[str] = None
|
||||
# 订阅信息
|
||||
subscribe: Optional[Subscribe] = None
|
||||
# 集信息 {集号: {download: 文件路径,library: 文件路径, backdrop: url, title: 标题, description: 描述}}
|
||||
episodes_info: Optional[Dict[int, SubscribeEpisodeInfo]] = {}
|
||||
episodes: Optional[Dict[int, SubscribeEpisodeInfo]] = {}
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
from typing import Optional
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceInfo:
|
||||
"""
|
||||
封装服务相关信息的数据类
|
||||
"""
|
||||
# 名称
|
||||
name: Optional[str] = None
|
||||
# 实例
|
||||
instance: Optional[Any] = None
|
||||
# 模块
|
||||
module: Optional[Any] = None
|
||||
# 类型
|
||||
type: Optional[str] = None
|
||||
# 配置
|
||||
config: Optional[Any] = None
|
||||
|
||||
|
||||
class MediaServerConf(BaseModel):
|
||||
"""
|
||||
媒体服务器配置
|
||||
|
||||
@@ -18,8 +18,10 @@ class TorrentStatus(Enum):
|
||||
class EventType(Enum):
|
||||
# 插件需要重载
|
||||
PluginReload = "plugin.reload"
|
||||
# 插件动作
|
||||
# 触发插件动作
|
||||
PluginAction = "plugin.action"
|
||||
# 插件触发事件
|
||||
PluginTriggered = "plugin.triggered"
|
||||
# 执行命令
|
||||
CommandExcute = "command.excute"
|
||||
# 站点已删除
|
||||
|
||||
30
database/versions/0fb94bf69b38_2_0_2.py
Normal file
30
database/versions/0fb94bf69b38_2_0_2.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""2.0.2
|
||||
|
||||
Revision ID: 0fb94bf69b38
|
||||
Revises: 262735d025da
|
||||
Create Date: 2024-09-30 10:03:58.546036
|
||||
|
||||
"""
|
||||
import contextlib
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0fb94bf69b38'
|
||||
down_revision = '262735d025da'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with contextlib.suppress(Exception):
|
||||
with op.batch_alter_table("siteuserdata") as batch_op:
|
||||
batch_op.add_column(sa.Column('name', sa.VARCHAR))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
pass
|
||||
Reference in New Issue
Block a user