智障回复功能

This commit is contained in:
snowy 2024-06-02 01:32:52 +08:00
parent be28116a98
commit 206651da94
16 changed files with 57013 additions and 16 deletions

View File

@ -1,6 +1,6 @@
import base64
import time
from typing import Any
from typing import Any, AnyStr
import nonebot
import pip
@ -211,7 +211,11 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
function_name = result.main_args.get("function")
args: tuple[str] = result.main_args.get("args", ())
_args = []
_kwargs = {}
_kwargs = {
"USER_ID" : str(event.user_id),
"GROUP_ID": str(event.group_id) if event.message_type == "group" else "0",
"BOT_ID" : str(bot.self_id)
}
for arg in args:
arg = arg.replace("\\=", "EQUAL_SIGN")
@ -227,7 +231,7 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
_args.append(arg.replace("EQUAL_SIGN", "="))
ly_func = get_function(function_name)
ly_func.bot = bot if "bot_id" not in _kwargs else nonebot.get_bot(_kwargs["bot_id"])
ly_func.bot = bot if "BOT_ID" not in _kwargs else nonebot.get_bot(_kwargs["BOT_ID"])
ly_func.matcher = matcher
await ly_func(*tuple(_args), **_kwargs)
@ -236,7 +240,7 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
@on_alconna(
command=Alconna(
"/api",
Args["api", str]["args", MultiVar(str), ()],
Args["api", str]["args", MultiVar(AnyStr), ()],
),
permission=SUPERUSER
).handle()
@ -253,10 +257,12 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
"""
api_name = result.main_args.get("api")
args: tuple[str] = result.main_args.get("args", ()) # 类似于url参数但每个参数间用空格分隔空格是%20
print(args)
args_dict = {}
for arg in args:
key, value = arg.split("=", 1)
args_dict[key] = unescape(value.replace("%20", " "))
if api_name in need_user_id and "user_id" not in args_dict:
@ -265,7 +271,10 @@ async def _(result: Arparma, bot: T_Bot, event: T_MessageEvent, matcher: Matcher
args_dict["group_id"] = str(event.group_id)
if "message" in args_dict:
args_dict["message"] = Message(args_dict["message"])
args_dict["message"] = Message(eval(args_dict["message"]))
if "messages" in args_dict:
args_dict["messages"] = Message(eval(args_dict["messages"]))
try:
result = await bot.call_api(api_name, **args_dict)

View File

@ -0,0 +1,18 @@
from nonebot.plugin import PluginMetadata
from .monitors import *
from .matchers import *
__author__ = "snowykami"
__plugin_meta__ = PluginMetadata(
name="轻雪智障回复",
description="",
usage="",
type="application",
homepage="https://github.com/snowykami/LiteyukiBot",
extra={
"liteyuki": True,
"toggleable" : True,
"default_enable" : True,
}
)

View File

@ -0,0 +1,85 @@
import asyncio
import random
from nonebot import Bot, on_message, get_driver, require
from nonebot.internal.matcher import Matcher
from nonebot.permission import SUPERUSER
from nonebot.rule import to_me
from nonebot.typing import T_State
from liteyuki.utils.base.ly_typing import T_MessageEvent
from .utils import get_keywords
from liteyuki.utils.base.word_bank import get_reply
from liteyuki.utils.event import get_message_type
from liteyuki.utils.base.permission import GROUP_ADMIN, GROUP_OWNER
from liteyuki.utils.base.data_manager import group_db, Group
require("nonebot_plugin_alconna")
from nonebot_plugin_alconna import on_alconna, Alconna, Args, Arparma
nicknames = set()
driver = get_driver()
group_reply_probability: dict[str, float] = {
}
default_reply_probability = 0.05
@on_alconna(
Alconna(
"set-reply-probability",
Args["probability", float, default_reply_probability],
),
aliases={"设置回复概率"},
permission=SUPERUSER | GROUP_ADMIN | GROUP_OWNER,
).handle()
async def _(result: Arparma, event: T_MessageEvent, matcher: Matcher):
# 修改内存和数据库的概率值
if get_message_type(event) == "group":
group_id = event.group_id
probability = result.main_args.get("probability")
# 保存到内存
group_reply_probability[group_id] = probability
# 保存到数据库
group: Group = group_db.where_one(Group(), "group_id = ?", group_id, default=Group(group_id=str(group_id)))
group.config["reply_probability"] = probability
group_db.save(group)
await matcher.send(f"已将群组{group_id}的回复概率设置为{probability}")
return
@driver.on_bot_connect
async def _(bot: Bot):
global nicknames
nicknames.update(bot.config.nickname)
# 从数据库加载群组的回复概率
groups = group_db.where_all(Group(), default=[])
for group in groups:
group_reply_probability[group.group_id] = group.config.get("reply_probability", default_reply_probability)
@on_message(priority=100).handle()
async def _(event: T_MessageEvent, bot: Bot, state: T_State, matcher: Matcher):
kws = await get_keywords(event.message.extract_plain_text())
tome = False
if await to_me()(event=event, bot=bot, state=state):
tome = True
else:
for kw in kws:
if kw in nicknames:
tome = True
break
# 回复概率
message_type = get_message_type(event)
if tome or message_type == "private":
p = 1.0
else:
p = group_reply_probability.get(event.group_id, default_reply_probability)
if random.random() < p:
await asyncio.sleep(random.random() * 2)
if reply := get_reply(kws):
await matcher.send(reply)
return

View File

@ -0,0 +1,13 @@
from jieba import lcut
from nonebot.utils import run_sync
@run_sync
def get_keywords(text: str) -> list[str, ...]:
"""
获取关键词
Args:
text: 文本
Returns:
"""
return lcut(text)

View File

@ -0,0 +1,3 @@
name: 轻雪词库
description: For Liteyuki Auto Reply
version: 2024.4.26

View File

@ -0,0 +1,19 @@
The MIT License (MIT)
Copyright (c) 2020 NoneBot Team
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,31 @@
# 这是什么 | What's this
一个特~~二刺螈~~文爱的适用于任何bot的词库<br>
好像有点涩?(不止一点
## 一些使用建议 | Using advices
- 词库返回中的“你”可替换成目标昵称。同理“我”也可替换成bot的昵称
- 如需使用分词建议匹配无结果时将本词库key打乱放回原句遍历判断
## 相关项目 | Related projects
> 欢迎提交pr以明示使用本词库的项目
- [MoeChat](https://github.com/Fzoss/MoeChat) 聊天UI
- [ATRI](https://github.com/Kyomotoi/ATRI) 高性能萝卜子!
- [绪山真寻Bot](https://github.com/HibiKier/zhenxun_bot) 非常可爱的绪山真寻bot
- [ZeroBot-Plugin](https://github.com/FloatTech/ZeroBot-Plugin) 基于 ZeroBot 的 OneBot 插件
- [Liteyuki Bot](https://github.com/snowyfirefly/Liteyuki-Bot) 非常可爱的,有独立思维(雾)的轻雪Bot
- [kmua bot](https://github.com/krau/kmua-bot) Telegram 上的可爱文爱 bot
## 贡献 | Contribute
只要你的词和回复够味,都可以向此库提交!
## 许可 | License
本项目使用 MIT
![](https://cdn.jsdelivr.net/gh/Kyomotoi/CDN@master/noting/88674944_p0.png)

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,7 @@ memory_database = {
class User(LiteModel):
TABLE_NAME:str = "user"
TABLE_NAME: str = "user"
user_id: str = Field(str(), alias="user_id")
username: str = Field(str(), alias="username")
profile: dict[str, str] = Field(dict(), alias="profile")
@ -34,6 +34,7 @@ class Group(LiteModel):
enabled_plugins: list[str] = Field([], alias="enabled_plugins")
disabled_plugins: list[str] = Field([], alias="disabled_plugins")
enable: bool = Field(True, alias="enable") # 群聊全局机器人是否启用
config: dict = Field({}, alias="config")
class InstalledPlugin(LiteModel):
@ -50,7 +51,7 @@ class GlobalPlugin(LiteModel):
class StoredConfig(LiteModel):
TABLE_NAME :str= "stored_config"
TABLE_NAME: str = "stored_config"
config: dict = {}

View File

@ -64,9 +64,23 @@ class LiteyukiFunction:
line: 行数
Returns:
"""
try:
if "${" in cmd:
# 此种情况下,{}内容不用管,只对${}内的内容进行format
for i in range(len(cmd) - 1):
if cmd[i] == "$" and cmd[i + 1] == "{":
end = cmd.find("}", i)
key = cmd[i + 2:end]
cmd = cmd.replace(f"${{{key}}}", str(self.kwargs_data.get(key, "")))
else:
cmd = cmd.format(*self.args_data, **self.kwargs_data)
except Exception as e:
pass
no_head = cmd.split(" ", 1)[1] if len(cmd.split(" ")) > 1 else ""
try:
head, args, kwargs = self.get_args(cmd)
head, cmd_args, cmd_kwargs = self.get_args(cmd)
except Exception as e:
error_msg = f"Parsing error in {self.name} at line {line}: {e}"
nonebot.logger.error(error_msg)
@ -75,7 +89,7 @@ class LiteyukiFunction:
if head == "var":
# 变量定义
self.kwargs_data.update(kwargs)
self.kwargs_data.update(cmd_kwargs)
elif head == "cmd":
# 在当前计算机上执行命令
@ -83,18 +97,18 @@ class LiteyukiFunction:
elif head == "api":
# 调用Bot API 需要Bot实例
await self.bot.call_api(args[1], **kwargs)
await self.bot.call_api(cmd_args[1], **cmd_kwargs)
elif head == "function":
# 调用轻雪函数
func = get_function(args[1])
func = get_function(cmd_args[1])
func.bot = self.bot
func.matcher = self.matcher
await func(*args[2:], **kwargs)
await func(*cmd_args[2:], **cmd_kwargs)
elif head == "sleep":
# 等待一段时间
await asyncio.sleep(float(args[1]))
await asyncio.sleep(float(cmd_args[1]))
elif head == "nohup":
# 挂起运行
@ -106,6 +120,7 @@ class LiteyukiFunction:
self.end = True
return 0
elif head == "await":
# 等待所有协程执行完毕
await asyncio.gather(*self.sub_tasks)

View File

@ -64,6 +64,11 @@ def load_resource_from_dir(path: str):
from liteyuki.utils.base.ly_function import load_from_dir
load_from_dir(os.path.join(path, "functions"))
if os.path.exists(os.path.join(path, "word_bank")):
# 加载词库
from liteyuki.utils.base.word_bank import load_from_dir
load_from_dir(os.path.join(path, "word_bank"))
_loaded_resource_packs.insert(0, ResourceMetadata(**metadata))

View File

@ -0,0 +1,57 @@
import json
import os
import random
from typing import Iterable
import nonebot
word_bank: dict[str, set[str]] = {}
def load_from_file(file_path: str):
"""
从json文件中加载词库
Args:
file_path: 文件路径
"""
with open(file_path, "r", encoding="utf-8") as file:
data = json.load(file)
for key, value_list in data.items():
if key not in word_bank:
word_bank[key] = set()
word_bank[key].update(value_list)
nonebot.logger.debug(f"Loaded word bank from {file_path}")
def load_from_dir(dir_path: str):
"""
从目录中加载词库
Args:
dir_path: 目录路径
"""
for file in os.listdir(dir_path):
try:
file_path = os.path.join(dir_path, file)
if os.path.isfile(file_path):
if file.endswith(".json"):
load_from_file(file_path)
except Exception as e:
nonebot.logger.error(f"Failed to load language data from {file}: {e}")
continue
def get_reply(kws: Iterable[str]) -> str | None:
"""
获取回复
Args:
kws: 关键词
Returns:
"""
for kw in kws:
if kw in word_bank:
return random.choice(list(word_bank[kw]))
return None

View File

@ -7,7 +7,7 @@ nb-cli~=1.4.1
nonebot2[fastapi,httpx,websockets]~=2.3.0
nonebot-plugin-htmlrender~=0.3.1
nonebot-adapter-onebot~=2.4.3
nonebot-plugin-alconna~=0.43.0
nonebot-plugin-alconna~=0.46.3
nonebot_plugin_apscheduler~=0.4.0
nonebot-adapter-satori~=0.11.5
packaging~=23.1