diff --git a/admin/dashboard/blueprints/message_push.py b/admin/dashboard/blueprints/message_push.py index aca9574..59cae3e 100644 --- a/admin/dashboard/blueprints/message_push.py +++ b/admin/dashboard/blueprints/message_push.py @@ -8,6 +8,8 @@ from datetime import datetime from flask import Blueprint, render_template, jsonify, request, current_app, session from pathlib import Path from werkzeug.utils import secure_filename + +from wechat_ipad.models.appmsg_xml import LINK_XML_NORMAL from .auth import login_required from loguru import logger @@ -268,32 +270,49 @@ def api_preview_task(task_id): preview_user = session.get('username') if not preview_user: return jsonify({"success": False, "error": "未登录或会话已过期"}), 401 - + # 获取预览接收者并解析JSON preview_recipients_str = task.get("preview_recipients", "[]") try: preview_recipients = json.loads(preview_recipients_str) except json.JSONDecodeError: return jsonify({"success": False, "error": "预览接收者格式错误"}), 400 - + if not preview_recipients: return jsonify({"success": False, "error": "未设置预览接收者"}), 400 - + # 为每个接收者发送预览消息 for recipient in preview_recipients: try: # 发送文本消息 if task.get('content_text'): send_message_in_thread(server.client.send_text_message, recipient, task['content_text']) - + # 发送图片消息 if task.get('content_image'): send_message_in_thread(server.client.send_image_message, recipient, Path(task['content_image'])) - + # 发送链接消息 if task.get('content_link'): - send_message_in_thread(server.client.send_link_message, recipient, task['content_link']) - + try: + link_data = json.loads(task['content_link']) + + # content_link json 读取内容 + xml_content = f"{LINK_XML_NORMAL}".format(title=link_data.get('title', ''), + des=link_data.get('des', ''), + url=link_data.get('url', ''), + thumburl=link_data.get('thumburl', '') + ) + + send_message_in_thread( + server.client.send_link_xml_message, + xml_content, + recipient + ) + except json.JSONDecodeError: + logger.error(f"解析链接内容失败: {task['content_link']}") + continue + # # 发送小程序消息 # if task.get('content_miniprogram'): # miniprogram = task['content_miniprogram'] @@ -347,17 +366,17 @@ def api_statistics(): try: # 获取任务数据库实例 db = current_app.dashboard_server.task_db - + # 获取各种状态的任务数量 total = db.get_tasks_count() scheduled = db.get_tasks_count_by_status('scheduled') paused = db.get_tasks_count_by_status('paused') completed = db.get_tasks_count_by_status('completed') failed = db.get_tasks_count_by_status('failed') - + # 获取今日任务数量 today = db.get_tasks_count_by_date(datetime.now().strftime('%Y-%m-%d')) - + return jsonify({ "success": True, "data": { @@ -382,28 +401,28 @@ def upload_file(): 'success': False, 'message': '没有文件' }) - + file = request.files['file'] if file.filename == '': return jsonify({ 'success': False, 'message': '没有选择文件' }) - + if file and allowed_file(file.filename): # 生成安全的文件名 filename = secure_filename(file.filename) # 生成唯一文件名 unique_filename = f"{uuid.uuid4().hex}_{filename}" - + # 确保上传目录存在 upload_folder = os.path.join(current_app.root_path, 'static', 'uploads') os.makedirs(upload_folder, exist_ok=True) - + # 保存文件 file_path = os.path.join(upload_folder, unique_filename) file.save(file_path) - + # 返回文件的绝对路径 return jsonify({ 'success': True, @@ -411,7 +430,7 @@ def upload_file(): 'url': file_path # 返回绝对路径 } }) - + return jsonify({ 'success': False, 'message': '不支持的文件类型' @@ -431,17 +450,17 @@ def audit_task(task_id): 'success': False, 'message': '任务不存在' }) - + # 检查任务状态 if task['status'] != 'draft': return jsonify({ 'success': False, 'message': '只能审核草稿状态的任务' }) - + # 更新任务状态为已排期 db.update_task(task_id, {'status': 'scheduled'}) - + # 记录操作日志 db.log_task_action({ 'log_id': f"log_{datetime.now().strftime('%Y%m%d%H%M%S')}", @@ -450,7 +469,7 @@ def audit_task(task_id): 'user_id': session.get('user_id'), 'changes': {'status': 'scheduled', 'action': 'audit'} }) - + return jsonify({ 'success': True, 'message': '审核成功' diff --git a/admin/dashboard/templates/message_push_management.html b/admin/dashboard/templates/message_push_management.html index 9eddad1..c935ab5 100644 --- a/admin/dashboard/templates/message_push_management.html +++ b/admin/dashboard/templates/message_push_management.html @@ -294,8 +294,26 @@ + + + + + + - + + + + + 点击上传 +
只能上传jpg/png文件,且不超过2MB
+
@@ -377,7 +395,12 @@ new Vue({ groups: [], content_text: '', content_image: '', - content_link: '', + content_link: { + title: '', + des: '', + url: '', + thumburl: '' + }, content_miniprogram: { title: '', path: '' @@ -412,7 +435,8 @@ new Vue({ }, imageList: [], previewVisible: false, - previewUrl: '' + previewUrl: '', + thumbnailList: [] } }, mounted() { @@ -513,7 +537,12 @@ new Vue({ groups: [], content_text: '', content_image: '', - content_link: '', + content_link: { + title: '', + des: '', + url: '', + thumburl: '' + }, content_miniprogram: { title: '', path: '' @@ -529,7 +558,13 @@ new Vue({ this.$refs.taskForm.validate(async (valid) => { if (valid) { try { - const response = await axios.post('/message_push/api/tasks', this.taskForm); + // 确保链接内容是JSON字符串 + const formData = { ...this.taskForm }; + if (formData.content_link) { + formData.content_link = JSON.stringify(formData.content_link); + } + + const response = await axios.post('/message_push/api/tasks', formData); if (response.data.success) { this.$message.success('保存任务成功'); this.taskDialogVisible = false; @@ -559,6 +594,31 @@ new Vue({ editTask(task) { this.dialogTitle = '编辑任务'; this.taskForm = { ...task }; + // 处理链接内容 + if (task.content_link) { + try { + this.taskForm.content_link = typeof task.content_link === 'string' + ? JSON.parse(task.content_link) + : task.content_link; + + // 如果有缩略图,显示缩略图 + if (this.taskForm.content_link.thumburl) { + const fileName = this.taskForm.content_link.thumburl.split('/').pop(); + this.thumbnailList = [{ + name: '已上传缩略图', + url: `/static/uploads/${fileName}` + }]; + } + } catch (e) { + console.error('解析链接内容失败:', e); + this.taskForm.content_link = { + title: '', + des: '', + url: '', + thumburl: '' + }; + } + } if (task.content_image) { // 编辑时显示图片 const fileName = task.content_image.split('/').pop(); @@ -728,6 +788,28 @@ new Vue({ this.previewVisible = true; }, + // 缩略图上传相关 + handleThumbnailSuccess(response, file) { + if (response.success) { + this.taskForm.content_link.thumburl = response.data.url; // 存储绝对路径 + // 显示时使用文件名 + const fileName = file.name; + this.thumbnailList = [{ + name: fileName, + url: `/static/uploads/${response.data.url.split('/').pop()}` // 显示时使用相对路径 + }]; + } else { + this.$message.error('上传失败'); + } + }, + + handleThumbnailPreview(file) { + // 预览时使用相对路径 + const fileName = file.url.split('/').pop(); + this.previewUrl = `/static/uploads/${fileName}`; + this.previewVisible = true; + }, + // 工具函数 getStatusType(status) { const typeMap = { diff --git a/db/task_db.py b/db/task_db.py index 0fc8d31..5396e91 100644 --- a/db/task_db.py +++ b/db/task_db.py @@ -27,7 +27,7 @@ class TaskDBOperator(BaseDBOperator): recurring_end DATETIME DEFAULT NULL, content_text TEXT(500), content_image VARCHAR(255), - content_link VARCHAR(255), + content_link JSON, content_miniprogram JSON, groups JSON, priority ENUM('high', 'medium', 'low') DEFAULT 'medium', @@ -47,8 +47,7 @@ class TaskDBOperator(BaseDBOperator): action ENUM('create', 'update', 'delete', 'pause', 'resume') NOT NULL, user_id VARCHAR(50) NOT NULL, changes JSON, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (task_id) REFERENCES t_push_tasks(task_id) + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP ) """) @@ -61,8 +60,7 @@ class TaskDBOperator(BaseDBOperator): recipients JSON NOT NULL, validation JSON, status ENUM('sent', 'confirmed', 'modified') DEFAULT 'sent', - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (task_id) REFERENCES t_push_tasks(task_id) + created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) """) @@ -73,8 +71,7 @@ class TaskDBOperator(BaseDBOperator): task_id VARCHAR(36) NOT NULL, user_id VARCHAR(50) NOT NULL, content TEXT NOT NULL, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (task_id) REFERENCES t_push_tasks(task_id) + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP ) """) diff --git a/plugins/group_member_change/main.py b/plugins/group_member_change/main.py index 954300d..2d573f3 100644 --- a/plugins/group_member_change/main.py +++ b/plugins/group_member_change/main.py @@ -1,16 +1,15 @@ +import xml.etree.ElementTree as ET from datetime import datetime from typing import Dict, Any, List, Optional, Tuple -import xml.etree.ElementTree as ET - from base.plugin_common.message_plugin_interface import MessagePluginInterface from base.plugin_common.plugin_interface import PluginStatus from db.connection import DBConnectionManager from db.contacts_db import ContactsDBOperator -from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager +from utils.robot_cmd.robot_command import PermissionStatus, GroupBotManager from utils.wechat.contact_manager import ContactManager from wechat_ipad import WechatAPIClient -from wechat_ipad.models.appmsg_xml import LINK_XML +from wechat_ipad.models.appmsg_xml import LINK_XML_WELCOME class GroupMemberChangePlugin(MessagePluginInterface): @@ -141,7 +140,7 @@ class GroupMemberChangePlugin(MessagePluginInterface): contact_db.save_chatroom_member_simple(roomid, member_details) except Exception as e: self.LOG.warning(f"新增群员信息失败: {e}") - xml_content = f"{LINK_XML}".format(nickname=nickname, now=now, head_url=head_url) + xml_content = f"{LINK_XML_WELCOME}".format(nickname=nickname, now=now, head_url=head_url) await bot.send_link_xml_message(xml_content, roomid) return True, "已发送进群欢迎语" diff --git a/plugins/message_push_task/main.py b/plugins/message_push_task/main.py index 8337deb..e76a00e 100644 --- a/plugins/message_push_task/main.py +++ b/plugins/message_push_task/main.py @@ -13,6 +13,7 @@ from utils.decorator.async_job import async_job from utils.decorator.plugin_decorators import plugin_stats_decorator from utils.robot_cmd.robot_command import PermissionStatus, GroupBotManager from wechat_ipad import WechatAPIClient +from wechat_ipad.models.appmsg_xml import LINK_XML_NORMAL class MessagePushTask(MessagePluginInterface): @@ -174,7 +175,14 @@ class MessagePushTask(MessagePluginInterface): # 发送链接消息 if content_link: - await self.bot.send_link_message(group_id, content_link) + # content_link json 读取内容 + link_data = json.loads(content_link) + xml_content = f"{LINK_XML_NORMAL}".format(title=link_data.get('title', ''), + des=link_data.get('des', ''), + url=link_data.get('url', ''), + thumburl=link_data.get('thumburl', '') + ) + await self.bot.send_link_xml_message(xml_content, group_id) # # 发送小程序消息 # if content_miniprogram: diff --git a/wechat_ipad/models/appmsg_xml.py b/wechat_ipad/models/appmsg_xml.py index 0976f0b..ca25e31 100644 --- a/wechat_ipad/models/appmsg_xml.py +++ b/wechat_ipad/models/appmsg_xml.py @@ -1,4 +1,4 @@ -LINK_XML = """ +LINK_XML_WELCOME = """ 👏欢迎 {nickname} 加入群聊!🎉 ⌚时间:{now} @@ -93,3 +93,50 @@ MUSIC_XML = """ """ +LINK_XML_NORMAL = """ + + {title} + {des} + view + 5 + 0 + + {url} + + + + + {thumburl} + + + + + + + + 0 + + + + + + + + 0 + + + + + + 0 + + + +WXID +0 + + 1 + + + +"""