mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-03-24 05:51:28 +08:00
Merge branch 'dev' of https://github.com/jxxghp/MoviePilot into feature/security
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
from typing import List, Any
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from typing import Any, List
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from app.db import get_db
|
||||
from app.db.models import User
|
||||
from app.db.models.message import Message
|
||||
from app.db.user_oper import get_current_active_superuser
|
||||
from app.helper.serviceconfig import ServiceConfigHelper
|
||||
from app.helper.service import ServiceConfigHelper
|
||||
from app.log import logger
|
||||
from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt
|
||||
from app.schemas.types import MessageChannel
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ async def webhook_message(background_tasks: BackgroundTasks,
|
||||
_: str = Depends(verify_apitoken)
|
||||
) -> Any:
|
||||
"""
|
||||
Webhook响应,配置请求中需要添加参数:token=API_TOKEN&source=消息配置名
|
||||
Webhook响应,配置请求中需要添加参数:token=API_TOKEN&source=媒体服务器名
|
||||
"""
|
||||
body = await request.body()
|
||||
form = await request.form()
|
||||
@@ -35,7 +35,7 @@ async def webhook_message(background_tasks: BackgroundTasks,
|
||||
def webhook_message(background_tasks: BackgroundTasks,
|
||||
request: Request, _: str = Depends(verify_apitoken)) -> Any:
|
||||
"""
|
||||
Webhook响应,配置请求中需要添加参数:token=API_TOKEN&source=消息配置名
|
||||
Webhook响应,配置请求中需要添加参数:token=API_TOKEN&source=媒体服务器名
|
||||
"""
|
||||
args = request.query_params
|
||||
background_tasks.add_task(start_webhook_chain, None, None, args)
|
||||
|
||||
@@ -17,7 +17,7 @@ from app.core.module import ModuleManager
|
||||
from app.db.message_oper import MessageOper
|
||||
from app.db.user_oper import UserOper
|
||||
from app.helper.message import MessageHelper
|
||||
from app.helper.serviceconfig import ServiceConfigHelper
|
||||
from app.helper.service import ServiceConfigHelper
|
||||
from app.log import logger
|
||||
from app.schemas import TransferInfo, TransferTorrent, ExistMediaInfo, DownloadingTorrent, CommingMessage, Notification, \
|
||||
WebhookEventInfo, TmdbEpisode, MediaPerson, FileItem
|
||||
@@ -101,7 +101,7 @@ class ChainBase(metaclass=ABCMeta):
|
||||
try:
|
||||
module_name = module.get_name()
|
||||
except Exception as err:
|
||||
logger.error(f"获取模块名称出错:{str(err)}")
|
||||
logger.debug(f"获取模块名称出错:{str(err)}")
|
||||
module_name = module_id
|
||||
try:
|
||||
func = getattr(module, method)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import json
|
||||
import threading
|
||||
from typing import List, Union, Optional, Generator
|
||||
|
||||
from app import schemas
|
||||
from app.chain import ChainBase
|
||||
from app.db.mediaserver_oper import MediaServerOper
|
||||
from app.helper.serviceconfig import ServiceConfigHelper
|
||||
from app.helper.service import ServiceConfigHelper
|
||||
from app.log import logger
|
||||
|
||||
lock = threading.Lock()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
from typing import Any, Optional, Dict, Union
|
||||
|
||||
|
||||
@@ -127,11 +127,11 @@ class TmdbChain(ChainBase, metaclass=Singleton):
|
||||
return None
|
||||
|
||||
@cached(cache=TTLCache(maxsize=1, ttl=3600))
|
||||
def get_trending_wallpapers(self, num: int = 10) -> Optional[List[str]]:
|
||||
def get_trending_wallpapers(self, num: int = 10) -> List[str]:
|
||||
"""
|
||||
获取所有流行壁纸
|
||||
"""
|
||||
infos = self.tmdb_trending()
|
||||
if infos:
|
||||
return [info.backdrop_path for info in infos if info and info.backdrop_path][:num]
|
||||
return None
|
||||
return []
|
||||
|
||||
@@ -5,7 +5,7 @@ from app.core.config import settings
|
||||
from app.core.event import eventmanager
|
||||
from app.helper.module import ModuleHelper
|
||||
from app.log import logger
|
||||
from app.schemas.types import EventType
|
||||
from app.schemas.types import EventType, ModuleType
|
||||
from app.utils.object import ObjectUtils
|
||||
from app.utils.singleton import Singleton
|
||||
|
||||
@@ -124,6 +124,17 @@ class ModuleManager(metaclass=Singleton):
|
||||
and ObjectUtils.check_method(getattr(module, method)):
|
||||
yield module
|
||||
|
||||
def get_running_type_modules(self, module_type: ModuleType) -> Generator:
|
||||
"""
|
||||
获取指定类型的模块列表
|
||||
"""
|
||||
if not self._running_modules:
|
||||
return []
|
||||
for _, module in self._running_modules.items():
|
||||
if hasattr(module, 'get_type') \
|
||||
and module.get_type() == module_type:
|
||||
yield module
|
||||
|
||||
def get_module(self, module_id: str) -> Any:
|
||||
"""
|
||||
根据模块id获取模块
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.models.plugindata import PluginData
|
||||
from app.utils.object import ObjectUtils
|
||||
|
||||
|
||||
class PluginDataOper(DbOper):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import Tuple, List
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import json
|
||||
from typing import Any, Union
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.models.systemconfig import SystemConfig
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.utils.object import ObjectUtils
|
||||
from app.utils.singleton import Singleton
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
import time
|
||||
from typing import Any, List
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Depends, HTTPException
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import json
|
||||
from typing import Any, Union, Dict, Optional
|
||||
|
||||
from app.db import DbOper
|
||||
from app.db.models.userconfig import UserConfig
|
||||
from app.schemas.types import UserConfigKey
|
||||
from app.utils.object import ObjectUtils
|
||||
from app.utils.singleton import Singleton
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
from app.helper.servicebase import ServiceBaseHelper
|
||||
from app.helper.service import ServiceBaseHelper
|
||||
from app.schemas import DownloaderConf, ServiceInfo
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.schemas.types import SystemConfigKey, ModuleType
|
||||
|
||||
|
||||
class DownloaderHelper(ServiceBaseHelper[DownloaderConf]):
|
||||
@@ -14,29 +14,24 @@ class DownloaderHelper(ServiceBaseHelper[DownloaderConf]):
|
||||
super().__init__(
|
||||
config_key=SystemConfigKey.Downloaders,
|
||||
conf_type=DownloaderConf,
|
||||
modules=["QbittorrentModule", "TransmissionModule"]
|
||||
module_type=ModuleType.Downloader
|
||||
)
|
||||
|
||||
def is_qbittorrent(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
def is_downloader(
|
||||
self,
|
||||
service_type: Optional[str] = None,
|
||||
service: Optional[ServiceInfo] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
判断指定的下载器是否为 qbittorrent 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
通用的下载器类型判断方法
|
||||
:param service_type: 下载器的类型名称(如 'qbittorrent', 'transmission')
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 qbittorrent,返回 True;否则返回 False。
|
||||
:return: 如果服务类型或实例为指定类型,返回 True;否则返回 False
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "qbittorrent" if service else False
|
||||
# 如果未提供 service 则通过 name 获取服务
|
||||
service = service or self.get_service(name=name)
|
||||
|
||||
def is_transmission(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
"""
|
||||
判断指定的下载器是否为 transmission 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 transmission,返回 True;否则返回 False。
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "transmission" if service else False
|
||||
# 判断服务类型是否为指定类型
|
||||
return bool(service and service.type == service_type)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
from app.helper.servicebase import ServiceBaseHelper
|
||||
from app.helper.service import ServiceBaseHelper
|
||||
from app.schemas import MediaServerConf, ServiceInfo
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.schemas.types import SystemConfigKey, ModuleType
|
||||
|
||||
|
||||
class MediaServerHelper(ServiceBaseHelper[MediaServerConf]):
|
||||
@@ -14,41 +14,24 @@ class MediaServerHelper(ServiceBaseHelper[MediaServerConf]):
|
||||
super().__init__(
|
||||
config_key=SystemConfigKey.MediaServers,
|
||||
conf_type=MediaServerConf,
|
||||
modules=["PlexModule", "EmbyModule", "JellyfinModule"]
|
||||
module_type=ModuleType.MediaServer
|
||||
)
|
||||
|
||||
def is_plex(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
def is_media_server(
|
||||
self,
|
||||
service_type: Optional[str] = None,
|
||||
service: Optional[ServiceInfo] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
判断指定的媒体服务器是否为 Plex 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
通用的媒体服务器类型判断方法
|
||||
:param service_type: 媒体服务器的类型名称(如 'plex', 'emby', 'jellyfin')
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 plex,返回 True;否则返回 False。
|
||||
:return: 如果服务类型或实例为指定类型,返回 True;否则返回 False
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "plex" if service else False
|
||||
# 如果未提供 service 则通过 name 获取服务
|
||||
service = service or self.get_service(name=name)
|
||||
|
||||
def is_emby(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
"""
|
||||
判断指定的媒体服务器是否为 Emby 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 emby,返回 True;否则返回 False。
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "emby" if service else False
|
||||
|
||||
def is_jellyfin(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
"""
|
||||
判断指定的媒体服务器是否为 Jellyfin 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 jellyfin,返回 True;否则返回 False。
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "jellyfin" if service else False
|
||||
# 判断服务类型是否为指定类型
|
||||
return bool(service and service.type == service_type)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from typing import Optional
|
||||
|
||||
from app.helper.servicebase import ServiceBaseHelper
|
||||
from app.helper.service import ServiceBaseHelper
|
||||
from app.schemas import NotificationConf, ServiceInfo
|
||||
from app.schemas.types import SystemConfigKey
|
||||
from app.schemas.types import SystemConfigKey, ModuleType
|
||||
|
||||
|
||||
class NotificationHelper(ServiceBaseHelper[NotificationConf]):
|
||||
@@ -14,84 +14,25 @@ class NotificationHelper(ServiceBaseHelper[NotificationConf]):
|
||||
super().__init__(
|
||||
config_key=SystemConfigKey.Notifications,
|
||||
conf_type=NotificationConf,
|
||||
modules=[
|
||||
"WechatModule",
|
||||
"WebPushModule",
|
||||
"VoceChatModule",
|
||||
"TelegramModule",
|
||||
"SynologyChatModule",
|
||||
"SlackModule"
|
||||
]
|
||||
module_type=ModuleType.Notification
|
||||
)
|
||||
|
||||
def is_wechat(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
def is_notification(
|
||||
self,
|
||||
service_type: Optional[str] = None,
|
||||
service: Optional[ServiceInfo] = None,
|
||||
name: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
判断指定的消息通知服务是否为 Wechat 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
通用的消息通知服务类型判断方法
|
||||
|
||||
:param service_type: 消息通知服务的类型名称(如 'wechat', 'voicechat', 'telegram', 等)
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 wechat,返回 True;否则返回 False。
|
||||
:return: 如果服务类型或实例为指定类型,返回 True;否则返回 False
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "wechat" if service else False
|
||||
# 如果未提供 service 则通过 name 获取服务
|
||||
service = service or self.get_service(name=name)
|
||||
|
||||
def is_webpush(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
"""
|
||||
判断指定的消息通知服务是否为 WebPush 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 webpush,返回 True;否则返回 False。
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "webpush" if service else False
|
||||
|
||||
def is_voicechat(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
"""
|
||||
判断指定的消息通知服务是否为 VoiceChat 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 voicechat,返回 True;否则返回 False。
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "voicechat" if service else False
|
||||
|
||||
def is_telegram(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
"""
|
||||
判断指定的消息通知服务是否为 Telegram 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 telegram,返回 True;否则返回 False。
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "telegram" if service else False
|
||||
|
||||
def is_synologychat(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
"""
|
||||
判断指定的消息通知服务是否为 SynologyChat 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 synologychat,返回 True;否则返回 False。
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "synologychat" if service else False
|
||||
|
||||
def is_slack(self, service: Optional[ServiceInfo] = None, name: Optional[str] = None) -> bool:
|
||||
"""
|
||||
判断指定的消息通知服务是否为 Slack 类型,需要传入 `service` 或 `name` 中的任一参数
|
||||
|
||||
:param service: 要判断的服务信息
|
||||
:param name: 服务的名称
|
||||
:return: 如果服务类型为 slack,返回 True;否则返回 False。
|
||||
"""
|
||||
if not service:
|
||||
service = self.get_service(name=name)
|
||||
return service.type == "slack" if service else False
|
||||
# 判断服务类型是否为指定类型
|
||||
return bool(service and service.type == service_type)
|
||||
|
||||
@@ -1,23 +1,83 @@
|
||||
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
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.schemas import DownloaderConf, MediaServerConf, NotificationConf, NotificationSwitchConf, ServiceInfo
|
||||
from app.schemas.types import NotificationType, SystemConfigKey, ModuleType
|
||||
|
||||
TConf = TypeVar("TConf")
|
||||
|
||||
|
||||
class ServiceConfigHelper:
|
||||
"""
|
||||
配置帮助类,获取不同类型的服务配置
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_configs(config_key: SystemConfigKey, conf_type: Type) -> List:
|
||||
"""
|
||||
通用获取配置的方法,根据 config_key 获取相应的配置并返回指定类型的配置列表
|
||||
|
||||
:param config_key: 系统配置的 key
|
||||
:param conf_type: 用于实例化配置对象的类类型
|
||||
:return: 配置对象列表
|
||||
"""
|
||||
config_data = SystemConfigOper().get(config_key)
|
||||
if not config_data:
|
||||
return []
|
||||
# 直接使用 conf_type 来实例化配置对象
|
||||
return [conf_type(**conf) for conf in config_data]
|
||||
|
||||
@staticmethod
|
||||
def get_downloader_configs() -> List[DownloaderConf]:
|
||||
"""
|
||||
获取下载器的配置
|
||||
"""
|
||||
return ServiceConfigHelper.get_configs(SystemConfigKey.Downloaders, DownloaderConf)
|
||||
|
||||
@staticmethod
|
||||
def get_mediaserver_configs() -> List[MediaServerConf]:
|
||||
"""
|
||||
获取媒体服务器的配置
|
||||
"""
|
||||
return ServiceConfigHelper.get_configs(SystemConfigKey.MediaServers, MediaServerConf)
|
||||
|
||||
@staticmethod
|
||||
def get_notification_configs() -> List[NotificationConf]:
|
||||
"""
|
||||
获取消息通知渠道的配置
|
||||
"""
|
||||
return ServiceConfigHelper.get_configs(SystemConfigKey.Notifications, NotificationConf)
|
||||
|
||||
@staticmethod
|
||||
def get_notification_switches() -> List[NotificationSwitchConf]:
|
||||
"""
|
||||
获取消息通知场景的开关
|
||||
"""
|
||||
return ServiceConfigHelper.get_configs(SystemConfigKey.NotificationSwitchs, NotificationSwitchConf)
|
||||
|
||||
@staticmethod
|
||||
def get_notification_switch(mtype: NotificationType) -> Optional[str]:
|
||||
"""
|
||||
获取指定类型的消息通知场景的开关
|
||||
"""
|
||||
switchs = ServiceConfigHelper.get_notification_switches()
|
||||
for switch in switchs:
|
||||
if switch.type == mtype.value:
|
||||
return switch.action
|
||||
return None
|
||||
|
||||
|
||||
class ServiceBaseHelper(Generic[TConf]):
|
||||
"""
|
||||
通用服务帮助类,抽象获取配置和服务实例的通用逻辑
|
||||
"""
|
||||
|
||||
def __init__(self, config_key: SystemConfigKey, conf_type: Type[TConf], modules: List[str]):
|
||||
def __init__(self, config_key: SystemConfigKey, conf_type: Type[TConf], module_type: ModuleType):
|
||||
self.modulemanager = ModuleManager()
|
||||
self.config_key = config_key
|
||||
self.conf_type = conf_type
|
||||
self.modules = modules
|
||||
self.module_type = module_type
|
||||
|
||||
def get_configs(self, include_disabled: bool = False) -> Dict[str, TConf]:
|
||||
"""
|
||||
@@ -47,8 +107,8 @@ class ServiceBaseHelper(Generic[TConf]):
|
||||
迭代所有模块的实例及其对应的配置,返回 ServiceInfo 实例
|
||||
"""
|
||||
configs = self.get_configs()
|
||||
for module_name in self.modules:
|
||||
module = self.modulemanager.get_running_module(module_name)
|
||||
modules = self.modulemanager.get_running_type_modules(self.module_type)
|
||||
for module in modules:
|
||||
if not module:
|
||||
continue
|
||||
module_instances = module.get_instances()
|
||||
@@ -81,9 +141,10 @@ class ServiceBaseHelper(Generic[TConf]):
|
||||
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) and
|
||||
(name_filters_set is None or service_info.name in name_filters_set)
|
||||
if service_info.config and (
|
||||
type_filter is None or service_info.type == type_filter
|
||||
) and (
|
||||
name_filters_set is None or service_info.name in name_filters_set)
|
||||
}
|
||||
|
||||
def get_service(self, name: str, type_filter: Optional[str] = None) -> Optional[ServiceInfo]:
|
||||
@@ -1,65 +0,0 @@
|
||||
from typing import List, Type, Optional
|
||||
|
||||
from app.db.systemconfig_oper import SystemConfigOper
|
||||
from app.schemas import DownloaderConf, MediaServerConf, NotificationConf, NotificationSwitchConf
|
||||
from app.schemas.types import SystemConfigKey, NotificationType
|
||||
|
||||
|
||||
class ServiceConfigHelper:
|
||||
"""
|
||||
配置帮助类,获取不同类型的服务配置
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_configs(config_key: SystemConfigKey, conf_type: Type) -> List:
|
||||
"""
|
||||
通用获取配置的方法,根据 config_key 获取相应的配置并返回指定类型的配置列表
|
||||
|
||||
:param config_key: 系统配置的 key
|
||||
:param conf_type: 用于实例化配置对象的类类型
|
||||
:return: 配置对象列表
|
||||
"""
|
||||
config_data = SystemConfigOper().get(config_key)
|
||||
if not config_data:
|
||||
return []
|
||||
# 直接使用 conf_type 来实例化配置对象
|
||||
return [conf_type(**conf) for conf in config_data]
|
||||
|
||||
@staticmethod
|
||||
def get_downloader_configs() -> List[DownloaderConf]:
|
||||
"""
|
||||
获取下载器的配置
|
||||
"""
|
||||
return ServiceConfigHelper.get_configs(SystemConfigKey.Downloaders, DownloaderConf)
|
||||
|
||||
@staticmethod
|
||||
def get_mediaserver_configs() -> List[MediaServerConf]:
|
||||
"""
|
||||
获取媒体服务器的配置
|
||||
"""
|
||||
return ServiceConfigHelper.get_configs(SystemConfigKey.MediaServers, MediaServerConf)
|
||||
|
||||
@staticmethod
|
||||
def get_notification_configs() -> List[NotificationConf]:
|
||||
"""
|
||||
获取消息通知渠道的配置
|
||||
"""
|
||||
return ServiceConfigHelper.get_configs(SystemConfigKey.Notifications, NotificationConf)
|
||||
|
||||
@staticmethod
|
||||
def get_notification_switches() -> List[NotificationSwitchConf]:
|
||||
"""
|
||||
获取消息通知场景的开关
|
||||
"""
|
||||
return ServiceConfigHelper.get_configs(SystemConfigKey.NotificationSwitchs, NotificationSwitchConf)
|
||||
|
||||
@staticmethod
|
||||
def get_notification_switch(mtype: NotificationType) -> Optional[str]:
|
||||
"""
|
||||
获取指定类型的消息通知场景的开关
|
||||
"""
|
||||
switchs = ServiceConfigHelper.get_notification_switches()
|
||||
for switch in switchs:
|
||||
if switch.type == mtype.value:
|
||||
return switch.action
|
||||
return None
|
||||
@@ -1,8 +1,9 @@
|
||||
from abc import abstractmethod, ABCMeta
|
||||
from typing import Generic, Tuple, Union, TypeVar, Type, Dict, Optional, Callable, Any
|
||||
|
||||
from app.helper.serviceconfig import ServiceConfigHelper
|
||||
from app.helper.service import ServiceConfigHelper
|
||||
from app.schemas import Notification, MessageChannel, NotificationConf, MediaServerConf, DownloaderConf
|
||||
from app.schemas.types import ModuleType
|
||||
|
||||
|
||||
class _ModuleBase(metaclass=ABCMeta):
|
||||
@@ -34,6 +35,14 @@ class _ModuleBase(metaclass=ABCMeta):
|
||||
"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -7,6 +7,7 @@ from app.core.meta import MetaBase
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.modules.bangumi.bangumi import BangumiApi
|
||||
from app.schemas.types import ModuleType
|
||||
from app.utils.http import RequestUtils
|
||||
|
||||
|
||||
@@ -37,6 +38,13 @@ class BangumiModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "Bangumi"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.MediaRecognize
|
||||
|
||||
def recognize_media(self, bangumiid: int = None,
|
||||
**kwargs) -> Optional[MediaInfo]:
|
||||
"""
|
||||
|
||||
@@ -14,7 +14,7 @@ from app.modules.douban.apiv2 import DoubanApi
|
||||
from app.modules.douban.douban_cache import DoubanCache
|
||||
from app.modules.douban.scraper import DoubanScraper
|
||||
from app.schemas import MediaPerson, APIRateLimitException
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MediaType, ModuleType
|
||||
from app.utils.common import retry
|
||||
from app.utils.http import RequestUtils
|
||||
from app.utils.limit import rate_limit_exponential
|
||||
@@ -51,6 +51,13 @@ class DoubanModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "豆瓣"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.MediaRecognize
|
||||
|
||||
def recognize_media(self, meta: MetaBase = None,
|
||||
mtype: MediaType = None,
|
||||
doubanid: str = None,
|
||||
|
||||
@@ -5,7 +5,6 @@ from app.core.context import MediaInfo
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MediaServerBase
|
||||
from app.modules.emby.emby import Emby
|
||||
from app.schemas import MediaServerConf
|
||||
from app.schemas.types import MediaType
|
||||
|
||||
|
||||
@@ -22,6 +21,10 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
|
||||
def get_name() -> str:
|
||||
return "Emby"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> str:
|
||||
return "mediaserver"
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
@@ -78,7 +81,10 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
|
||||
server: Emby = self.get_instance(source)
|
||||
if not server:
|
||||
return None
|
||||
return server.get_webhook_message(form, args)
|
||||
result = server.get_webhook_message(form, args)
|
||||
if result:
|
||||
result.server_name = source
|
||||
return result
|
||||
|
||||
for server in self.get_instances().values():
|
||||
if server:
|
||||
|
||||
@@ -6,7 +6,7 @@ from app.core.context import MediaInfo, settings
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.utils.http import RequestUtils
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MediaType, ModuleType
|
||||
|
||||
|
||||
class FanartModule(_ModuleBase):
|
||||
@@ -335,6 +335,13 @@ class FanartModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "Fanart"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Other
|
||||
|
||||
def obtain_images(self, mediainfo: MediaInfo) -> Optional[MediaInfo]:
|
||||
"""
|
||||
获取图片
|
||||
|
||||
@@ -17,7 +17,7 @@ from app.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.modules.filemanager.storages import StorageBase
|
||||
from app.schemas import TransferInfo, ExistMediaInfo, TmdbEpisode, TransferDirectoryConf, FileItem, StorageUsage
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MediaType, ModuleType
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
lock = Lock()
|
||||
@@ -44,6 +44,13 @@ class FileManagerModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "文件整理"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Other
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from app.helper.rule import RuleHelper
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.modules.filter.RuleParser import RuleParser
|
||||
from app.schemas.types import ModuleType
|
||||
from app.utils.string import StringUtils
|
||||
|
||||
|
||||
@@ -159,6 +160,13 @@ class FilterModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "过滤器"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Other
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from app.modules.indexer.spider.tnode import TNodeSpider
|
||||
from app.modules.indexer.spider.torrentleech import TorrentLeech
|
||||
from app.modules.indexer.spider.yema import YemaSpider
|
||||
from app.schemas import SiteUserData
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MediaType, ModuleType
|
||||
from app.utils.string import StringUtils
|
||||
|
||||
|
||||
@@ -40,6 +40,13 @@ class IndexerModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "站点索引"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Indexer
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ from app.core.context import MediaInfo
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MediaServerBase
|
||||
from app.modules.jellyfin.jellyfin import Jellyfin
|
||||
from app.schemas import MediaServerConf
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MediaType, ModuleType
|
||||
|
||||
|
||||
class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
|
||||
@@ -22,6 +21,13 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
|
||||
def get_name() -> str:
|
||||
return "Jellyfin"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.MediaServer
|
||||
|
||||
def init_setting(self) -> Tuple[str, Union[str, bool]]:
|
||||
pass
|
||||
|
||||
@@ -78,7 +84,10 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
|
||||
server: Jellyfin = self.get_instance(source)
|
||||
if not server:
|
||||
return None
|
||||
return server.get_webhook_message(body)
|
||||
result = server.get_webhook_message(body)
|
||||
if result:
|
||||
result.server_name = source
|
||||
return result
|
||||
|
||||
for server in self.get_instances().values():
|
||||
if server:
|
||||
|
||||
@@ -5,8 +5,7 @@ from app.core.context import MediaInfo
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MediaServerBase
|
||||
from app.modules.plex.plex import Plex
|
||||
from app.schemas import MediaServerConf
|
||||
from app.schemas.types import MediaType
|
||||
from app.schemas.types import MediaType, ModuleType
|
||||
|
||||
|
||||
class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
|
||||
@@ -22,6 +21,13 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
|
||||
def get_name() -> str:
|
||||
return "Plex"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.MediaServer
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
@@ -64,7 +70,10 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
|
||||
server: Plex = self.get_instance(source)
|
||||
if not server:
|
||||
return None
|
||||
return server.get_webhook_message(form)
|
||||
result = server.get_webhook_message(form)
|
||||
if result:
|
||||
result.server_name = source
|
||||
return result
|
||||
|
||||
for server in self.get_instances().values():
|
||||
if server:
|
||||
|
||||
@@ -12,7 +12,7 @@ from app.log import logger
|
||||
from app.modules import _ModuleBase, _DownloaderBase
|
||||
from app.modules.qbittorrent.qbittorrent import Qbittorrent
|
||||
from app.schemas import TransferTorrent, DownloadingTorrent
|
||||
from app.schemas.types import TorrentStatus
|
||||
from app.schemas.types import TorrentStatus, ModuleType
|
||||
from app.utils.string import StringUtils
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
@@ -30,6 +30,13 @@ class QbittorrentModule(_ModuleBase, _DownloaderBase[Qbittorrent]):
|
||||
def get_name() -> str:
|
||||
return "Qbittorrent"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Downloader
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -82,9 +82,9 @@ class Qbittorrent:
|
||||
logger.error(f"qbittorrent 连接出错:{str(err)}")
|
||||
return None
|
||||
|
||||
def get_torrents(self, ids: Union[str, list] = None,
|
||||
status: Union[str, list] = None,
|
||||
tags: Union[str, list] = None) -> Tuple[List[TorrentDictionary], bool]:
|
||||
def get_torrents(self, ids: Optional[Union[str, list]] = None,
|
||||
status: Optional[str] = None,
|
||||
tags: Optional[Union[str, list]] = None) -> Tuple[List[TorrentDictionary], bool]:
|
||||
"""
|
||||
获取种子列表
|
||||
return: 种子列表, 是否发生异常
|
||||
@@ -117,7 +117,7 @@ class Qbittorrent:
|
||||
if not self.qbc:
|
||||
return None
|
||||
# completed会包含移动状态 改为获取seeding状态 包含活动上传, 正在做种, 及强制做种
|
||||
torrents, error = self.get_torrents(status=["seeding"], ids=ids, tags=tags)
|
||||
torrents, error = self.get_torrents(status="seeding", ids=ids, tags=tags)
|
||||
return None if error else torrents or []
|
||||
|
||||
def get_downloading_torrents(self, ids: Union[str, list] = None,
|
||||
@@ -129,7 +129,7 @@ class Qbittorrent:
|
||||
if not self.qbc:
|
||||
return None
|
||||
torrents, error = self.get_torrents(ids=ids,
|
||||
status=["downloading"],
|
||||
status="downloading",
|
||||
tags=tags)
|
||||
return None if error else torrents or []
|
||||
|
||||
@@ -195,7 +195,7 @@ class Qbittorrent:
|
||||
logger.error(f"设置强制作种出错:{str(err)}")
|
||||
|
||||
def __get_last_add_torrentid_by_tag(self, tags: Union[str, list],
|
||||
status: Union[str, list] = None) -> Optional[str]:
|
||||
status: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
根据种子的下载链接获取下载中或暂停的钟子的ID
|
||||
:return: 种子ID
|
||||
@@ -211,7 +211,7 @@ class Qbittorrent:
|
||||
return None
|
||||
|
||||
def get_torrent_id_by_tag(self, tags: Union[str, list],
|
||||
status: Union[str, list] = None) -> Optional[str]:
|
||||
status: Optional[str] = None) -> Optional[str]:
|
||||
"""
|
||||
通过标签多次尝试获取刚添加的种子ID,并移除标签
|
||||
"""
|
||||
|
||||
@@ -2,12 +2,12 @@ import json
|
||||
import re
|
||||
from typing import Optional, Union, List, Tuple, Any
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.context import MediaInfo, Context
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.slack.slack import Slack
|
||||
from app.schemas import MessageChannel, CommingMessage, Notification
|
||||
from app.schemas.types import ModuleType
|
||||
|
||||
|
||||
class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||
@@ -23,6 +23,13 @@ class SlackModule(_ModuleBase, _MessageBase[Slack]):
|
||||
def get_name() -> str:
|
||||
return "Slack"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Notification
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
停止模块
|
||||
|
||||
@@ -10,6 +10,7 @@ from app.core.context import Context
|
||||
from app.helper.torrent import TorrentHelper
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.schemas.types import ModuleType
|
||||
from app.utils.http import RequestUtils
|
||||
from app.utils.string import StringUtils
|
||||
from app.utils.system import SystemUtils
|
||||
@@ -32,6 +33,13 @@ class SubtitleModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "站点字幕"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Other
|
||||
|
||||
def init_setting(self) -> Tuple[str, Union[str, bool]]:
|
||||
pass
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.synologychat.synologychat import SynologyChat
|
||||
from app.schemas import MessageChannel, CommingMessage, Notification
|
||||
from app.schemas.types import ModuleType
|
||||
|
||||
|
||||
class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]):
|
||||
@@ -20,6 +21,13 @@ class SynologyChatModule(_ModuleBase, _MessageBase[SynologyChat]):
|
||||
def get_name() -> str:
|
||||
return "Synology Chat"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Notification
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import json
|
||||
from typing import Optional, Union, List, Tuple, Any, Dict
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.context import MediaInfo, Context
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.telegram.telegram import Telegram
|
||||
from app.schemas import MessageChannel, CommingMessage, Notification
|
||||
from app.schemas.types import ModuleType
|
||||
|
||||
|
||||
class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
|
||||
@@ -22,6 +22,13 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
|
||||
def get_name() -> str:
|
||||
return "Telegram"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Notification
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
停止模块
|
||||
|
||||
@@ -13,7 +13,7 @@ from app.modules.themoviedb.scraper import TmdbScraper
|
||||
from app.modules.themoviedb.tmdb_cache import TmdbCache
|
||||
from app.modules.themoviedb.tmdbapi import TmdbApi
|
||||
from app.schemas import MediaPerson
|
||||
from app.schemas.types import MediaType, MediaImageType
|
||||
from app.schemas.types import MediaType, MediaImageType, ModuleType
|
||||
from app.utils.http import RequestUtils
|
||||
|
||||
|
||||
@@ -41,6 +41,13 @@ class TheMovieDbModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "TheMovieDb"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.MediaRecognize
|
||||
|
||||
def stop(self):
|
||||
self.cache.save()
|
||||
self.tmdb.close()
|
||||
|
||||
@@ -770,7 +770,7 @@ class TmdbApi:
|
||||
logger.debug(f"{tmdbid} 查询结果:{tmdbinfo.get('title')}")
|
||||
return tmdbinfo or {}
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return None
|
||||
|
||||
def __get_tv_detail(self,
|
||||
@@ -953,7 +953,7 @@ class TmdbApi:
|
||||
logger.debug(f"{tmdbid} 查询结果:{tmdbinfo.get('name')}")
|
||||
return tmdbinfo or {}
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return None
|
||||
|
||||
def get_tv_season_detail(self, tmdbid: int, season: int):
|
||||
@@ -1027,7 +1027,7 @@ class TmdbApi:
|
||||
tmdbinfo = self.season.details(tv_id=tmdbid, season_num=season)
|
||||
return tmdbinfo or {}
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return {}
|
||||
|
||||
def get_tv_episode_detail(self, tmdbid: int, season: int, episode: int) -> dict:
|
||||
@@ -1044,7 +1044,7 @@ class TmdbApi:
|
||||
tmdbinfo = self.episode.details(tv_id=tmdbid, season_num=season, episode_num=episode)
|
||||
return tmdbinfo or {}
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return {}
|
||||
|
||||
def discover_movies(self, **kwargs) -> List[dict]:
|
||||
@@ -1064,7 +1064,7 @@ class TmdbApi:
|
||||
info['media_type'] = MediaType.MOVIE
|
||||
return tmdbinfo or []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def discover_tvs(self, **kwargs) -> List[dict]:
|
||||
@@ -1084,7 +1084,7 @@ class TmdbApi:
|
||||
info['media_type'] = MediaType.TV
|
||||
return tmdbinfo or []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def discover_trending(self, page: int = 1) -> List[dict]:
|
||||
@@ -1097,7 +1097,7 @@ class TmdbApi:
|
||||
logger.debug(f"正在获取流行趋势:page={page} ...")
|
||||
return self.trending.all_week(page=page)
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def get_movie_images(self, tmdbid: int) -> dict:
|
||||
@@ -1110,7 +1110,7 @@ class TmdbApi:
|
||||
logger.debug(f"正在获取电影图片:{tmdbid}...")
|
||||
return self.movie.images(movie_id=tmdbid) or {}
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return {}
|
||||
|
||||
def get_tv_images(self, tmdbid: int) -> dict:
|
||||
@@ -1123,7 +1123,7 @@ class TmdbApi:
|
||||
logger.debug(f"正在获取电视剧图片:{tmdbid}...")
|
||||
return self.tv.images(tv_id=tmdbid) or {}
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return {}
|
||||
|
||||
def get_movie_similar(self, tmdbid: int) -> List[dict]:
|
||||
@@ -1136,7 +1136,7 @@ class TmdbApi:
|
||||
logger.debug(f"正在获取相似电影:{tmdbid}...")
|
||||
return self.movie.similar(movie_id=tmdbid) or []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def get_tv_similar(self, tmdbid: int) -> List[dict]:
|
||||
@@ -1149,7 +1149,7 @@ class TmdbApi:
|
||||
logger.debug(f"正在获取相似电视剧:{tmdbid}...")
|
||||
return self.tv.similar(tv_id=tmdbid) or []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def get_movie_recommend(self, tmdbid: int) -> List[dict]:
|
||||
@@ -1162,7 +1162,7 @@ class TmdbApi:
|
||||
logger.debug(f"正在获取推荐电影:{tmdbid}...")
|
||||
return self.movie.recommendations(movie_id=tmdbid) or []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def get_tv_recommend(self, tmdbid: int) -> List[dict]:
|
||||
@@ -1175,7 +1175,7 @@ class TmdbApi:
|
||||
logger.debug(f"正在获取推荐电视剧:{tmdbid}...")
|
||||
return self.tv.recommendations(tv_id=tmdbid) or []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def get_movie_credits(self, tmdbid: int, page: int = 1, count: int = 24) -> List[dict]:
|
||||
@@ -1192,7 +1192,7 @@ class TmdbApi:
|
||||
return cast[(page - 1) * count: page * count]
|
||||
return []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def get_tv_credits(self, tmdbid: int, page: int = 1, count: int = 24) -> List[dict]:
|
||||
@@ -1209,7 +1209,7 @@ class TmdbApi:
|
||||
return cast[(page - 1) * count: page * count]
|
||||
return []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def get_person_detail(self, person_id: int) -> dict:
|
||||
@@ -1242,7 +1242,7 @@ class TmdbApi:
|
||||
logger.debug(f"正在获取人物详情:{person_id}...")
|
||||
return self.person.details(person_id=person_id) or {}
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return {}
|
||||
|
||||
def get_person_credits(self, person_id: int, page: int = 1, count: int = 24) -> List[dict]:
|
||||
@@ -1263,7 +1263,7 @@ class TmdbApi:
|
||||
return cast[(page - 1) * count: page * count]
|
||||
return []
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return []
|
||||
|
||||
def clear_cache(self):
|
||||
@@ -1301,7 +1301,7 @@ class TmdbApi:
|
||||
episode_years[order] = str(first_date).split("-")[0]
|
||||
return episode_years
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
logger.error(str(e))
|
||||
return {}
|
||||
|
||||
def close(self):
|
||||
|
||||
@@ -4,6 +4,7 @@ from app.core.config import settings
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase
|
||||
from app.modules.thetvdb import tvdbapi
|
||||
from app.schemas.types import ModuleType
|
||||
from app.utils.http import RequestUtils
|
||||
|
||||
|
||||
@@ -20,6 +21,13 @@ class TheTvDbModule(_ModuleBase):
|
||||
def get_name() -> str:
|
||||
return "TheTvDb"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.MediaRecognize
|
||||
|
||||
def stop(self):
|
||||
self.tvdb.close()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from app.log import logger
|
||||
from app.modules import _ModuleBase, _DownloaderBase
|
||||
from app.modules.transmission.transmission import Transmission
|
||||
from app.schemas import TransferTorrent, DownloadingTorrent
|
||||
from app.schemas.types import TorrentStatus
|
||||
from app.schemas.types import TorrentStatus, ModuleType
|
||||
from app.utils.string import StringUtils
|
||||
from app.utils.system import SystemUtils
|
||||
|
||||
@@ -30,6 +30,13 @@ class TransmissionModule(_ModuleBase, _DownloaderBase[Transmission]):
|
||||
def get_name() -> str:
|
||||
return "Transmission"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Downloader
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import json
|
||||
from typing import Optional, Union, List, Tuple, Any, Dict
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.context import Context, MediaInfo
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.vocechat.vocechat import VoceChat
|
||||
from app.schemas import MessageChannel, CommingMessage, Notification
|
||||
from app.schemas.types import ModuleType
|
||||
|
||||
|
||||
class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
|
||||
@@ -22,6 +22,13 @@ class VoceChatModule(_ModuleBase, _MessageBase[VoceChat]):
|
||||
def get_name() -> str:
|
||||
return "VoceChat"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Notification
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from app.core.config import global_vars, settings
|
||||
from app.log import logger
|
||||
from app.modules import _ModuleBase, _MessageBase
|
||||
from app.schemas import Notification
|
||||
from app.schemas.types import ModuleType
|
||||
|
||||
|
||||
class WebPushModule(_ModuleBase, _MessageBase):
|
||||
@@ -21,6 +22,13 @@ class WebPushModule(_ModuleBase, _MessageBase):
|
||||
def get_name() -> str:
|
||||
return "WebPush"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Notification
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ from app.modules import _ModuleBase, _MessageBase
|
||||
from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt
|
||||
from app.modules.wechat.wechat import WeChat
|
||||
from app.schemas import MessageChannel, CommingMessage, Notification
|
||||
from app.schemas.types import ModuleType
|
||||
from app.utils.dom import DomUtils
|
||||
|
||||
|
||||
@@ -23,6 +24,13 @@ class WechatModule(_ModuleBase, _MessageBase[WeChat]):
|
||||
def get_name() -> str:
|
||||
return "微信"
|
||||
|
||||
@staticmethod
|
||||
def get_type() -> ModuleType:
|
||||
"""
|
||||
获取模块类型
|
||||
"""
|
||||
return ModuleType.Notification
|
||||
|
||||
def stop(self):
|
||||
pass
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@ class WebhookEventInfo(BaseModel):
|
||||
"""
|
||||
event: Optional[str] = None
|
||||
channel: Optional[str] = None
|
||||
server_name: Optional[str] = None
|
||||
item_type: Optional[str] = None
|
||||
item_name: Optional[str] = None
|
||||
item_id: Optional[str] = None
|
||||
|
||||
@@ -179,3 +179,19 @@ class StorageSchema(Enum):
|
||||
Alipan = "alipan"
|
||||
U115 = "u115"
|
||||
Rclone = "rclone"
|
||||
|
||||
|
||||
# 模块类型
|
||||
class ModuleType(Enum):
|
||||
# 下载器
|
||||
Downloader = "downloader"
|
||||
# 媒体服务器
|
||||
MediaServer = "mediaserver"
|
||||
# 消息服务
|
||||
Notification = "notification"
|
||||
# 媒体识别
|
||||
MediaRecognize = "mediarecognize"
|
||||
# 站点索引
|
||||
Indexer = "indexer"
|
||||
# 其它
|
||||
Other = "other"
|
||||
|
||||
@@ -185,7 +185,7 @@ class StringUtils:
|
||||
忽略特殊字符
|
||||
"""
|
||||
# 需要忽略的特殊字符
|
||||
CONVERT_EMPTY_CHARS = r"[、.。,,·::;;!!'’\"“”()()\[\]【】「」\-——\+\|\\_/&#~~]"
|
||||
CONVERT_EMPTY_CHARS = r"[、.。,,·::;;!!'’\"“”()()\[\]【】「」\-—―\+\|\\_/&#~~]"
|
||||
if not text:
|
||||
return text
|
||||
if not isinstance(text, list):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Optional
|
||||
from typing import Optional, List
|
||||
|
||||
from cachetools import TTLCache, cached
|
||||
|
||||
@@ -94,7 +94,7 @@ class WebUtils:
|
||||
|
||||
@staticmethod
|
||||
@cached(cache=TTLCache(maxsize=1, ttl=3600))
|
||||
def get_bing_wallpapers(num: int = 7) -> Optional[str]:
|
||||
def get_bing_wallpapers(num: int = 7) -> List[str]:
|
||||
"""
|
||||
获取7天的Bing每日壁纸
|
||||
"""
|
||||
@@ -107,4 +107,4 @@ class WebUtils:
|
||||
return [f"https://cn.bing.com{image.get('url')}" for image in result.get('images') or []]
|
||||
except Exception as err:
|
||||
print(str(err))
|
||||
return None
|
||||
return []
|
||||
|
||||
@@ -23,7 +23,7 @@ APScheduler~=3.10.1
|
||||
cryptography~=43.0.0
|
||||
pytz~=2023.3
|
||||
pycryptodome~=3.20.0
|
||||
qbittorrent-api==2023.5.48
|
||||
qbittorrent-api==2024.9.67
|
||||
plexapi~=4.15.16
|
||||
transmission-rpc~=4.3.0
|
||||
Jinja2~=3.1.4
|
||||
|
||||
47
update
47
update
@@ -192,6 +192,46 @@ function test_connectivity_github() {
|
||||
esac
|
||||
}
|
||||
|
||||
function compare_versions() {
|
||||
local v1="$1"
|
||||
local v2="$2"
|
||||
local parts1=()
|
||||
local parts2=()
|
||||
IFS='.-' read -ra parts1 <<< "$v1"
|
||||
IFS='.-' read -ra parts2 <<< "$v2"
|
||||
local i
|
||||
for ((i = 0; i < ${#parts1[@]} || i < ${#parts2[@]}; i++)); do
|
||||
local part1="${parts1[i]:-0}"
|
||||
local part2="${parts2[i]:-0}"
|
||||
if [[ $part1 =~ ^[0-9]+$ && $part2 =~ ^[0-9]+$ ]]; then
|
||||
if ((part1 > part2)); then
|
||||
return 1
|
||||
elif ((part1 < part2)); then
|
||||
return 2
|
||||
fi
|
||||
else
|
||||
# 定义一个非数字的排序顺序的优先级,特殊后缀版本比较
|
||||
local order=(alpha beta rc stable)
|
||||
local index1=-1
|
||||
local index2=-1
|
||||
for ((j = 0; j < ${#order[@]}; j++)); do
|
||||
if [[ $part1 = "${order[j]}" ]]; then
|
||||
index1=$j
|
||||
fi
|
||||
if [[ $part2 = "${order[j]}" ]]; then
|
||||
index2=$j
|
||||
fi
|
||||
done
|
||||
if ((index1 > index2)); then
|
||||
return 1
|
||||
elif ((index1 < index2)); then
|
||||
return 2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ "${MOVIEPILOT_AUTO_UPDATE}" = "true" ]] || [[ "${MOVIEPILOT_AUTO_UPDATE}" = "release" ]] || [[ "${MOVIEPILOT_AUTO_UPDATE}" = "dev" ]]; then
|
||||
# 优先级:镜像站 > 全局 > 不代理
|
||||
# pip
|
||||
@@ -231,11 +271,14 @@ if [[ "${MOVIEPILOT_AUTO_UPDATE}" = "true" ]] || [[ "${MOVIEPILOT_AUTO_UPDATE}"
|
||||
if [[ "${new_version}" == *v* ]]; then
|
||||
release_version=${new_version}
|
||||
INFO "最新版本号:${release_version}"
|
||||
if [ "${current_version}" != "${release_version}" ]; then
|
||||
# 使用版本号比较函数进行比较
|
||||
if compare_versions "${current_version}" "${release_version}"; then
|
||||
WARN "当前版本已是最新版本,跳过更新步骤..."
|
||||
elif [ $? -eq 2 ]; then
|
||||
INFO "发现新版本,开始自动升级..."
|
||||
install_backend_and_download_resources "tags/${release_version}.zip"
|
||||
else
|
||||
INFO "未发现新版本,跳过更新步骤..."
|
||||
WARN "当前版本高于远程版本,跳过更新步骤..."
|
||||
fi
|
||||
else
|
||||
WARN "最新版本号获取失败,继续启动..."
|
||||
|
||||
Reference in New Issue
Block a user