zhenxun_bot/zhenxun/plugins/bym_ai/__init__.py

284 lines
9.8 KiB
Python
Raw Normal View History

2025-07-01 16:56:34 +08:00
import asyncio
from pathlib import Path
import random
from httpx import HTTPStatusError
from nonebot import on_message
from nonebot.adapters import Bot, Event
from nonebot.plugin import PluginMetadata
from nonebot_plugin_alconna import UniMsg, Voice
from nonebot_plugin_uninfo import Uninfo
from zhenxun.configs.config import BotConfig
from zhenxun.configs.path_config import IMAGE_PATH
from zhenxun.configs.utils import (
AICallableParam,
AICallableProperties,
AICallableTag,
PluginExtraData,
RegisterConfig,
)
from zhenxun.services.log import logger
from zhenxun.services.plugin_init import PluginInit
from zhenxun.utils.depends import CheckConfig, UserName
from zhenxun.utils.message import MessageUtils
from .bym_gift import ICON_PATH
from .bym_gift.data_source import send_gift
from .bym_gift.gift_reg import driver
from .config import Arparma, FunctionParam
from .data_source import ChatManager, base_config, split_text
from .exception import GiftRepeatSendException, NotResultException
from .goods_register import driver # noqa: F401
from .models.bym_chat import BymChat
__plugin_meta__ = PluginMetadata(
name="BYM_AI",
description=f"{BotConfig.self_nickname}想成为人类...",
usage=f"""
你问小真寻的愿望
{BotConfig.self_nickname}说她想成为人类
""".strip(),
extra=PluginExtraData(
author="Chtholly & HibiKier",
version="0.3",
ignore_prompt=True,
configs=[
RegisterConfig(
key="BYM_AI_CHAT_URL",
value=None,
help="ai聊天接口地址可以填入url和平台名称当你使用平台名称时默认使用平台官方api, 目前有[gemini, DeepSeek, 硅基流动, 阿里云百炼, 百度智能云, 字节火山引擎], 填入对应名称即可, 如 gemini",
),
RegisterConfig(
key="BYM_AI_CHAT_TOKEN",
value=None,
help="ai聊天接口密钥使用列表",
type=list[str],
),
RegisterConfig(
key="BYM_AI_CHAT_MODEL",
value=None,
help="ai聊天接口模型",
),
RegisterConfig(
key="BYM_AI_TOOL_MODEL",
value=None,
help="ai工具接口模型",
),
RegisterConfig(
key="BYM_AI_CHAT",
value=True,
help="是否开启伪人回复",
default_value=True,
type=bool,
),
RegisterConfig(
key="BYM_AI_CHAT_RATE",
value=0.05,
help="伪人回复概率 0-1",
default_value=0.05,
type=float,
),
RegisterConfig(
key="BYM_AI_CHAT_SMART",
value=False,
help="是否开启智能模式",
default_value=False,
type=bool,
),
RegisterConfig(
key="BYM_AI_TTS_URL",
value=None,
help="tts接口地址",
),
RegisterConfig(
key="BYM_AI_TTS_TOKEN",
value=None,
help="tts接口密钥",
),
RegisterConfig(
key="BYM_AI_TTS_VOICE",
value=None,
help="tts接口音色",
),
RegisterConfig(
key="ENABLE_IMPRESSION",
value=True,
help="使用签到数据作为基础好感度",
default_value=True,
type=bool,
),
RegisterConfig(
key="GROUP_CACHE_SIZE",
value=40,
help="群组内聊天记录数据大小",
default_value=40,
type=int,
),
RegisterConfig(
key="CACHE_SIZE",
value=40,
help="私聊下缓存聊天记录数据大小(每位用户)",
default_value=40,
type=int,
),
RegisterConfig(
key="ENABLE_GROUP_CHAT",
value=True,
help="在群组中时共用缓存",
default_value=True,
type=bool,
),
],
smart_tools=[
AICallableTag(
name="call_send_gift",
description="想给某人送礼物时,调用此方法,并且将返回值发送",
parameters=AICallableParam(
type="object",
properties={
"user_id": AICallableProperties(
type="string", description="用户的id"
),
},
required=["user_id"],
),
func=send_gift,
)
],
).to_dict(),
)
async def rule(event: Event, session: Uninfo) -> bool:
if event.is_tome():
"""at自身必定回复"""
return True
if not base_config.get("BYM_AI_CHAT"):
return False
if event.is_tome() and not session.group:
"""私聊过滤"""
return False
rate = base_config.get("BYM_AI_CHAT_RATE") or 0
return random.random() <= rate
_matcher = on_message(priority=998, rule=rule)
@_matcher.handle(parameterless=[CheckConfig(config="BYM_AI_CHAT_TOKEN")])
async def _(
bot: Bot,
event: Event,
message: UniMsg,
session: Uninfo,
uname: str = UserName(),
):
if not message.extract_plain_text().strip():
if event.is_tome():
await MessageUtils.build_message(ChatManager.hello()).finish()
return
fun_param = FunctionParam(
bot=bot,
event=event,
arparma=Arparma(head_result="BYM_AI"),
session=session,
message=message,
)
group_id = session.group.id if session.group else None
is_bym = not event.is_tome()
try:
try:
result = await ChatManager.get_result(
bot, session, group_id, uname, message, is_bym, fun_param
)
except HTTPStatusError as e:
logger.error("BYM AI 请求失败", "BYM_AI", session=session, e=e)
return await MessageUtils.build_message(
f"请求失败了哦code: {e.response.status_code}"
).send(reply_to=True)
except NotResultException:
return await MessageUtils.build_message("请求没有结果呢...").send(
reply_to=True
)
if is_bym:
"""伪人回复,切割文本"""
if result:
for r, delay in split_text(result):
await MessageUtils.build_message(r).send()
await asyncio.sleep(delay)
else:
try:
if result:
await MessageUtils.build_message(result).send(
reply_to=bool(group_id)
)
if tts_data := await ChatManager.tts(result):
await MessageUtils.build_message(Voice(raw=tts_data)).send()
elif not base_config.get("BYM_AI_CHAT_SMART"):
await MessageUtils.build_message(ChatManager.no_result()).send()
else:
await MessageUtils.build_message(
f"{BotConfig.self_nickname}并不想理你..."
).send(reply_to=True)
if (
event.is_tome()
and result
and (plain_text := message.extract_plain_text())
):
await BymChat.create(
user_id=session.user.id,
group_id=group_id,
plain_text=plain_text,
result=result,
)
logger.info(
f"BYM AI 问题: {message} | 回答: {result}",
"BYM_AI",
session=session,
)
except HTTPStatusError as e:
logger.error("BYM AI 请求失败", "BYM_AI", session=session, e=e)
await MessageUtils.build_message(
f"请求失败了哦code: {e.response.status_code}"
).send(reply_to=True)
except NotResultException:
await MessageUtils.build_message("请求没有结果呢...").send(
reply_to=True
)
except GiftRepeatSendException:
logger.warning("BYM AI 重复发送礼物", "BYM_AI", session=session)
await MessageUtils.build_message(
f"今天已经收过{BotConfig.self_nickname}的礼物了哦~"
).finish(reply_to=True)
except Exception as e:
logger.error("BYM AI 其他错误", "BYM_AI", session=session, e=e)
await MessageUtils.build_message("发生了一些异常,想要休息一下...").finish(
reply_to=True
)
RESOURCE_FILES = [
IMAGE_PATH / "shop_icon" / "reload_ai_card.png",
IMAGE_PATH / "shop_icon" / "reload_ai_card1.png",
]
GIFT_FILES = [ICON_PATH / "wallet.png", ICON_PATH / "hairpin.png"]
class MyPluginInit(PluginInit):
async def install(self):
for res_file in RESOURCE_FILES + GIFT_FILES:
res = Path(__file__).parent / res_file.name
if res.exists():
if res_file.exists():
res_file.unlink()
res.rename(res_file)
logger.info(f"更新 BYM_AI 资源文件成功 {res} -> {res_file}")
async def remove(self):
for res_file in RESOURCE_FILES + GIFT_FILES:
if res_file.exists():
res_file.unlink()
logger.info(f"删除 BYM_AI 资源文件成功 {res_file}")