Merge remote-tracking branch 'origin/main'

This commit is contained in:
thsrite
2024-06-11 14:33:40 +08:00
10 changed files with 687 additions and 71 deletions

View File

@@ -11,7 +11,7 @@ MoviePilot三方插件市场https://github.com/thsrite/MoviePilot-Plugins/
- 站点数据统计 v1.4 (无未读消息版本)(废弃)
- 站点未读消息 v1.9 (依赖于[站点数据统计]插件)
- [云盘Strm生成 v4.0](docs%2FCloudStrm.md)
- [云盘Strm生成 v4.2](docs%2FCloudStrm.md)
- [云盘Strm生成增量版 v1.0](docs%2FCloudStrmIncrement.md)
- [Strm文件模式转换 v1.0](docs%2FStrmConvert.md)
- 清理订阅缓存 v1.0
@@ -28,7 +28,7 @@ MoviePilot三方插件市场https://github.com/thsrite/MoviePilot-Plugins/
- [短剧刮削 v3.2](docs%2FShortPlayMonitor.md)
- 云盘实时监控 v2.2
- 源文件恢复 v1.2
- [微信消息转发 v2.5](docs%2FWeChatForward.md)
- [微信消息转发 v2.6](docs%2FWeChatForward.md)
- 订阅下载统计 v1.5
- [自定义命令 v1.7](docs%2FCustomCommand.md)
- docker自定义任务 v1.3
@@ -39,4 +39,6 @@ MoviePilot三方插件市场https://github.com/thsrite/MoviePilot-Plugins/
- Emby媒体标签 v1.1
- 热门媒体订阅 v1.7
- [HomePage v1.2](docs%2FHomePage.md)
- 目录监控(统一入库消息增强版) v1.0
- 目录监控(统一入库消息增强版) v1.0
- Sql执行器 v1.2
- 命令执行器 v1.2

BIN
icons/command.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
icons/sqlite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -3,11 +3,13 @@
"name": "云盘Strm生成",
"description": "监控文件创建生成Strm文件。",
"labels": "云盘",
"version": "4.0",
"version": "4.2",
"icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/create.png",
"author": "thsrite",
"level": 1,
"history": {
"v4.2": "扩展名转小写",
"v4.1": "支持自定义媒体类型",
"v4.0": "回归老版本",
"v3.8": "支持增量路径、支持自定义媒体类型(注:本次更新需修改配置使用)",
"v3.7": "api模式支持启用https",
@@ -211,7 +213,7 @@
},
"ActorSubscribe": {
"name": "演员订阅",
"description": "自动订阅指定演员热映或最新电影电视剧。",
"description": "自动订阅指定演员热映电影电视剧。",
"labels": "订阅",
"version": "2.1",
"icon": "Mdcng_A.png",
@@ -286,11 +288,13 @@
"name": "微信消息转发",
"description": "根据正则转发通知到其他WeChat应用。",
"labels": "消息通知",
"version": "2.5",
"version": "2.6",
"icon": "Wechat_A.png",
"author": "thsrite",
"level": 1,
"history": {
"v2.6": "已完成订阅额外消息查询订阅历史订阅用户",
"v2.5.1": "修复token过期重发未存储userid问题",
"v2.5": "增强额外消息发送",
"v2.4": "修复配置修改后不重建缓存bug",
"v2.3": "增加重建缓存,丰富转发历史",
@@ -476,5 +480,33 @@
"history": {
"v1.0": "同步merge主仓库[目录监控]插件增加统一发送消息逻辑Testing…"
}
},
"SqlExecute": {
"name": "Sql执行器",
"description": "自定义MoviePilot数据库Sql执行。",
"labels": "工具",
"version": "1.2",
"icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/sqlite.png",
"author": "thsrite",
"level": 1,
"history": {
"v1.2": "调整交互命令返回信息",
"v1.1": "支持交互命令/sql [command]执行需主程序1.9.4+",
"v1.0": "自定义MoviePilot数据库Sql执行"
}
},
"CommandExecute": {
"name": "命令执行器",
"description": "自定义容器命令执行。",
"labels": "工具",
"version": "1.2",
"icon": "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/command.png",
"author": "thsrite",
"level": 1,
"history": {
"v1.2": "调整交互命令返回信息",
"v1.1": "支持交互命令/cmd [sql]执行需主程序1.9.4+",
"v1.0": "自定义容器命令执行"
}
}
}

View File

@@ -23,7 +23,7 @@ class ActorSubscribe(_PluginBase):
# 插件名称
plugin_name = "演员订阅"
# 插件描述
plugin_desc = "自动订阅指定演员热映或最新电影电视剧。"
plugin_desc = "自动订阅指定演员热映电影电视剧。"
# 插件图标
plugin_icon = "Mdcng_A.png"
# 插件版本
@@ -250,7 +250,8 @@ class ActorSubscribe(_PluginBase):
if actor and actor in subscribe_actors:
# 开始订阅
logger.info(
f"{mediainfo.type.value} {mediainfo.title_year} TMDBID {mediainfo.tmdb_id} DOUBANID {mediainfo.douban_id} 命中订阅演员 {actor}开始订阅")
f"{mediainfo.type.value} {mediainfo.title_year} TMDBID {mediainfo.tmdb_id} DOUBANID {mediainfo.douban_id} 命中订阅演员 {actor}"
f"开始订阅。订阅规则:{self._quality} {self._resolution} {self._effect} {self._username}")
is_subscribe = True
# 添加订阅
self.subscribechain.add(title=mediainfo.title,
@@ -283,6 +284,7 @@ class ActorSubscribe(_PluginBase):
# 保存历史记录
self.save_data('history', history)
self.save_data('already_handle', already_handle)
logger.info(f"演员订阅任务完成")
def __get_douban_actors(self, mediainfo: MediaInfo, season: int = None) -> List[dict]:
"""

View File

@@ -26,7 +26,7 @@ class CloudStrm(_PluginBase):
# 插件图标
plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/create.png"
# 插件版本
plugin_version = "4.0"
plugin_version = "4.2"
# 插件作者
plugin_author = "thsrite"
# 作者主页
@@ -56,6 +56,7 @@ class CloudStrm(_PluginBase):
_cloudurlconf = {}
_cloudpathconf = {}
__cloud_files = []
_rmt_mediaext = ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
# 定时器
_scheduler: Optional[BackgroundScheduler] = None
@@ -78,6 +79,8 @@ class CloudStrm(_PluginBase):
self._https = config.get("https")
self._copy_files = config.get("copy_files")
self._monitor_confs = config.get("monitor_confs")
self._rmt_mediaext = config.get(
"rmt_mediaext") or ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
# 停止现有任务
self.stop_service()
@@ -233,7 +236,8 @@ class CloudStrm(_PluginBase):
continue
# 不复制非媒体文件时直接过滤掉非媒体文件
if not self._copy_files and Path(file).suffix not in settings.RMT_MEDIAEXT:
if not self._copy_files and Path(file).suffix.lower() not in [ext.strip() for ext in
self._rmt_mediaext.split(",")]:
continue
if source_file not in self.__cloud_files:
@@ -280,7 +284,8 @@ class CloudStrm(_PluginBase):
continue
# 不复制非媒体文件时直接过滤掉非媒体文件
if not self._copy_files and Path(file).suffix not in settings.RMT_MEDIAEXT:
if not self._copy_files and Path(file).suffix.lower() not in [ext.strip() for ext in
self._rmt_mediaext.split(",")]:
continue
logger.info(f"扫描到新文件 {source_file},正在开始处理")
@@ -343,7 +348,8 @@ class CloudStrm(_PluginBase):
os.makedirs(Path(dest_file).parent)
# 视频文件创建.strm文件
if Path(dest_file).suffix in settings.RMT_MEDIAEXT:
if Path(dest_file).suffix.lower() in [ext.strip() for ext in
self._rmt_mediaext.split(",")]:
# 创建.strm文件
self.__create_strm_file(scheme="https" if self._https else "http",
dest_file=dest_file,
@@ -433,7 +439,8 @@ class CloudStrm(_PluginBase):
"copy_files": self._copy_files,
"https": self._https,
"cron": self._cron,
"monitor_confs": self._monitor_confs
"monitor_confs": self._monitor_confs,
"rmt_mediaext": self._rmt_mediaext
})
def get_state(self) -> bool:
@@ -638,6 +645,28 @@ class CloudStrm(_PluginBase):
},
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'rmt_mediaext',
'label': '视频格式',
'rows': 2,
'placeholder': ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
@@ -737,7 +766,8 @@ class CloudStrm(_PluginBase):
"rebuild": False,
"copy_files": False,
"https": False,
"monitor_confs": ""
"monitor_confs": "",
"rmt_mediaext": ".mp4, .mkv, .ts, .iso,.rmvb, .avi, .mov, .mpeg,.mpg, .wmv, .3gp, .asf, .m4v, .flv, .m2ts, .strm,.tp, .f4v"
}
def get_page(self) -> List[dict]:

View File

@@ -236,7 +236,8 @@ class CloudStrmIncrement(_PluginBase):
break
if str(parent_path.parent) != str(Path(increment_file).root):
# 父目录非根目录,才删除父目录
if not SystemUtils.exits_files(parent_path, settings.RMT_MEDIAEXT):
if not SystemUtils.exits_files(parent_path,
[ext.strip() for ext in self._rmt_mediaext.split(",")]):
# 当前路径下没有媒体文件则删除
shutil.rmtree(parent_path)
logger.warn(f"增量非保留目录 {parent_path} 已删除")

View File

@@ -0,0 +1,242 @@
import subprocess
from app.core.event import eventmanager, Event
from app.plugins import _PluginBase
from typing import Any, List, Dict, Tuple
from app.log import logger
from app.schemas.types import EventType, MessageChannel
class CommandExecute(_PluginBase):
# 插件名称
plugin_name = "命令执行器"
# 插件描述
plugin_desc = "自定义容器命令执行。"
# 插件图标
plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/command.png"
# 插件版本
plugin_version = "1.2"
# 插件作者
plugin_author = "thsrite"
# 作者主页
author_url = "https://github.com/thsrite"
# 插件配置项ID前缀
plugin_config_prefix = "commandexecute_"
# 加载顺序
plugin_order = 99
# 可使用的用户级别
auth_level = 1
# 私有属性
_onlyonce = None
_command = None
def init_plugin(self, config: dict = None):
if config:
self._onlyonce = config.get("onlyonce")
self._command = config.get("command")
if self._onlyonce and self._command:
# 执行SQL语句
try:
for command in self._command.split("\n"):
logger.info(f"开始执行命令 {command}")
ouptut = self.execute_command(command)
# logger.info('\n'.join(ouptut))
except Exception as e:
logger.error(f"命令执行失败 {str(e)}")
return
finally:
self._onlyonce = False
self.update_config({
"onlyonce": self._onlyonce,
"command": self._command
})
@staticmethod
def execute_command(command: str):
"""
执行命令
:param command: 命令
"""
result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
ouptut = []
while True:
error = result.stderr.readline().decode("utf-8")
if error == '' and result.poll() is not None:
break
if error:
logger.info(error.strip())
ouptut.append(error.strip())
while True:
output = result.stdout.readline().decode("utf-8")
if output == '' and result.poll() is not None:
break
if output:
logger.info(output.strip())
ouptut.append(output.strip())
return ouptut
@eventmanager.register(EventType.PluginAction)
def execute(self, event: Event = None):
if event:
event_data = event.event_data
if not event_data or event_data.get("action") != "command_execute":
return
logger.info(f"收到命令执行事件 ...{event_data}")
args = event_data.get("args")
if not args:
return
logger.info(f"收到命令,开始执行命令 ...{args}")
ouptut = self.execute_command(args)
result = '\n'.join(ouptut)
if event.event_data.get("channel") == MessageChannel.Telegram:
result = f"```plaintext\n{result}\n```"
self.post_message(channel=event.event_data.get("channel"),
title="命令执行结果",
text=result,
userid=event.event_data.get("user"))
def get_state(self) -> bool:
return True
@staticmethod
def get_command() -> List[Dict[str, Any]]:
"""
定义远程控制命令
:return: 命令关键字、事件、描述、附带数据
"""
return [{
"cmd": "/cmd",
"event": EventType.PluginAction,
"desc": "自定义命令执行",
"category": "",
"data": {
"action": "command_execute"
}
}]
def get_api(self) -> List[Dict[str, Any]]:
pass
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
"""
拼装插件配置页面需要返回两块数据1、页面配置2、数据结构
"""
return [
{
'component': 'VForm',
'content': [
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '执行命令'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'command',
'rows': '2',
'label': 'command命令',
'placeholder': '一行一条'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VAlert',
'props': {
'type': 'info',
'variant': 'tonal'
},
'content': [
{
'component': 'span',
'text': '执行日志将会输出到控制台,请谨慎操作。'
}
]
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VAlert',
'props': {
'type': 'info',
'variant': 'tonal'
},
'content': [
{
'component': 'span',
'text': '可使用交互命令/cmd ls'
}
]
}
]
}
]
}
]
}
], {
"onlyonce": False,
"command": "",
}
def get_page(self) -> List[dict]:
pass
def stop_service(self):
"""
退出插件
"""
pass

View File

@@ -0,0 +1,279 @@
import sqlite3
from app.core.event import eventmanager, Event
from app.plugins import _PluginBase
from typing import Any, List, Dict, Tuple
from app.log import logger
from app.schemas.types import EventType, MessageChannel
class SqlExecute(_PluginBase):
# 插件名称
plugin_name = "Sql执行器"
# 插件描述
plugin_desc = "自定义MoviePilot数据库Sql执行。"
# 插件图标
plugin_icon = "https://raw.githubusercontent.com/thsrite/MoviePilot-Plugins/main/icons/sqlite.png"
# 插件版本
plugin_version = "1.2"
# 插件作者
plugin_author = "thsrite"
# 作者主页
author_url = "https://github.com/thsrite"
# 插件配置项ID前缀
plugin_config_prefix = "sqlexecute_"
# 加载顺序
plugin_order = 99
# 可使用的用户级别
auth_level = 1
# 私有属性
_onlyonce = None
_sql = None
def init_plugin(self, config: dict = None):
if config:
self._onlyonce = config.get("onlyonce")
self._sql = config.get("sql")
if self._onlyonce and self._sql:
# 读取sqlite数据
try:
gradedb = sqlite3.connect("/config/user.db")
except Exception as e:
logger.error(f"数据库链接失败 {str(e)}")
return
# 创建游标cursor来执行execute语句
cursor = gradedb.cursor()
# 执行SQL语句
try:
for sql in self._sql.split("\n"):
logger.info(f"开始执行SQL语句 {sql}")
# 执行SQL语句
cursor.execute(sql)
rows = cursor.fetchall()
if 'select' in sql.lower():
# 获取列名
columns = [desc[0] for desc in cursor.description]
# 将查询结果转换为key-value对的列表
results = []
for row in rows:
result = dict(zip(columns, row))
results.append(result)
result = "\n".join([str(i) for i in results])
else:
result = "\n".join([str(i) for i in rows])
result = str(result).replace("'", "\"")
logger.info(result)
except Exception as e:
logger.error(f"SQL语句执行失败 {str(e)}")
return
finally:
# 关闭游标
cursor.close()
self._onlyonce = False
self.update_config({
"onlyonce": self._onlyonce,
"sql": self._sql
})
@eventmanager.register(EventType.PluginAction)
def execute(self, event: Event = None):
if event:
event_data = event.event_data
if not event_data or event_data.get("action") != "sql_execute":
return
args = event_data.get("args")
if not args:
return
logger.info(f"收到命令开始执行SQL ...{args}")
# 读取sqlite数据
try:
gradedb = sqlite3.connect("/config/user.db")
except Exception as e:
logger.error(f"数据库链接失败 {str(e)}")
return
# 创建游标cursor来执行execute语句
cursor = gradedb.cursor()
# 执行SQL语句
try:
# 执行SQL语句
cursor.execute(args)
rows = cursor.fetchall()
if 'select' in args.lower():
# 获取列名
columns = [desc[0] for desc in cursor.description]
# 将查询结果转换为key-value对的列表
results = []
for row in rows:
result = dict(zip(columns, row))
results.append(result)
result = "\n".join([str(i) for i in results])
else:
result = "\n".join([str(i) for i in rows])
result = str(result).replace("'", "\"")
logger.info(result)
if event.event_data.get("channel") == MessageChannel.Telegram:
result = f"```plaintext\n{result}\n```"
self.post_message(channel=event.event_data.get("channel"),
title="SQL执行结果",
text=result,
userid=event.event_data.get("user"))
except Exception as e:
logger.error(f"SQL语句执行失败 {str(e)}")
return
finally:
# 关闭游标
cursor.close()
def get_state(self) -> bool:
return True
@staticmethod
def get_command() -> List[Dict[str, Any]]:
"""
定义远程控制命令
:return: 命令关键字、事件、描述、附带数据
"""
return [{
"cmd": "/sql",
"event": EventType.PluginAction,
"desc": "自定义sql执行",
"category": "",
"data": {
"action": "sql_execute"
}
}]
def get_api(self) -> List[Dict[str, Any]]:
pass
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
"""
拼装插件配置页面需要返回两块数据1、页面配置2、数据结构
"""
return [
{
'component': 'VForm',
'content': [
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
'md': 6
},
'content': [
{
'component': 'VSwitch',
'props': {
'model': 'onlyonce',
'label': '执行sql'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VTextarea',
'props': {
'model': 'sql',
'rows': '2',
'label': 'sql语句',
'placeholder': '一行一条'
}
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VAlert',
'props': {
'type': 'info',
'variant': 'tonal'
},
'content': [
{
'component': 'span',
'text': '执行日志将会输出到控制台,请谨慎操作。'
}
]
}
]
}
]
},
{
'component': 'VRow',
'content': [
{
'component': 'VCol',
'props': {
'cols': 12,
},
'content': [
{
'component': 'VAlert',
'props': {
'type': 'info',
'variant': 'tonal'
},
'content': [
{
'component': 'span',
'text': '可使用交互命令/sql select *****'
}
]
}
]
}
]
}
]
}
], {
"onlyonce": False,
"sql": "",
}
def get_page(self) -> List[dict]:
pass
def stop_service(self):
"""
退出插件
"""
pass

View File

@@ -4,6 +4,7 @@ import time
from datetime import datetime
from app.core.config import settings
from app.db.models.subscribehistory import SubscribeHistory
from app.db.subscribe_oper import SubscribeOper
from app.plugins import _PluginBase
from app.core.event import eventmanager
@@ -21,7 +22,7 @@ class WeChatForward(_PluginBase):
# 插件图标
plugin_icon = "Wechat_A.png"
# 插件版本
plugin_version = "2.5"
plugin_version = "2.6"
# 插件作者
plugin_author = "thsrite"
# 作者主页
@@ -715,63 +716,86 @@ class WeChatForward(_PluginBase):
if str(extra_msg).find('{name}') != -1:
extra_msg = extra_msg.replace('{name}', self.__parse_tv_title(title))
# 搜索消息获取消息text中的用户
result = re.search(r"用户:(.*?)\n", text)
if not result:
# 订阅消息获取消息text中的用户
pattern = r"来自用户:(.*?)$"
result = re.search(pattern, text)
# 订阅完成消息单独处理
if "已完成订阅" in str(title):
# 查订阅历史的用户
subscribes = SubscribeHistory().list()
# 倒叙
subscribes = sorted(subscribes, key=lambda x: x.id, reverse=True)
for subscribe in subscribes:
# 匹配订阅title
if f"{subscribe.name} ({subscribe.year}) 已完成订阅" == title \
or f"{subscribe.name} ({subscribe.year}) S{str(subscribe.season).rjust(2, '0')} 已完成订阅" == title:
user_id = subscribe.username
logger.info(f"{title} 获取到订阅用户 {user_id}")
if user_id and any(user_id == user for user in extra_userid.split(",")):
logger.info(f"{title} 消息用户 {user_id} 匹配到目标用户 {extra_userid}")
self.__send_image_message(title=title,
text=extra_msg,
userid=user_id,
access_token=access_token,
appid=wechat_appid,
image_url=subscribe.backdrop)
logger.info(f"{wechat_appid} 发送额外消息 {extra_msg} 成功")
break
else:
# 搜索消息获取消息text中的用户
result = re.search(r"用户:(.*?)\n", text)
if not result:
logger.error("未获取到用户,跳过处理")
continue
# 获取消息text中的用户
user_id = result.group(1)
logger.info(f"获取到消息用户 {user_id}")
if user_id and any(user_id == user for user in extra_userid.split(",")):
if "开始下载" in str(title):
# 判断是否重复发送10分钟内重复消息title、重复userid算重复消息
extra_history_time = self._extra_msg_history.get(
f"{user_id}-{self.__parse_tv_title(title)}") or None
# 只处理下载消息
if extra_history_time:
logger.info(
f"获取到额外消息上次发送时间 {datetime.strptime(extra_history_time, '%Y-%m-%d %H:%M:%S')}")
if (datetime.now() - datetime.strptime(extra_history_time,
'%Y-%m-%d %H:%M:%S')).total_seconds() < 600:
logger.warn(
f"额外消息 {self.__parse_tv_title(title)} 十分钟内重复发送,跳过。")
continue
# 判断当前用户是否订阅,是否订阅后续消息
subscribes = SubscribeOper().list_by_username(username=str(user_id),
state="R",
mtype=MediaType.TV.value)
is_subscribe = False
for subscribe in subscribes:
# 匹配订阅title
if f"{subscribe.name} ({subscribe.year})" in title:
is_subscribe = True
break
# 电视剧之前该用户订阅下载过,不再发送额外消息
if is_subscribe:
logger.warn(
f"额外消息 {self.__parse_tv_title(title)} 用户 {user_id} 已订阅,不再发送额外消息。")
# 订阅消息获取消息text中的用户
pattern = r"来自用户:(.*?)$"
result = re.search(pattern, text)
if not result:
logger.error(f"{title} 未获取到用户,跳过处理")
continue
logger.info(f"消息用户{user_id} 匹配到目标用户 {extra_userid}")
# 获取消息text中的用户
user_id = result.group(1)
logger.info(f"{title} 获取到消息用户 {user_id}")
if user_id and any(user_id == user for user in extra_userid.split(",")):
if "开始下载" in str(title):
# 判断是否重复发送10分钟内重复消息title、重复userid算重复消息
extra_history_time = self._extra_msg_history.get(
f"{user_id}-{self.__parse_tv_title(title)}") or None
# 只处理下载消息
if extra_history_time:
logger.info(
f"{title} 获取到额外消息上次发送时间 {datetime.strptime(extra_history_time, '%Y-%m-%d %H:%M:%S')}")
if (datetime.now() - datetime.strptime(extra_history_time,
'%Y-%m-%d %H:%M:%S')).total_seconds() < 600:
logger.warn(
f"{title} 额外消息 {self.__parse_tv_title(title)} 十分钟内重复发送,跳过。")
continue
# 判断当前用户是否订阅,是否订阅后续消息
subscribes = SubscribeOper().list_by_username(username=str(user_id),
state="R",
mtype=MediaType.TV.value)
is_subscribe = False
for subscribe in subscribes:
# 匹配订阅title
if f"{subscribe.name} ({subscribe.year})" in title:
is_subscribe = True
break
self.__send_message(title=extra_msg,
userid=user_id,
access_token=access_token,
appid=wechat_appid)
logger.info(f"{wechat_appid} 发送额外消息 {extra_msg} 成功")
# 保存已发送消息
if "开始下载" in str(title):
self._extra_msg_history[
f"{user_id}-{self.__parse_tv_title(title)}"] = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
is_save_history = True
# 电视剧之前该用户订阅下载过,不再发送额外消息
if is_subscribe:
logger.warn(
f"{title} 额外消息 {self.__parse_tv_title(title)} 用户 {user_id} 已订阅,不再发送额外消息。")
continue
logger.info(f"{title} 消息用户 {user_id} 匹配到目标用户 {extra_userid}")
self.__send_message(title=extra_msg,
userid=user_id,
access_token=access_token,
appid=wechat_appid)
logger.info(f"{title} {wechat_appid} 发送额外消息 {extra_msg} 成功")
# 保存已发送消息
if "开始下载" in str(title):
self._extra_msg_history[
f"{user_id}-{self.__parse_tv_title(title)}"] = time.strftime(
"%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
is_save_history = True
# 保存额外消息历史
if is_save_history:
@@ -789,10 +813,13 @@ class WeChatForward(_PluginBase):
# 电影 功夫熊猫 (2008) 已添加订阅
# 电视剧 追风者 (2024) S01 E01-E04 开始下载
# 电视剧 追风者 (2024) S01 已添加订阅
# 电视剧 追风者 (2024) S01 已完成订阅
if '开始下载' in sub_title_str:
continue
if '已添加订阅' in sub_title_str:
continue
if '已完成订阅' in sub_title_str:
continue
_title += f"{sub_title_str} "
return self.__convert_season_episode(str(_title.rstrip()))
@@ -888,7 +915,7 @@ class WeChatForward(_PluginBase):
return self.__post_request(access_token=access_token, req_json=req_json, appid=appid, title=title, text=text,
userid=userid)
def __send_image_message(self, title: str, text: str, image_url: str, userid: str = None,
def __send_image_message(self, title: str, image_url: str, text: str = None, userid: str = None,
access_token: str = None, appid: int = None) -> Optional[bool]:
"""
发送图文消息
@@ -972,7 +999,8 @@ class WeChatForward(_PluginBase):
appid=appid,
title=title,
retry=retry,
text=text)
text=text,
userid=userid)
return False
elif res is not None:
logger.error(