Merge branch 'debug' of https://github.com/SengokuCola/MaiMBot into debug
This commit is contained in:
8
.github/workflows/ruff.yml
vendored
Normal file
8
.github/workflows/ruff.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
name: Ruff
|
||||||
|
on: [ push, pull_request ]
|
||||||
|
jobs:
|
||||||
|
ruff:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: astral-sh/ruff-action@v3
|
||||||
88
bot.py
88
bot.py
@@ -17,19 +17,6 @@ env_mask = {key: os.getenv(key) for key in os.environ}
|
|||||||
|
|
||||||
uvicorn_server = None
|
uvicorn_server = None
|
||||||
|
|
||||||
# 配置日志
|
|
||||||
log_path = os.path.join(os.getcwd(), "logs")
|
|
||||||
if not os.path.exists(log_path):
|
|
||||||
os.makedirs(log_path)
|
|
||||||
|
|
||||||
# 添加文件日志,启用rotation和retention
|
|
||||||
logger.add(
|
|
||||||
os.path.join(log_path, "maimbot_{time:YYYY-MM-DD}.log"),
|
|
||||||
rotation="00:00", # 每天0点创建新文件
|
|
||||||
retention="30 days", # 保留30天的日志
|
|
||||||
level="INFO",
|
|
||||||
encoding="utf-8"
|
|
||||||
)
|
|
||||||
|
|
||||||
def easter_egg():
|
def easter_egg():
|
||||||
# 彩蛋
|
# 彩蛋
|
||||||
@@ -76,7 +63,7 @@ def init_env():
|
|||||||
|
|
||||||
# 首先加载基础环境变量.env
|
# 首先加载基础环境变量.env
|
||||||
if os.path.exists(".env"):
|
if os.path.exists(".env"):
|
||||||
load_dotenv(".env",override=True)
|
load_dotenv(".env", override=True)
|
||||||
logger.success("成功加载基础环境变量配置")
|
logger.success("成功加载基础环境变量配置")
|
||||||
|
|
||||||
|
|
||||||
@@ -90,10 +77,7 @@ def load_env():
|
|||||||
logger.success("加载开发环境变量配置")
|
logger.success("加载开发环境变量配置")
|
||||||
load_dotenv(".env.dev", override=True) # override=True 允许覆盖已存在的环境变量
|
load_dotenv(".env.dev", override=True) # override=True 允许覆盖已存在的环境变量
|
||||||
|
|
||||||
fn_map = {
|
fn_map = {"prod": prod, "dev": dev}
|
||||||
"prod": prod,
|
|
||||||
"dev": dev
|
|
||||||
}
|
|
||||||
|
|
||||||
env = os.getenv("ENVIRONMENT")
|
env = os.getenv("ENVIRONMENT")
|
||||||
logger.info(f"[load_env] 当前的 ENVIRONMENT 变量值:{env}")
|
logger.info(f"[load_env] 当前的 ENVIRONMENT 变量值:{env}")
|
||||||
@@ -109,28 +93,45 @@ def load_env():
|
|||||||
logger.error(f"ENVIRONMENT 配置错误,请检查 .env 文件中的 ENVIRONMENT 变量及对应 .env.{env} 是否存在")
|
logger.error(f"ENVIRONMENT 配置错误,请检查 .env 文件中的 ENVIRONMENT 变量及对应 .env.{env} 是否存在")
|
||||||
RuntimeError(f"ENVIRONMENT 配置错误,请检查 .env 文件中的 ENVIRONMENT 变量及对应 .env.{env} 是否存在")
|
RuntimeError(f"ENVIRONMENT 配置错误,请检查 .env 文件中的 ENVIRONMENT 变量及对应 .env.{env} 是否存在")
|
||||||
|
|
||||||
def load_logger():
|
|
||||||
logger.remove() # 移除默认配置
|
|
||||||
if os.getenv("ENVIRONMENT") == "dev":
|
|
||||||
logger.add(
|
|
||||||
sys.stderr,
|
|
||||||
format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> <fg #777777>|</> <level>{level: <7}</level> <fg "
|
|
||||||
"#777777>|</> <cyan>{name:.<8}</cyan>:<cyan>{function:.<8}</cyan>:<cyan>{line: >4}</cyan> <fg "
|
|
||||||
"#777777>-</> <level>{message}</level>",
|
|
||||||
colorize=True,
|
|
||||||
level=os.getenv("LOG_LEVEL", "DEBUG"), # 根据环境设置日志级别,默认为DEBUG
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.add(
|
|
||||||
sys.stderr,
|
|
||||||
format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> <fg #777777>|</> <level>{level: <7}</level> <fg "
|
|
||||||
"#777777>|</> <cyan>{name:.<8}</cyan>:<cyan>{function:.<8}</cyan>:<cyan>{line: >4}</cyan> <fg "
|
|
||||||
"#777777>-</> <level>{message}</level>",
|
|
||||||
colorize=True,
|
|
||||||
level=os.getenv("LOG_LEVEL", "INFO"), # 根据环境设置日志级别,默认为INFO
|
|
||||||
filter=lambda record: "nonebot" not in record["name"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def load_logger():
|
||||||
|
logger.remove()
|
||||||
|
|
||||||
|
# 配置日志基础路径
|
||||||
|
log_path = os.path.join(os.getcwd(), "logs")
|
||||||
|
if not os.path.exists(log_path):
|
||||||
|
os.makedirs(log_path)
|
||||||
|
|
||||||
|
current_env = os.getenv("ENVIRONMENT", "dev")
|
||||||
|
|
||||||
|
# 公共配置参数
|
||||||
|
log_level = os.getenv("LOG_LEVEL", "INFO" if current_env == "prod" else "DEBUG")
|
||||||
|
log_filter = lambda record: (
|
||||||
|
("nonebot" not in record["name"] or record["level"].no >= logger.level("ERROR").no)
|
||||||
|
if current_env == "prod"
|
||||||
|
else True
|
||||||
|
)
|
||||||
|
log_format = (
|
||||||
|
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> "
|
||||||
|
"<fg #777777>|</> <level>{level: <7}</level> "
|
||||||
|
"<fg #777777>|</> <cyan>{name:.<8}</cyan>:<cyan>{function:.<8}</cyan>:<cyan>{line: >4}</cyan> "
|
||||||
|
"<fg #777777>-</> <level>{message}</level>"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 日志文件储存至/logs
|
||||||
|
logger.add(
|
||||||
|
os.path.join(log_path, "maimbot_{time:YYYY-MM-DD}.log"),
|
||||||
|
rotation="00:00",
|
||||||
|
retention="30 days",
|
||||||
|
format=log_format,
|
||||||
|
colorize=False,
|
||||||
|
level=log_level,
|
||||||
|
filter=log_filter,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 终端输出
|
||||||
|
logger.add(sys.stderr, format=log_format, colorize=True, level=log_level, filter=log_filter)
|
||||||
|
|
||||||
|
|
||||||
def scan_provider(env_config: dict):
|
def scan_provider(env_config: dict):
|
||||||
@@ -160,10 +161,7 @@ def scan_provider(env_config: dict):
|
|||||||
# 检查每个 provider 是否同时存在 url 和 key
|
# 检查每个 provider 是否同时存在 url 和 key
|
||||||
for provider_name, config in provider.items():
|
for provider_name, config in provider.items():
|
||||||
if config["url"] is None or config["key"] is None:
|
if config["url"] is None or config["key"] is None:
|
||||||
logger.error(
|
logger.error(f"provider 内容:{config}\nenv_config 内容:{env_config}")
|
||||||
f"provider 内容:{config}\n"
|
|
||||||
f"env_config 内容:{env_config}"
|
|
||||||
)
|
|
||||||
raise ValueError(f"请检查 '{provider_name}' 提供商配置是否丢失 BASE_URL 或 KEY 环境变量")
|
raise ValueError(f"请检查 '{provider_name}' 提供商配置是否丢失 BASE_URL 或 KEY 环境变量")
|
||||||
|
|
||||||
|
|
||||||
@@ -192,7 +190,7 @@ async def uvicorn_main():
|
|||||||
reload=os.getenv("ENVIRONMENT") == "dev",
|
reload=os.getenv("ENVIRONMENT") == "dev",
|
||||||
timeout_graceful_shutdown=5,
|
timeout_graceful_shutdown=5,
|
||||||
log_config=None,
|
log_config=None,
|
||||||
access_log=False
|
access_log=False,
|
||||||
)
|
)
|
||||||
server = uvicorn.Server(config)
|
server = uvicorn.Server(config)
|
||||||
uvicorn_server = server
|
uvicorn_server = server
|
||||||
@@ -202,7 +200,7 @@ async def uvicorn_main():
|
|||||||
def raw_main():
|
def raw_main():
|
||||||
# 利用 TZ 环境变量设定程序工作的时区
|
# 利用 TZ 环境变量设定程序工作的时区
|
||||||
# 仅保证行为一致,不依赖 localtime(),实际对生产环境几乎没有作用
|
# 仅保证行为一致,不依赖 localtime(),实际对生产环境几乎没有作用
|
||||||
if platform.system().lower() != 'windows':
|
if platform.system().lower() != "windows":
|
||||||
time.tzset()
|
time.tzset()
|
||||||
|
|
||||||
easter_egg()
|
easter_egg()
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ def __create_database_instance():
|
|||||||
|
|
||||||
if username and password:
|
if username and password:
|
||||||
# 如果有用户名和密码,使用认证连接
|
# 如果有用户名和密码,使用认证连接
|
||||||
return MongoClient(
|
return MongoClient(host, port, username=username, password=password, authSource=auth_source)
|
||||||
host, port, username=username, password=password, authSource=auth_source
|
|
||||||
)
|
|
||||||
|
|
||||||
# 否则使用无认证连接
|
# 否则使用无认证连接
|
||||||
return MongoClient(host, port)
|
return MongoClient(host, port)
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import time
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from nonebot import get_driver, on_message, require
|
from nonebot import get_driver, on_message, on_notice, require
|
||||||
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageSegment,MessageEvent
|
from nonebot.rule import to_me
|
||||||
|
from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageSegment, MessageEvent, NoticeEvent
|
||||||
from nonebot.typing import T_State
|
from nonebot.typing import T_State
|
||||||
|
|
||||||
from ..moods.moods import MoodManager # 导入情绪管理器
|
from ..moods.moods import MoodManager # 导入情绪管理器
|
||||||
@@ -39,6 +40,8 @@ logger.debug(f"正在唤醒{global_config.BOT_NICKNAME}......")
|
|||||||
chat_bot = ChatBot()
|
chat_bot = ChatBot()
|
||||||
# 注册消息处理器
|
# 注册消息处理器
|
||||||
msg_in = on_message(priority=5)
|
msg_in = on_message(priority=5)
|
||||||
|
# 注册和bot相关的通知处理器
|
||||||
|
notice_matcher = on_notice(priority=1)
|
||||||
# 创建定时任务
|
# 创建定时任务
|
||||||
scheduler = require("nonebot_plugin_apscheduler").scheduler
|
scheduler = require("nonebot_plugin_apscheduler").scheduler
|
||||||
|
|
||||||
@@ -95,19 +98,24 @@ async def _(bot: Bot, event: MessageEvent, state: T_State):
|
|||||||
await chat_bot.handle_message(event, bot)
|
await chat_bot.handle_message(event, bot)
|
||||||
|
|
||||||
|
|
||||||
|
@notice_matcher.handle()
|
||||||
|
async def _(bot: Bot, event: NoticeEvent, state: T_State):
|
||||||
|
logger.debug(f"收到通知:{event}")
|
||||||
|
await chat_bot.handle_notice(event, bot)
|
||||||
|
|
||||||
|
|
||||||
# 添加build_memory定时任务
|
# 添加build_memory定时任务
|
||||||
@scheduler.scheduled_job("interval", seconds=global_config.build_memory_interval, id="build_memory")
|
@scheduler.scheduled_job("interval", seconds=global_config.build_memory_interval, id="build_memory")
|
||||||
async def build_memory_task():
|
async def build_memory_task():
|
||||||
"""每build_memory_interval秒执行一次记忆构建"""
|
"""每build_memory_interval秒执行一次记忆构建"""
|
||||||
logger.debug(
|
logger.debug("[记忆构建]------------------------------------开始构建记忆--------------------------------------")
|
||||||
"[记忆构建]"
|
|
||||||
"------------------------------------开始构建记忆--------------------------------------")
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
await hippocampus.operation_build_memory(chat_size=20)
|
await hippocampus.operation_build_memory(chat_size=20)
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
logger.success(
|
logger.success(
|
||||||
f"[记忆构建]--------------------------记忆构建完成:耗时: {end_time - start_time:.2f} "
|
f"[记忆构建]--------------------------记忆构建完成:耗时: {end_time - start_time:.2f} "
|
||||||
"秒-------------------------------------------")
|
"秒-------------------------------------------"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@scheduler.scheduled_job("interval", seconds=global_config.forget_memory_interval, id="forget_memory")
|
@scheduler.scheduled_job("interval", seconds=global_config.forget_memory_interval, id="forget_memory")
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from nonebot.adapters.onebot.v11 import (
|
|||||||
GroupMessageEvent,
|
GroupMessageEvent,
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
PrivateMessageEvent,
|
PrivateMessageEvent,
|
||||||
|
NoticeEvent,
|
||||||
|
PokeNotifyEvent,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..memory_system.memory import hippocampus
|
from ..memory_system.memory import hippocampus
|
||||||
@@ -25,6 +27,7 @@ from .relationship_manager import relationship_manager
|
|||||||
from .storage import MessageStorage
|
from .storage import MessageStorage
|
||||||
from .utils import calculate_typing_time, is_mentioned_bot_in_message
|
from .utils import calculate_typing_time, is_mentioned_bot_in_message
|
||||||
from .utils_image import image_path_to_base64
|
from .utils_image import image_path_to_base64
|
||||||
|
from .utils_user import get_user_nickname, get_user_cardname, get_groupname
|
||||||
from .willing_manager import willing_manager # 导入意愿管理器
|
from .willing_manager import willing_manager # 导入意愿管理器
|
||||||
from .message_base import UserInfo, GroupInfo, Seg
|
from .message_base import UserInfo, GroupInfo, Seg
|
||||||
|
|
||||||
@@ -46,6 +49,69 @@ class ChatBot:
|
|||||||
if not self._started:
|
if not self._started:
|
||||||
self._started = True
|
self._started = True
|
||||||
|
|
||||||
|
async def handle_notice(self, event: NoticeEvent, bot: Bot) -> None:
|
||||||
|
"""处理收到的通知"""
|
||||||
|
# 戳一戳通知
|
||||||
|
if isinstance(event, PokeNotifyEvent):
|
||||||
|
# 用户屏蔽,不区分私聊/群聊
|
||||||
|
if event.user_id in global_config.ban_user_id:
|
||||||
|
return
|
||||||
|
reply_poke_probability = 1 # 回复戳一戳的概率
|
||||||
|
|
||||||
|
if random() < reply_poke_probability:
|
||||||
|
user_info = UserInfo(
|
||||||
|
user_id=event.user_id,
|
||||||
|
user_nickname=get_user_nickname(event.user_id) or None,
|
||||||
|
user_cardname=get_user_cardname(event.user_id) or None,
|
||||||
|
platform="qq",
|
||||||
|
)
|
||||||
|
group_info = GroupInfo(group_id=event.group_id, group_name=None, platform="qq")
|
||||||
|
message_cq = MessageRecvCQ(
|
||||||
|
message_id=None,
|
||||||
|
user_info=user_info,
|
||||||
|
raw_message=str("[戳了戳]你"),
|
||||||
|
group_info=group_info,
|
||||||
|
reply_message=None,
|
||||||
|
platform="qq",
|
||||||
|
)
|
||||||
|
message_json = message_cq.to_dict()
|
||||||
|
|
||||||
|
# 进入maimbot
|
||||||
|
message = MessageRecv(message_json)
|
||||||
|
groupinfo = message.message_info.group_info
|
||||||
|
userinfo = message.message_info.user_info
|
||||||
|
messageinfo = message.message_info
|
||||||
|
|
||||||
|
chat = await chat_manager.get_or_create_stream(
|
||||||
|
platform=messageinfo.platform, user_info=userinfo, group_info=groupinfo
|
||||||
|
)
|
||||||
|
message.update_chat_stream(chat)
|
||||||
|
await message.process()
|
||||||
|
|
||||||
|
bot_user_info = UserInfo(
|
||||||
|
user_id=global_config.BOT_QQ,
|
||||||
|
user_nickname=global_config.BOT_NICKNAME,
|
||||||
|
platform=messageinfo.platform,
|
||||||
|
)
|
||||||
|
|
||||||
|
response, raw_content = await self.gpt.generate_response(message)
|
||||||
|
|
||||||
|
if response:
|
||||||
|
for msg in response:
|
||||||
|
message_segment = Seg(type="text", data=msg)
|
||||||
|
|
||||||
|
bot_message = MessageSending(
|
||||||
|
message_id=None,
|
||||||
|
chat_stream=chat,
|
||||||
|
bot_user_info=bot_user_info,
|
||||||
|
sender_info=userinfo,
|
||||||
|
message_segment=message_segment,
|
||||||
|
reply=None,
|
||||||
|
is_head=False,
|
||||||
|
is_emoji=False,
|
||||||
|
)
|
||||||
|
message_manager.add_message(bot_message)
|
||||||
|
|
||||||
async def handle_message(self, event: MessageEvent, bot: Bot) -> None:
|
async def handle_message(self, event: MessageEvent, bot: Bot) -> None:
|
||||||
"""处理收到的消息"""
|
"""处理收到的消息"""
|
||||||
|
|
||||||
@@ -54,7 +120,10 @@ class ChatBot:
|
|||||||
# 用户屏蔽,不区分私聊/群聊
|
# 用户屏蔽,不区分私聊/群聊
|
||||||
if event.user_id in global_config.ban_user_id:
|
if event.user_id in global_config.ban_user_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if event.reply and hasattr(event.reply, 'sender') and hasattr(event.reply.sender, 'user_id') and event.reply.sender.user_id in global_config.ban_user_id:
|
||||||
|
logger.debug(f"跳过处理回复来自被ban用户 {event.reply.sender.user_id} 的消息")
|
||||||
|
return
|
||||||
# 处理私聊消息
|
# 处理私聊消息
|
||||||
if isinstance(event, PrivateMessageEvent):
|
if isinstance(event, PrivateMessageEvent):
|
||||||
if not global_config.enable_friend_chat: # 私聊过滤
|
if not global_config.enable_friend_chat: # 私聊过滤
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class LLM_request:
|
|||||||
# 常见Error Code Mapping
|
# 常见Error Code Mapping
|
||||||
error_code_mapping = {
|
error_code_mapping = {
|
||||||
400: "参数不正确",
|
400: "参数不正确",
|
||||||
401: "API key 错误,认证失败",
|
401: "API key 错误,认证失败,请检查/config/bot_config.toml和.env.prod中的配置是否正确哦~",
|
||||||
402: "账号余额不足",
|
402: "账号余额不足",
|
||||||
403: "需要实名,或余额不足",
|
403: "需要实名,或余额不足",
|
||||||
404: "Not Found",
|
404: "Not Found",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ CHAT_ANY_WHERE_BASE_URL=https://api.chatanywhere.tech/v1
|
|||||||
SILICONFLOW_BASE_URL=https://api.siliconflow.cn/v1/
|
SILICONFLOW_BASE_URL=https://api.siliconflow.cn/v1/
|
||||||
DEEP_SEEK_BASE_URL=https://api.deepseek.com/v1
|
DEEP_SEEK_BASE_URL=https://api.deepseek.com/v1
|
||||||
|
|
||||||
#定义你要用的api的base_url
|
#定义你要用的api的key(需要去对应网站申请哦)
|
||||||
DEEP_SEEK_KEY=
|
DEEP_SEEK_KEY=
|
||||||
CHAT_ANY_WHERE_KEY=
|
CHAT_ANY_WHERE_KEY=
|
||||||
SILICONFLOW_KEY=
|
SILICONFLOW_KEY=
|
||||||
|
|||||||
Reference in New Issue
Block a user