forked from bot/app
✨ 智障回复功能
This commit is contained in:
parent
be28116a98
commit
206651da94
@ -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)
|
||||
|
18
liteyuki/plugins/liteyuki_smart_reply/__init__.py
Normal file
18
liteyuki/plugins/liteyuki_smart_reply/__init__.py
Normal 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,
|
||||
}
|
||||
)
|
85
liteyuki/plugins/liteyuki_smart_reply/matchers.py
Normal file
85
liteyuki/plugins/liteyuki_smart_reply/matchers.py
Normal 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
|
0
liteyuki/plugins/liteyuki_smart_reply/monitors.py
Normal file
0
liteyuki/plugins/liteyuki_smart_reply/monitors.py
Normal file
13
liteyuki/plugins/liteyuki_smart_reply/utils.py
Normal file
13
liteyuki/plugins/liteyuki_smart_reply/utils.py
Normal 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)
|
3
liteyuki/resources/liteyuki_words/metadata.yml
Normal file
3
liteyuki/resources/liteyuki_words/metadata.yml
Normal file
@ -0,0 +1,3 @@
|
||||
name: 轻雪词库
|
||||
description: For Liteyuki Auto Reply
|
||||
version: 2024.4.26
|
19
liteyuki/resources/liteyuki_words/word_bank/LICENSE
Normal file
19
liteyuki/resources/liteyuki_words/word_bank/LICENSE
Normal 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.
|
31
liteyuki/resources/liteyuki_words/word_bank/README.md
Normal file
31
liteyuki/resources/liteyuki_words/word_bank/README.md
Normal 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)
|
1924
liteyuki/resources/liteyuki_words/word_bank/data.json
Normal file
1924
liteyuki/resources/liteyuki_words/word_bank/data.json
Normal file
File diff suppressed because it is too large
Load Diff
15410
liteyuki/resources/liteyuki_words/word_bank/傲娇系二次元bot词库5千词V1.2.json
Normal file
15410
liteyuki/resources/liteyuki_words/word_bank/傲娇系二次元bot词库5千词V1.2.json
Normal file
File diff suppressed because it is too large
Load Diff
39407
liteyuki/resources/liteyuki_words/word_bank/可爱系二次元bot词库1.5万词V1.2.json
Normal file
39407
liteyuki/resources/liteyuki_words/word_bank/可爱系二次元bot词库1.5万词V1.2.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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 = {}
|
||||
|
||||
|
||||
@ -78,8 +79,8 @@ def set_memory_data(key: str, value) -> None:
|
||||
|
||||
"""
|
||||
return memory_database.update({
|
||||
key: value
|
||||
})
|
||||
key: value
|
||||
})
|
||||
|
||||
|
||||
def get_memory_data(key: str, default=None) -> any:
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
||||
|
||||
|
57
liteyuki/utils/base/word_bank.py
Normal file
57
liteyuki/utils/base/word_bank.py
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user