Merge remote-tracking branch 'upstream/dev' into feature/db

This commit is contained in:
InfinityPacer
2024-09-30 14:31:20 +08:00
21 changed files with 325 additions and 100 deletions
+3
View File
@@ -14,6 +14,9 @@ app/plugins/**
config/cookies/** config/cookies/**
config/user.db config/user.db
config/sites/** config/sites/**
config/logs/
config/temp/
config/cache/
*.pyc *.pyc
*.log *.log
.vscode .vscode
+1 -1
View File
@@ -398,7 +398,7 @@ def user_subscribes(
return Subscribe.list_by_username(db, username) 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( def subscribe_files(
subscribe_id: int, subscribe_id: int,
db: Session = Depends(get_db), db: Session = Depends(get_db),
+44 -16
View File
@@ -30,6 +30,10 @@ class MediaChain(ChainBase, metaclass=Singleton):
# 临时识别结果 {title, name, year, season, episode} # 临时识别结果 {title, name, year, season, episode}
recognize_temp: Optional[dict] = None recognize_temp: Optional[dict] = None
def __init__(self):
super().__init__()
self.storagechain = StorageChain()
def metadata_nfo(self, meta: MetaBase, mediainfo: MediaInfo, def metadata_nfo(self, meta: MetaBase, mediainfo: MediaInfo,
season: int = None, episode: int = None) -> Optional[str]: season: int = None, episode: int = None) -> Optional[str]:
""" """
@@ -348,37 +352,35 @@ class MediaChain(ChainBase, metaclass=Singleton):
def scrape_metadata(self, fileitem: schemas.FileItem, def scrape_metadata(self, fileitem: schemas.FileItem,
meta: MetaBase = None, mediainfo: MediaInfo = None, 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): 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], def __save_file(_fileitem: schemas.FileItem, _path: Path, _content: Union[bytes, str]):
overwrite: bool = False):
""" """
保存或上传文件 保存或上传文件
:param _fileitem: 关联的媒体文件项 :param _fileitem: 关联的媒体文件项
:param _path: 元数据文件路径 :param _path: 元数据文件路径
:param _content: 文件内容 :param _content: 文件内容
:param overwrite: 是否覆盖
""" """
if not overwrite and _path.exists():
return
tmp_file = settings.TEMP_PATH / _path.name tmp_file = settings.TEMP_PATH / _path.name
tmp_file.write_bytes(_content) 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}") 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(): if tmp_file.exists():
tmp_file.unlink() tmp_file.unlink()
@@ -419,7 +421,11 @@ class MediaChain(ChainBase, metaclass=Singleton):
logger.warn(f"{filepath.name} nfo文件生成失败!") logger.warn(f"{filepath.name} nfo文件生成失败!")
return return
# 保存或上传nfo文件到上级目录 # 保存或上传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: else:
# 电影目录 # 电影目录
files = __list_files(_fileitem=fileitem) files = __list_files(_fileitem=fileitem)
@@ -438,6 +444,9 @@ class MediaChain(ChainBase, metaclass=Singleton):
and attr_value.startswith("http"): and attr_value.startswith("http"):
image_name = attr_name.replace("_path", "") + Path(attr_value).suffix image_name = attr_name.replace("_path", "") + Path(attr_value).suffix
image_path = filepath / image_name 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) content = __download_image(_url=attr_value)
# 写入图片到当前目录 # 写入图片到当前目录
@@ -461,17 +470,24 @@ class MediaChain(ChainBase, metaclass=Singleton):
logger.warn(f"{filepath.name} nfo生成失败!") logger.warn(f"{filepath.name} nfo生成失败!")
return return
# 保存或上传nfo文件到上级目录 # 保存或上传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, image_dict = self.metadata_img(mediainfo=file_mediainfo,
season=file_meta.begin_season, episode=file_meta.begin_episode) season=file_meta.begin_season, episode=file_meta.begin_episode)
if image_dict: if image_dict:
for episode, image_url in image_dict.items(): for episode, image_url in image_dict.items():
image_path = filepath.with_suffix(Path(image_url).suffix) 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) content = __download_image(image_url)
# 保存图片文件到当前目录 # 保存图片文件到当前目录
__save_file(_fileitem=fileitem, _path=image_path, _content=content) __save_file(_fileitem=parent, _path=image_path, _content=content)
else: else:
# 当前为目录,处理目录内的文件 # 当前为目录,处理目录内的文件
@@ -493,12 +509,18 @@ class MediaChain(ChainBase, metaclass=Singleton):
return return
# 写入nfo到根目录 # 写入nfo到根目录
nfo_path = filepath / "season.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) __save_file(_fileitem=fileitem, _path=nfo_path, _content=season_nfo)
# TMDB季poster图片 # TMDB季poster图片
image_dict = self.metadata_img(mediainfo=mediainfo, season=season_meta.begin_season) image_dict = self.metadata_img(mediainfo=mediainfo, season=season_meta.begin_season)
if image_dict: if image_dict:
for image_name, image_url in image_dict.items(): for image_name, image_url in image_dict.items():
image_path = filepath.with_name(image_name) 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) content = __download_image(image_url)
# 保存图片文件到当前目录 # 保存图片文件到当前目录
@@ -511,12 +533,18 @@ class MediaChain(ChainBase, metaclass=Singleton):
return return
# 写入tvshow nfo到根目录 # 写入tvshow nfo到根目录
nfo_path = filepath / "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) __save_file(_fileitem=fileitem, _path=nfo_path, _content=tv_nfo)
# 生成目录图片 # 生成目录图片
image_dict = self.metadata_img(mediainfo=mediainfo) image_dict = self.metadata_img(mediainfo=mediainfo)
if image_dict: if image_dict:
for image_name, image_url in image_dict.items(): for image_name, image_url in image_dict.items():
image_path = filepath / image_name 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) content = __download_image(image_url)
# 保存图片文件到当前目录 # 保存图片文件到当前目录
+1
View File
@@ -64,6 +64,7 @@ class SiteChain(ChainBase):
userdata: SiteUserData = self.run_module("refresh_userdata", site=site) userdata: SiteUserData = self.run_module("refresh_userdata", site=site)
if userdata: if userdata:
self.siteoper.update_userdata(domain=StringUtils.get_url_domain(site.get("domain")), self.siteoper.update_userdata(domain=StringUtils.get_url_domain(site.get("domain")),
name=site.get("name"),
payload=userdata.dict()) payload=userdata.dict())
return userdata return userdata
+37 -37
View File
@@ -25,7 +25,8 @@ from app.helper.message import MessageHelper
from app.helper.subscribe import SubscribeHelper from app.helper.subscribe import SubscribeHelper
from app.helper.torrent import TorrentHelper from app.helper.torrent import TorrentHelper
from app.log import logger 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 from app.schemas.types import MediaType, SystemConfigKey, MessageChannel, NotificationType, EventType
@@ -1167,47 +1168,34 @@ class SubscribeChain(ChainBase):
return return
# 返回订阅数据 # 返回订阅数据
subscribe_info = SubscrbieInfo( 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={}
)
# 所有集的数据 # 所有集的数据
episodes_info = {} episodes: Dict[int, SubscribeEpisodeInfo] = {}
if subscribe.tmdbid and subscribe.type == MediaType.TV.value: if subscribe.tmdbid and subscribe.type == MediaType.TV.value:
# 查询TMDB中的集信息 # 查询TMDB中的集信息
tmdb_episodes = self.tmdbchain.tmdb_episodes( tmdb_episodes = self.tmdbchain.tmdb_episodes(
tmdbid=subscribe.tmdb_id, tmdbid=subscribe.tmdbid,
season=subscribe.season season=subscribe.season
) )
if tmdb_episodes: if tmdb_episodes:
for episode in tmdb_episodes: for episode in tmdb_episodes:
episode_info = SubscribeEpisodeInfo() info = SubscribeEpisodeInfo()
episodes_info.title = episode.name info.title = episode.name
episodes_info.description = episode.overview info.description = episode.overview
episodes_info.backdrop = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/w500${episode.still_path}" info.backdrop = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/w500${episode.still_path}"
episodes_info[episode.episode_number] = episode_info episodes[episode.episode_number] = info
elif subscribe.type == MediaType.TV.value: elif subscribe.type == MediaType.TV.value:
# 根据开始结束集计算集信息 # 根据开始结束集计算集信息
for i in range(subscribe.start_episode or 1, subscribe.total_episode + 1): for i in range(subscribe.start_episode or 1, subscribe.total_episode + 1):
episode_info = SubscribeEpisodeInfo() info = SubscribeEpisodeInfo()
episode_info.title = f'{i}' info.title = f'{i}'
episodes_info[i] = episode_info episodes[i] = info
else: else:
# 电影 # 电影
episode_info = SubscribeEpisodeInfo() info = SubscribeEpisodeInfo()
episode_info.title = subscribe.name info.title = subscribe.name
episodes_info[0] = episode_info episodes[0] = info
# 所有下载记录 # 所有下载记录
download_his = self.downloadhis.get_by_mediaid(tmdbid=subscribe.tmdbid, doubanid=subscribe.doubanid) download_his = self.downloadhis.get_by_mediaid(tmdbid=subscribe.tmdbid, doubanid=subscribe.doubanid)
@@ -1221,14 +1209,20 @@ class SubscribeChain(ChainBase):
for file in files: for file in files:
# 识别文件名 # 识别文件名
file_meta = MetaInfo(file.filepath) 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: if subscribe.type == MediaType.TV.value:
episode_number = file_meta.begin_episode episode_number = file_meta.begin_episode
if episode_number and episodes_info.get(episode_number): if episode_number and episodes.get(episode_number):
episodes_info[episode_number].download_file = file.fullpath episodes[episode_number].download.append(file_info)
episodes_info[episode_number].torrent = torrent_url
else: else:
episodes_info[0].download_file = file.fullpath episodes[0].download.append(file_info)
episodes_info[0].torrent = torrent_url
# 生成元数据 # 生成元数据
meta = MetaInfo(subscribe.name) meta = MetaInfo(subscribe.name)
@@ -1255,13 +1249,19 @@ class SubscribeChain(ChainBase):
for fileitem in library_fileitems: for fileitem in library_fileitems:
# 识别文件名 # 识别文件名
file_meta = MetaInfo(fileitem.path) file_meta = MetaInfo(fileitem.path)
# 媒体库文件信息
file_info = SubscribeLibraryFileInfo(
storage=fileitem.storage,
file_path=fileitem.path,
)
if subscribe.type == MediaType.TV.value: if subscribe.type == MediaType.TV.value:
episode_number = file_meta.begin_episode episode_number = file_meta.begin_episode
if episode_number and episodes_info.get(episode_number): if episode_number and episodes.get(episode_number):
episodes_info[episode_number].library_file = fileitem.path episodes[episode_number].library.append(file_info)
else: 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 return subscribe_info
+2
View File
@@ -13,6 +13,8 @@ class SiteUserData(Base):
id = Column(Integer, Sequence('id'), primary_key=True, index=True) id = Column(Integer, Sequence('id'), primary_key=True, index=True)
# 站点域名 # 站点域名
domain = Column(String, index=True) domain = Column(String, index=True)
# 站点名称
name = Column(String)
# 用户名 # 用户名
username = Column(String) username = Column(String)
# 用户ID # 用户ID
+8 -1
View File
@@ -104,7 +104,7 @@ class SiteOper(DbOper):
}) })
return True, "更新站点RSS地址成功" 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') current_time = datetime.now().strftime('%H:%M:%S')
payload.update({ payload.update({
"domain": domain, "domain": domain,
"name": name,
"updated_day": current_day, "updated_day": current_day,
"updated_time": current_time "updated_time": current_time
}) })
@@ -127,6 +128,12 @@ class SiteOper(DbOper):
SiteUserData(**payload).create(self._db) SiteUserData(**payload).create(self._db)
return True, "更新站点用户数据成功" 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]: def get_userdata_by_domain(self, domain: str, workdate: str = None) -> List[SiteUserData]:
""" """
获取站点用户数据 获取站点用户数据
+13 -2
View File
@@ -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"]
)
+13 -2
View File
@@ -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"]
)
+15 -3
View File
@@ -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
View 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
+1 -1
View File
@@ -56,7 +56,7 @@ class ServiceConfigHelper:
@staticmethod @staticmethod
def get_notification_switch(mtype: NotificationType) -> Optional[str]: def get_notification_switch(mtype: NotificationType) -> Optional[str]:
""" """
获取消息通知场景开关 获取指定类型的消息通知场景开关
""" """
switchs = ServiceConfigHelper.get_notification_switches() switchs = ServiceConfigHelper.get_notification_switches()
for switch in switchs: for switch in switchs:
+8
View File
@@ -90,6 +90,14 @@ class ServiceBase(Generic[TService, TConf], metaclass=ABCMeta):
# 如果传入的是工厂函数,直接调用工厂函数 # 如果传入的是工厂函数,直接调用工厂函数
self._instances[conf.name] = service_type(conf) 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]: def get_instance(self, name: str) -> Optional[TService]:
""" """
获取服务实例 获取服务实例
+1 -1
View File
@@ -187,7 +187,7 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
""" """
server: Emby = self.get_instance(server) server: Emby = self.get_instance(server)
if server: if server:
yield from server.get_items(library_id, start_index, limit) return server.get_items(library_id, start_index, limit)
return None return None
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
-3
View File
@@ -1093,9 +1093,6 @@ class FileManagerModule(_ModuleBase):
media_path = dir_path / rel_path.parts[0] media_path = dir_path / rel_path.parts[0]
else: else:
continue continue
# 检查媒体文件夹是否存在
if not media_path.exists():
continue
# 检索媒体文件 # 检索媒体文件
fileitem = storage_oper.get_item(media_path) fileitem = storage_oper.get_item(media_path)
if not fileitem: if not fileitem:
+1 -1
View File
@@ -185,7 +185,7 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
""" """
server: Jellyfin = self.get_instance(server) server: Jellyfin = self.get_instance(server)
if server: if server:
yield from server.get_items(library_id, start_index, limit) return server.get_items(library_id, start_index, limit)
return None return None
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
+1 -1
View File
@@ -173,7 +173,7 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
""" """
server: Plex = self.get_instance(server) server: Plex = self.get_instance(server)
if server: if server:
yield from server.get_items(library_id, start_index, limit) return server.get_items(library_id, start_index, limit)
return None return None
def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]: def mediaserver_iteminfo(self, server: str, item_id: str) -> Optional[schemas.MediaServerItem]:
+27 -29
View File
@@ -81,43 +81,41 @@ class Subscribe(BaseModel):
orm_mode = True 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): class SubscribeEpisodeInfo(BaseModel):
# 种子地址
torrent: Optional[str] = None
# 下载文件路程
download_file: Optional[str] = None
# 媒体库文件路径
library_file: Optional[str] = None
# 标题 # 标题
title: Optional[str] = None title: Optional[str] = None
# 描述 # 描述
description: Optional[str] = None description: Optional[str] = None
# 背景图 # 背景图
backdrop: Optional[str] = None backdrop: Optional[str] = None
# 下载文件信息
download: Optional[List[SubscribeDownloadFileInfo]] = []
# 媒体库文件信息
library: Optional[List[SubscribeLibraryFileInfo]] = []
class SubscrbieInfo(BaseModel): class SubscrbieInfo(BaseModel):
# 订阅ID # 订阅信息
id: Optional[int] = None subscribe: Optional[Subscribe] = 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
# 集信息 {集号: {download: 文件路径,library: 文件路径, backdrop: url, title: 标题, description: 描述}} # 集信息 {集号: {download: 文件路径,library: 文件路径, backdrop: url, title: 标题, description: 描述}}
episodes_info: Optional[Dict[int, SubscribeEpisodeInfo]] = {} episodes: Optional[Dict[int, SubscribeEpisodeInfo]] = {}
+19 -1
View File
@@ -1,8 +1,26 @@
from typing import Optional from dataclasses import dataclass
from typing import Optional, Any
from pydantic import BaseModel 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): class MediaServerConf(BaseModel):
""" """
媒体服务器配置 媒体服务器配置
+3 -1
View File
@@ -18,8 +18,10 @@ class TorrentStatus(Enum):
class EventType(Enum): class EventType(Enum):
# 插件需要重载 # 插件需要重载
PluginReload = "plugin.reload" PluginReload = "plugin.reload"
# 插件动作 # 触发插件动作
PluginAction = "plugin.action" PluginAction = "plugin.action"
# 插件触发事件
PluginTriggered = "plugin.triggered"
# 执行命令 # 执行命令
CommandExcute = "command.excute" CommandExcute = "command.excute"
# 站点已删除 # 站点已删除
+30
View 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