Merge pull request #2918 from InfinityPacer/feature/event

This commit is contained in:
jxxghp
2024-10-23 20:15:51 +08:00
committed by GitHub
6 changed files with 87 additions and 8 deletions

View File

@@ -202,9 +202,9 @@ class UserChain(ChainBase, metaclass=Singleton):
# 触发认证通过的拦截事件
intercept_event = self.eventmanager.send_event(
etype=ChainEventType.AuthIntercept,
data=AuthInterceptCredentials(username=username, channel=channel, service=service, token=token)
data=AuthInterceptCredentials(username=username, channel=channel, service=service,
token=token, status="completed")
)
if intercept_event and intercept_event.event_data:
intercept_data: AuthInterceptCredentials = intercept_event.event_data
if intercept_data.cancel:

View File

@@ -2,11 +2,12 @@ from typing import Any, Generator, List, Optional, Tuple, Union
from app import schemas
from app.core.context import MediaInfo
from app.core.event import eventmanager
from app.log import logger
from app.modules import _MediaServerBase, _ModuleBase
from app.modules.emby.emby import Emby
from app.schemas.event import AuthCredentials
from app.schemas.types import MediaType, ModuleType
from app.schemas.event import AuthCredentials, AuthInterceptCredentials
from app.schemas.types import MediaType, ModuleType, ChainEventType
class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
@@ -75,6 +76,16 @@ class EmbyModule(_ModuleBase, _MediaServerBase[Emby]):
if not credentials or credentials.grant_type != "password":
return None
for name, server in self.get_instances().items():
# 触发认证拦截事件
intercept_event = eventmanager.send_event(
etype=ChainEventType.AuthIntercept,
data=AuthInterceptCredentials(username=credentials.username, channel=self.get_name(),
service=name, status="triggered")
)
if intercept_event and intercept_event.event_data:
intercept_data: AuthInterceptCredentials = intercept_event.event_data
if intercept_data.cancel:
continue
token = server.authenticate(credentials.username, credentials.password)
if token:
credentials.channel = self.get_name()

View File

@@ -2,11 +2,12 @@ from typing import Any, Generator, List, Optional, Tuple, Union
from app import schemas
from app.core.context import MediaInfo
from app.core.event import eventmanager
from app.log import logger
from app.modules import _MediaServerBase, _ModuleBase
from app.modules.jellyfin.jellyfin import Jellyfin
from app.schemas.event import AuthCredentials
from app.schemas.types import MediaType, ModuleType
from app.schemas.event import AuthCredentials, AuthInterceptCredentials
from app.schemas.types import MediaType, ModuleType, ChainEventType
class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
@@ -75,6 +76,16 @@ class JellyfinModule(_ModuleBase, _MediaServerBase[Jellyfin]):
if not credentials or credentials.grant_type != "password":
return None
for name, server in self.get_instances().items():
# 触发认证拦截事件
intercept_event = eventmanager.send_event(
etype=ChainEventType.AuthIntercept,
data=AuthInterceptCredentials(username=credentials.username, channel=self.get_name(),
service=name, status="triggered")
)
if intercept_event and intercept_event.event_data:
intercept_data: AuthInterceptCredentials = intercept_event.event_data
if intercept_data.cancel:
continue
token = server.authenticate(credentials.username, credentials.password)
if token:
credentials.channel = self.get_name()

View File

@@ -2,10 +2,12 @@ from typing import Optional, Tuple, Union, Any, List, Generator
from app import schemas
from app.core.context import MediaInfo
from app.core.event import eventmanager
from app.log import logger
from app.modules import _ModuleBase, _MediaServerBase
from app.modules.plex.plex import Plex
from app.schemas.types import MediaType, ModuleType
from app.schemas.event import AuthCredentials, AuthInterceptCredentials
from app.schemas.types import MediaType, ModuleType, ChainEventType
class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
@@ -64,6 +66,37 @@ class PlexModule(_ModuleBase, _MediaServerBase[Plex]):
logger.info(f"Plex {name} 服务器连接断开,尝试重连 ...")
server.reconnect()
def user_authenticate(self, credentials: AuthCredentials) -> Optional[AuthCredentials]:
"""
使用Plex用户辅助完成用户认证
:param credentials: 认证数据
:return: 认证数据
"""
# Plex认证
if not credentials or credentials.grant_type != "password":
return None
for name, server in self.get_instances().items():
# 触发认证拦截事件
intercept_event = eventmanager.send_event(
etype=ChainEventType.AuthIntercept,
data=AuthInterceptCredentials(username=credentials.username, channel=self.get_name(),
service=name, status="triggered")
)
if intercept_event and intercept_event.event_data:
intercept_data: AuthInterceptCredentials = intercept_event.event_data
if intercept_data.cancel:
continue
auth_result = server.authenticate(credentials.username, credentials.password)
if auth_result:
token, username = auth_result
credentials.channel = self.get_name()
credentials.service = name
credentials.token = token
# Plex 传入可能为邮箱,这里调整为用户名返回
credentials.username = username
return credentials
return None
def webhook_parser(self, body: Any, form: Any, args: Any) -> Optional[schemas.WebhookEventInfo]:
"""
解析Webhook报文体

View File

@@ -5,6 +5,7 @@ from urllib.parse import quote_plus
from cachetools import TTLCache, cached
from plexapi import media
from plexapi.myplex import MyPlexAccount
from plexapi.server import PlexServer
from requests import Response, Session
@@ -61,6 +62,27 @@ class Plex:
self._plex = None
logger.error(f"Plex服务器连接失败{str(e)}")
def authenticate(self, username: str, password: str) -> Optional[Tuple[str, str]]:
"""
用户认证
:param username: 用户名
:param password: 密码
:return: 认证成功返回 (token, 用户名),否则返回 None
"""
if not username or not password:
return None
try:
account = MyPlexAccount(username=username, password=password, remember=False)
if account:
plex = PlexServer(self._host, account.authToken)
if not plex:
return None
return account.authToken, account.username
except Exception as e:
# 处理认证失败或网络错误等情况
logger.error(f"Authentication failed: {e}")
return None
@cached(cache=TTLCache(maxsize=100, ttl=86400))
def __get_library_images(self, library_key: str, mtype: int) -> Optional[List[str]]:
"""

View File

@@ -74,15 +74,17 @@ class AuthInterceptCredentials(ChainEventData):
channel (str): 认证渠道
service (str): 服务名称
token (str): 认证令牌
status (str): 认证状态,"triggered""completed" 两个状态
# 输出参数
source (str): 拦截源,默认值为 "未知拦截源"
cancel (bool): 是否取消认证,默认值为 False
"""
# 输入参数
username: str = Field(..., description="用户名")
username: Optional[str] = Field(..., description="用户名")
channel: str = Field(..., description="认证渠道")
service: str = Field(..., description="服务名称")
status: str = Field(..., description="认证状态, 包含 'triggered' 表示认证触发,'completed' 表示认证成功")
token: Optional[str] = Field(None, description="认证令牌")
# 输出参数