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}")