优化了CQ码获取逻辑和图片下载逻辑
去除硬编码的“麦麦”()
pull/12/head
SengokuCola 2025-02-28 22:56:05 +08:00
parent 7b9b604811
commit 7494fff1ed
13 changed files with 271 additions and 207 deletions

View File

@ -5,7 +5,7 @@
![Python Version](https://img.shields.io/badge/Python-3.8-blue) ![Python Version](https://img.shields.io/badge/Python-3.8-blue)
![License](https://img.shields.io/badge/license-GNL-green) ![License](https://img.shields.io/github/license/SengokuCola/MaiMBot)
![Status](https://img.shields.io/badge/状态-开发中-yellow) ![Status](https://img.shields.io/badge/状态-开发中-yellow)
</div> </div>
@ -20,7 +20,7 @@
> ⚠️ **警告**请自行了解qqbot的风险麦麦有时候一天被腾讯肘七八次 > ⚠️ **警告**请自行了解qqbot的风险麦麦有时候一天被腾讯肘七八次
> ⚠️ **警告**由于麦麦一直在迭代所以可能存在一些bug请自行测试包括胡言乱语 > ⚠️ **警告**由于麦麦一直在迭代所以可能存在一些bug请自行测试包括胡言乱语
关于麦麦的开发和部署相关的讨论群(不建议发布无关消息) 关于麦麦的开发和部署相关的讨论群(不建议发布无关消息)这里不会有麦麦发言!
<div align="center"> <div align="center">
<img src="docs/qq.png" width="300" /> <img src="docs/qq.png" width="300" />

View File

@ -32,7 +32,7 @@ from .relationship_manager import relationship_manager
# 初始化表情管理器 # 初始化表情管理器
emoji_manager.initialize() emoji_manager.initialize()
print("\033[1;32m正在唤醒麦麦......\033[0m") print(f"\033[1;32m正在唤醒{global_config.BOT_NICKNAME}......\033[0m")
# 创建机器人实例 # 创建机器人实例
chat_bot = ChatBot(global_config) chat_bot = ChatBot(global_config)
@ -54,11 +54,11 @@ async def start_background_tasks():
@driver.on_bot_connect @driver.on_bot_connect
async def _(bot: Bot): async def _(bot: Bot):
"""Bot连接成功时的处理""" """Bot连接成功时的处理"""
print("\033[1;38;5;208m-----------麦麦成功连接!-----------\033[0m") print(f"\033[1;38;5;208m-----------{global_config.BOT_NICKNAME}成功连接!-----------\033[0m")
message_sender.set_bot(bot) message_sender.set_bot(bot)
asyncio.create_task(message_sender.start_processor(bot)) asyncio.create_task(message_sender.start_processor(bot))
await willing_manager.ensure_started() await willing_manager.ensure_started()
print("\033[1;38;5;208m-----------麦麦消息发送器已启动!-----------\033[0m") print("\033[1;38;5;208m-----------消息发送器已启动!-----------\033[0m")
asyncio.create_task(emoji_manager._periodic_scan(interval_MINS=global_config.EMOJI_REGISTER_INTERVAL)) asyncio.create_task(emoji_manager._periodic_scan(interval_MINS=global_config.EMOJI_REGISTER_INTERVAL))
print("\033[1;38;5;208m-----------开始偷表情包!-----------\033[0m") print("\033[1;38;5;208m-----------开始偷表情包!-----------\033[0m")

View File

@ -33,14 +33,6 @@ class ChatBot:
if not self._started: if not self._started:
# 只保留必要的任务 # 只保留必要的任务
self._started = True self._started = True
def is_mentioned_bot(self, message: Message) -> bool:
"""检查消息是否提到了机器人"""
keywords = ['麦麦']
for keyword in keywords:
if keyword in message.processed_plain_text:
return True
return False
async def handle_message(self, event: GroupMessageEvent, bot: Bot) -> None: async def handle_message(self, event: GroupMessageEvent, bot: Bot) -> None:
@ -159,7 +151,7 @@ class ChatBot:
raw_message=msg, raw_message=msg,
plain_text=msg, plain_text=msg,
processed_plain_text=msg, processed_plain_text=msg,
user_nickname="麦麦", user_nickname=global_config.BOT_NICKNAME,
group_name=message.group_name, group_name=message.group_name,
time=timepoint time=timepoint
) )
@ -187,7 +179,7 @@ class ChatBot:
raw_message=emoji_cq, raw_message=emoji_cq,
plain_text=emoji_cq, plain_text=emoji_cq,
processed_plain_text=emoji_cq, processed_plain_text=emoji_cq,
user_nickname="麦麦", user_nickname=global_config.BOT_NICKNAME,
group_name=message.group_name, group_name=message.group_name,
time=bot_response_time, time=bot_response_time,
is_emoji=True is_emoji=True

View File

@ -16,6 +16,10 @@ emoji_chance = 0.2
check_interval = 120 check_interval = 120
register_interval = 10 register_interval = 10
[cq_code]
enable_pic_translate = true
[response] [response]
api_using = "siliconflow" api_using = "siliconflow"
model_r1_probability = 0.8 model_r1_probability = 0.8

View File

@ -10,11 +10,11 @@ import tomli # 添加这行导入
# logger.remove() # logger.remove()
# # 只禁用 INFO 级别的日志输出到控制台 # # 只禁用 INFO 级别的日志输出到控制台
logging.getLogger('nonebot').handlers.clear() # logging.getLogger('nonebot').handlers.clear()
console_handler = logging.StreamHandler() # console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING) # 只输出 WARNING 及以上级别 # console_handler.setLevel(logging.WARNING) # 只输出 WARNING 及以上级别
logging.getLogger('nonebot').addHandler(console_handler) # logging.getLogger('nonebot').addHandler(console_handler)
logging.getLogger('nonebot').setLevel(logging.WARNING) # logging.getLogger('nonebot').setLevel(logging.WARNING)
@dataclass @dataclass
class BotConfig: class BotConfig:
@ -33,6 +33,8 @@ class BotConfig:
MAX_CONTEXT_SIZE: int = 15 # 上下文最大消息数 MAX_CONTEXT_SIZE: int = 15 # 上下文最大消息数
emoji_chance: float = 0.2 # 发送表情包的基础概率 emoji_chance: float = 0.2 # 发送表情包的基础概率
ENABLE_PIC_TRANSLATE: bool = True # 是否启用图片翻译
talk_allowed_groups = set() talk_allowed_groups = set()
talk_frequency_down_groups = set() talk_frequency_down_groups = set()
ban_user_id = set() ban_user_id = set()
@ -65,6 +67,10 @@ class BotConfig:
config.EMOJI_CHECK_INTERVAL = emoji_config.get("check_interval", config.EMOJI_CHECK_INTERVAL) config.EMOJI_CHECK_INTERVAL = emoji_config.get("check_interval", config.EMOJI_CHECK_INTERVAL)
config.EMOJI_REGISTER_INTERVAL = emoji_config.get("register_interval", config.EMOJI_REGISTER_INTERVAL) config.EMOJI_REGISTER_INTERVAL = emoji_config.get("register_interval", config.EMOJI_REGISTER_INTERVAL)
if "cq_code" in toml_dict:
cq_code_config = toml_dict["cq_code"]
config.ENABLE_PIC_TRANSLATE = cq_code_config.get("enable_pic_translate", config.ENABLE_PIC_TRANSLATE)
# 机器人基础配置 # 机器人基础配置
if "bot" in toml_dict: if "bot" in toml_dict:
bot_config = toml_dict["bot"] bot_config = toml_dict["bot"]

View File

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Optional from typing import Dict, Optional, List, Union
import html import html
import requests import requests
import base64 import base64
@ -12,6 +12,13 @@ from nonebot.adapters.onebot.v11 import Bot
from .config import global_config, llm_config from .config import global_config, llm_config
import time import time
import asyncio import asyncio
#解析各种CQ码
#包含CQ码类
@dataclass @dataclass
class CQCode: class CQCode:
""" """
@ -25,13 +32,14 @@ class CQCode:
""" """
type: str type: str
params: Dict[str, str] params: Dict[str, str]
raw_code: str # raw_code: str
group_id: int group_id: int
user_id: int user_id: int
group_name: str = "" group_name: str = ""
user_nickname: str = "" user_nickname: str = ""
translated_plain_text: Optional[str] = None translated_plain_text: Optional[str] = None
reply_message: Dict = None # 存储回复消息 reply_message: Dict = None # 存储回复消息
image_base64: Optional[str] = None
@classmethod @classmethod
def from_cq_code(cls, cq_code: str, reply: Dict = None) -> 'CQCode': def from_cq_code(cls, cq_code: str, reply: Dict = None) -> 'CQCode':
@ -39,6 +47,9 @@ class CQCode:
从CQ码字符串创建CQCode对象 从CQ码字符串创建CQCode对象
例如[CQ:image,file=1.jpg,url=http://example.com/1.jpg] 例如[CQ:image,file=1.jpg,url=http://example.com/1.jpg]
""" """
if not cq_code.startswith('[CQ:'):
return cls('text', {'text': cq_code}, cq_code, group_id=0, user_id=0)
# 移除前后的[] # 移除前后的[]
content = cq_code[1:-1] content = cq_code[1:-1]
# 分离类型和参数部分 # 分离类型和参数部分
@ -69,7 +80,10 @@ class CQCode:
if self.type == 'text': if self.type == 'text':
self.translated_plain_text = self.params.get('text', '') self.translated_plain_text = self.params.get('text', '')
elif self.type == 'image': elif self.type == 'image':
self.translated_plain_text = self.translate_image() if self.params.get('sub_type') == '0':
self.translated_plain_text = self.translate_image()
else:
self.translated_plain_text = self.translate_emoji()
elif self.type == 'at': elif self.type == 'at':
from .message import Message from .message import Message
message_obj = Message( message_obj = Message(
@ -87,16 +101,8 @@ class CQCode:
else: else:
self.translated_plain_text = f"[{self.type}]" self.translated_plain_text = f"[{self.type}]"
def translate_image(self) -> str: def get_img(self):
"""处理图片类型的CQ码区分普通图片和表情包""" '''
if 'url' not in self.params:
return '[图片]'
# 获取子类型,默认为普通图片(0)
sub_type = int(self.params.get('sub_type', '0'))
is_emoji = (sub_type == 1)
# 添加更多请求头
headers = { headers = {
'User-Agent': 'QQ/8.9.68.11565 CFNetwork/1220.1 Darwin/20.3.0', 'User-Agent': 'QQ/8.9.68.11565 CFNetwork/1220.1 Darwin/20.3.0',
'Accept': 'image/*;q=0.8', 'Accept': 'image/*;q=0.8',
@ -105,64 +111,71 @@ class CQCode:
'Cache-Control': 'no-cache', 'Cache-Control': 'no-cache',
'Pragma': 'no-cache' 'Pragma': 'no-cache'
} }
'''
# 处理URL编码问题 headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36',
'Accept': 'text/html, application/xhtml xml, */*',
'Accept-Encoding': 'gbk, GB2312',
'Accept-Language': 'zh-cn',
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache'
}
url = html.unescape(self.params['url']) url = html.unescape(self.params['url'])
if not url.startswith(('http://', 'https://')): if not url.startswith(('http://', 'https://')):
return '[图片]' # 直接返回而不是抛出异常 return None # 直接返回而不是抛出异常
try: max_retries = 3
# 下载图片,增加重试机制 for retry in range(max_retries):
max_retries = 3 try:
for retry in range(max_retries): response = requests.get(url, headers=headers, timeout=10, verify=False)
try: if response.status_code == 200:
response = requests.get(url, headers=headers, timeout=10, verify=False) break
if response.status_code == 200: elif response.status_code == 400 and 'multimedia.nt.qq.com.cn' in url:
break # 对于腾讯多媒体服务器的链接,直接返回图片描述
elif response.status_code == 400 and 'multimedia.nt.qq.com.cn' in url: return None
# 对于腾讯多媒体服务器的链接,直接返回图片描述 time.sleep(1) # 重试前等待1秒
if sub_type == 1: except requests.RequestException:
return '[QQ表情]' if retry == max_retries - 1:
return '[图片]' raise
time.sleep(1) # 重试前等待1秒 time.sleep(1)
except requests.RequestException: if response.status_code != 200:
if retry == max_retries - 1: print(f"\033[1;31m[警告]\033[0m 图片下载失败: HTTP {response.status_code}, URL: {url}")
raise return None # 直接返回而不是抛出异常
time.sleep(1) #检查是否为图片
content_type = response.headers.get('content-type', '')
if response.status_code != 200: if not content_type.startswith('image/'):
print(f"\033[1;31m[警告]\033[0m 图片下载失败: HTTP {response.status_code}, URL: {url}") print(f"\033[1;31m[警告]\033[0m 非图片类型响应: {content_type}")
return '[图片]' # 直接返回而不是抛出异常 return None # 直接返回而不是抛出异常
content = response.content
# 检查响应内容类型 image_base64 = base64.b64encode(content).decode('utf-8')
content_type = response.headers.get('content-type', '') if image_base64:
if not content_type.startswith('image/'): self.image_base64 = image_base64
print(f"\033[1;31m[警告]\033[0m 非图片类型响应: {content_type}") return image_base64
return '[图片]' # 直接返回而不是抛出异常 else:
return None
content = response.content
image_base64 = base64.b64encode(content).decode('utf-8') def translate_emoji(self) -> str:
"""处理表情包类型的CQ码"""
# 根据子类型选择不同的处理方式 if 'url' not in self.params:
if sub_type == 1: # 表情包 return '[表情包]'
try: base64 = self.get_img()
return self.get_emoji_description(image_base64) if base64:
except Exception as e: return self.get_image_description(base64)
print(f"\033[1;31m[警告]\033[0m 表情描述生成失败: {str(e)}") else:
return '[QQ表情]' return '[表情]'
elif sub_type == 0: # 普通图片
try:
return self.get_image_description(image_base64) def translate_image(self) -> str:
except Exception as e: """处理图片类型的CQ码区分普通图片和表情包"""
print(f"\033[1;31m[警告]\033[0m 图片描述生成失败: {str(e)}") #没有url直接返回默认文本
return '[图片]' if 'url' not in self.params:
else: # 其他类型都按普通图片处理 return '[图片]'
return '[图片]' base64 = self.get_img()
if base64:
except Exception as e: return self.get_image_description(base64)
print(f"\033[1;31m[警告]\033[0m 图片处理失败: {str(e)}") else:
return '[图片]' # 出现任何错误都返回默认文本而不是抛出异常 return '[图片]'
def get_emoji_description(self, image_base64: str) -> str: def get_emoji_description(self, image_base64: str) -> str:
"""调用AI接口获取表情包描述""" """调用AI接口获取表情包描述"""
@ -254,53 +267,6 @@ class CQCode:
raise ValueError(f"AI接口调用失败: {response.text}") raise ValueError(f"AI接口调用失败: {response.text}")
def get_image_description_is_setu(self, image_base64: str) -> str:
"""调用AI接口获取普通图片描述"""
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {llm_config.SILICONFLOW_API_KEY}"
}
payload = {
"model": "deepseek-ai/deepseek-vl2",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "请回答我这张图片是否涉及涩情、情色、裸露或性暗示,请严格判断,有任何涩情迹象就回答是,请用是或否回答"
},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_base64}"
}
}
]
}
],
"max_tokens": 300,
"temperature": 0.6
}
response = requests.post(
f"{llm_config.SILICONFLOW_BASE_URL}chat/completions",
headers=headers,
json=payload,
timeout=30
)
if response.status_code == 200:
result_json = response.json()
if "choices" in result_json and len(result_json["choices"]) > 0:
description = result_json["choices"][0]["message"]["content"]
# 如果描述中包含"否",返回否,其他情况返回是
return "" if "" in description else ""
raise ValueError(f"AI接口调用失败: {response.text}")
def translate_forward(self) -> str: def translate_forward(self) -> str:
"""处理转发消息""" """处理转发消息"""
try: try:
@ -391,7 +357,7 @@ class CQCode:
group_id=self.group_id group_id=self.group_id
) )
if message_obj.user_id == global_config.BOT_QQ: if message_obj.user_id == global_config.BOT_QQ:
return f"[回复 麦麦 的消息: {message_obj.processed_plain_text}]" return f"[回复 {global_config.BOT_NICKNAME} 的消息: {message_obj.processed_plain_text}]"
else: else:
return f"[回复 {self.reply_message.sender.nickname} 的消息: {message_obj.processed_plain_text}]" return f"[回复 {self.reply_message.sender.nickname} 的消息: {message_obj.processed_plain_text}]"
@ -424,7 +390,41 @@ class CQCode:
.replace(',', '&#44;') .replace(',', '&#44;')
# 生成CQ码设置sub_type=1表示这是表情包 # 生成CQ码设置sub_type=1表示这是表情包
return f"[CQ:image,file=file:///{escaped_path},sub_type=1]" return f"[CQ:image,file=file:///{escaped_path},sub_type=1]"
class CQCode_tool:
@staticmethod
def cq_from_dict_to_class(cq_code: Dict, reply: Optional[Dict] = None) -> CQCode:
"""
将CQ码字典转换为CQCode对象
Args:
cq_code: CQ码字典
reply: 回复消息的字典可选
Returns:
CQCode对象
"""
# 处理字典形式的CQ码
# 从cq_code字典中获取type字段的值,如果不存在则默认为'text'
cq_type = cq_code.get('type', 'text')
params = {}
if cq_type == 'text':
params['text'] = cq_code.get('data', {}).get('text', '')
else:
params = cq_code.get('data', {})
instance = CQCode(
type=cq_type,
params=params,
group_id=0,
user_id=0,
reply_message=reply
)
# 进行翻译处理
instance.translate()
return instance
@staticmethod @staticmethod
def create_reply_cq(message_id: int) -> str: def create_reply_cq(message_id: int) -> str:
""" """
@ -434,4 +434,7 @@ class CQCode:
Returns: Returns:
回复CQ码字符串 回复CQ码字符串
""" """
return f"[CQ:reply,id={message_id}]" return f"[CQ:reply,id={message_id}]"
cq_code_tool = CQCode_tool()

View File

@ -58,7 +58,7 @@ class LLMResponseGenerator:
else: else:
self.current_model_type = 'r1_distill' # 默认使用 R1-Distill self.current_model_type = 'r1_distill' # 默认使用 R1-Distill
print(f"+++++++++++++++++麦麦{self.current_model_type}思考中+++++++++++++++++") print(f"+++++++++++++++++{global_config.BOT_NICKNAME}{self.current_model_type}思考中+++++++++++++++++")
if self.current_model_type == 'r1': if self.current_model_type == 'r1':
model_response = await self._generate_r1_response(message) model_response = await self._generate_r1_response(message)
elif self.current_model_type == 'v3': elif self.current_model_type == 'v3':
@ -67,7 +67,7 @@ class LLMResponseGenerator:
model_response = await self._generate_r1_distill_response(message) model_response = await self._generate_r1_distill_response(message)
# 打印情感标签 # 打印情感标签
print(f'麦麦的回复是:{model_response}') print(f'{global_config.BOT_NICKNAME}的回复是:{model_response}')
model_response, emotion = await self._process_response(model_response) model_response, emotion = await self._process_response(model_response)
if model_response: if model_response:

View File

@ -8,8 +8,9 @@ from ...common.database import Database
from PIL import Image from PIL import Image
from .config import BotConfig, global_config from .config import BotConfig, global_config
import urllib3 import urllib3
from .cq_code import CQCode
from .utils_user import get_user_nickname from .utils_user import get_user_nickname
from .utils_cq import parse_cq_code
from .cq_code import cq_code_tool,CQCode
Message = ForwardRef('Message') # 添加这行 Message = ForwardRef('Message') # 添加这行
@ -63,12 +64,10 @@ class Message:
self.group_name = self.get_groupname(self.group_id) self.group_name = self.get_groupname(self.group_id)
if not self.processed_plain_text: if not self.processed_plain_text:
# 解析消息片段
if self.raw_message: if self.raw_message:
# print(f"\033[1;34m[调试信息]\033[0m 原始消息: {self.raw_message}")
self.message_segments = self.parse_message_segments(str(self.raw_message)) self.message_segments = self.parse_message_segments(str(self.raw_message))
self.processed_plain_text = ' '.join( self.processed_plain_text = ' '.join(
seg['translated_text'] seg.translated_plain_text
for seg in self.message_segments for seg in self.message_segments
) )
@ -87,96 +86,80 @@ class Message:
else: else:
return f"{group_id}" return f"{group_id}"
def parse_message_segments(self, message: str) -> List[Dict]: def parse_message_segments(self, message: str) -> List[CQCode]:
""" """
将消息解析为片段列表包括纯文本和CQ码 将消息解析为片段列表包括纯文本和CQ码
返回的列表中每个元素都是字典包含 返回的列表中每个元素都是字典包含
- type: 'text' CQ码类型 - cq_code_list:分割出的聊天对象包括文本和CQ码
- data: 对于text类型是文本内容对于CQ码是参数字典 - trans_list:翻译后的对象列表
- translated_text: 经过处理后的文本
""" """
segments = [] cq_code_dict_list = []
start = 0 trans_list = []
start = 0
print(f"\033[1;34m[调试信息]\033[0m 原始消息: {message}")
while True: while True:
# 查找下一个CQ码的开始位置 # 查找下一个CQ码的开始位置
cq_start = message.find('[CQ:', start) cq_start = message.find('[CQ:', start)
#如果没有cq码直接返回文本内容
if cq_start == -1: if cq_start == -1:
# 如果没有找到更多CQ码添加剩余文本 # 如果没有找到更多CQ码添加剩余文本
if start < len(message): if start < len(message):
text = message[start:].strip() text = message[start:].strip()
if text: # 只添加非空文本 if text: # 只添加非空文本
segments.append({ cq_code_dict_list.append(parse_cq_code(text))
'type': 'text',
'data': {'text': text},
'translated_text': text
})
break break
# 添加CQ码前的文本 # 添加CQ码前的文本
if cq_start > start: if cq_start > start:
text = message[start:cq_start].strip() text = message[start:cq_start].strip()
if text: # 只添加非空文本 if text: # 只添加非空文本
segments.append({ cq_code_dict_list.append(parse_cq_code(text))
'type': 'text',
'data': {'text': text},
'translated_text': text
})
# 查找CQ码的结束位置 # 查找CQ码的结束位置
cq_end = message.find(']', cq_start) cq_end = message.find(']', cq_start)
if cq_end == -1: if cq_end == -1:
# CQ码未闭合作为普通文本处理 # CQ码未闭合作为普通文本处理
text = message[cq_start:].strip() text = message[cq_start:].strip()
if text: if text:
segments.append({ cq_code_dict_list.append(parse_cq_code(text))
'type': 'text',
'data': {'text': text},
'translated_text': text
})
break break
# 提取完整的CQ码并创建CQCode对象
cq_code = message[cq_start:cq_end + 1] cq_code = message[cq_start:cq_end + 1]
try:
cq_obj = CQCode.from_cq_code(cq_code,reply = self.reply_message)
# 设置必要的属性
segments.append({
'type': cq_obj.type,
'data': cq_obj.params,
'translated_text': cq_obj.translated_plain_text
})
except Exception as e:
import traceback
print(f"\033[1;31m[错误]\033[0m 处理CQ码失败: {str(e)}")
print(f"CQ码内容: {cq_code}")
print(f"当前消息属性:")
print(f"- group_id: {self.group_id}")
print(f"- user_id: {self.user_id}")
print(f"- user_nickname: {self.user_nickname}")
print(f"- group_name: {self.group_name}")
print("详细错误信息:")
print(traceback.format_exc())
# 处理失败时将CQ码作为普通文本处理
segments.append({
'type': 'text',
'data': {'text': cq_code},
'translated_text': cq_code
})
#将cq_code解析成字典
cq_code_dict_list.append(parse_cq_code(cq_code))
# 更新start位置到当前CQ码之后
start = cq_end + 1 start = cq_end + 1
print(f"\033[1;34m[调试信息]\033[0m 提取的消息对象:列表: {cq_code_dict_list}")
#判定是否是表情包消息,以及是否含有表情包
if len(segments) == 1 and segments[0]['type'] == 'image': if len(cq_code_dict_list) == 1 and cq_code_dict_list[0]['type'] == 'image':
self.is_emoji = True self.is_emoji = True
self.has_emoji_emoji = True self.has_emoji_emoji = True
else: else:
for segment in segments: for segment in cq_code_dict_list:
if segment['type'] == 'image' and segment['data'].get('sub_type') == '1': if segment['type'] == 'image' and segment['data'].get('sub_type') == '1':
self.has_emoji_emoji = True self.has_emoji_emoji = True
break break
#翻译作为字典的CQ码
for _code_item in cq_code_dict_list:
#一个一个CQ码处理
message_obj = cq_code_tool.cq_from_dict_to_class(_code_item,reply = self.reply_message)
trans_list.append(message_obj)
# except Exception as e:
# import traceback
# print(f"\033[1;31m[错误]\033[0m 处理CQ码失败: {str(e)}")
# print(f"CQ码内容: {cq_code}")
# print(f"当前消息属性:")
# print(f"- group_id: {self.group_id}")
# print(f"- user_id: {self.user_id}")
# print(f"- user_nickname: {self.user_nickname}")
# print(f"- group_name: {self.group_name}")
# print("详细错误信息:")
# print(traceback.format_exc())
return segments return trans_list
class Message_Thinking: class Message_Thinking:
"""消息思考类""" """消息思考类"""

View File

@ -9,8 +9,11 @@ from collections import deque
import time import time
from .storage import MessageStorage from .storage import MessageStorage
from .config import global_config from .config import global_config
from .cq_code import cq_code_tool
if os.name == "nt": if os.name == "nt":
from .message_visualizer import message_visualizer from .message_visualizer import message_visualizer
class SendTemp: class SendTemp:
@ -194,7 +197,7 @@ class MessageSendControl:
print(f"- 群组: {group_id} - 内容: {message.processed_plain_text}") print(f"- 群组: {group_id} - 内容: {message.processed_plain_text}")
cost_time = round(time.time(), 2) - message.time cost_time = round(time.time(), 2) - message.time
if cost_time > 40: if cost_time > 40:
message.processed_plain_text = CQCode.create_reply_cq(message.message_based_id) + message.processed_plain_text message.processed_plain_text = cq_code_tool.create_reply_cq(message.message_based_id) + message.processed_plain_text
cur_time = time.time() cur_time = time.time()
await self._current_bot.send_group_msg( await self._current_bot.send_group_msg(
group_id=group_id, group_id=group_id,
@ -204,7 +207,7 @@ class MessageSendControl:
cost_time = round(time.time(), 2) - cur_time cost_time = round(time.time(), 2) - cur_time
print(f"\033[1;34m[调试]\033[0m 消息发送时间: {cost_time}") print(f"\033[1;34m[调试]\033[0m 消息发送时间: {cost_time}")
current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.time)) current_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.time))
print(f"\033[1;32m群 {group_id} 消息, 用户 麦麦, 时间: {current_time}:\033[0m {str(message.processed_plain_text)}") print(f"\033[1;32m群 {group_id} 消息, 用户 {global_config.BOT_NICKNAME}, 时间: {current_time}:\033[0m {str(message.processed_plain_text)}")
await self.storage.store_message(message, None) await self.storage.store_message(message, None)
queue.update_send_time() queue.update_send_time()
if queue.has_messages(): if queue.has_messages():

View File

@ -5,6 +5,7 @@ from ..schedule.schedule_generator import bot_schedule
import os import os
from .utils import get_embedding, combine_messages, get_recent_group_messages from .utils import get_embedding, combine_messages, get_recent_group_messages
from ...common.database import Database from ...common.database import Database
from .config import global_config
# 获取当前文件的绝对路径 # 获取当前文件的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
@ -67,7 +68,7 @@ class PromptBuilder:
chat_talking_prompt = '' chat_talking_prompt = ''
if group_id: if group_id:
# 从数据库获取最近消息 # 从数据库获取最近消息
message_objects = get_recent_group_messages(self.db, group_id, limit=16) message_objects = get_recent_group_messages(self.db, group_id, limit=global_config.MAX_CONTEXT_SIZE)
if message_objects: if message_objects:
chat_talking_prompt = combine_messages(message_objects) chat_talking_prompt = combine_messages(message_objects)
# print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的最近{len(message_objects)}条消息记录") # print(f"\033[1;34m[调试]\033[0m 已从数据库获取群 {group_id} 的最近{len(message_objects)}条消息记录")
@ -88,12 +89,12 @@ class PromptBuilder:
prompt_personality = '' prompt_personality = ''
personality_choice = random.random() personality_choice = random.random()
if personality_choice < 4/6: # 第一种人格 if personality_choice < 4/6: # 第一种人格
prompt_personality = f'''你的网名叫麦麦是一个学习地质的女大学生喜欢摄影你会刷贴吧你正在浏览qq群,{promt_info_prompt}, prompt_personality = f'''你的网名叫{global_config.BOT_NICKNAME}是一个学习地质的女大学生喜欢摄影你会刷贴吧你正在浏览qq群,{promt_info_prompt},
{activate_prompt} {activate_prompt}
现在请你给出日常且口语化的回复平淡一些尽量简短一些{is_bot_prompt} 现在请你给出日常且口语化的回复平淡一些尽量简短一些{is_bot_prompt}
请注意把握群里的聊天内容不要回复的太有条理可以有个性''' 请注意把握群里的聊天内容不要回复的太有条理可以有个性'''
elif personality_choice < 1: # 第二种人格 elif personality_choice < 1: # 第二种人格
prompt_personality = f'''你的网名叫麦麦,是一个女大学生,你有黑色头发,你会刷小红书, 你正在浏览qq群,{promt_info_prompt}, prompt_personality = f'''你的网名叫{global_config.BOT_NICKNAME},是一个女大学生,你有黑色头发,你会刷小红书, 你正在浏览qq群,{promt_info_prompt},
{activate_prompt} {activate_prompt}
现在请你给出日常且口语化的回复请表现你自己的见解不要一昧迎合尽量简短一些{is_bot_prompt} 现在请你给出日常且口语化的回复请表现你自己的见解不要一昧迎合尽量简短一些{is_bot_prompt}
请你表达自己的见解和观点可以有个性''' 请你表达自己的见解和观点可以有个性'''

View File

@ -4,7 +4,7 @@ from typing import List
from .message import Message from .message import Message
import requests import requests
import numpy as np import numpy as np
from .config import llm_config from .config import llm_config, global_config
import re import re
@ -29,7 +29,7 @@ def combine_messages(messages: List[Message]) -> str:
def is_mentioned_bot_in_message(message: Message) -> bool: def is_mentioned_bot_in_message(message: Message) -> bool:
"""检查消息是否提到了机器人""" """检查消息是否提到了机器人"""
keywords = ['麦麦', '麦哲伦'] keywords = [global_config.BOT_NICKNAME]
for keyword in keywords: for keyword in keywords:
if keyword in message.processed_plain_text: if keyword in message.processed_plain_text:
return True return True
@ -37,7 +37,7 @@ def is_mentioned_bot_in_message(message: Message) -> bool:
def is_mentioned_bot_in_txt(message: str) -> bool: def is_mentioned_bot_in_txt(message: str) -> bool:
"""检查消息是否提到了机器人""" """检查消息是否提到了机器人"""
keywords = ['麦麦', '麦哲伦'] keywords = [global_config.BOT_NICKNAME]
for keyword in keywords: for keyword in keywords:
if keyword in message: if keyword in message:
return True return True
@ -315,7 +315,7 @@ def process_llm_response(text: str) -> List[str]:
# 检查分割后的消息数量是否过多超过3条 # 检查分割后的消息数量是否过多超过3条
if len(sentences) > 3: if len(sentences) > 3:
print(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复") print(f"分割后消息数量过多 ({len(sentences)} 条),返回默认回复")
return ['麦麦不知道哦'] return [f'{global_config.BOT_NICKNAME}不知道哦']
return sentences return sentences

View File

@ -0,0 +1,72 @@
def parse_cq_code(cq_code: str) -> dict:
"""
将CQ码解析为字典对象
Args:
cq_code (str): CQ码字符串 [CQ:image,file=xxx.jpg,url=http://xxx]
Returns:
dict: 包含type和参数的字典 {'type': 'image', 'data': {'file': 'xxx.jpg', 'url': 'http://xxx'}}
"""
# 检查是否是有效的CQ码
if not (cq_code.startswith('[CQ:') and cq_code.endswith(']')):
return {'type': 'text', 'data': {'text': cq_code}}
# 移除前后的 [CQ: 和 ]
content = cq_code[4:-1]
# 分离类型和参数
parts = content.split(',')
if len(parts) < 1:
return {'type': 'text', 'data': {'text': cq_code}}
cq_type = parts[0]
params = {}
# 处理参数部分
if len(parts) > 1:
# 遍历所有参数
for part in parts[1:]:
if '=' in part:
key, value = part.split('=', 1)
params[key.strip()] = value.strip()
return {
'type': cq_type,
'data': params
}
if __name__ == "__main__":
# 测试用例列表
test_cases = [
# 测试图片CQ码
'[CQ:image,summary=,file={6E392FD2-AAA1-5192-F52A-F724A8EC7998}.gif,sub_type=1,url=https://gchat.qpic.cn/gchatpic_new/0/0-0-6E392FD2AAA15192F52AF724A8EC7998/0,file_size=861609]',
# 测试at CQ码
'[CQ:at,qq=123456]',
# 测试普通文本
'Hello World',
# 测试face表情CQ码
'[CQ:face,id=123]',
# 测试含有多个逗号的URL
'[CQ:image,url=https://example.com/image,with,commas.jpg]',
# 测试空参数
'[CQ:image,summary=]',
# 测试非法CQ码
'[CQ:]',
'[CQ:invalid'
]
# 测试每个用例
for i, test_case in enumerate(test_cases, 1):
print(f"\n测试用例 {i}:")
print(f"输入: {test_case}")
result = parse_cq_code(test_case)
print(f"输出: {result}")
print("-" * 50)

View File

@ -49,7 +49,7 @@ class ScheduleGenerator:
elif read_only == False: elif read_only == False:
print(f"{date_str}的日程不存在,准备生成新的日程。") print(f"{date_str}的日程不存在,准备生成新的日程。")
prompt = f"""我是麦麦一个地质学大二女大学生喜欢刷qq贴吧知乎和小红书请为我生成{date_str}{weekday})的日程安排,包括: prompt = f"""我是{global_config.BOT_NICKNAME}一个地质学大二女大学生喜欢刷qq贴吧知乎和小红书请为我生成{date_str}{weekday})的日程安排,包括:
1. 早上的学习和工作安排 1. 早上的学习和工作安排
2. 下午的活动和任务 2. 下午的活动和任务
3. 晚上的计划和休息时间 3. 晚上的计划和休息时间