diff --git a/admin/dashboard/blueprints/message_push.py b/admin/dashboard/blueprints/message_push.py index 7c7cc6a..b6ccd80 100644 --- a/admin/dashboard/blueprints/message_push.py +++ b/admin/dashboard/blueprints/message_push.py @@ -1,13 +1,14 @@ import asyncio import threading from concurrent.futures import ThreadPoolExecutor - -from flask import Blueprint, render_template, jsonify, request, current_app, session -from .auth import login_required -from loguru import logger +import os import json import uuid from datetime import datetime +from flask import Blueprint, render_template, jsonify, request, current_app, session +from werkzeug.utils import secure_filename +from .auth import login_required +from loguru import logger # 创建消息推送管理蓝图 message_push_bp = Blueprint('message_push', __name__, url_prefix='/message_push') @@ -19,6 +20,9 @@ message_thread_pool = ThreadPoolExecutor(max_workers=10, thread_name_prefix="mes shared_loop = None loop_lock = threading.Lock() +# 允许的图片文件扩展名 +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} + def get_or_create_loop(): """获取或创建共享的事件循环""" @@ -62,6 +66,10 @@ def send_message_in_thread(func, *args, **kwargs): message_thread_pool.submit(run) +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + + # 消息推送管理页面 @message_push_bp.route('/') @login_required @@ -363,3 +371,47 @@ def api_statistics(): except Exception as e: logger.error(f"获取任务统计信息失败: {e}") return jsonify({"success": False, "error": str(e)}), 500 + + +@message_push_bp.route('/api/upload', methods=['POST']) +def upload_file(): + """处理图片上传""" + if 'file' not in request.files: + return jsonify({ + '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, + 'data': { + 'url': file_path # 返回绝对路径 + } + }) + + return jsonify({ + 'success': False, + 'message': '不支持的文件类型' + }) diff --git a/admin/dashboard/templates/message_push_management.html b/admin/dashboard/templates/message_push_management.html index 795bc2b..c03f57d 100644 --- a/admin/dashboard/templates/message_push_management.html +++ b/admin/dashboard/templates/message_push_management.html @@ -553,9 +553,11 @@ new Vue({ this.dialogTitle = '编辑任务'; this.taskForm = { ...task }; if (task.content_image) { + // 编辑时显示图片 + const fileName = task.content_image.split('/').pop(); this.imageList = [{ name: '已上传图片', - url: task.content_image + url: `/static/uploads/${fileName}` // 显示时使用相对路径 }]; } this.taskDialogVisible = true; @@ -685,10 +687,12 @@ new Vue({ // 图片上传相关 handleImageSuccess(response, file) { if (response.success) { - this.taskForm.content_image = response.data.url; + this.taskForm.content_image = response.data.url; // 存储绝对路径 + // 显示时使用文件名 + const fileName = file.name; this.imageList = [{ - name: file.name, - url: response.data.url + name: fileName, + url: `/static/uploads/${response.data.url.split('/').pop()}` // 显示时使用相对路径 }]; } else { this.$message.error('上传失败'); @@ -711,7 +715,9 @@ new Vue({ }, handleImagePreview(file) { - this.previewUrl = file.url; + // 预览时使用相对路径 + const fileName = file.url.split('/').pop(); + this.previewUrl = `/static/uploads/${fileName}`; this.previewVisible = true; }, diff --git a/plugins/guess_song/main.py b/plugins/guess_song/main.py index 6b3b25b..90e9126 100644 --- a/plugins/guess_song/main.py +++ b/plugins/guess_song/main.py @@ -11,6 +11,7 @@ from typing import Dict, Any, List, Optional, Tuple from base.plugin_common.message_plugin_interface import MessagePluginInterface from base.plugin_common.plugin_interface import PluginStatus from utils.decorator.plugin_decorators import plugin_stats_decorator +from utils.revoke.message_auto_revoke import MessageAutoRevoke from utils.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager from utils.decorator.points_decorator import plugin_points_cost, points_reward_decorator from wechat_ipad import WechatAPIClient @@ -116,6 +117,7 @@ class GuessSongPlugin(MessagePluginInterface): def __init__(self): super().__init__() + self.revoke = None self.feature = self.register_feature() self.redis_db = None @@ -202,6 +204,7 @@ class GuessSongPlugin(MessagePluginInterface): roomid = message.get("roomid", "") gbm: GroupBotManager = message.get("gbm") bot: WechatAPIClient = message.get("bot") + self.revoke: MessageAutoRevoke = message.get("revoke") # 使用roomid或sender作为游戏会话ID session_id = roomid if roomid else sender @@ -224,9 +227,10 @@ class GuessSongPlugin(MessagePluginInterface): if content: # 有内容,视为答案 return await self._check_answer(message) else: # 没有内容,提示已有游戏在进行中 - await bot.send_text_message(session_id, + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, f"⚠️ 当前已有猜歌游戏在进行中,请直接回复 [猜歌名 歌名] 进行猜测\n或回复 [猜歌名 下一首] 跳过当前歌曲", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 4) return True, "已有游戏进行中" # 否则开始新游戏(可以指定歌手或随机) @@ -239,7 +243,8 @@ class GuessSongPlugin(MessagePluginInterface): # 搜索歌曲 song_info = await self._get_random_song(singer_name) if not song_info or not song_info.get("play_url"): - await bot.send_text_message(session_id, f"❌未找到{singer_name or '随机'}歌曲,请重试", sender) + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, f"❌未找到{singer_name or '随机'}歌曲,请重试", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 4) return False, "未找到歌曲" # 保存游戏会话 @@ -258,9 +263,10 @@ class GuessSongPlugin(MessagePluginInterface): self.redis_db.save_game_session(session_id, game_data) # 发送游戏开始消息 - await bot.send_text_message(session_id, + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, f"🎵 猜歌名游戏开始!\n请听10秒钟歌曲片段,然后回复[猜歌名 歌名]来猜测歌曲名称。\n回复[猜歌名 下一首]可跳过当前歌曲。\n歌手: {song_info.get('singer_name', '未知')}", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 30) # 发送10秒音频片段 await self._send_song_clip(bot, song_info, session_id) @@ -269,7 +275,8 @@ class GuessSongPlugin(MessagePluginInterface): except Exception as e: self.LOG.error(f"开始猜歌游戏出错: {e}") - await bot.send_text_message(session_id, f"❌开始猜歌游戏出错,请稍后重试", sender) + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, f"❌开始猜歌游戏出错,请稍后重试", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 5) return False, f"处理出错: {e}" @points_reward_decorator(5, "game", "猜歌名游戏", FEATURE_KEY) @@ -293,6 +300,8 @@ class GuessSongPlugin(MessagePluginInterface): game_data = self.redis_db.get_game_session(session_id) if not game_data: + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, "没有进行中的游戏", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 5) return False, "没有进行中的游戏" correct_answer = game_data.get("song_name", "") @@ -326,14 +335,17 @@ class GuessSongPlugin(MessagePluginInterface): self.redis_db.save_game_session(session_id, game_data) # 告知用户答案错误 - await bot.send_text_message(session_id, f"❌ 答案错误,请继续猜测!") + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, f"❌ 答案错误,请继续猜测!", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 4) + return False, "答案错误" except Exception as e: self.LOG.error(f"检查答案出错: {e}") session_id = message.get("roomid", "") or message.get("sender", "") sender = message.get("sender", "") bot = message.get("bot") - await bot.send_text_message(session_id, f"❌检查答案出错,请稍后重试", sender) + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, f"❌检查答案出错,请稍后重试", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 4) return False, f"处理出错: {e}" async def _get_random_song(self, singer_name: Optional[str]) -> Dict[str, Any]: @@ -471,7 +483,8 @@ class GuessSongPlugin(MessagePluginInterface): current_game = self.redis_db.get_game_session(session_id) if not current_game or current_game.get("status") != "playing": - await bot.send_text_message(session_id, f"⚠️ 当前没有进行中的猜歌游戏", sender) + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, f"⚠️ 当前没有进行中的猜歌游戏", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 4) return False, "没有进行中的游戏" # 显示当前歌曲答案 @@ -492,5 +505,6 @@ class GuessSongPlugin(MessagePluginInterface): except Exception as e: self.LOG.error(f"跳过当前歌曲出错: {e}") - await bot.send_text_message(session_id, f"❌跳过当前歌曲出错,请稍后重试", sender) + client_msg_id, create_time, new_msg_id = await bot.send_text_message(session_id, f"❌跳过当前歌曲出错,请稍后重试", sender) + self.revoke.add_message_to_revoke(session_id, client_msg_id, create_time, new_msg_id, 4) return False, f"处理出错: {e}"