Merge branch 'dev' of https://github.com/jxxghp/MoviePilot into feature/security

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