Merge branch 'main' of github.com:lingjiameng/MoviePilot

This commit is contained in:
ljmeng 2024-03-16 15:35:02 +08:00
commit 6ba40edeb4
21 changed files with 323 additions and 71 deletions

View File

@ -219,6 +219,14 @@ location / {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
} }
``` ```
- 反代使用ssl时需要开启`http2`,否则会导致日志加载时间过长或不可用。以`Nginx`为例:
```nginx configuration
server{
listen 443 ssl;
http2 on;
...
}
```
- 新建的企业微信应用需要固定公网IP的代理才能收到消息代理添加以下代码 - 新建的企业微信应用需要固定公网IP的代理才能收到消息代理添加以下代码
```nginx configuration ```nginx configuration
location /cgi-bin/gettoken { location /cgi-bin/gettoken {

View File

@ -90,7 +90,7 @@ def movie_top250(page: int = 1,
""" """
浏览豆瓣剧集信息 浏览豆瓣剧集信息
""" """
movies = DoubanChain().movie_top250(page=page, count=count) movies = DoubanChain().movie_top250(page=page, count=count) or []
return [MediaInfo(douban_info=movie).to_dict() for movie in movies] return [MediaInfo(douban_info=movie).to_dict() for movie in movies]
@ -101,7 +101,7 @@ def tv_weekly_chinese(page: int = 1,
""" """
中国每周剧集口碑榜 中国每周剧集口碑榜
""" """
tvs = DoubanChain().tv_weekly_chinese(page=page, count=count) tvs = DoubanChain().tv_weekly_chinese(page=page, count=count) or []
return [MediaInfo(douban_info=tv).to_dict() for tv in tvs] return [MediaInfo(douban_info=tv).to_dict() for tv in tvs]
@ -112,7 +112,7 @@ def tv_weekly_global(page: int = 1,
""" """
全球每周剧集口碑榜 全球每周剧集口碑榜
""" """
tvs = DoubanChain().tv_weekly_global(page=page, count=count) tvs = DoubanChain().tv_weekly_global(page=page, count=count) or []
return [MediaInfo(douban_info=tv).to_dict() for tv in tvs] return [MediaInfo(douban_info=tv).to_dict() for tv in tvs]
@ -123,7 +123,7 @@ def tv_animation(page: int = 1,
""" """
热门动画剧集 热门动画剧集
""" """
tvs = DoubanChain().tv_animation(page=page, count=count) tvs = DoubanChain().tv_animation(page=page, count=count) or []
return [MediaInfo(douban_info=tv).to_dict() for tv in tvs] return [MediaInfo(douban_info=tv).to_dict() for tv in tvs]
@ -134,7 +134,7 @@ def movie_hot(page: int = 1,
""" """
热门电影 热门电影
""" """
movies = DoubanChain().movie_hot(page=page, count=count) movies = DoubanChain().movie_hot(page=page, count=count) or []
return [MediaInfo(douban_info=movie).to_dict() for movie in movies] return [MediaInfo(douban_info=movie).to_dict() for movie in movies]
@ -145,7 +145,7 @@ def tv_hot(page: int = 1,
""" """
热门电视剧 热门电视剧
""" """
tvs = DoubanChain().tv_hot(page=page, count=count) tvs = DoubanChain().tv_hot(page=page, count=count) or []
return [MediaInfo(douban_info=tv).to_dict() for tv in tvs] return [MediaInfo(douban_info=tv).to_dict() for tv in tvs]

View File

@ -113,20 +113,6 @@ def media_info(mediaid: str, type_name: str,
doubanid = mediaid[7:] doubanid = mediaid[7:]
if not tmdbid and not doubanid: if not tmdbid and not doubanid:
return schemas.MediaInfo() return schemas.MediaInfo()
if settings.RECOGNIZE_SOURCE == "themoviedb":
if not tmdbid and doubanid:
tmdbinfo = MediaChain().get_tmdbinfo_by_doubanid(doubanid=doubanid, mtype=mtype)
if tmdbinfo:
tmdbid = tmdbinfo.get("id")
else:
return schemas.MediaInfo()
else:
if not doubanid and tmdbid:
doubaninfo = MediaChain().get_doubaninfo_by_tmdbid(tmdbid=tmdbid, mtype=mtype)
if doubaninfo:
doubanid = doubaninfo.get("id")
else:
return schemas.MediaInfo()
mediainfo = MediaChain().recognize_media(tmdbid=tmdbid, doubanid=doubanid, mtype=mtype) mediainfo = MediaChain().recognize_media(tmdbid=tmdbid, doubanid=doubanid, mtype=mtype)
if mediainfo: if mediainfo:
MediaChain().obtain_images(mediainfo) MediaChain().obtain_images(mediainfo)

View File

@ -1,18 +1,24 @@
import json
from typing import Union, Any, List from typing import Union, Any, List
from fastapi import APIRouter, BackgroundTasks, Depends from fastapi import APIRouter, BackgroundTasks, Depends
from fastapi import Request from fastapi import Request
from sqlalchemy.orm import Session
from starlette.responses import PlainTextResponse from starlette.responses import PlainTextResponse
from app import schemas from app import schemas
from app.chain.message import MessageChain from app.chain.message import MessageChain
from app.core.config import settings from app.core.config import settings
from app.core.security import verify_token from app.core.security import verify_token
from app.db import get_db
from app.db.models import User
from app.db.models.message import Message
from app.db.systemconfig_oper import SystemConfigOper from app.db.systemconfig_oper import SystemConfigOper
from app.db.userauth import get_current_active_superuser
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 import NotificationSwitch from app.schemas import NotificationSwitch
from app.schemas.types import SystemConfigKey, NotificationType from app.schemas.types import SystemConfigKey, NotificationType, MessageChannel
router = APIRouter() router = APIRouter()
@ -36,6 +42,39 @@ async def user_message(background_tasks: BackgroundTasks, request: Request):
return schemas.Response(success=True) return schemas.Response(success=True)
@router.post("/web", summary="接收WEB消息", response_model=schemas.Response)
async def web_message(text: str, current_user: User = Depends(get_current_active_superuser)):
"""
WEB消息响应
"""
MessageChain().handle_message(
channel=MessageChannel.Web,
userid=current_user.id,
username=current_user.name,
text=text
)
return schemas.Response(success=True)
@router.get("/web", summary="获取WEB消息", response_model=List[dict])
def get_web_message(_: schemas.TokenPayload = Depends(verify_token),
db: Session = Depends(get_db),
page: int = 1,
count: int = 20):
"""
获取WEB消息列表
"""
ret_messages = []
messages = Message.list_by_page(db, page=page, count=count)
for message in messages:
try:
ret_messages.append(message.to_dict())
except Exception as e:
logger.error(f"获取WEB消息列表失败: {str(e)}")
continue
return ret_messages
def wechat_verify(echostr: str, msg_signature: str, def wechat_verify(echostr: str, msg_signature: str,
timestamp: Union[str, int], nonce: str) -> Any: timestamp: Union[str, int], nonce: str) -> Any:
""" """
@ -103,7 +142,7 @@ def read_switchs(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
def set_switchs(switchs: List[NotificationSwitch], def set_switchs(switchs: List[NotificationSwitch],
_: schemas.TokenPayload = Depends(verify_token)) -> Any: _: schemas.TokenPayload = Depends(verify_token)) -> Any:
""" """
查询通知消息渠道开关 设置通知消息渠道开关
""" """
switch_list = [] switch_list = []
for switch in switchs: for switch in switchs:

View File

@ -138,7 +138,7 @@ def set_setting(key: str, value: Union[list, dict, bool, int, str] = None,
@router.get("/message", summary="实时消息") @router.get("/message", summary="实时消息")
def get_message(token: str): def get_message(token: str, role: str = "sys"):
""" """
实时获取系统消息返回格式为SSE 实时获取系统消息返回格式为SSE
""" """
@ -152,7 +152,7 @@ def get_message(token: str):
def event_generator(): def event_generator():
while True: while True:
detail = message.get() detail = message.get(role)
yield 'data: %s\n\n' % (detail or '') yield 'data: %s\n\n' % (detail or '')
time.sleep(3) time.sleep(3)

View File

@ -15,6 +15,8 @@ from app.core.context import MediaInfo, TorrentInfo
from app.core.event import EventManager from app.core.event import EventManager
from app.core.meta import MetaBase from app.core.meta import MetaBase
from app.core.module import ModuleManager from app.core.module import ModuleManager
from app.db.message_oper import MessageOper
from app.helper.message import MessageHelper
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 WebhookEventInfo, TmdbEpisode
@ -33,6 +35,8 @@ class ChainBase(metaclass=ABCMeta):
""" """
self.modulemanager = ModuleManager() self.modulemanager = ModuleManager()
self.eventmanager = EventManager() self.eventmanager = EventManager()
self.messageoper = MessageOper()
self.messagehelper = MessageHelper()
@staticmethod @staticmethod
def load_cache(filename: str) -> Any: def load_cache(filename: str) -> Any:
@ -403,6 +407,10 @@ class ChainBase(metaclass=ABCMeta):
:param message: 消息体 :param message: 消息体
:return: 成功或失败 :return: 成功或失败
""" """
logger.info(f"发送消息channel={message.channel}"
f"title={message.title}, "
f"text={message.text}"
f"userid={message.userid}")
# 发送事件 # 发送事件
self.eventmanager.send_event(etype=EventType.NoticeMessage, self.eventmanager.send_event(etype=EventType.NoticeMessage,
data={ data={
@ -413,10 +421,13 @@ class ChainBase(metaclass=ABCMeta):
"image": message.image, "image": message.image,
"userid": message.userid, "userid": message.userid,
}) })
logger.info(f"发送消息channel={message.channel}" # 保存消息
f"title={message.title}, " self.messagehelper.put(message, role="user")
f"text={message.text}" self.messageoper.add(channel=message.channel, mtype=message.mtype,
f"userid={message.userid}") title=message.title, text=message.text,
image=message.image, link=message.link,
userid=message.userid, action=1)
# 发送
self.run_module("post_message", message=message) self.run_module("post_message", message=message)
def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]: def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]:

View File

@ -12,9 +12,11 @@ from app.core.config import settings
from app.core.context import MediaInfo, Context from app.core.context import MediaInfo, Context
from app.core.event import EventManager from app.core.event import EventManager
from app.core.meta import MetaBase from app.core.meta import MetaBase
from app.db.message_oper import MessageOper
from app.helper.message import MessageHelper
from app.helper.torrent import TorrentHelper from app.helper.torrent import TorrentHelper
from app.log import logger from app.log import logger
from app.schemas import Notification, NotExistMediaInfo from app.schemas import Notification, NotExistMediaInfo, CommingMessage
from app.schemas.types import EventType, MessageChannel, MediaType from app.schemas.types import EventType, MessageChannel, MediaType
from app.utils.string import StringUtils from app.utils.string import StringUtils
@ -43,6 +45,8 @@ class MessageChain(ChainBase):
self.mediachain = MediaChain() self.mediachain = MediaChain()
self.eventmanager = EventManager() self.eventmanager = EventManager()
self.torrenthelper = TorrentHelper() self.torrenthelper = TorrentHelper()
self.messagehelper = MessageHelper()
self.messageoper = MessageOper()
def __get_noexits_info( def __get_noexits_info(
self, self,
@ -100,10 +104,8 @@ class MessageChain(ChainBase):
def process(self, body: Any, form: Any, args: Any) -> None: def process(self, body: Any, form: Any, args: Any) -> None:
""" """
识别消息内容执行操作 调用模块识别消息内容
""" """
# 申明全局变量
global _current_page, _current_meta, _current_media
# 获取消息内容 # 获取消息内容
info = self.message_parser(body=body, form=form, args=args) info = self.message_parser(body=body, form=form, args=args)
if not info: if not info:
@ -122,10 +124,35 @@ class MessageChain(ChainBase):
if not text: if not text:
logger.debug(f'未识别到消息内容::{body}{form}{args}') logger.debug(f'未识别到消息内容::{body}{form}{args}')
return return
# 处理消息
self.handle_message(channel=channel, userid=userid, username=username, text=text)
def handle_message(self, channel: MessageChannel, userid: Union[str, int], username: str, text: str) -> None:
"""
识别消息内容执行操作
"""
# 申明全局变量
global _current_page, _current_meta, _current_media
# 加载缓存 # 加载缓存
user_cache: Dict[str, dict] = self.load_cache(self._cache_file) or {} user_cache: Dict[str, dict] = self.load_cache(self._cache_file) or {}
# 处理消息 # 处理消息
logger.info(f'收到用户消息内容,用户:{userid},内容:{text}') logger.info(f'收到用户消息内容,用户:{userid},内容:{text}')
# 保存消息
self.messagehelper.put(
CommingMessage(
userid=userid,
username=username,
channel=channel,
text=text
), role="user")
self.messageoper.add(
channel=channel,
userid=userid,
username=username,
text=text,
action=0
)
# 处理消息
if text.startswith('/'): if text.startswith('/'):
# 执行命令 # 执行命令
self.eventmanager.send_event( self.eventmanager.send_event(

View File

@ -124,14 +124,12 @@ class SearchChain(ChainBase):
if keyword: if keyword:
keywords = [keyword] keywords = [keyword]
else: else:
keywords = list( # 去重去空,但要保持顺序
{ keywords = list(dict.fromkeys([k for k in [mediainfo.title,
mediainfo.title,
mediainfo.original_title, mediainfo.original_title,
mediainfo.en_title, mediainfo.en_title,
mediainfo.sg_title mediainfo.sg_title] if k]))
} - {None}
)
# 执行搜索 # 执行搜索
torrents: List[TorrentInfo] = self.__search_all_sites( torrents: List[TorrentInfo] = self.__search_all_sites(
mediainfo=mediainfo, mediainfo=mediainfo,

View File

@ -304,6 +304,8 @@ class SiteChain(ChainBase):
if not site_info: if not site_info:
return False, f"站点【{url}】不存在" return False, f"站点【{url}】不存在"
# 模拟登录
try:
# 特殊站点测试 # 特殊站点测试
if self.special_site_test.get(domain): if self.special_site_test.get(domain):
return self.special_site_test[domain](site_info) return self.special_site_test[domain](site_info)
@ -316,8 +318,7 @@ class SiteChain(ChainBase):
public = site_info.public public = site_info.public
proxies = settings.PROXY if site_info.proxy else None proxies = settings.PROXY if site_info.proxy else None
proxy_server = settings.PROXY_SERVER if site_info.proxy else None proxy_server = settings.PROXY_SERVER if site_info.proxy else None
# 模拟登录
try:
# 访问链接 # 访问链接
if render: if render:
page_source = PlaywrightHelper().get_page_source(url=site_url, page_source = PlaywrightHelper().get_page_source(url=site_url,

57
app/db/message_oper.py Normal file
View File

@ -0,0 +1,57 @@
import time
from typing import Optional, Union
from sqlalchemy.orm import Session
from app.db import DbOper
from app.db.models.message import Message
from app.schemas import MessageChannel, NotificationType
class MessageOper(DbOper):
"""
消息数据管理
"""
def __init__(self, db: Session = None):
super().__init__(db)
def add(self,
channel: MessageChannel = None,
mtype: NotificationType = None,
title: str = None,
text: str = None,
image: str = None,
link: str = None,
userid: str = None,
action: int = 1,
**kwargs):
"""
新增媒体服务器数据
:param channel: 消息渠道
:param mtype: 消息类型
:param title: 标题
:param text: 文本内容
:param image: 图片
:param link: 链接
:param userid: 用户ID
:param action: 消息方向0-接收息1-发送消息
"""
kwargs.update({
"channel": channel.value if channel else '',
"mtype": mtype.value if mtype else '',
"title": title,
"text": text,
"image": image,
"link": link,
"userid": userid,
"action": action,
"reg_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
})
Message(**kwargs).create(self._db)
def list_by_page(self, page: int = 1, count: int = 30) -> Optional[str]:
"""
获取媒体服务器数据ID
"""
return Message.list_by_page(self._db, page, count)

View File

@ -200,6 +200,7 @@ class DownloadFiles(Base):
result = db.query(DownloadFiles).filter(DownloadFiles.savepath == savepath).all() result = db.query(DownloadFiles).filter(DownloadFiles.savepath == savepath).all()
return list(result) return list(result)
@staticmethod
@db_update @db_update
def delete_by_fullpath(db: Session, fullpath: str): def delete_by_fullpath(db: Session, fullpath: str):
db.query(DownloadFiles).filter(DownloadFiles.fullpath == fullpath, db.query(DownloadFiles).filter(DownloadFiles.fullpath == fullpath,

36
app/db/models/message.py Normal file
View File

@ -0,0 +1,36 @@
from sqlalchemy import Column, Integer, String, Sequence
from sqlalchemy.orm import Session
from app.db import db_query, Base
class Message(Base):
"""
消息表
"""
id = Column(Integer, Sequence('id'), primary_key=True, index=True)
# 消息渠道
channel = Column(String, nullable=False)
# 消息类型
mtype = Column(String, nullable=False)
# 标题
title = Column(String)
# 文本内容
text = Column(String)
# 图片
image = Column(String)
# 链接
link = Column(String)
# 用户ID
userid = Column(String)
# 登记时间
reg_time = Column(String)
# 消息方向0-接收息1-发送消息
action = Column(Integer)
@staticmethod
@db_query
def list_by_page(db: Session, page: int = 1, count: int = 30):
result = db.query(Message).order_by(Message.reg_time.desc()).offset((page - 1) * count).limit(
count).all()
return list(result)

View File

@ -1,19 +1,44 @@
import json
import queue import queue
import time
from typing import Optional, Any
from app.utils.singleton import Singleton from app.utils.singleton import Singleton
class MessageHelper(metaclass=Singleton): class MessageHelper(metaclass=Singleton):
""" """
消息队列管理器 消息队列管理器包括系统消息和用户消息
""" """
def __init__(self): def __init__(self):
self.queue = queue.Queue() self.sys_queue = queue.Queue()
self.user_queue = queue.Queue()
def put(self, message: str): def put(self, message: Any, role: str = "sys"):
self.queue.put(message) """
存消息
:param message: 消息
:param role: 消息通道 sys/user
"""
if role == "sys":
self.sys_queue.put(message)
else:
if isinstance(message, str):
self.user_queue.put(message)
elif hasattr(message, "to_dict"):
content = message.to_dict()
content['date'] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
self.user_queue.put(json.dumps(content))
def get(self): def get(self, role: str = "sys") -> Optional[str]:
if not self.queue.empty(): """
return self.queue.get(block=False) 取消息
:param role: 消息通道 sys/user
"""
if role == "sys":
if not self.sys_queue.empty():
return self.sys_queue.get(block=False)
else:
if not self.user_queue.empty():
return self.user_queue.get(block=False)
return None return None

View File

@ -57,7 +57,11 @@ class DoubanModule(_ModuleBase):
:param cache: 是否使用缓存 :param cache: 是否使用缓存
:return: 识别的媒体信息包括剧集信息 :return: 识别的媒体信息包括剧集信息
""" """
if settings.RECOGNIZE_SOURCE != "douban": if not doubanid and not meta:
return None
if meta and not doubanid \
and settings.RECOGNIZE_SOURCE != "douban":
return None return None
if not meta: if not meta:

View File

@ -38,12 +38,12 @@ class FileTransferModule(_ModuleBase):
for path in [settings.DOWNLOAD_PATH, for path in [settings.DOWNLOAD_PATH,
settings.DOWNLOAD_MOVIE_PATH, settings.DOWNLOAD_MOVIE_PATH,
settings.DOWNLOAD_TV_PATH, settings.DOWNLOAD_TV_PATH,
settings.DWONLOAD_ANIME_PATH]: settings.DOWNLOAD_ANIME_PATH]:
if not path: if not path:
continue continue
download_path = Path(path) download_path = Path(path)
if not download_path.exists(): if not download_path.exists():
return False, f"目录 {download_path} 不存在" return False, f"下载目录 {download_path} 不存在"
download_paths.append(path) download_paths.append(path)
# 下载目录的设备ID # 下载目录的设备ID
download_devids = [Path(path).stat().st_dev for path in download_paths] download_devids = [Path(path).stat().st_dev for path in download_paths]
@ -54,7 +54,7 @@ class FileTransferModule(_ModuleBase):
for path in settings.LIBRARY_PATHS: for path in settings.LIBRARY_PATHS:
library_path = Path(path) library_path = Path(path)
if not library_path.exists(): if not library_path.exists():
return False, f"目录不存在:{library_path}" return False, f"媒体库目录不存在:{library_path}"
if settings.DOWNLOADER_MONITOR and settings.TRANSFER_TYPE == "link": if settings.DOWNLOADER_MONITOR and settings.TRANSFER_TYPE == "link":
if library_path.stat().st_dev not in download_devids: if library_path.stat().st_dev not in download_devids:
return False, f"媒体库目录 {library_path} " \ return False, f"媒体库目录 {library_path} " \

View File

@ -372,6 +372,24 @@ class Qbittorrent:
logger.error(f"设置速度限制出错:{str(err)}") logger.error(f"设置速度限制出错:{str(err)}")
return False return False
def get_speed_limit(self):
"""
获取QB速度
:return: 返回download_limit 和upload_limit 默认是0
"""
if not self.qbc:
return False
download_limit = 0
upload_limit = 0
try:
download_limit = self.qbc.transfer.download_limit
upload_limit = self.qbc.transfer.upload_limit
except Exception as err:
logger.error(f"获取速度限制出错:{str(err)}")
return (download_limit/1024, upload_limit/1024)
def recheck_torrents(self, ids: Union[str, list]) -> bool: def recheck_torrents(self, ids: Union[str, list]) -> bool:
""" """
重新校验种子 重新校验种子

View File

@ -67,7 +67,11 @@ class TheMovieDbModule(_ModuleBase):
:param cache: 是否使用缓存 :param cache: 是否使用缓存
:return: 识别的媒体信息包括剧集信息 :return: 识别的媒体信息包括剧集信息
""" """
if settings.RECOGNIZE_SOURCE != "themoviedb": if not tmdbid and not meta:
return None
if meta and not tmdbid \
and settings.RECOGNIZE_SOURCE != "themoviedb":
return None return None
if not meta: if not meta:
@ -182,7 +186,7 @@ class TheMovieDbModule(_ModuleBase):
:param season: 季号 :param season: 季号
""" """
# 搜索 # 搜索
logger.info(f"开始使用 名称:{name}年份:{year} 匹配TMDB信息 ...") logger.info(f"开始使用 名称:{name} 年份:{year} 匹配TMDB信息 ...")
info = self.tmdb.match(name=name, info = self.tmdb.match(name=name,
year=year, year=year,
mtype=mtype, mtype=mtype,

View File

@ -189,9 +189,16 @@ class TmdbHelper:
season_year, season_year,
season_number) season_number)
if not info: if not info:
year_range = [year]
if year:
year_range.append(str(int(year) + 1))
year_range.append(str(int(year) - 1))
for year in year_range:
logger.debug( logger.debug(
f"正在识别{mtype.value}{name}, 年份={year} ...") f"正在识别{mtype.value}{name}, 年份={year} ...")
info = self.__search_tv_by_name(name, year) info = self.__search_tv_by_name(name, year)
if info:
break
if info: if info:
info['media_type'] = MediaType.TV info['media_type'] = MediaType.TV
# 返回 # 返回

View File

@ -17,6 +17,20 @@ class CommingMessage(BaseModel):
channel: Optional[MessageChannel] = None channel: Optional[MessageChannel] = None
# 消息体 # 消息体
text: Optional[str] = None text: Optional[str] = None
# 时间
date: Optional[str] = None
# 消息方向
action: Optional[int] = 0
def to_dict(self):
"""
转换为字典
"""
items = self.dict()
for k, v in items.items():
if isinstance(v, MessageChannel):
items[k] = v.value
return items
class Notification(BaseModel): class Notification(BaseModel):
@ -37,6 +51,21 @@ class Notification(BaseModel):
link: Optional[str] = None link: Optional[str] = None
# 用户ID # 用户ID
userid: Optional[Union[str, int]] = None userid: Optional[Union[str, int]] = None
# 时间
date: Optional[str] = None
# 消息方向
action: Optional[int] = 1
def to_dict(self):
"""
转换为字典
"""
items = self.dict()
for k, v in items.items():
if isinstance(v, MessageChannel) \
or isinstance(v, NotificationType):
items[k] = v.value
return items
class NotificationSwitch(BaseModel): class NotificationSwitch(BaseModel):

View File

@ -117,3 +117,4 @@ class MessageChannel(Enum):
Slack = "Slack" Slack = "Slack"
SynologyChat = "SynologyChat" SynologyChat = "SynologyChat"
VoceChat = "VoceChat" VoceChat = "VoceChat"
Web = "Web"

View File

@ -1 +1 @@
APP_VERSION = 'v1.7.1' APP_VERSION = 'v1.7.2'