From 512685a7072a4997d1d595b9a6306f01e61d0e88 Mon Sep 17 00:00:00 2001
From: CMHopeSunshine <277073121@qq.com>
Date: Sun, 20 Nov 2022 19:51:47 +0800
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20`=E5=91=BD=E4=BB=A4=E5=88=AB?=
=?UTF-8?q?=E5=90=8D`=E5=A2=9E=E5=BC=BA=EF=BC=8C=E5=8F=AF=E4=BB=A5?=
=?UTF-8?q?=E5=9C=A8`Web=20UI`=E4=B8=AD=E8=BF=9B=E8=A1=8C=E8=AE=BE?=
=?UTF-8?q?=E7=BD=AE=EF=BC=8C=E4=BC=98=E5=8C=96=E7=BE=A4=E8=81=8A=E5=AD=A6?=
=?UTF-8?q?=E4=B9=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
LittlePaimon/config/__init__.py | 1 +
LittlePaimon/config/command/__init__.py | 46 +++++
LittlePaimon/config/config/model.py | 2 +
LittlePaimon/database/__init__.py | 91 ++++++----
LittlePaimon/database/models/__init__.py | 6 +-
LittlePaimon/database/models/learning_chat.py | 171 ------------------
.../database/models/{manager.py => manage.py} | 28 +++
LittlePaimon/database/models/memory_db.py | 16 ++
LittlePaimon/plugins/Learning_Chat/handler.py | 72 +++++---
LittlePaimon/plugins/Learning_Chat/models.py | 3 +-
.../plugins/Learning_Chat/web_page.py | 30 +--
.../plugins/alias_manager/__init__.py | 127 -------------
.../plugins/alias_manager/alias_list.py | 61 -------
LittlePaimon/plugins/alias_manager/handler.py | 30 ---
LittlePaimon/plugins/alias_manager/parser.py | 48 -----
LittlePaimon/web/api/__init__.py | 2 +
LittlePaimon/web/api/bot_info.py | 19 +-
LittlePaimon/web/api/command_alias.py | 55 ++++++
LittlePaimon/web/pages/command_alias.py | 71 ++++++++
LittlePaimon/web/pages/main.py | 3 +-
LittlePaimon/web/pages/plugin_manage.py | 5 +-
21 files changed, 362 insertions(+), 525 deletions(-)
create mode 100644 LittlePaimon/config/command/__init__.py
delete mode 100644 LittlePaimon/database/models/learning_chat.py
rename LittlePaimon/database/models/{manager.py => manage.py} (61%)
create mode 100644 LittlePaimon/database/models/memory_db.py
delete mode 100644 LittlePaimon/plugins/alias_manager/__init__.py
delete mode 100644 LittlePaimon/plugins/alias_manager/alias_list.py
delete mode 100644 LittlePaimon/plugins/alias_manager/handler.py
delete mode 100644 LittlePaimon/plugins/alias_manager/parser.py
create mode 100644 LittlePaimon/web/api/command_alias.py
create mode 100644 LittlePaimon/web/pages/command_alias.py
diff --git a/LittlePaimon/config/__init__.py b/LittlePaimon/config/__init__.py
index c4775a1..e23c13b 100644
--- a/LittlePaimon/config/__init__.py
+++ b/LittlePaimon/config/__init__.py
@@ -1,2 +1,3 @@
from .config.manage import ConfigManager, ConfigModel, config
from .plugin.manage import PluginManager, HIDDEN_PLUGINS, MatcherInfo, PluginInfo
+from .command import handle_command_alias
diff --git a/LittlePaimon/config/command/__init__.py b/LittlePaimon/config/command/__init__.py
new file mode 100644
index 0000000..8b7521f
--- /dev/null
+++ b/LittlePaimon/config/command/__init__.py
@@ -0,0 +1,46 @@
+import re
+from typing import List
+
+from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent
+from nonebot.message import event_preprocessor
+from tortoise.queryset import Q
+
+from LittlePaimon.config import config
+from LittlePaimon.database import CommandAlias, AliasMode
+
+
+@event_preprocessor
+async def handle_command_alias(event: MessageEvent):
+ if not config.command_alias_enable:
+ return
+ msgs = event.get_message()
+ if len(msgs) < 1 or msgs[0].type != 'text':
+ return
+ msg = str(msgs[0]).lstrip()
+ if not msg:
+ return
+ if isinstance(event, GroupMessageEvent):
+ filter_arg = Q(group_id=str(event.group_id)) | Q(group_id='all')
+ else:
+ filter_arg = Q(group_id='all')
+ all_alias = await CommandAlias.filter(filter_arg).order_by('priority')
+ new_msg = modify_msg(all_alias, msg)
+ event.message[0].data['text'] = new_msg
+
+
+def combine_msg(new_command: str, extra_msg: str, is_reverse: bool):
+ return (new_command + extra_msg) if not is_reverse else (extra_msg + new_command)
+
+
+def modify_msg(all_alias: List[CommandAlias], msg: str):
+ for alias in all_alias:
+ if alias.is_regex:
+ msg = re.sub(alias.alias, alias.command, msg)
+ else:
+ if alias.mode == AliasMode.prefix and msg.startswith(alias.alias):
+ msg = combine_msg(alias.command, msg[len(alias.alias):], alias.is_reverse)
+ elif alias.mode == AliasMode.suffix and msg.endswith(alias.alias):
+ msg = combine_msg(msg[:-len(alias.alias)], alias.command, alias.is_reverse)
+ elif alias.mode == AliasMode.full_match and msg == alias.alias:
+ msg = alias.command
+ return msg
diff --git a/LittlePaimon/config/config/model.py b/LittlePaimon/config/config/model.py
index d9d0719..a572b2e 100644
--- a/LittlePaimon/config/config/model.py
+++ b/LittlePaimon/config/config/model.py
@@ -45,6 +45,8 @@ class ConfigModel(BaseModel):
secret_key: str = Field('49c294d32f69b732ef6447c18379451ce1738922a75cd1d4812ef150318a2ed0', alias='Web端token密钥')
admin_theme: Literal['default', 'antd', 'ang', 'dark'] = Field('default', alias='Web端主题')
+ command_alias_enable: bool = Field(True, alias='启用命令别名')
+
@property
def alias_dict(self):
return {v.alias: k for k, v in self.__fields__.items()}
diff --git a/LittlePaimon/database/__init__.py b/LittlePaimon/database/__init__.py
index 6373ebb..8eb3f88 100644
--- a/LittlePaimon/database/__init__.py
+++ b/LittlePaimon/database/__init__.py
@@ -4,66 +4,75 @@ from pathlib import Path
from tortoise import Tortoise
from nonebot.log import logger
from LittlePaimon.utils import scheduler, logger as my_logger
-from LittlePaimon.utils.path import GENSHIN_DB_PATH, SUB_DB_PATH, GENSHIN_VOICE_DB_PATH, MANAGER_DB_PATH, YSC_TEMP_IMG_PATH
+from LittlePaimon.utils.path import GENSHIN_DB_PATH, SUB_DB_PATH, GENSHIN_VOICE_DB_PATH, MANAGER_DB_PATH, \
+ YSC_TEMP_IMG_PATH
from .models import *
+
DATABASE = {
- "connections": {
- "paimon_genshin": {
- "engine": "tortoise.backends.sqlite",
- "credentials": {"file_path": GENSHIN_DB_PATH},
+ 'connections': {
+ 'paimon_genshin': {
+ 'engine': 'tortoise.backends.sqlite',
+ 'credentials': {'file_path': GENSHIN_DB_PATH},
},
- "paimon_subscription": {
- "engine": "tortoise.backends.sqlite",
- "credentials": {"file_path": SUB_DB_PATH},
+ 'paimon_subscription': {
+ 'engine': 'tortoise.backends.sqlite',
+ 'credentials': {'file_path': SUB_DB_PATH},
},
'paimon_genshin_voice': {
- "engine": "tortoise.backends.sqlite",
- "credentials": {"file_path": GENSHIN_VOICE_DB_PATH},
+ 'engine': 'tortoise.backends.sqlite',
+ 'credentials': {'file_path': GENSHIN_VOICE_DB_PATH},
},
- 'paimon_manager': {
- "engine": "tortoise.backends.sqlite",
- "credentials": {"file_path": MANAGER_DB_PATH},
- }
+ 'paimon_manage': {
+ 'engine': 'tortoise.backends.sqlite',
+ 'credentials': {'file_path': MANAGER_DB_PATH},
+ },
+ # 'memory_db': 'sqlite://:memory:'
},
- "apps": {
- "paimon_genshin": {
- "models": ['LittlePaimon.database.models.player_info',
- 'LittlePaimon.database.models.abyss_info',
- 'LittlePaimon.database.models.character',
- 'LittlePaimon.database.models.cookie'],
- "default_connection": "paimon_genshin",
+ 'apps': {
+ 'paimon_genshin': {
+ 'models': [player_info.__name__,
+ abyss_info.__name__,
+ character.__name__,
+ cookie.__name__],
+ 'default_connection': 'paimon_genshin',
},
- "paimon_subscription": {
- "models": ['LittlePaimon.database.models.subscription'],
- "default_connection": "paimon_subscription",
+ 'paimon_subscription': {
+ 'models': [subscription.__name__],
+ 'default_connection': 'paimon_subscription',
},
- "paimon_genshin_voice": {
- "models": ['LittlePaimon.database.models.genshin_voice'],
- "default_connection": "paimon_genshin_voice",
+ 'paimon_genshin_voice': {
+ 'models': [genshin_voice.__name__],
+ 'default_connection': 'paimon_genshin_voice',
},
- "paimon_manager": {
- "models": ['LittlePaimon.database.models.manager'],
- "default_connection": "paimon_manager",
- }
+ 'paimon_manage': {
+ 'models': [manage.__name__],
+ 'default_connection': 'paimon_manage',
+ },
+ # 'memory_db': {
+ # 'models': [memory_db.__name__],
+ # 'default_connection': 'memory_db',
+ # }
},
+ 'use_tz': False,
+ 'timezone': 'Asia/Shanghai'
}
-def register_database(db_name: str, models: List[Union[str, Path]], db_path: Optional[Union[str, Path]]):
+def register_database(db_name: str, models: str, db_path: Optional[Union[str, Path]]):
"""
注册数据库
"""
if db_name in DATABASE['connections'] and db_name in DATABASE['apps']:
- DATABASE['apps'][db_name]['models'].extend(models)
+ DATABASE['apps'][db_name]['models'].append(models)
else:
DATABASE['connections'][db_name] = {
- "engine": "tortoise.backends.sqlite",
- "credentials": {"file_path": db_path},
+ 'engine': 'tortoise.backends.sqlite',
+ 'credentials': {'file_path': db_path},
}
DATABASE['apps'][db_name] = {
- "models": models,
- "default_connection": db_name,
+ 'models': [models],
+ 'default_connection': db_name,
}
@@ -74,9 +83,9 @@ async def connect():
try:
await Tortoise.init(DATABASE)
await Tortoise.generate_schemas()
- logger.opt(colors=True).success("[数据库]连接成功")
+ logger.opt(colors=True).success('[数据库]连接成功')
except Exception as e:
- logger.opt(colors=True).warning(f"[数据库]连接失败:{e}")
+ logger.opt(colors=True).warning(f'[数据库]连接失败:{e}')
raise e
@@ -85,7 +94,7 @@ async def disconnect():
断开数据库连接
"""
await Tortoise.close_connections()
- logger.opt(colors=True).success("[数据库]连接已断开")
+ logger.opt(colors=True).success('[数据库]连接已断开')
@scheduler.scheduled_job('cron', hour=0, minute=0, misfire_grace_time=10)
@@ -112,3 +121,5 @@ async def daily_reset():
if YSC_TEMP_IMG_PATH.exists():
shutil.rmtree(YSC_TEMP_IMG_PATH)
YSC_TEMP_IMG_PATH.mkdir(parents=True, exist_ok=True)
+
+ await MysAuthKey.filter()
diff --git a/LittlePaimon/database/models/__init__.py b/LittlePaimon/database/models/__init__.py
index 071aaec..7775989 100644
--- a/LittlePaimon/database/models/__init__.py
+++ b/LittlePaimon/database/models/__init__.py
@@ -3,6 +3,8 @@ from .cookie import *
from .player_info import *
from .subscription import *
from .genshin_voice import *
-from .manager import *
+from .manage import *
from .abyss_info import *
-from .learning_chat import *
+# from .memory_db import *
+
+from . import abyss_info, character, cookie, genshin_voice, manage, other, player_info, subscription
diff --git a/LittlePaimon/database/models/learning_chat.py b/LittlePaimon/database/models/learning_chat.py
deleted file mode 100644
index f5535f0..0000000
--- a/LittlePaimon/database/models/learning_chat.py
+++ /dev/null
@@ -1,171 +0,0 @@
-import time
-from typing import List, Optional, Iterator
-from pydantic import BaseModel
-
-try:
- import ujson as json
-except ImportError:
- import json
-
-from tortoise import fields
-from tortoise.models import Model
-
-
-class BanWord(BaseModel):
- keywords: str
- group_id: int
- reason: Optional[str]
- time: Optional[int]
-
-
-class BanWords(BaseModel):
- bans: List[BanWord] = []
-
- def __len__(self):
- return len(self.bans)
-
- def __getitem__(self, item):
- return self.bans[item]
-
- def __setitem__(self, key, value):
- self.bans[key] = value
-
- def __delitem__(self, key):
- del self.bans[key]
-
- def __iter__(self) -> Iterator[BanWord]:
- return iter(self.bans)
-
- def __reversed__(self):
- return reversed(self.bans)
-
- def append(self, ban: BanWord):
- self.bans.append(ban)
-
- def index(self, ban: BanWord) -> int:
- return self.bans.index(ban)
-
-
-
- # @staticmethod
- # def tortoise_decoder(text: str) -> List["BanWord"]:
- # print('ban_decoder:', text)
- # return [BanWord.parse_obj(item) for item in json.loads(text)]
- #
- # @staticmethod
- # def tortoise_encoder(models: List["BanWord"]) -> str:
- # print('ban_encoder:', models)
- # if not models:
- # return ''
- # elif isinstance(models[0], BanWord):
- # return json.dumps([model.dict() for model in models])
-
-
-class Answer(BaseModel):
- keywords: str
- group_id: int
- count: int
- time: int
- messages: List[str]
-
- # @staticmethod
- # def tortoise_decoder(text: str) -> List["Answer"]:
- # print('answer_decoder:', text)
- # return [Answer.parse_obj(item) for item in json.loads(text)]
- #
- # @staticmethod
- # def tortoise_encoder(models: List["Answer"]) -> str:
- # print('answer_encoder:', models)
- # if not models:
- # return ''
- # elif isinstance(models[0], BanWord):
- # return json.dumps([model.dict() for model in models])
-
-
-class Answers(BaseModel):
- answers: List[Answer] = []
-
- def __len__(self):
- return len(self.answers)
-
- def __getitem__(self, item):
- return self.answers[item]
-
- def __setitem__(self, key, value):
- self.answers[key] = value
-
- def __delitem__(self, key):
- del self.answers[key]
-
- def __iter__(self) -> Iterator[Answer]:
- return iter(self.answers)
-
- def __reversed__(self):
- return reversed(self.answers)
-
- def append(self, answer: Answer):
- self.answers.append(answer)
-
- def index(self, answer: Answer) -> int:
- return self.answers.index(answer)
-
-
-class Message(Model):
- id: int = fields.IntField(pk=True, generated=True, auto_increment=True)
- """自增主键"""
- group_id: int = fields.IntField()
- """群id"""
- user_id: int = fields.IntField()
- """用户id"""
- raw_message: str = fields.TextField()
- """原始消息"""
- is_plain_text: bool = fields.BooleanField()
- """是否为纯文本"""
- plain_text: str = fields.TextField()
- """纯文本"""
- keywords: str = fields.TextField()
- """关键词"""
- time: int = fields.IntField()
- """时间戳"""
-
- class Meta:
- table = 'Message'
- indexes = ('time',)
- ordering = ['-time']
-
-
-class Context(Model):
- id: int = fields.IntField(pk=True, generated=True, auto_increment=True)
- """自增主键"""
- keywords: str = fields.TextField()
- """关键词"""
- time: int = fields.IntField(default=int(time.time()))
- """时间戳"""
- count: int = fields.IntField(default=1)
- """次数"""
- answers: Answers = fields.JSONField(encoder=Answers.json, decoder=Answers.parse_raw, default=Answers())
- """答案列表"""
- clear_time: Optional[int] = fields.IntField(null=True)
- """清除时间戳"""
- ban: BanWords = fields.JSONField(encoder=BanWords.json, decoder=BanWords.parse_raw, default=BanWords())
- """禁用词列表"""
-
- class Meta:
- table = 'Context'
- indexes = ('keywords', 'count', 'time')
- ordering = ['-time', '-count']
-
-
-class BlackList(Model):
- id: int = fields.IntField(pk=True, generated=True, auto_increment=True)
- """自增主键"""
- group_id: int = fields.IntField()
- """群id"""
- answers: List[str] = fields.JSONField(default=[])
- """答案"""
- answers_reserve: List[str] = fields.JSONField(default=[])
- """备用答案"""
-
- class Meta:
- table = 'BlackList'
- indexes = ('group_id',)
diff --git a/LittlePaimon/database/models/manager.py b/LittlePaimon/database/models/manage.py
similarity index 61%
rename from LittlePaimon/database/models/manager.py
rename to LittlePaimon/database/models/manage.py
index e2a5b20..9665e67 100644
--- a/LittlePaimon/database/models/manager.py
+++ b/LittlePaimon/database/models/manage.py
@@ -1,5 +1,6 @@
import datetime
from typing import List
+from enum import Enum
from tortoise import fields
from tortoise.models import Model
@@ -43,3 +44,30 @@ class PluginStatistics(Model):
class Meta:
table = 'plugin_statistics'
+
+
+class AliasMode(Enum):
+ prefix: str = '前缀'
+ suffix: str = '后缀'
+ full_match: str = '全匹配'
+
+
+class CommandAlias(Model):
+ id = fields.IntField(pk=True, generated=True, auto_increment=True)
+ command: str = fields.TextField()
+ """目标命令"""
+ alias: str = fields.TextField()
+ """命令别名"""
+ mode: AliasMode = fields.CharEnumField(AliasMode, max_length=10)
+ """别名模式"""
+ is_regex: bool = fields.BooleanField(default=False)
+ """是否为正则表达式"""
+ is_reverse: bool = fields.BooleanField(default=False)
+ """是否反转"""
+ group_id: str = fields.CharField(max_length=30)
+ """启用的群id,all为全局启用"""
+ priority: int = fields.IntField(default=99)
+ """优先级,数字越大优先级越高"""
+
+ class Meta:
+ table = 'command_alias'
diff --git a/LittlePaimon/database/models/memory_db.py b/LittlePaimon/database/models/memory_db.py
new file mode 100644
index 0000000..4fdbe25
--- /dev/null
+++ b/LittlePaimon/database/models/memory_db.py
@@ -0,0 +1,16 @@
+import datetime
+
+from tortoise import fields
+from tortoise.models import Model
+
+
+class MysAuthKey(Model):
+ id: int = fields.IntField(pk=True, generated=True, auto_increment=True)
+ user_id: str = fields.TextField()
+ """用户id"""
+ uid: str = fields.TextField()
+ """原神uid"""
+ authkey: str = fields.TextField()
+ """authkey"""
+ generate_time: datetime.datetime = fields.DatetimeField(auto_now_add=True)
+ """生成时间"""
diff --git a/LittlePaimon/plugins/Learning_Chat/handler.py b/LittlePaimon/plugins/Learning_Chat/handler.py
index bdb76b3..02a0807 100644
--- a/LittlePaimon/plugins/Learning_Chat/handler.py
+++ b/LittlePaimon/plugins/Learning_Chat/handler.py
@@ -67,32 +67,40 @@ class LearningChat:
self.role = 'superuser' if event.user_id in SUPERUSERS else event.sender.role
self.config = config_manager.get_group_config(self.data.group_id)
self.ban_users = set(chat_config.ban_users + self.config.ban_users)
+ self.ban_words = set(chat_config.ban_words + self.config.ban_words)
async def _learn(self) -> Result:
# logger.debug('群聊学习', f'收到来自群{self.data.group_id}的消息{self.data.message}')
- if not chat_config.total_enable or not self.config.enable or self.data.user_id in self.ban_users:
- # 如果未开启群聊学习或者发言人在屏蔽列表中,跳过
+ if not chat_config.total_enable or not self.config.enable:
+ logger.debug('群聊学习', f'➤该群{self.data.group_id}未开启群聊学习,跳过')
+ # 如果未开启群聊学习,跳过
return Result.Pass
+ elif self.data.user_id in self.ban_users:
+ # 发言人在屏蔽列表中,跳过
+ logger.debug('群聊学习', f'➤发言人{self.data.user_id}在屏蔽列表中,跳过')
elif self.to_me and '不可以' in self.data.message:
# 如果是对某句话进行禁言
return Result.Ban
- elif self.to_me and any(w in self.data.message for w in {'学说话', '快学', '开启学习', '闭嘴', '别学', '关闭学习'}):
+ elif self.to_me and any(
+ w in self.data.message for w in {'学说话', '快学', '开启学习', '闭嘴', '别学', '关闭学习'}):
return Result.SetEnable
elif not await self._check_allow(self.data):
# 本消息不合法,跳过
+ logger.debug('群聊学习', f'➤消息未通过校验,跳过')
return Result.Pass
elif self.reply:
# 如果是回复消息
if not (message := await ChatMessage.get_or_none(message_id=self.reply.message_id)):
# 回复的消息在数据库中有记录
- logger.debug('群聊学习', '➤是否学习:回复的消息不在数据库中,不学习')
+ logger.debug('群聊学习', '➤回复的消息不在数据库中,跳过')
return Result.Pass
if message.user_id in self.ban_users:
# 且回复的人不在屏蔽列表中
+ logger.debug('群聊学习', '➤回复的人在屏蔽列表中,跳过')
return Result.Pass
if not await self._check_allow(message):
# 且回复的内容通过校验
- logger.debug('群聊学习', '➤是否学习:回复的消息未通过校验,不学习')
+ logger.debug('群聊学习', '➤回复的消息未通过校验,跳过')
return Result.Pass
# 则将该回复作为该消息的答案
await self._set_answer(message)
@@ -102,7 +110,7 @@ class LearningChat:
# 获取本群一个小时内的最后5条消息
if messages[0].message == self.data.message:
# 判断是否为复读中
- logger.debug('群聊学习', '➤是否学习:复读中,不学习')
+ logger.debug('群聊学习', '➤复读中,跳过')
return Result.Repeat
for message in messages:
# 如果5条内有相关信息,就作为该消息的答案
@@ -114,7 +122,7 @@ class LearningChat:
# 如果没有相关信息
if messages[0].user_id in self.ban_users or not await self._check_allow(messages[0]):
# 且最后一条消息的发送者不在屏蔽列表中并通过校验
- logger.debug('群聊学习', '➤是否学习:最后一条消息未通过校验,不学习')
+ logger.debug('群聊学习', '➤最后一条消息未通过校验,跳过')
return Result.Pass
# 则作为最后一条消息的答案
await self._set_answer(messages[0])
@@ -162,23 +170,24 @@ class LearningChat:
elif result == Result.Repeat and (messages := await ChatMessage.filter(group_id=self.data.group_id,
time__gte=self.data.time - 3600).limit(
self.config.repeat_threshold)):
- # 如果达到阈值,进行复读
+ # 如果达到阈值,且bot没有回复过,且不是全都为同一个人在说,则进行复读
if len(messages) >= self.config.repeat_threshold and all(
- message.message == self.data.message and message.user_id != self.bot_id for message in
- messages):
+ message.message == self.data.message and message.user_id != self.bot_id
+ for message in messages) and not all(
+ message.user_id == messages[0].user_id for message in messages):
if random.random() < self.config.break_probability:
- logger.debug('群聊学习', f'➤➤是否回复:达到复读阈值,打断复读!')
+ logger.debug('群聊学习', f'➤➤达到复读阈值,打断复读!')
return [random.choice(BREAK_REPEAT_WORDS)]
else:
- logger.debug('群聊学习', f'➤➤是否回复:达到复读阈值,复读{messages[0].message}')
+ logger.debug('群聊学习', f'➤➤达到复读阈值,复读{messages[0].message}')
return [self.data.message]
else:
# 回复
if self.data.is_plain_text and len(self.data.plain_text) <= 1:
- logger.debug('群聊学习', '➤➤是否回复:消息过短,不回复')
+ logger.debug('群聊学习', '➤➤消息过短,不回复')
return None
if not (context := await ChatContext.get_or_none(keywords=self.data.keywords)):
- logger.debug('群聊学习', '➤➤是否回复:尚未有已学习的回复,不回复')
+ logger.debug('群聊学习', '➤➤尚未有已学习的回复,不回复')
return None
# 获取回复阈值
@@ -195,7 +204,7 @@ class LearningChat:
else:
answer_count_threshold = 1
cross_group_threshold = 1
-
+ logger.debug('群聊学习', f'➤➤本次回复阈值为{answer_count_threshold},跨群阈值为{cross_group_threshold}')
# 获取满足跨群条件的回复
answers_cross = await ChatAnswer.filter(context=context, count__gte=answer_count_threshold,
keywords__in=await ChatAnswer.annotate(
@@ -215,7 +224,7 @@ class LearningChat:
# answer.count -= answer_count_threshold - 1
candidate_answers.append(answer)
if not candidate_answers:
- logger.debug('群聊学习', '➤➤是否回复:没有符合条件的候选回复')
+ logger.debug('群聊学习', '➤➤没有符合条件的候选回复')
return None
# 从候选回复中进行选择
@@ -225,13 +234,13 @@ class LearningChat:
per_list.append(1 - sum(per_list))
answer_dict = tuple(zip(candidate_answers, per_list))
logger.debug('群聊学习',
- f'➤➤是否回复:候选回复有{"|".join([f"""{a.keywords}({round(p, 3)})""" for a, p in answer_dict])}|不回复({round(per_list[-1], 3)})')
+ f'➤➤候选回复有{"|".join([f"""{a.keywords}({round(p, 3)})""" for a, p in answer_dict])}|不回复({round(per_list[-1], 3)})')
if (result := random.choices(candidate_answers + [None], weights=per_list)[0]) is None:
- logger.debug('群聊学习', '➤➤是否回复:但不进行回复')
+ logger.debug('群聊学习', '➤➤但不进行回复')
return None
result_message = random.choice(result.messages)
- logger.debug('群聊学习', f'➤➤是否回复:将回复{result_message}')
+ logger.debug('群聊学习', f'➤➤将回复{result_message}')
return [result_message]
async def _ban(self, message_id: Optional[int] = None) -> bool:
@@ -345,9 +354,13 @@ class LearningChat:
popularity: List[Tuple[int, List[ChatMessage]]] = sorted(total_messages.items(),
key=cmp_to_key(group_popularity_cmp))
- logger.debug('群聊学习', f'主动发言:群热度排行{">".join([str(g[0]) for g in popularity])}')
+ logger.debug('群聊学习', f'主动发言:群热度排行{">>".join([str(g[0]) for g in popularity])}')
for group_id, messages in popularity:
+ if len(messages) < 30:
+ continue
+
config = config_manager.get_group_config(group_id)
+ ban_words = set(chat_config.ban_words + config.ban_words + ['[CQ:xml', '[CQ:json', '[CQ:at', '[CQ:video', '[CQ:record', '[CQ:share'])
# 是否开启了主动发言
if not config.speak_enable:
@@ -378,6 +391,12 @@ class LearningChat:
weights=[answer.count + 1 if answer.time >= today_time else answer.count
for answer in answers])[0]
message = random.choice(answer.messages)
+ if len(message) < 2:
+ continue
+ if message.startswith('[') and message.endswith(']'):
+ continue
+ if any(word in message for word in ban_words):
+ continue
speak_list.append(message)
while random.random() < config.speak_continuously_probability and len(
speak_list) < config.speak_continuously_max_len:
@@ -390,6 +409,12 @@ class LearningChat:
weights=[a.count + 1 if a.time >= today_time else a.count
for a in follow_answers])[0]
message = random.choice(answer.messages)
+ if len(message) < 2:
+ continue
+ if message.startswith('[') and message.endswith(']'):
+ continue
+ if any(word in message for word in ban_words):
+ continue
speak_list.append(message)
else:
break
@@ -439,18 +464,19 @@ class LearningChat:
async def _check_allow(self, message: Union[ChatMessage, ChatAnswer]) -> bool:
raw_message = message.message if isinstance(message, ChatMessage) else message.messages[0]
- keywords = message.keywords
+ if len(raw_message) < 2:
+ return False
if any(i in raw_message for i in
{'[CQ:xml', '[CQ:json', '[CQ:at', '[CQ:video', '[CQ:record', '[CQ:share'}):
# logger.debug('群聊学习', f'➤检验{keywords}不通过')
return False
- if any(i in raw_message for i in self.config.ban_words):
+ if any(i in raw_message for i in self.ban_words):
# logger.debug('群聊学习', f'➤检验{keywords}不通过')
return False
if raw_message.startswith('[') and raw_message.endswith(']'):
# logger.debug('群聊学习', f'➤检验{keywords}不通过')
return False
- if ban_word := await ChatBlackList.get_or_none(keywords=keywords):
+ if ban_word := await ChatBlackList.get_or_none(keywords=message.keywords):
if ban_word.global_ban or message.group_id in ban_word.ban_group_id:
# logger.debug('群聊学习', f'➤检验{keywords}不通过')
return False
diff --git a/LittlePaimon/plugins/Learning_Chat/models.py b/LittlePaimon/plugins/Learning_Chat/models.py
index 8adda9d..302b8fe 100644
--- a/LittlePaimon/plugins/Learning_Chat/models.py
+++ b/LittlePaimon/plugins/Learning_Chat/models.py
@@ -18,6 +18,7 @@ from tortoise.models import Model
from LittlePaimon.database import register_database
from LittlePaimon.utils.path import DATABASE_PATH
from .config import config_manager
+
config = config_manager.config
JSON_DUMPS = functools.partial(json.dumps, ensure_ascii=False)
@@ -125,4 +126,4 @@ class ChatBlackList(Model):
indexes = ('keywords',)
-register_database(db_name='LearningChat', models=['LittlePaimon.plugins.Learning_Chat.models'], db_path=DATABASE_PATH / 'LearningChat.db')
+register_database(db_name='LearningChat', models=__name__, db_path=DATABASE_PATH / 'LearningChat.db')
diff --git a/LittlePaimon/plugins/Learning_Chat/web_page.py b/LittlePaimon/plugins/Learning_Chat/web_page.py
index 4c83123..400b9da 100644
--- a/LittlePaimon/plugins/Learning_Chat/web_page.py
+++ b/LittlePaimon/plugins/Learning_Chat/web_page.py
@@ -36,7 +36,7 @@ global_config_form = Form(
actions=[Action(label='保存', level=LevelEnum.success, type='submit'),
Action(label='重置', level=LevelEnum.warning, type='reset')]
)
-group_select = Select(label='分群配置', name='group_id', source='/LittlePaimon/api/get_group_list',
+group_select = Select(label='分群配置', name='group_id', source='${group_list}',
placeholder='选择群')
group_config_form = Form(
title='分群配置',
@@ -106,9 +106,10 @@ blacklist_table = TableCRUD(mode='table',
api='delete:/LittlePaimon/api/delete_chat?type=blacklist&id=${id}')
],
footable=True,
- columns=[TableColumn(type='tpl', tpl='${keywords|truncate:15}', label='内容/关键词',
+ columns=[TableColumn(type='tpl', tpl='${keywords|truncate:20}', label='内容/关键词',
name='keywords',
searchable=True, popOver={'mode': 'dialog', 'title': '全文',
+ 'className': 'break-all',
'body': {'type': 'tpl',
'tpl': '${keywords}'}}),
TableColumn(label='已禁用的群', name='bans', searchable=True),
@@ -117,7 +118,7 @@ message_table = TableCRUD(mode='table',
title='',
syncLocation=False,
api='/LittlePaimon/api/get_chat_messages',
- interval=6000,
+ interval=12000,
headerToolbar=[ActionType.Ajax(label='删除所有聊天记录',
level=LevelEnum.warning,
confirmText='确定要删除所有聊天记录吗?',
@@ -135,8 +136,9 @@ message_table = TableCRUD(mode='table',
columns=[TableColumn(label='消息ID', name='message_id'),
TableColumn(label='群ID', name='group_id', searchable=True),
TableColumn(label='用户ID', name='user_id', searchable=True),
- TableColumn(type='tpl', tpl='${message|truncate:15}', label='消息', name='message',
+ TableColumn(type='tpl', tpl='${raw_message|truncate:20}', label='消息', name='message',
searchable=True, popOver={'mode': 'dialog', 'title': '消息全文',
+ 'className': 'break-all',
'body': {'type': 'tpl',
'tpl': '${raw_message}'}}),
TableColumn(type='tpl', tpl='${time|date:YYYY-MM-DD HH\\:mm\\:ss}', label='时间',
@@ -147,7 +149,7 @@ answer_table = TableCRUD(
syncLocation=False,
footable=True,
api='/LittlePaimon/api/get_chat_answers',
- interval=6000,
+ interval=12000,
headerToolbar=[ActionType.Ajax(label='删除所有已学习的回复',
level=LevelEnum.warning,
confirmText='确定要删除所有已学习的回复吗?',
@@ -162,8 +164,8 @@ answer_table = TableCRUD(
api='delete:/LittlePaimon/api/delete_chat?type=answer&id=${id}')],
columns=[TableColumn(label='ID', name='id', visible=False),
TableColumn(label='群ID', name='group_id', searchable=True),
- TableColumn(type='tpl', tpl='${keywords|truncate:15}', label='内容/关键词', name='keywords',
- searchable=True, popOver={'mode': 'dialog', 'title': '内容全文',
+ TableColumn(type='tpl', tpl='${keywords|truncate:20}', label='内容/关键词', name='keywords',
+ searchable=True, popOver={'mode': 'dialog', 'title': '内容全文', 'className': 'break-all',
'body': {'type': 'tpl', 'tpl': '${keywords}'}}),
TableColumn(type='tpl', tpl='${time|date:YYYY-MM-DD HH\\:mm\\:ss}', label='最后学习时间', name='time',
sortable=True),
@@ -176,7 +178,7 @@ answer_table_on_context = TableCRUD(
syncLocation=False,
footable=True,
api='/LittlePaimon/api/get_chat_answers?context_id=${id}&page=${page}&perPage=${perPage}&orderBy=${orderBy}&orderDir=${orderDir}',
- interval=6000,
+ interval=12000,
headerToolbar=[ActionType.Ajax(label='删除该内容所有回复',
level=LevelEnum.warning,
confirmText='确定要删除该条内容已学习的回复吗?',
@@ -191,8 +193,8 @@ answer_table_on_context = TableCRUD(
api='delete:/LittlePaimon/api/delete_chat?type=answer&id=${id}')],
columns=[TableColumn(label='ID', name='id', visible=False),
TableColumn(label='群ID', name='group_id'),
- TableColumn(type='tpl', tpl='${keywords|truncate:15}', label='内容/关键词', name='keywords',
- searchable=True, popOver={'mode': 'dialog', 'title': '内容全文',
+ TableColumn(type='tpl', tpl='${keywords|truncate:20}', label='内容/关键词', name='keywords',
+ searchable=True, popOver={'mode': 'dialog', 'title': '内容全文', 'className': 'break-all',
'body': {'type': 'tpl', 'tpl': '${keywords}'}}),
TableColumn(type='tpl', tpl='${time|date:YYYY-MM-DD HH\\:mm\\:ss}', label='最后学习时间', name='time',
sortable=True),
@@ -204,7 +206,7 @@ context_table = TableCRUD(mode='table',
title='',
syncLocation=False,
api='/LittlePaimon/api/get_chat_contexts',
- interval=6000,
+ interval=12000,
headerToolbar=[ActionType.Ajax(label='删除所有学习内容',
level=LevelEnum.warning,
confirmText='确定要删除所有已学习的内容吗?',
@@ -225,9 +227,9 @@ context_table = TableCRUD(mode='table',
],
footable=True,
columns=[TableColumn(label='ID', name='id', visible=False),
- TableColumn(type='tpl', tpl='${keywords|truncate:15}', label='内容/关键词',
+ TableColumn(type='tpl', tpl='${keywords|truncate:20}', label='内容/关键词',
name='keywords', searchable=True,
- popOver={'mode': 'dialog', 'title': '内容全文',
+ popOver={'mode': 'dialog', 'title': '内容全文', 'className': 'break-all',
'body': {'type': 'tpl', 'tpl': '${keywords}'}}),
TableColumn(type='tpl', tpl='${time|date:YYYY-MM-DD HH\\:mm\\:ss}',
label='最后学习时间', name='time', sortable=True),
@@ -271,6 +273,6 @@ blacklist_page = PageSchema(url='/chat/blacklist', icon='fa fa-ban', label='禁
database_page = PageSchema(label='数据库', icon='fa fa-database',
children=[message_page, context_page, answer_page, blacklist_page])
config_page = PageSchema(url='/chat/configs', icon='fa fa-wrench', label='配置',
- schema=Page(title='配置', body=[global_config_form, group_select, group_config_form]))
+ schema=Page(title='配置', initApi='/LittlePaimon/api/get_group_list', body=[global_config_form, group_select, group_config_form]))
chat_page = PageSchema(label='群聊学习', icon='fa fa-wechat (alias)', children=[config_page, database_page])
admin_app.pages[0].children.append(chat_page)
diff --git a/LittlePaimon/plugins/alias_manager/__init__.py b/LittlePaimon/plugins/alias_manager/__init__.py
deleted file mode 100644
index 2558924..0000000
--- a/LittlePaimon/plugins/alias_manager/__init__.py
+++ /dev/null
@@ -1,127 +0,0 @@
-from argparse import Namespace
-from nonebot import on_shell_command
-from nonebot.rule import ArgumentParser
-from nonebot.plugin import PluginMetadata
-from nonebot.params import ShellCommandArgs
-from nonebot.adapters.onebot.v11 import Bot, MessageEvent, PrivateMessageEvent
-
-from .handler import handle, get_id
-from .alias_list import aliases
-
-__plugin_meta__ = PluginMetadata(
- name="命令别名",
- description="为机器人指令创建别名",
- usage=(
- "添加别名:alias {name}={command}\n"
- "查看别名:alias {name}\n"
- "别名列表:alias -p\n"
- "删除别名:unalias {name}\n"
- "清空别名:unalias -a"
- ),
- extra={
- "unique_name": "alias",
- "example": "alias '喷水'='echo 呼风唤雨'\nunalias '喷水'",
- "author": "meetwq ",
- "version": "0.3.2",
- },
-)
-
-
-alias_parser = ArgumentParser()
-alias_parser.add_argument("-p", "--print", action="store_true")
-alias_parser.add_argument("-g", "--globally", action="store_true")
-alias_parser.add_argument("names", nargs="*")
-
-alias = on_shell_command("alias", parser=alias_parser, priority=10, block=True)
-
-unalias_parser = ArgumentParser()
-unalias_parser.add_argument("-a", "--all", action="store_true")
-unalias_parser.add_argument("-g", "--globally", action="store_true")
-unalias_parser.add_argument("names", nargs="*")
-
-unalias = on_shell_command("unalias", parser=unalias_parser, priority=10)
-
-
-@alias.handle()
-async def _(bot: Bot, event: MessageEvent, args: Namespace = ShellCommandArgs()):
- gl = args.globally
- id = "global" if gl else get_id(event)
- word = "全局别名" if gl else "别名"
-
- if args.print:
- message = "全局别名:" if gl else ""
- alias_all = aliases.get_alias_all(id)
- for name in sorted(alias_all):
- message += f"\n{name}='{alias_all[name]}'"
- if not gl:
- alias_all_gl = aliases.get_alias_all("global")
- if alias_all_gl:
- message += "\n全局别名:"
- for name in sorted(alias_all_gl):
- message += f"\n{name}='{alias_all_gl[name]}'"
- message = message.strip()
- if message:
- await alias.finish(message)
- else:
- await alias.finish(f"尚未添加任何{word}")
-
- is_admin = event.sender.role in ["admin", "owner"]
- is_superuser = str(event.user_id) in bot.config.superusers
- is_private = isinstance(event, PrivateMessageEvent)
-
- if gl and not is_superuser:
- await alias.finish("管理全局别名需要超级用户权限!")
-
- if not (is_admin or is_superuser or is_private):
- await alias.finish("管理别名需要群管理员权限!")
-
- message = ""
- names = args.names
- for name in names:
- if "=" in name:
- name, command = name.split("=", 1)
- if name and command and aliases.add_alias(id, name, command):
- message += f"成功添加{word}:{name}='{command}'\n"
- else:
- command = aliases.get_alias(id, name)
- if command:
- message += f"{name}='{command}'\n"
- else:
- message += f"不存在的{word}:{name}\n"
-
- message = message.strip()
- if message:
- await alias.send(message)
-
-
-@unalias.handle()
-async def _(bot: Bot, event: MessageEvent, args: Namespace = ShellCommandArgs()):
- gl = args.globally
- id = "global" if gl else get_id(event)
- word = "全局别名" if gl else "别名"
-
- is_admin = event.sender.role in ["admin", "owner"]
- is_superuser = str(event.user_id) in bot.config.superusers
- is_private = isinstance(event, PrivateMessageEvent)
-
- if gl and not is_superuser:
- await alias.finish("管理全局别名需要超级用户权限!")
-
- if not (is_admin or is_superuser or is_private):
- await alias.finish("管理别名需要群管理员权限!")
-
- if args.all and aliases.del_alias_all(id):
- await unalias.finish(f"成功删除所有{word}")
-
- message = ""
- names = args.names
- for name in names:
- if aliases.get_alias(id, name):
- if aliases.del_alias(id, name):
- message += f"成功删除{word}:{name}\n"
- else:
- message += f"不存在的{word}:{name}\n"
-
- message = message.strip()
- if message:
- await unalias.send(message)
\ No newline at end of file
diff --git a/LittlePaimon/plugins/alias_manager/alias_list.py b/LittlePaimon/plugins/alias_manager/alias_list.py
deleted file mode 100644
index 44a6980..0000000
--- a/LittlePaimon/plugins/alias_manager/alias_list.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import json
-from pathlib import Path
-
-data_path = Path("data/alias")
-if not data_path.exists():
- data_path.mkdir(parents=True)
-
-
-class AliasList:
- def __init__(self, path: Path):
- self.path = path
- self.list = self._load_alias()
-
- def _load_alias(self) -> dict:
- if self.path.exists():
- return json.load(self.path.open("r", encoding="utf-8"))
- else:
- return {}
-
- def _dump_alias(self) -> bool:
- json.dump(
- self.list,
- self.path.open("w", encoding="utf-8"),
- indent=4,
- separators=(",", ": "),
- ensure_ascii=False,
- )
- return True
-
- def add_alias(self, id: str, name: str, command: str) -> bool:
- if id not in self.list:
- self.list[id] = {}
- self.list[id][name] = command
- return self._dump_alias()
-
- def del_alias(self, id: str, name: str) -> bool:
- if id not in self.list:
- return False
- self.list[id].pop(name, "")
- if not self.list[id]:
- self.list.pop(id, {})
- return self._dump_alias()
-
- def del_alias_all(self, id: str) -> bool:
- self.list.pop(id, {})
- return self._dump_alias()
-
- def get_alias(self, id: str, name: str) -> str:
- if id not in self.list:
- return ""
- if name not in self.list[id]:
- return ""
- return self.list[id][name]
-
- def get_alias_all(self, id: str) -> dict:
- if id not in self.list:
- return {}
- return self.list[id].copy()
-
-
-aliases = AliasList(data_path / "aliases.json")
\ No newline at end of file
diff --git a/LittlePaimon/plugins/alias_manager/handler.py b/LittlePaimon/plugins/alias_manager/handler.py
deleted file mode 100644
index f3549ad..0000000
--- a/LittlePaimon/plugins/alias_manager/handler.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent
-from nonebot.message import event_preprocessor
-
-from .parser import parse_msg
-
-
-@event_preprocessor
-async def handle(event: MessageEvent):
- msgs = event.get_message()
- if len(msgs) < 1 or msgs[0].type != "text":
- return
- msg = str(msgs[0]).lstrip()
- if not msg:
- return
-
-
- try:
- msg = parse_msg(msg, get_id(event))
- event.message[0].data["text"] = msg
- except Exception:
- return
-
-
-def get_id(event: MessageEvent) -> str:
- if event.message_type == 'group':
- return 'group_' + str(event.group_id)
- elif event.message_type == 'guild':
- return 'guild_' + str(event.guild_id)
- else:
- return 'private_' + str(event.user_id)
\ No newline at end of file
diff --git a/LittlePaimon/plugins/alias_manager/parser.py b/LittlePaimon/plugins/alias_manager/parser.py
deleted file mode 100644
index 6c3d065..0000000
--- a/LittlePaimon/plugins/alias_manager/parser.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import shlex
-from expandvars import expand, ExpandvarsException
-
-from .alias_list import aliases
-
-
-def parse_msg(msg: str, id: str) -> str:
- alias_all = aliases.get_alias_all(id)
- alias_all_gl = aliases.get_alias_all("global")
- alias_all_gl.update(alias_all)
- for name in sorted(alias_all_gl, reverse=True):
- if msg.startswith(name):
- return replace_msg(name, msg, alias_all_gl[name])
- return msg
-
-
-def replace_msg(cmd: str, msg: str, alias: str) -> str:
- if "$" not in alias:
- return alias + msg[len(cmd):]
-
- args = parse_args(cmd, msg)
- env = set_env(args)
- return parse_alias(alias, env)
-
-
-def parse_args(cmd: str, msg: str) -> list:
- if cmd.strip() == msg.strip():
- return []
- arg = msg[len(cmd):]
- try:
- return shlex.split(arg)
- except ValueError:
- return [arg]
-
-
-def set_env(args: list) -> dict:
- env = {}
- for i, arg in enumerate(args, start=1):
- env[str(i)] = arg
- env["a"] = " ".join(args)
- return env
-
-
-def parse_alias(alias: str, env: dict = {}) -> str:
- try:
- return expand(alias, environ=env)
- except ExpandvarsException:
- return alias
diff --git a/LittlePaimon/web/api/__init__.py b/LittlePaimon/web/api/__init__.py
index 101239d..921e951 100644
--- a/LittlePaimon/web/api/__init__.py
+++ b/LittlePaimon/web/api/__init__.py
@@ -6,6 +6,7 @@ from .login import route as login_route
from .plugin import route as plugin_route
from .status import route as status_route
from .utils import authentication
+from .command_alias import route as command_alias_route
BaseApiRouter = APIRouter(prefix='/LittlePaimon/api')
@@ -14,3 +15,4 @@ BaseApiRouter.include_router(plugin_route)
BaseApiRouter.include_router(bot_info_route)
BaseApiRouter.include_router(status_route)
BaseApiRouter.include_router(login_route)
+BaseApiRouter.include_router(command_alias_route)
diff --git a/LittlePaimon/web/api/bot_info.py b/LittlePaimon/web/api/bot_info.py
index 1dad1af..db5a09d 100644
--- a/LittlePaimon/web/api/bot_info.py
+++ b/LittlePaimon/web/api/bot_info.py
@@ -19,10 +19,20 @@ route = APIRouter()
@route.get('/get_group_list', response_class=JSONResponse, dependencies=[authentication()])
-async def get_group_list():
+@cache(datetime.timedelta(minutes=3))
+async def get_group_list(include_all: bool = False):
try:
group_list = await get_bot().get_group_list()
- return [{'label': f'{group["group_name"]}({group["group_id"]})', 'value': group['group_id']} for group in group_list]
+ group_list = [{'label': f'{group["group_name"]}({group["group_id"]})', 'value': group['group_id']} for group in group_list]
+ if include_all:
+ group_list.insert(0, {'label': '全局', 'value': 'all'})
+ return {
+ 'status': 0,
+ 'msg': 'ok',
+ 'data': {
+ 'group_list': group_list
+ }
+ }
except ValueError:
return {
'status': -100,
@@ -31,6 +41,7 @@ async def get_group_list():
@route.get('/get_group_members', response_class=JSONResponse, dependencies=[authentication()])
+@cache(datetime.timedelta(minutes=3))
async def get_group_members(group_id: int):
try:
return await get_bot().get_group_member_list(group_id=group_id)
@@ -42,7 +53,7 @@ async def get_group_members(group_id: int):
@route.get('/get_groups_and_members', response_class=JSONResponse, dependencies=[authentication()])
-@cache(datetime.timedelta(minutes=10))
+@cache(datetime.timedelta(minutes=3))
async def get_groups_and_members():
result = []
try:
@@ -87,7 +98,7 @@ async def get_groups_and_members():
@route.get('/get_friend_list', response_class=JSONResponse, dependencies=[authentication()])
-@cache(datetime.timedelta(minutes=10))
+@cache(datetime.timedelta(minutes=3))
async def get_friend_list():
try:
bot: Bot = get_bot()
diff --git a/LittlePaimon/web/api/command_alias.py b/LittlePaimon/web/api/command_alias.py
new file mode 100644
index 0000000..effdbae
--- /dev/null
+++ b/LittlePaimon/web/api/command_alias.py
@@ -0,0 +1,55 @@
+from fastapi import APIRouter
+from fastapi.responses import JSONResponse
+
+from tortoise.queryset import Q
+from LittlePaimon.database import CommandAlias
+from LittlePaimon.config import ConfigManager
+from LittlePaimon.config.command import modify_msg
+
+from .utils import authentication
+
+route = APIRouter()
+
+
+@route.get('/command_alias', response_class=JSONResponse, dependencies=[authentication()])
+async def get_command_alias():
+ alias = await CommandAlias.all().order_by('priority').values()
+ return {
+ 'status': 0,
+ 'msg': 'ok',
+ 'data': {
+ 'command_alias_enable': ConfigManager.config.command_alias_enable,
+ 'items': alias,
+ }
+ }
+
+
+@route.post('/command_alias', response_class=JSONResponse, dependencies=[authentication()])
+async def add_command_alias(data: dict):
+ ConfigManager.config.update(command_alias_enable=data['command_alias_enable'])
+ ConfigManager.save()
+ data = data['items']
+ await CommandAlias.filter(id__not_in=[a['id'] for a in data if a.get('id')]).delete()
+ for alias in data:
+ alias['priority'] = data.index(alias)
+ if alias.get('id'):
+ await CommandAlias.filter(id=alias.pop('id')).update(**alias)
+ else:
+ await CommandAlias.create(**alias)
+ return {
+ 'status': 0,
+ 'msg': '命令别名保存成功'
+ }
+
+
+@route.get('/test_command_alias', response_class=JSONResponse, dependencies=[authentication()])
+async def test_command_alias(group_id: str, message: str):
+ all_alias = await CommandAlias.filter(Q(group_id=group_id) | Q(group_id='all')).order_by('priority')
+ msg = modify_msg(all_alias, message)
+ return {
+ 'status': 0,
+ 'msg': '测试成功',
+ 'data': {
+ 'new_msg': msg
+ }
+ }
diff --git a/LittlePaimon/web/pages/command_alias.py b/LittlePaimon/web/pages/command_alias.py
new file mode 100644
index 0000000..4e5dc52
--- /dev/null
+++ b/LittlePaimon/web/pages/command_alias.py
@@ -0,0 +1,71 @@
+from amis import Form, Switch, InputSubForm, Hidden, ButtonGroupSelect, InputText, Radios, Checkbox, Select, Static, Alert, Html, PageSchema, Page
+
+main_form = Form(
+ title='命令别名',
+ initApi='get:/LittlePaimon/api/command_alias',
+ api='post:/LittlePaimon/api/command_alias',
+ submitText='保存',
+ body=[
+ Switch(name='command_alias_enable', label='功能开关', onText='全局启用', offText='全局关闭'),
+ InputSubForm(
+ name='items',
+ label='已设置的命令别名',
+ multiple=True,
+ btnLabel='${alias} >> ${command}',
+ draggable=True,
+ addable=True,
+ removable=True,
+ addButtonText='添加命令别名',
+ showErrorMsg=False,
+ form=Form(
+ title='命令别名',
+ body=[
+ Hidden(name='id'),
+ ButtonGroupSelect(name='is_regex', label='匹配模式', value=False,
+ options=[
+ {'label': '普通匹配', 'value': False},
+ {'label': '正则匹配', 'value': True}
+ ]),
+ InputText(name='alias', label='命令别名', required=True),
+ InputText(name='command', label='原命令', required=True),
+ Radios(name='mode', label='匹配位置', value='前缀', hiddenOn='${is_regex == true}', required=True,
+ options=[
+ {
+ 'label': '前缀',
+ 'value': '前缀'
+ },
+ {
+ 'label': '后缀',
+ 'value': '后缀'
+ },
+ {
+ 'label': '全匹配',
+ 'value': '全匹配'
+ }
+ ]),
+ Checkbox(name='is_reverse', label='是否反转', hiddenOn='${is_regex == true}'),
+ Select(name='group_id', label='设置群', value='all', required=True,
+ source='${group_list}')
+ ]
+ )
+ )
+ ]
+)
+
+test_form = Form(
+ title='测试',
+ api='get:/LittlePaimon/api/test_command_alias?group_id=${group_id}&message=${message}',
+ submitText='测试',
+ body=[
+ Select(name='group_id', label='测试群', value='all', required=True, source='${group_list}'),
+ InputText(name='message', label='测试消息', required=True),
+ Static(className='text-red-600', name='new_msg', label='命令别名修改后消息', visibleOn="typeof data.new_msg !== 'undefined'")
+ ]
+)
+
+tips = Alert(level='info',
+ body=Html(html='命令别名的详细用法和配置例子可以在文档中查看,配置保存后,可以在下方的"测试"一栏进行实时测试。'))
+
+
+page = PageSchema(url='/bot_config/command_alias', icon='fa fa-angle-double-right', label='命令别名',
+ schema=Page(title='', initApi='/LittlePaimon/api/get_group_list?include_all=true', body=[tips, main_form, test_form]))
diff --git a/LittlePaimon/web/pages/main.py b/LittlePaimon/web/pages/main.py
index 70b6891..8e8d8fb 100644
--- a/LittlePaimon/web/pages/main.py
+++ b/LittlePaimon/web/pages/main.py
@@ -5,6 +5,7 @@ from .home_page import page as home_page
from .plugin_manage import page as plugin_manage_page
from .private_cookie import page as private_cookie_page
from .public_cookie import page as public_cookie_page
+from .command_alias import page as command_alias_page
github_logo = Tpl(className='w-full',
@@ -21,7 +22,7 @@ admin_app = App(brandName='LittlePaimon',
PageSchema(label='Cookie管理', icon='fa fa-key',
children=[public_cookie_page, private_cookie_page]),
PageSchema(label='机器人配置', icon='fa fa-wrench',
- children=[plugin_manage_page, config_page])
+ children=[plugin_manage_page, config_page, command_alias_page])
]}],
footer=f'')
diff --git a/LittlePaimon/web/pages/plugin_manage.py b/LittlePaimon/web/pages/plugin_manage.py
index 1b69917..e1cfc63 100644
--- a/LittlePaimon/web/pages/plugin_manage.py
+++ b/LittlePaimon/web/pages/plugin_manage.py
@@ -121,12 +121,11 @@ cards_curd = CardsCRUD(mode='cards',
syncLocation=False,
api='/LittlePaimon/api/get_plugins',
loadDataOnce=True,
- source='${rows | filter:name:match:keywords_name | filter:description:match:keywords_description | filter:status:match:status}',
+ source='${rows | filter:name:match:keywords_name | filter:description:match:keywords_description}',
filter={
'body': [
InputText(name='keywords_name', label='插件名'),
- InputText(name='keywords_description', label='插件描述'),
- Switch(name='status', label='插件状态', onText='启用', offText='禁用')
+ InputText(name='keywords_description', label='插件描述')
]
},
perPage=12,