diff --git a/README.md b/README.md index e439530..7a6a2b0 100644 --- a/README.md +++ b/README.md @@ -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 \ No newline at end of file +- 目录监控(统一入库消息增强版) v1.0 +- Sql执行器 v1.2 +- 命令执行器 v1.2 \ No newline at end of file diff --git a/icons/command.png b/icons/command.png new file mode 100644 index 0000000..f077761 Binary files /dev/null and b/icons/command.png differ diff --git a/icons/sqlite.png b/icons/sqlite.png new file mode 100644 index 0000000..eb2e910 Binary files /dev/null and b/icons/sqlite.png differ diff --git a/package.json b/package.json index 9acf429..0e540bb 100644 --- a/package.json +++ b/package.json @@ -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": "自定义容器命令执行" + } } } diff --git a/plugins/actorsubscribe/__init__.py b/plugins/actorsubscribe/__init__.py index 2b953e1..e5aceca 100644 --- a/plugins/actorsubscribe/__init__.py +++ b/plugins/actorsubscribe/__init__.py @@ -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]: """ diff --git a/plugins/cloudstrm/__init__.py b/plugins/cloudstrm/__init__.py index c6b159a..c2eb3b7 100644 --- a/plugins/cloudstrm/__init__.py +++ b/plugins/cloudstrm/__init__.py @@ -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]: diff --git a/plugins/cloudstrmincrement/__init__.py b/plugins/cloudstrmincrement/__init__.py index 5f1049b..1dff14c 100644 --- a/plugins/cloudstrmincrement/__init__.py +++ b/plugins/cloudstrmincrement/__init__.py @@ -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} 已删除") diff --git a/plugins/commandexecute/__init__.py b/plugins/commandexecute/__init__.py new file mode 100644 index 0000000..8d16d91 --- /dev/null +++ b/plugins/commandexecute/__init__.py @@ -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 diff --git a/plugins/sqlexecute/__init__.py b/plugins/sqlexecute/__init__.py new file mode 100644 index 0000000..d544aa5 --- /dev/null +++ b/plugins/sqlexecute/__init__.py @@ -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来执行executeSQL语句 + 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来执行executeSQL语句 + 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 diff --git a/plugins/wechatforward/__init__.py b/plugins/wechatforward/__init__.py index 8ce0736..84d92ab 100644 --- a/plugins/wechatforward/__init__.py +++ b/plugins/wechatforward/__init__.py @@ -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(