mirror of
https://github.com/thsrite/MoviePilot-Plugins.git
synced 2026-06-02 23:16:47 +00:00
Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -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
BIN
icons/command.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
BIN
icons/sqlite.png
Normal file
BIN
icons/sqlite.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
38
package.json
38
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": "自定义容器命令执行"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]:
|
||||
"""
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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} 已删除")
|
||||
|
||||
242
plugins/commandexecute/__init__.py
Normal file
242
plugins/commandexecute/__init__.py
Normal 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
|
||||
279
plugins/sqlexecute/__init__.py
Normal file
279
plugins/sqlexecute/__init__.py
Normal 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来执行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
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user