Files
abot/plugins/game_task/main.py

702 lines
30 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import random
from datetime import datetime
from typing import Dict, Any, List, Optional, Tuple
from loguru import logger
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.robot_cmd.robot_command import Feature, PermissionStatus, GroupBotManager
from utils.decorator.points_decorator import points_reward_decorator
from db.connection import DBConnectionManager
from db.encyclopedia import EncyclopediaDB
import json
from utils.ai.unified_llm import UnifiedLLMClient
class GameTaskPlugin(MessagePluginInterface):
"""游戏任务插件"""
# 功能权限常量
FEATURE_KEY = "TASK_GAME"
FEATURE_DESCRIPTION = "📚 百科答题 [/t, /s, /a 任务ID 答案]"
@property
def feature_key(self) -> Optional[str]:
return self.FEATURE_KEY
@property
def feature_description(self) -> Optional[str]:
return self.FEATURE_DESCRIPTION
@property
def name(self) -> str:
return "百科问答"
@property
def version(self) -> str:
return "1.0.0"
@property
def description(self) -> str:
return "百科问答游戏系统,支持问答积分"
@property
def author(self) -> str:
return "liu.wei"
@property
def command_prefix(self) -> Optional[str]:
return "" # 不需要前缀,直接匹配命令
@property
def commands(self) -> List[str]:
return self._commands
def __init__(self):
super().__init__()
self.LOG = logger
# 注册功能权限
self.feature = self.register_feature()
def initialize(self, context: Dict[str, Any]) -> bool:
"""初始化插件"""
self.LOG.debug(f"正在初始化 {self.name} 插件...")
# 保存上下文对象
self.event_system = context.get("event_system")
# 初始化配置
self._commands = self._config.get("GameTask", {}).get("command",
["/t", "/a", "/s", "/r", "/l", "/h"])
self.command_format = self._config.get("GameTask", {}).get("command-format", """
🎮 百科问答指令:
/s - 加入游戏
/t - 获取任务
/a <任务ID> <答案> - 提交答案
/r - 查看排行榜
/l - 查看活跃任务
/h - 查看未完成任务
""")
plugin_config = self._config.get("GameTask", {})
llm_config = plugin_config.get("llm", {}) or {}
if not llm_config:
# 严格场景路由:仅通过 scene 映射具体后端与模型参数。
llm_config = {
"scene": plugin_config.get("scene", ""),
}
self.llm_client = UnifiedLLMClient(llm_config)
self.enable = plugin_config.get("enable", True)
# 初始化数据库连接
self.db_manager = DBConnectionManager.get_instance()
self.encyclopedia_db = EncyclopediaDB(self.db_manager)
self.LOG.debug(f"[{self.name}] 插件初始化完成,指令:{self._commands}")
return True
def start(self) -> bool:
"""启动插件"""
self.LOG.debug(f"[{self.name}] 插件已启动")
self.status = PluginStatus.RUNNING
return True
def stop(self) -> bool:
"""停止插件"""
self.LOG.info(f"[{self.name}] 插件已停止")
self.status = PluginStatus.STOPPED
return True
def can_process(self, message: Dict[str, Any]) -> bool:
"""检查是否可以处理该消息"""
if not self.enable:
return False
content = str(message.get("content", "")).strip()
command = content.split(" ")[0]
return command in self._commands
def calculate_game_points(self, message: Dict[str, Any], success: bool, response: str) -> int:
"""计算游戏积分"""
if not success:
return 0
try:
return int(response)
except (TypeError, ValueError):
return 0
@plugin_stats_decorator(plugin_name="百科问答")
async def process_message(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
"""处理消息"""
content = str(message.get("content", "")).strip()
command = content.split(" ")[0].lower()
sender = message.get("sender")
roomid = message.get("roomid", "")
gbm: GroupBotManager = message.get("gbm")
all_contacts = message.get("all_contacts", {})
self.LOG.debug(f"插件执行: {self.name}{content}")
# 检查权限
if roomid and gbm.get_group_permission(roomid, self.feature) == PermissionStatus.DISABLED:
return False, "没有权限"
try:
# 获取用户昵称
wx_nick_name = all_contacts.get(sender, sender)
if command == "/s":
await self._handle_join_game(sender, roomid, wx_nick_name)
return True, "加入游戏成功"
elif command == "/t":
await self._handle_get_task(sender, roomid)
return True, "获取任务成功"
elif command == "/a":
# 这里传递整个消息对象给处理方法
return await self._handle_submit_answer(message)
elif command == "/r":
await self._handle_show_rank(sender, roomid)
return True, "显示排行榜成功"
elif command == "/l":
await self._handle_show_active_tasks(sender, roomid)
return True, "显示活跃任务成功"
elif command == "/h":
await self._handle_list_uncompleted_tasks(sender, roomid)
return True, "列举未完成任务成功"
else:
await self.bot.send_at_message((roomid if roomid else sender), f"❌未知命令!\n{self.command_format}",
[sender])
return False, "未知命令"
except Exception as e:
self.LOG.error(f"处理消息出错: {e}")
return False, f"处理出错: {e}"
def get_schedule_actions(self) -> List[Dict[str, Any]]:
"""声明百科问答插件支持的可调度动作。"""
return [
{
"action_key": "random_task_assignment",
"name": "群随机发题",
"description": "在配置时间给目标群随机发放一条百科问答任务",
"trigger_type": "at_times",
"trigger_config": {"time_list": ["17:58"]},
"target_scope": "all_enabled_groups",
"target_config": {},
"payload": {},
# 兼容旧逻辑:任务默认启用。
"default_enabled": True,
}
]
async def run_scheduled_action(self, action_key: str, context: Dict[str, Any]) -> Dict[str, Any]:
"""执行后台调度动作并返回执行统计。"""
if action_key != "random_task_assignment":
return {
"success": False,
"summary": f"不支持的动作: {action_key}",
"detail": {"action_key": action_key},
}
target_groups = [str(g).strip() for g in (context.get("target_groups") or []) if str(g).strip()]
result = await self.run_random_task_assignment(target_groups=target_groups)
return {
"success": bool(result.get("failed_groups", 0) == 0),
"summary": (
f"发题完成: 候选{result.get('candidate_groups', 0)}群,"
f"成功{result.get('success_groups', 0)}群,失败{result.get('failed_groups', 0)}"
),
"detail": result,
}
async def _handle_join_game(self, sender: str, roomid: str, wx_nick_name: str) -> None:
"""处理加入游戏请求"""
try:
# 检查并添加群聊
if not self.encyclopedia_db.check_group_exists(roomid):
self.encyclopedia_db.add_group(roomid)
await self.bot.send_text_message(
(roomid if roomid else sender),
f"🎉 群 {roomid} 已就位,准备开燥!",
sender
)
# 检查并添加玩家
player = self.encyclopedia_db.get_player(sender, roomid)
if not player:
self.encyclopedia_db.add_player(sender, roomid, wx_nick_name)
await self.bot.send_text_message(
(roomid if roomid else sender),
f"🎉 哇塞,{wx_nick_name} 你来啦!\n"
f"🌟 群 {roomid} 瞬间燃爆!\n"
f"🎈 快来接任务,秀翻全场!指令: /t",
sender
)
except Exception as e:
self.LOG.error(f"加入游戏出错: {e}")
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😔 加入游戏出错,请稍后再试!",
sender
)
async def _handle_get_task(self, sender: str, roomid: str) -> None:
"""处理获取任务请求"""
try:
# 获取群内所有玩家
players = self.encyclopedia_db.get_all_players_in_group(roomid)
# 如果群里没有玩家,自动初始化并添加当前用户
if not players:
# 检查并添加群聊
if not self.encyclopedia_db.check_group_exists(roomid):
self.encyclopedia_db.add_group(roomid)
await self.bot.send_text_message(
(roomid if roomid else sender),
f"🎉 群 {roomid} 已就位,准备开燥!",
sender
)
# 获取用户昵称 (从all_contacts中获取可能不可行因为这里没有all_contacts参数)
# 使用sender作为临时昵称
wx_nick_name = sender
# 添加当前用户为玩家
self.encyclopedia_db.add_player(sender, roomid, wx_nick_name)
await self.bot.send_text_message(
(roomid if roomid else sender),
f"🎉 哇塞,{wx_nick_name} 你是第一个玩家!\n"
f"🌟 已自动为你加入游戏!\n"
f"🎈 现在就为你准备题目...",
sender
)
# 更新玩家列表
players = self.encyclopedia_db.get_all_players_in_group(roomid)
player_dict = {p['player_id']: p['player_name'] for p in players}
if sender not in player_dict:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😅 嘿,你谁啊?\n"
f"🌟 先用 /s 报名,不然没法玩哦!",
sender
)
return
# 以下是原有的出题逻辑
task = self.game_question_json("请出题!")
category = task["category"]
question = task["question"]
answer = task["answer"]
score = int(task["score"])
description = task.get("description", "")
# 创建活跃任务
active_task_id = self.encyclopedia_db.create_active_task(
roomid, question, answer, score, description, sender
)
if not active_task_id:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😔 任务创建失败,请稍后再试!",
sender
)
return
await self.bot.send_text_message(
(roomid if roomid else sender),
f"🎁 {player_dict[sender]},你的专属任务闪亮登场!\n"
f"🎀 任务ID: {active_task_id}\n"
f"🎈 问题:[{category}]{question}\n"
f"🌼 积分:{score}\n"
f"🌈 快上答案:/a {active_task_id} 答案",
sender
)
except Exception as e:
self.LOG.error(f"获取任务出错: {e}")
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😔 获取任务出错,请稍后再试!",
sender
)
@points_reward_decorator(calculate_game_points, "game", "百科答题奖励", FEATURE_KEY)
async def _handle_submit_answer(self, message: Dict[str, Any]) -> Tuple[bool, Optional[str]]:
"""处理提交答案请求"""
try:
content = str(message.get("content", "")).strip()
sender = message.get("sender")
roomid = message.get("roomid", "")
parts = content.split(" ", 2)
if len(parts) < 3:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😅 喂,格式不对啊!\n"
f"🌟 正确姿势:/a [任务ID] [答案]\n"
f"🎈 比如:/a 1 钒",
sender
)
return False, "0"
task_id = parts[1]
answer = parts[2]
# 获取玩家信息
player = self.encyclopedia_db.get_player(sender, roomid)
if not player:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😅 嘿,你是路人甲吗?\n"
f"🌟 用 /s 先加入群 {roomid} 吧!",
sender
)
return False, "0"
player_name = player['player_name']
if not task_id.isdigit():
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😅 喂任务ID得是数字好吗\n"
f"🌟 比如1\n"
f"🎈 别瞎搞,重新来!",
sender
)
return False, "0"
active_task_id = int(task_id)
# 获取任务信息
task_data = self.encyclopedia_db.get_task_by_id(roomid, active_task_id)
if not task_data:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😔 哎哟,任务 task_{active_task_id} 不翼而飞啦!\n"
f"🌼 可能被别人抢先一步咯!",
sender
)
return False, "0"
if task_data['status'] == 'completed':
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😄 哈哈,你慢了一步!\n"
f"🌟 任务 task_{active_task_id} 已经完结\n"
f"🎈 快去抢新任务吧!",
sender
)
return False, "0"
question = task_data['question']
correct_answer_db = task_data['answer'].lower()
top_score = task_data['score']
holder_id = task_data['holder_id']
# 获取任务持有者信息
holder = self.encyclopedia_db.get_task_holder(roomid, holder_id)
holder_name = holder['player_name'] if holder else "未知玩家"
answer_json = {"question": question, "top_score": str(top_score), "answer": answer}
result = self.game_answer_json(answer_json)
points = int(result["score"])
description = result["description"]
is_correct = points > 0
# 记录答题历史
self.encyclopedia_db.add_task_history(
roomid, active_task_id, sender, answer, is_correct, points
)
if is_correct:
# 完成任务
self.encyclopedia_db.complete_task(active_task_id)
if sender == holder_id:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"🎉 {player_name} 你是天才吗?\n"
f"🌟 任务:{question}\n"
f"🎈 答对啦,简直无敌!\n"
f"🌈 奖励:{points}\n"
f"🎀 彩蛋:{description}",
sender
)
else:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"🎉 {player_name} 抢答王上线!\n"
f"🌟 任务:{question}\n"
f"🎈 原主:{holder_name} 被你截胡啦!\n"
f"🌈 狂揽 {points} 分,太骚了!\n"
f"🎀 彩蛋:{description}",
sender
)
else:
# 扣除积分
self.encyclopedia_db.update_player_points(sender, roomid, -1)
points = -1
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😅 {player_name} 你这是要笑死我吗?\n"
f"🌼 任务:{question}\n"
f"🎈 你答:{answer}\n"
f"🌟 正确答案:{correct_answer_db}\n"
f"🌈 扣 1 分,别哭哦!\n"
f"🎀 提示:{description}\n"
f"🌟 任务ID: {active_task_id} 还能抢救一下!",
sender
)
return True, str(points)
except Exception as e:
self.LOG.error(f"提交答案出错: {e}")
return False, "0"
async def _handle_show_rank(self, sender: str, roomid: str) -> None:
"""处理显示排行榜请求"""
try:
# 获取排行榜
ranks = self.encyclopedia_db.get_player_ranking(roomid, 10)
if not ranks:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😔 群 {roomid} 冷冷清清\n"
f"🌟 快来一起燥起来吧!",
sender
)
return
rank_text = f"🎉 群答题 {roomid} 排行榜Top 10来啦\n"
for i, row in enumerate(ranks, 1):
rank_text += f"🐓 {i}. {row['player_name']}: {row['points']}\n"
await self.bot.send_at_message(
(roomid if roomid else sender),
rank_text,
[sender]
)
except Exception as e:
self.LOG.error(f"显示排行榜出错: {e}")
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😔 获取排行榜出错,请稍后再试!",
sender
)
async def _handle_show_active_tasks(self, sender: str, roomid: str) -> None:
"""处理显示活跃任务请求"""
try:
# 获取活跃任务
tasks = self.encyclopedia_db.get_active_tasks_in_group(roomid)
if not tasks:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😄 群 {roomid} 现在一片祥和\n"
f"🌟 没任务?快用 /t 搞一个!",
sender
)
return
task_text = f"🎉 群 {roomid} 活跃任务速递:\n"
for task in tasks:
task_text += (
f"🌈 任务ID: task_{task['active_task_id']}\n"
f"🎀 问题:{task['question']}\n"
f"🌼 大佬:{task['player_name']}\n"
)
await self.bot.send_text_message(
(roomid if roomid else sender),
task_text,
sender
)
except Exception as e:
self.LOG.error(f"显示活跃任务出错: {e}")
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😔 获取活跃任务出错,请稍后再试!",
sender
)
async def _handle_list_uncompleted_tasks(self, sender: str, roomid: str) -> None:
"""处理列举未完成任务请求"""
try:
# 获取未完成任务
tasks = self.encyclopedia_db.get_active_tasks_in_group(roomid)
if not tasks:
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😄 群 {roomid} 全员开挂?\n"
f"🌟 没未完成任务,快用 /t 再战!",
sender
)
return
task_text = f"🎉 群 {roomid} 未完成任务大曝光:\n"
for task in tasks:
task_text += (
f"🌈 任务ID: task_{task['active_task_id']}\n"
f"🎀 问题:{task['question']}\n"
f"🌼 主人:{task['player_name']}\n"
)
await self.bot.send_text_message(
(roomid if roomid else sender),
task_text,
sender
)
except Exception as e:
self.LOG.error(f"列举未完成任务出错: {e}")
await self.bot.send_text_message(
(roomid if roomid else sender),
f"😔 获取未完成任务出错,请稍后再试!",
sender
)
async def run_random_task_assignment(self, target_groups: Optional[List[str]] = None) -> Dict[str, int]:
"""定时任务:随机发题,排除 23:00-08:00。
Args:
target_groups: 指定目标群列表;为空时按原逻辑扫描全部游戏群。
Returns:
dict: 执行统计信息。
"""
current_hour = datetime.now().hour
if current_hour >= 23 or current_hour < 9:
self.LOG.info(f"当前时间 {current_hour}:00 在23:00-08:00区间跳过任务发放")
return {"candidate_groups": 0, "success_groups": 0, "failed_groups": 0}
try:
# 获取所有群聊
groups = self.encyclopedia_db.get_all_groups()
target_group_set = {g for g in (target_groups or []) if g}
candidate_groups = 0
success_groups = 0
failed_groups = 0
for group in groups:
if target_group_set and group not in target_group_set:
continue
# 检查权限
if GroupBotManager.get_group_permission(group,self.feature) == PermissionStatus.DISABLED:
continue
candidate_groups += 1
# 获取群内所有玩家
players = self.encyclopedia_db.get_all_players_in_group(group)
if not players:
continue
# 随机选择一个玩家
holder = random.choice(players)
holder_id = holder['player_id']
holder_name = holder['player_name']
# 创建任务
task = self.game_question_json("请出题!")
category = task["category"]
question = task["question"]
answer = task["answer"]
score = int(task["score"])
description = task.get("description", "")
# 创建活跃任务
active_task_id = self.encyclopedia_db.create_active_task(
group, question, answer, score, description, holder_id
)
if active_task_id:
await self.bot.send_at_message(
group,
f"🎁 新任务来袭,够不够刺激?\n"
f"🎀 任务ID: {active_task_id}\n"
f"🌟 幸运鹅:{holder_name}\n"
f"🎈 问题:[{category}]{question}\n"
f"🌼 积分:{score}\n"
f"🌈 抢答格式:/a {active_task_id} 答案",
[holder_id])
success_groups += 1
else:
failed_groups += 1
return {
"candidate_groups": candidate_groups,
"success_groups": success_groups,
"failed_groups": failed_groups,
}
except Exception as e:
self.LOG.error(f"定时任务出错: {e}")
return {"candidate_groups": 0, "success_groups": 0, "failed_groups": 1}
# 解析JSON
def extract_content(self, data_string):
try:
data = json.loads(data_string)
# 提取content字段
content = data["choices"][0]["message"].get("content", "")
return content
except json.JSONDecodeError:
print("Invalid JSON")
return None
def message_task_json(self, prompt, content):
response = self.llm_client.generate(
system_prompt=prompt,
user_prompt=str(content),
user="game_task_bot",
)
if not response or not response.get("text"):
raise RuntimeError(f"LLM 调用失败: {self.llm_client.last_error}")
return json.loads(response["text"])
def game_question_json(self, question):
fields = [
"近现代史", "战争与政治", "文化遗产与考古学", "进化论", "动植物学", "基因与遗传学",
"生态学", "有机化学", "无机化学", "生物化学", "环境化学", "人文地理", "自然地理",
"地质学", "气候变化", "古典文学", "现代文学", "小说与戏剧", "诗歌与散文", "美术",
"音乐", "戏剧与舞蹈", "电影与媒体", "西方哲学", "东方哲学", "道德与伦理学", "逻辑与认识论",
"人类学", "心理学", "政治学", "经济学", "编程语言", "人工智能", "数据科学", "网络与安全",
"机械工程", "电气工程", "化学工程", "土木工程", "解剖学", "生理学", "临床医学", "药学与护理",
"球类运动", "奥林匹克运动", "运动心理学", "健身与营养", "世界宗教", "神话与民间故事",
"宗教哲学", "语法与词汇", "语言习得", "方言与语言变异", "宏观经济学", "微观经济学",
"国际贸易", "金融与投资", "民法与刑法", "国际法", "知识产权法", "环境法"
]
# 随机选择一个领域
selected_field = random.choice(fields)
# 输出随机选择的领域
print(f"随机选择的领域是:{selected_field}")
question = question + f"随机选择的领域是:{selected_field}"
prompt = """
请根据以下要求,随机生成一个问题,确保每次提问涉及不同领域,且不重复:
1. 每个问题应该覆盖以下任意领域:近现代史、战争与政治、文化遗产与考古学、进化论、动植物学、基因与遗传学、生态学、有机化学、无机化学、生物化学、环境化学、人文地理、自然地理、地质学、气候变化、古典文学、现代文学、小说与戏剧、诗歌与散文、美术、音乐、戏剧与舞蹈、电影与媒体、西方哲学、东方哲学、道德与伦理学、逻辑与认识论、人类学、心理学、政治学、经济学、编程语言、人工智能、数据科学、网络与安全、机械工程、电气工程、化学工程、土木工程、解剖学、生理学、临床医学、药学与护理、球类运动、奥林匹克运动、运动心理学、健身与营养、世界宗教、神话与民间故事、宗教哲学、语法与词汇、语言习得、方言与语言变异、宏观经济学、微观经济学、国际贸易、金融与投资、民法与刑法、国际法、知识产权法、环境法。
2. 问题应简洁,具有一定难度,易于理解且充满乐趣,适合百科类知识问答。
3. 每个问题应独立,且问题之间无连贯性或延续性。
4. 避免重复提问或产生相似的问题,确保问题新颖。
5. 对于每个问题提供难度评分1-10分。在问题答对后用户可以根据难度给出相应的分数。
6. 答案控制长度在25字以内。
输出格式要求如下仅返回JSON格式确保不添加多余的符号
{
"category":"人文地理"
"question": "哪个国家最早将玫瑰与爱情联系起来?",
"score": "1",
"answer": "波斯",
"description": "波斯文化中,玫瑰被广泛认为是象征爱情的花卉,其象征意义逐渐传入西方。"
}
"""
return self.message_task_json(prompt, question)
def game_answer_json(self, answer):
prompt = "你是一个益智百科问答大师,可以根据用户回答的答案进行判断,并且对问题(question)答案(answer)进行打分,打分时请参考最高分要求(top_score)告知用户能获得多少分请在description中描述打分理由请只返回JSON格式的内容格式要求如下(请不要加上markdown 的符号){\"question\": \"哪个国家最早将玫瑰与爱情联系起来?\", \"score\":\"1\", \"answer\": \"波斯\",\"description\":\"描述问题答案的原因\"}"
return self.message_task_json(prompt, answer)