add notification link

This commit is contained in:
jxxghp 2024-06-08 10:47:50 +08:00
parent 748de0ff00
commit e05c643a6b
21 changed files with 148 additions and 54 deletions

View File

@ -114,6 +114,7 @@ MoviePilot需要配套下载器和媒体服务器配合使用。
- **❗SUPERUSER** 超级管理员用户名,默认`admin`,安装后使用该用户登录后台管理界面,**注意:启动一次后再次修改该值不会生效,除非删除数据库文件!** - **❗SUPERUSER** 超级管理员用户名,默认`admin`,安装后使用该用户登录后台管理界面,**注意:启动一次后再次修改该值不会生效,除非删除数据库文件!**
- **❗API_TOKEN** API密钥默认`moviepilot`在媒体服务器Webhook、微信回调等地址配置中需要加上`?token=`该值,建议修改为复杂字符串 - **❗API_TOKEN** API密钥默认`moviepilot`在媒体服务器Webhook、微信回调等地址配置中需要加上`?token=`该值,建议修改为复杂字符串
- **APP_DOMAIN** MoviePilot WEB使用的域名用于生成跳转链接等
- **BIG_MEMORY_MODE** 大内存模式,默认为`false`,开启后会增加缓存数量,占用更多的内存,但响应速度会更快 - **BIG_MEMORY_MODE** 大内存模式,默认为`false`,开启后会增加缓存数量,占用更多的内存,但响应速度会更快
- **DOH_ENABLE** DNS over HTTPS开关`true`/`false`,默认`true`开启后会使用DOH对api.themoviedb.org等域名进行解析以减少被DNS污染的情况提升网络连通性 - **DOH_ENABLE** DNS over HTTPS开关`true`/`false`,默认`true`开启后会使用DOH对api.themoviedb.org等域名进行解析以减少被DNS污染的情况提升网络连通性
- **META_CACHE_EXPIRE** 元数据识别缓存过期时间小时数字型不配置或者配置为0时使用系统默认大内存模式为7天否则为3天调大该值可减少themoviedb的访问次数 - **META_CACHE_EXPIRE** 元数据识别缓存过期时间小时数字型不配置或者配置为0时使用系统默认大内存模式为7天否则为3天调大该值可减少themoviedb的访问次数

View File

@ -89,7 +89,8 @@ class DownloadChain(ChainBase):
title=f"{mediainfo.title_year} " title=f"{mediainfo.title_year} "
f"{'%s %s' % (meta.season, download_episodes) if download_episodes else meta.season_episode} 开始下载", f"{'%s %s' % (meta.season, download_episodes) if download_episodes else meta.season_episode} 开始下载",
text=msg_text, text=msg_text,
image=mediainfo.get_message_image())) image=mediainfo.get_message_image(),
link=settings.MP_DOMAIN('/#/downloading')))
def download_torrent(self, torrent: TorrentInfo, def download_torrent(self, torrent: TorrentInfo,
channel: MessageChannel = None, channel: MessageChannel = None,
@ -841,7 +842,9 @@ class DownloadChain(ChainBase):
channel=channel, channel=channel,
mtype=NotificationType.Download, mtype=NotificationType.Download,
title="没有正在下载的任务!", title="没有正在下载的任务!",
userid=userid)) userid=userid,
link=settings.MP_DOMAIN('#/downloading')
))
return return
# 发送消息 # 发送消息
title = f"{len(torrents)} 个任务正在下载:" title = f"{len(torrents)} 个任务正在下载:"
@ -853,8 +856,13 @@ class DownloadChain(ChainBase):
f"{round(torrent.progress, 1)}%") f"{round(torrent.progress, 1)}%")
index += 1 index += 1
self.post_message(Notification( self.post_message(Notification(
channel=channel, mtype=NotificationType.Download, channel=channel,
title=title, text="\n".join(messages), userid=userid)) mtype=NotificationType.Download,
title=title,
text="\n".join(messages),
userid=userid,
link=settings.MP_DOMAIN('#/downloading')
))
def downloading(self) -> List[DownloadingTorrent]: def downloading(self) -> List[DownloadingTorrent]:
""" """

View File

@ -520,5 +520,6 @@ class MessageChain(ChainBase):
self.post_torrents_message(Notification( self.post_torrents_message(Notification(
channel=channel, channel=channel,
title=title, title=title,
userid=userid userid=userid,
link=settings.MP_DOMAIN('#/resource')
), torrents=items) ), torrents=items)

View File

@ -109,11 +109,11 @@ class SiteChain(ChainBase):
user_agent = site.ua or settings.USER_AGENT user_agent = site.ua or settings.USER_AGENT
url = f"{site.url}api/member/profile" url = f"{site.url}api/member/profile"
headers = { headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"User-Agent": user_agent, "User-Agent": user_agent,
"Accept": "application/json, text/plain, */*", "Accept": "application/json, text/plain, */*",
"Authorization": site.token "Authorization": site.token
} }
res = RequestUtils( res = RequestUtils(
headers=headers, headers=headers,
proxies=settings.PROXY if site.proxy else None, proxies=settings.PROXY if site.proxy else None,
@ -457,7 +457,8 @@ class SiteChain(ChainBase):
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
title="没有维护任何站点信息!", title="没有维护任何站点信息!",
userid=userid)) userid=userid,
link=settings.MP_DOMAIN('#/site')))
title = f"共有 {len(site_list)} 个站点,回复对应指令操作:" \ title = f"共有 {len(site_list)} 个站点,回复对应指令操作:" \
f"\n- 禁用站点:/site_disable [id]" \ f"\n- 禁用站点:/site_disable [id]" \
f"\n- 启用站点:/site_enable [id]" \ f"\n- 启用站点:/site_enable [id]" \
@ -475,7 +476,8 @@ class SiteChain(ChainBase):
# 发送列表 # 发送列表
self.post_message(Notification( self.post_message(Notification(
channel=channel, channel=channel,
title=title, text="\n".join(messages), userid=userid)) title=title, text="\n".join(messages), userid=userid,
link=settings.MP_DOMAIN('#/site')))
def remote_disable(self, arg_str, channel: MessageChannel, userid: Union[str, int] = None): def remote_disable(self, arg_str, channel: MessageChannel, userid: Union[str, int] = None):
""" """

View File

@ -169,10 +169,15 @@ class SubscribeChain(ChainBase):
else: else:
text = f"评分:{mediainfo.vote_average}" text = f"评分:{mediainfo.vote_average}"
# 群发 # 群发
if mediainfo.type == MediaType.TV:
link = settings.MP_DOMAIN('#/subscribe-tv?tab=mysub')
else:
link = settings.MP_DOMAIN('#/subscribe-movie?tab=mysub')
self.post_message(Notification(mtype=NotificationType.Subscribe, self.post_message(Notification(mtype=NotificationType.Subscribe,
title=f"{mediainfo.title_year} {metainfo.season} 已添加订阅", title=f"{mediainfo.title_year} {metainfo.season} 已添加订阅",
text=text, text=text,
image=mediainfo.get_message_image())) image=mediainfo.get_message_image(),
link=link))
# 发送事件 # 发送事件
EventManager().send_event(EventType.SubscribeAdded, { EventManager().send_event(EventType.SubscribeAdded, {
"subscribe_id": sid, "subscribe_id": sid,
@ -922,9 +927,14 @@ class SubscribeChain(ChainBase):
# 删除订阅 # 删除订阅
self.subscribeoper.delete(subscribe.id) self.subscribeoper.delete(subscribe.id)
# 发送通知 # 发送通知
if mediainfo.type == MediaType.TV:
link = settings.MP_DOMAIN('#/subscribe-tv?tab=mysub')
else:
link = settings.MP_DOMAIN('#/subscribe-movie?tab=mysub')
self.post_message(Notification(mtype=NotificationType.Subscribe, self.post_message(Notification(mtype=NotificationType.Subscribe,
title=f'{mediainfo.title_year} {meta.season} 已完成{msgstr}', title=f'{mediainfo.title_year} {meta.season} 已完成{msgstr}',
image=mediainfo.get_message_image())) image=mediainfo.get_message_image(),
link=link))
# 发送事件 # 发送事件
EventManager().send_event(EventType.SubscribeComplete, { EventManager().send_event(EventType.SubscribeComplete, {
"subscribe_id": subscribe.id, "subscribe_id": subscribe.id,

View File

@ -99,7 +99,8 @@ class TorrentsChain(ChainBase, metaclass=Singleton):
if not site.get("rss"): if not site.get("rss"):
logger.error(f'站点 {domain} 未配置RSS地址') logger.error(f'站点 {domain} 未配置RSS地址')
return [] return []
rss_items = self.rsshelper.parse(site.get("rss"), True if site.get("proxy") else False, timeout=int(site.get("timeout") or 30)) rss_items = self.rsshelper.parse(site.get("rss"), True if site.get("proxy") else False,
timeout=int(site.get("timeout") or 30))
if rss_items is None: if rss_items is None:
# rss过期尝试保留原配置生成新的rss # rss过期尝试保留原配置生成新的rss
self.__renew_rss_url(domain=domain, site=site) self.__renew_rss_url(domain=domain, site=site)
@ -253,10 +254,14 @@ class TorrentsChain(ChainBase, metaclass=Singleton):
else: else:
# 发送消息 # 发送消息
self.post_message( self.post_message(
Notification(mtype=NotificationType.SiteMessage, title=f"站点 {domain} RSS链接已过期")) Notification(mtype=NotificationType.SiteMessage, title=f"站点 {domain} RSS链接已过期",
link=settings.MP_DOMAIN('#/site'))
)
else: else:
self.post_message( self.post_message(
Notification(mtype=NotificationType.SiteMessage, title=f"站点 {domain} RSS链接已过期")) Notification(mtype=NotificationType.SiteMessage, title=f"站点 {domain} RSS链接已过期",
link=settings.MP_DOMAIN('#/site')))
except Exception as e: except Exception as e:
logger.error(f"站点 {domain} RSS链接自动获取失败{str(e)} - {traceback.format_exc()}") logger.error(f"站点 {domain} RSS链接自动获取失败{str(e)} - {traceback.format_exc()}")
self.post_message(Notification(mtype=NotificationType.SiteMessage, title=f"站点 {domain} RSS链接已过期")) self.post_message(Notification(mtype=NotificationType.SiteMessage, title=f"站点 {domain} RSS链接已过期",
link=settings.MP_DOMAIN('#/site')))

View File

@ -269,7 +269,8 @@ class TransferChain(ChainBase):
self.post_message(Notification( self.post_message(Notification(
mtype=NotificationType.Manual, mtype=NotificationType.Manual,
title=f"{file_path.name} 未识别到媒体信息,无法入库!", title=f"{file_path.name} 未识别到媒体信息,无法入库!",
text=f"回复:```\n/redo {his.id} [tmdbid]|[类型]\n``` 手动识别转移。" text=f"回复:```\n/redo {his.id} [tmdbid]|[类型]\n``` 手动识别转移。",
link=settings.MP_DOMAIN('#/history')
)) ))
# 计数 # 计数
processed_num += 1 processed_num += 1
@ -332,7 +333,8 @@ class TransferChain(ChainBase):
mtype=NotificationType.Manual, mtype=NotificationType.Manual,
title=f"{file_mediainfo.title_year} {file_meta.season_episode} 入库失败!", title=f"{file_mediainfo.title_year} {file_meta.season_episode} 入库失败!",
text=f"原因:{transferinfo.message or '未知'}", text=f"原因:{transferinfo.message or '未知'}",
image=file_mediainfo.get_message_image() image=file_mediainfo.get_message_image(),
link=settings.MP_DOMAIN('#/history')
)) ))
# 计数 # 计数
processed_num += 1 processed_num += 1
@ -506,7 +508,7 @@ class TransferChain(ChainBase):
mediaid=media_id) mediaid=media_id)
if not state: if not state:
self.post_message(Notification(channel=channel, title="手动整理失败", self.post_message(Notification(channel=channel, title="手动整理失败",
text=errmsg, userid=userid)) text=errmsg, userid=userid, link=settings.MP_DOMAIN('#/history')))
return return
def re_transfer(self, logid: int, mtype: MediaType = None, def re_transfer(self, logid: int, mtype: MediaType = None,
@ -642,7 +644,8 @@ class TransferChain(ChainBase):
# 发送 # 发送
self.post_message(Notification( self.post_message(Notification(
mtype=NotificationType.Organize, mtype=NotificationType.Organize,
title=msg_title, text=msg_str, image=mediainfo.get_message_image())) title=msg_title, text=msg_str, image=mediainfo.get_message_image(),
link=settings.MP_DOMAIN('#/history')))
def delete_files(self, path: Path) -> Tuple[bool, str]: def delete_files(self, path: Path) -> Tuple[bool, str]:
""" """

View File

@ -15,6 +15,8 @@ class Settings(BaseSettings):
""" """
# 项目名称 # 项目名称
PROJECT_NAME = "MoviePilot" PROJECT_NAME = "MoviePilot"
# 域名 格式https://movie-pilot.org
APP_DOMAIN: str = ""
# API路径 # API路径
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"
# 前端资源路径 # 前端资源路径
@ -380,6 +382,16 @@ class Settings(BaseSettings):
"privateKey": "JTixnYY0vEw97t9uukfO3UWKfHKJdT5kCQDiv3gu894" "privateKey": "JTixnYY0vEw97t9uukfO3UWKfHKJdT5kCQDiv3gu894"
} }
def MP_DOMAIN(self, url: str = None):
if not self.APP_DOMAIN:
return None
domain = self.APP_DOMAIN.rstrip("/")
if not domain.startswith("http"):
domain = "http://" + domain
if not url:
return domain
return domain + "/" + url.lstrip("/")
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
with self.CONFIG_PATH as p: with self.CONFIG_PATH as p:

View File

@ -156,7 +156,8 @@ def check_auth():
Notification( Notification(
mtype=NotificationType.Manual, mtype=NotificationType.Manual,
title="MoviePilot用户认证", title="MoviePilot用户认证",
text=err_msg text=err_msg,
link=settings.MP_DOMAIN('#/site')
) )
) )
@ -165,6 +166,7 @@ def singal_handle():
""" """
监听停止信号 监听停止信号
""" """
def stop_event(signum: int, _: FrameType): def stop_event(signum: int, _: FrameType):
""" """
SIGTERM信号处理 SIGTERM信号处理

View File

@ -202,7 +202,7 @@ class SlackModule(_ModuleBase):
:return: 成功或失败 :return: 成功或失败
""" """
self.slack.send_msg(title=message.title, text=message.text, self.slack.send_msg(title=message.title, text=message.text,
image=message.image, userid=message.userid) image=message.image, userid=message.userid, link=message.link)
@checkMessage(MessageChannel.Slack) @checkMessage(MessageChannel.Slack)
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

@ -93,13 +93,13 @@ class Slack:
""" """
return True if self._client else False return True if self._client else False
def send_msg(self, title: str, text: str = "", image: str = "", url: str = "", userid: str = ""): def send_msg(self, title: str, text: str = "", image: str = "", link: str = "", userid: str = ""):
""" """
发送Telegram消息 发送Telegram消息
:param title: 消息标题 :param title: 消息标题
:param text: 消息内容 :param text: 消息内容
:param image: 消息图片地址 :param image: 消息图片地址
:param url: 点击消息转转的URL :param link: 点击消息转转的URL
:param userid: 用户ID如有则只发消息给该用户 :param userid: 用户ID如有则只发消息给该用户
:user_id: 发送消息的目标用户ID为空则发给管理员 :user_id: 发送消息的目标用户ID为空则发给管理员
""" """
@ -132,7 +132,7 @@ class Slack:
"alt_text": f"{title}" "alt_text": f"{title}"
}}) }})
# 链接 # 链接
if url: if link:
blocks.append({ blocks.append({
"type": "actions", "type": "actions",
"elements": [ "elements": [
@ -144,7 +144,7 @@ class Slack:
"emoji": True "emoji": True
}, },
"value": "click_me_url", "value": "click_me_url",
"url": f"{url}", "url": f"{link}",
"action_id": "actionId-url" "action_id": "actionId-url"
} }
] ]

View File

@ -74,7 +74,7 @@ class SynologyChatModule(_ModuleBase):
:return: 成功或失败 :return: 成功或失败
""" """
self.synologychat.send_msg(title=message.title, text=message.text, self.synologychat.send_msg(title=message.title, text=message.text,
image=message.image, userid=message.userid) image=message.image, userid=message.userid, link=message.link)
@checkMessage(MessageChannel.SynologyChat) @checkMessage(MessageChannel.SynologyChat)
def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]: def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]:
@ -95,4 +95,5 @@ class SynologyChatModule(_ModuleBase):
:param torrents: 种子列表 :param torrents: 种子列表
:return: 成功或失败 :return: 成功或失败
""" """
return self.synologychat.send_torrents_msg(title=message.title, torrents=torrents, userid=message.userid) return self.synologychat.send_torrents_msg(title=message.title, torrents=torrents,
userid=message.userid, link=message.link)

View File

@ -36,7 +36,8 @@ class SynologyChat:
return True return True
return False return False
def send_msg(self, title: str, text: str = "", image: str = "", userid: str = "") -> Optional[bool]: def send_msg(self, title: str, text: str = "", image: str = "",
userid: str = "", link: str = None) -> Optional[bool]:
""" """
发送Telegram消息 发送Telegram消息
:param title: 消息标题 :param title: 消息标题
@ -44,6 +45,7 @@ class SynologyChat:
:param image: 消息图片地址 :param image: 消息图片地址
:param userid: 用户ID如有则只发消息给该用户 :param userid: 用户ID如有则只发消息给该用户
:user_id: 发送消息的目标用户ID为空则发给管理员 :user_id: 发送消息的目标用户ID为空则发给管理员
:param link: 链接地址
""" """
if not title and not text: if not title and not text:
logger.error("标题和内容不能同时为空") logger.error("标题和内容不能同时为空")
@ -64,6 +66,10 @@ class SynologyChat:
caption = "*%s*\n%s" % (title, text.replace("\n\n", "\n")) caption = "*%s*\n%s" % (title, text.replace("\n\n", "\n"))
else: else:
caption = title caption = title
if link:
caption = f"{caption}\n[查看详情]({link})"
payload_data = {'text': quote(caption)} payload_data = {'text': quote(caption)}
if image: if image:
payload_data['file_url'] = quote(image) payload_data['file_url'] = quote(image)
@ -127,7 +133,7 @@ class SynologyChat:
return False return False
def send_torrents_msg(self, torrents: List[Context], def send_torrents_msg(self, torrents: List[Context],
userid: str = "", title: str = "") -> Optional[bool]: userid: str = "", title: str = "", link: str = None) -> Optional[bool]:
""" """
发送列表消息 发送列表消息
""" """
@ -157,6 +163,9 @@ class SynologyChat:
f"_{description}_" f"_{description}_"
index += 1 index += 1
if link:
caption = f"{caption}\n[查看详情]({link})"
if userid: if userid:
userids = [int(userid)] userids = [int(userid)]
else: else:

View File

@ -110,7 +110,7 @@ class TelegramModule(_ModuleBase):
:return: 成功或失败 :return: 成功或失败
""" """
self.telegram.send_msg(title=message.title, text=message.text, self.telegram.send_msg(title=message.title, text=message.text,
image=message.image, userid=message.userid) image=message.image, userid=message.userid, link=message.link)
@checkMessage(MessageChannel.Telegram) @checkMessage(MessageChannel.Telegram)
def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]: def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]:
@ -121,7 +121,7 @@ class TelegramModule(_ModuleBase):
:return: 成功或失败 :return: 成功或失败
""" """
return self.telegram.send_meidas_msg(title=message.title, medias=medias, return self.telegram.send_meidas_msg(title=message.title, medias=medias,
userid=message.userid) userid=message.userid, link=message.link)
@checkMessage(MessageChannel.Telegram) @checkMessage(MessageChannel.Telegram)
def post_torrents_message(self, message: Notification, torrents: List[Context]) -> Optional[bool]: def post_torrents_message(self, message: Notification, torrents: List[Context]) -> Optional[bool]:
@ -131,7 +131,8 @@ class TelegramModule(_ModuleBase):
:param torrents: 种子列表 :param torrents: 种子列表
:return: 成功或失败 :return: 成功或失败
""" """
return self.telegram.send_torrents_msg(title=message.title, torrents=torrents, userid=message.userid) return self.telegram.send_torrents_msg(title=message.title, torrents=torrents,
userid=message.userid, link=message.link)
def register_commands(self, commands: Dict[str, dict]): def register_commands(self, commands: Dict[str, dict]):
""" """

View File

@ -67,13 +67,15 @@ class Telegram:
""" """
return self._bot is not None return self._bot is not None
def send_msg(self, title: str, text: str = "", image: str = "", userid: str = "") -> Optional[bool]: def send_msg(self, title: str, text: str = "", image: str = "",
userid: str = "", link: str = None) -> Optional[bool]:
""" """
发送Telegram消息 发送Telegram消息
:param title: 消息标题 :param title: 消息标题
:param text: 消息内容 :param text: 消息内容
:param image: 消息图片地址 :param image: 消息图片地址
:param userid: 用户ID如有则只发消息给该用户 :param userid: 用户ID如有则只发消息给该用户
:param link: 跳转链接
:userid: 发送消息的目标用户ID为空则发给管理员 :userid: 发送消息的目标用户ID为空则发给管理员
""" """
if not self._telegram_token or not self._telegram_chat_id: if not self._telegram_token or not self._telegram_chat_id:
@ -89,6 +91,9 @@ class Telegram:
else: else:
caption = f"*{title}*" caption = f"*{title}*"
if link:
caption = f"{caption}\n[查看详情]({link})"
if userid: if userid:
chat_id = userid chat_id = userid
else: else:
@ -100,7 +105,8 @@ class Telegram:
logger.error(f"发送消息失败:{msg_e}") logger.error(f"发送消息失败:{msg_e}")
return False return False
def send_meidas_msg(self, medias: List[MediaInfo], userid: str = "", title: str = "") -> Optional[bool]: def send_meidas_msg(self, medias: List[MediaInfo], userid: str = "",
title: str = "", link: str = None) -> Optional[bool]:
""" """
发送媒体列表消息 发送媒体列表消息
""" """
@ -127,6 +133,9 @@ class Telegram:
f"类型:{media.type.value}") f"类型:{media.type.value}")
index += 1 index += 1
if link:
caption = f"{caption}\n[查看详情]({link})"
if userid: if userid:
chat_id = userid chat_id = userid
else: else:
@ -139,7 +148,7 @@ class Telegram:
return False return False
def send_torrents_msg(self, torrents: List[Context], def send_torrents_msg(self, torrents: List[Context],
userid: str = "", title: str = "") -> Optional[bool]: userid: str = "", title: str = "", link: str = None) -> Optional[bool]:
""" """
发送列表消息 发送列表消息
""" """
@ -168,6 +177,9 @@ class Telegram:
f"{StringUtils.str_filesize(torrent.size)} {free} {seeder}" f"{StringUtils.str_filesize(torrent.size)} {free} {seeder}"
index += 1 index += 1
if link:
caption = f"{caption}\n[查看详情]({link})"
if userid: if userid:
chat_id = userid chat_id = userid
else: else:

View File

@ -103,7 +103,8 @@ class VoceChatModule(_ModuleBase):
:param message: 消息内容 :param message: 消息内容
:return: 成功或失败 :return: 成功或失败
""" """
self.vocechat.send_msg(title=message.title, text=message.text, userid=message.userid) self.vocechat.send_msg(title=message.title, text=message.text,
userid=message.userid, link=message.link)
@checkMessage(MessageChannel.VoceChat) @checkMessage(MessageChannel.VoceChat)
def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]: def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]:
@ -116,7 +117,8 @@ class VoceChatModule(_ModuleBase):
# 先发送标题 # 先发送标题
self.vocechat.send_msg(title=message.title, userid=message.userid) self.vocechat.send_msg(title=message.title, userid=message.userid)
# 再发送内容 # 再发送内容
return self.vocechat.send_medias_msg(title=message.title, medias=medias, userid=message.userid) return self.vocechat.send_medias_msg(title=message.title, medias=medias,
userid=message.userid, link=message.link)
@checkMessage(MessageChannel.VoceChat) @checkMessage(MessageChannel.VoceChat)
def post_torrents_message(self, message: Notification, torrents: List[Context]) -> Optional[bool]: def post_torrents_message(self, message: Notification, torrents: List[Context]) -> Optional[bool]:
@ -126,7 +128,8 @@ class VoceChatModule(_ModuleBase):
:param torrents: 种子列表 :param torrents: 种子列表
:return: 成功或失败 :return: 成功或失败
""" """
return self.vocechat.send_torrents_msg(title=message.title, torrents=torrents, userid=message.userid) return self.vocechat.send_torrents_msg(title=message.title, torrents=torrents,
userid=message.userid, link=message.link)
def register_commands(self, commands: Dict[str, dict]): def register_commands(self, commands: Dict[str, dict]):
pass pass

View File

@ -58,12 +58,14 @@ class VoceChat:
if result and result.status_code == 200: if result and result.status_code == 200:
return result.json() return result.json()
def send_msg(self, title: str, text: str = "", userid: str = None) -> Optional[bool]: def send_msg(self, title: str, text: str = "",
userid: str = None, link: str = None) -> Optional[bool]:
""" """
微信消息发送入口支持文本图片链接跳转指定发送对象 微信消息发送入口支持文本图片链接跳转指定发送对象
:param title: 消息标题 :param title: 消息标题
:param text: 消息内容 :param text: 消息内容
:param userid: 消息发送对象的ID为空则发给所有人 :param userid: 消息发送对象的ID为空则发给所有人
:param link: 消息链接
:return: 发送状态错误信息 :return: 发送状态错误信息
""" """
if not self._client: if not self._client:
@ -79,6 +81,9 @@ class VoceChat:
else: else:
caption = f"**{title}**" caption = f"**{title}**"
if link:
caption = f"{caption}\n[查看详情]({link})"
if userid: if userid:
chat_id = userid chat_id = userid
else: else:
@ -90,7 +95,8 @@ class VoceChat:
logger.error(f"发送消息失败:{msg_e}") logger.error(f"发送消息失败:{msg_e}")
return False return False
def send_medias_msg(self, title: str, medias: List[MediaInfo], userid: str = "") -> Optional[bool]: def send_medias_msg(self, title: str, medias: List[MediaInfo],
userid: str = "", link: str = None) -> Optional[bool]:
""" """
发送列表类消息 发送列表类消息
""" """
@ -115,6 +121,9 @@ class VoceChat:
f"类型:{media.type.value}") f"类型:{media.type.value}")
index += 1 index += 1
if link:
caption = f"{caption}\n[查看详情]({link})"
if userid: if userid:
chat_id = userid chat_id = userid
else: else:
@ -127,7 +136,7 @@ class VoceChat:
return False return False
def send_torrents_msg(self, torrents: List[Context], def send_torrents_msg(self, torrents: List[Context],
userid: str = "", title: str = "") -> Optional[bool]: userid: str = "", title: str = "", link: str = None) -> Optional[bool]:
""" """
发送列表消息 发送列表消息
""" """
@ -155,6 +164,9 @@ class VoceChat:
f"{StringUtils.str_filesize(torrent.size)} {free} {seeder}" f"{StringUtils.str_filesize(torrent.size)} {free} {seeder}"
index += 1 index += 1
if link:
caption = f"{caption}\n[查看详情]({link})"
if userid: if userid:
chat_id = userid chat_id = userid
else: else:

View File

@ -141,7 +141,7 @@ class WechatModule(_ModuleBase):
:return: 成功或失败 :return: 成功或失败
""" """
self.wechat.send_msg(title=message.title, text=message.text, self.wechat.send_msg(title=message.title, text=message.text,
image=message.image, userid=message.userid) image=message.image, userid=message.userid, link=message.link)
@checkMessage(MessageChannel.Wechat) @checkMessage(MessageChannel.Wechat)
def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]: def post_medias_message(self, message: Notification, medias: List[MediaInfo]) -> Optional[bool]:
@ -152,7 +152,7 @@ class WechatModule(_ModuleBase):
:return: 成功或失败 :return: 成功或失败
""" """
# 先发送标题 # 先发送标题
self.wechat.send_msg(title=message.title, userid=message.userid) self.wechat.send_msg(title=message.title, userid=message.userid, link=message.link)
# 再发送内容 # 再发送内容
return self.wechat.send_medias_msg(medias=medias, userid=message.userid) return self.wechat.send_medias_msg(medias=medias, userid=message.userid)
@ -164,7 +164,8 @@ class WechatModule(_ModuleBase):
:param torrents: 种子列表 :param torrents: 种子列表
:return: 成功或失败 :return: 成功或失败
""" """
return self.wechat.send_torrents_msg(title=message.title, torrents=torrents, userid=message.userid) return self.wechat.send_torrents_msg(title=message.title, torrents=torrents,
userid=message.userid, link=message.link)
def register_commands(self, commands: Dict[str, dict]): def register_commands(self, commands: Dict[str, dict]):
""" """

View File

@ -88,12 +88,14 @@ class WeChat:
return None return None
return self._access_token return self._access_token
def __send_message(self, title: str, text: str = None, userid: str = None) -> Optional[bool]: def __send_message(self, title: str, text: str = None,
userid: str = None, link: str = None) -> Optional[bool]:
""" """
发送文本消息 发送文本消息
:param title: 消息标题 :param title: 消息标题
:param text: 消息内容 :param text: 消息内容
:param userid: 消息发送对象的ID为空则发给所有人 :param userid: 消息发送对象的ID为空则发给所有人
:param link: 跳转链接
:return: 发送状态错误信息 :return: 发送状态错误信息
""" """
message_url = self._send_msg_url % self.__get_access_token() message_url = self._send_msg_url % self.__get_access_token()
@ -102,8 +104,12 @@ class WeChat:
else: else:
conent = title conent = title
if link:
conent = f"{conent}\n点击查看:{link}"
if not userid: if not userid:
userid = "@all" userid = "@all"
req_json = { req_json = {
"touser": userid, "touser": userid,
"msgtype": "text", "msgtype": "text",
@ -148,13 +154,15 @@ class WeChat:
} }
return self.__post_request(message_url, req_json) return self.__post_request(message_url, req_json)
def send_msg(self, title: str, text: str = "", image: str = "", userid: str = None) -> Optional[bool]: def send_msg(self, title: str, text: str = "", image: str = "",
userid: str = None, link: str = None) -> Optional[bool]:
""" """
微信消息发送入口支持文本图片链接跳转指定发送对象 微信消息发送入口支持文本图片链接跳转指定发送对象
:param title: 消息标题 :param title: 消息标题
:param text: 消息内容 :param text: 消息内容
:param image: 图片地址 :param image: 图片地址
:param userid: 消息发送对象的ID为空则发给所有人 :param userid: 消息发送对象的ID为空则发给所有人
:param link: 跳转链接
:return: 发送状态错误信息 :return: 发送状态错误信息
""" """
if not self.__get_access_token(): if not self.__get_access_token():
@ -162,9 +170,9 @@ class WeChat:
return None return None
if image: if image:
ret_code = self.__send_image_message(title, text, image, userid) ret_code = self.__send_image_message(title=title, text=text, image_url=image, userid=userid)
else: else:
ret_code = self.__send_message(title, text, userid) ret_code = self.__send_message(title=title, text=text, userid=userid, link=link)
return ret_code return ret_code
@ -205,7 +213,7 @@ class WeChat:
return self.__post_request(message_url, req_json) return self.__post_request(message_url, req_json)
def send_torrents_msg(self, torrents: List[Context], def send_torrents_msg(self, torrents: List[Context],
userid: str = "", title: str = "") -> Optional[bool]: userid: str = "", title: str = "", link: str = None) -> Optional[bool]:
""" """
发送列表消息 发送列表消息
""" """
@ -215,7 +223,7 @@ class WeChat:
# 先发送标题 # 先发送标题
if title: if title:
self.__send_message(title=title, userid=userid) self.__send_message(title=title, userid=userid, link=link)
# 发送列表 # 发送列表
message_url = self._send_msg_url % self.__get_access_token() message_url = self._send_msg_url % self.__get_access_token()

View File

@ -229,6 +229,8 @@ class _PluginBase(metaclass=ABCMeta):
""" """
发送消息 发送消息
""" """
if not link:
link = settings.MP_DOMAIN(f"#/plugins?tab=installed&id={self.__class__.__name__}")
self.chain.post_message(Notification( self.chain.post_message(Notification(
channel=channel, mtype=mtype, title=title, text=text, channel=channel, mtype=mtype, title=title, text=text,
image=image, link=link, userid=userid image=image, link=link, userid=userid

View File

@ -90,7 +90,8 @@ class Scheduler(metaclass=Singleton):
Notification( Notification(
mtype=NotificationType.Manual, mtype=NotificationType.Manual,
title="MoviePilot用户认证成功", title="MoviePilot用户认证成功",
text=f"使用站点:{msg}" text=f"使用站点:{msg}",
link=settings.MP_DOMAIN('#/site')
) )
) )
else: else: