Compare commits

...

32 Commits

Author SHA1 Message Date
Soulter 0c8ebc2b06 chore: clean up 2025-02-16 16:52:13 +08:00
Soulter b3b5ebc2ca v3.4.28 2025-02-16 16:19:03 +08:00
Soulter b8aa23ccc5 🐛fix: 修复转发消息的字数阈值功能#510 2025-02-16 15:54:29 +08:00
Soulter 364843db29 Merge pull request #389 from Nothingness-Void/新增过滤掉正则表达式内容
新增过滤掉正则表达式内容
2025-02-16 15:28:51 +08:00
Soulter aa56c8f7e6 Merge branch 'master' into 新增过滤掉正则表达式内容 2025-02-16 15:27:30 +08:00
Soulter 8e9fd27058 merge branch master 2025-02-16 15:17:44 +08:00
Soulter b75908cb2a Merge pull request #517 from Cvandia/master
 feat: 添加命令和命令组的别名支持
2025-02-16 14:51:47 +08:00
Soulter af6df49ce1 perf: 补充别名为可选参数以前向兼容 2025-02-16 14:50:49 +08:00
Cvandia bd3bdb5769 feat: 添加命令和命令组的别名支持 2025-02-16 14:44:17 +08:00
Soulter 98fe193b21 Merge pull request #477 from AraragiEro/master
[Feature] 希望添加更为灵活的filter.permission_type使用方式,使用户能自定义权限类型
2025-02-16 13:53:07 +08:00
Soulter 26cbc9e8b1 chore: cleanup 2025-02-16 13:32:28 +08:00
Alero ebb8c43fd0 bug: 尝试修复cleancode错误 2025-02-16 10:56:17 +08:00
Soulter 8c7344f1c4 👌perf(qq): supports to pass OneBot notice, request event 2025-02-16 01:04:08 +08:00
Soulter 5c32a17787 👌perf: 优化了分段回复和回复时at,引用都打开时的一些体验性问题 2025-02-15 19:29:34 +08:00
Soulter aff520e69a fix: 修复 Dify 下无法主动回复的问题 #494 2025-02-15 18:31:21 +08:00
Alero 45e627c33c fix: a bug when add filter to root command group 2025-02-14 23:52:31 +08:00
Alero 7a1b158f83 fix: cleancode err 2025-02-14 22:46:22 +08:00
Alero 6374c5d49d fix: add & | operation to customfilter 2025-02-14 22:33:32 +08:00
Alero fd460b19d4 fix: cleancode err 2025-02-14 20:43:54 +08:00
Alero dff7cc4ca5 feat: when custom filter cant pass, won't raise error anymore.
and when you use a command group and dont have custom filter access, the return group tree wont contain the command that you dont have permisson.
2025-02-14 20:34:31 +08:00
Alero d013320bec feat: more powerful CustomFilter 2025-02-14 19:15:19 +08:00
Soulter fc6dcfaf21 🐛 fix: cannot search plugin 2025-02-14 18:45:56 +08:00
Soulter a001270bd2 feat: webui supports to search plugin via name 2025-02-14 18:43:04 +08:00
Soulter 9e67883fbd 🐛 fix: add no_proxy env vars to support localhost requests, fix 502 error when use ollama #504 2025-02-14 16:51:02 +08:00
Soulter f1a448708c 🐛 fix: segmented reply caused incomplete non-llm-response #503 2025-02-14 16:19:09 +08:00
Soulter a4bfa96502 feat: 支持自定义 Dify 工作流文本输入变量名 #441 2025-02-14 15:41:02 +08:00
Soulter 595b83a256 🐛 FIX: cannot send file in private chat when turn on the reply with quote #262 2025-02-14 14:41:41 +08:00
Alero c6f037cae2 fix: a undefine mistake 2025-02-12 03:25:01 +08:00
Alero f049830e28 Merge branch 'master' of github.com:AraragiEro/AstrBot 2025-02-12 03:06:23 +08:00
Alero dd1995ae0b feat: add a way to define custom permission filter. 2025-02-12 03:05:51 +08:00
Xu Void 7155b4f0ac Update default.py 2025-02-08 10:16:31 +08:00
Xu Void 0021cfc4bc 新增过滤掉正则表达式内容
Fixes #338

新增过滤掉正则表达式内容

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/Soulter/AstrBot/issues/338?shareId=XXXX-XXXX-XXXX-XXXX).
2025-02-06 15:28:28 +08:00
25 changed files with 503 additions and 156 deletions
+4
View File
@@ -5,6 +5,7 @@ from astrbot.core.star.register import (
register_regex as regex,
register_platform_adapter_type as platform_adapter_type,
register_permission_type as permission_type,
register_custom_filter as custom_filter,
register_on_llm_request as on_llm_request,
register_on_llm_response as on_llm_response,
register_llm_tool as llm_tool,
@@ -15,6 +16,7 @@ from astrbot.core.star.register import (
from astrbot.core.star.filter.event_message_type import EventMessageTypeFilter, EventMessageType
from astrbot.core.star.filter.platform_adapter_type import PlatformAdapterTypeFilter, PlatformAdapterType
from astrbot.core.star.filter.permission import PermissionTypeFilter, PermissionType
from astrbot.core.star.filter.custom_filter import CustomFilter
__all__ = [
'command',
@@ -28,6 +30,8 @@ __all__ = [
'PlatformAdapterTypeFilter',
'PlatformAdapterType',
'PermissionTypeFilter',
'CustomFilter',
'custom_filter',
'PermissionType',
'on_llm_request',
'llm_tool',
+16 -2
View File
@@ -2,7 +2,7 @@
如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。
"""
VERSION = "3.4.27"
VERSION = "3.4.28"
DB_PATH = "data/data_v3.db"
# 默认配置
@@ -32,7 +32,8 @@ DEFAULT_CONFIG = {
"interval": "1.5,3.5",
"log_base": 2.6,
"words_count_threshold": 150,
"regex": ".*?[。?!~…]+|.+$"
"regex": ".*?[。?!~…]+|.+$",
"content_cleanup_rule": "",
},
"no_permission_reply": True,
},
@@ -271,6 +272,12 @@ CONFIG_METADATA_2 = {
"obvious_hint": True,
"hint": "用于分隔一段消息。默认情况下会根据句号、问号等标点符号分隔。re.findall(r'<regex>', text)",
},
"content_cleanup_rule": {
"description": "过滤分段后的内容",
"type": "string",
"obvious_hint": True,
"hint": "移除分段后的内容中的指定的内容。支持正则表达式。如填写 `[。?!]` 将移除所有的句号、问号、感叹号。re.sub(r'<regex>', '', text)",
},
},
},
"reply_prefix": {
@@ -496,6 +503,7 @@ CONFIG_METADATA_2 = {
"dify_api_key": "",
"dify_api_base": "https://api.dify.ai/v1",
"dify_workflow_output_key": "",
"dify_query_input_key": "astrbot_text_query",
"timeout": 60,
},
"whisper(API)": {
@@ -647,6 +655,12 @@ CONFIG_METADATA_2 = {
"type": "string",
"hint": "Dify Workflow 输出变量名。当应用类型为 workflow 时才使用。默认为 astrbot_wf_output。",
},
"dify_query_input_key": {
"description": "Prompt 输入变量名",
"type": "string",
"hint": "发送的消息文本内容对应的输入变量名。默认为 astrbot_text_query。",
"obvious": True,
}
},
},
"provider_settings": {
+2 -1
View File
@@ -27,7 +27,8 @@ class AstrBotCoreLifecycle:
os.environ['https_proxy'] = self.astrbot_config['http_proxy']
os.environ['http_proxy'] = self.astrbot_config['http_proxy']
os.environ['no_proxy'] = 'localhost,127.0.0.1'
async def initialize(self):
logger.info("AstrBot v"+ VERSION)
if os.environ.get("TESTING", ""):
@@ -35,7 +35,6 @@ class ProcessStage(Stage):
# 生成器返回值处理
if isinstance(resp, ProviderRequest):
# Handler 的 LLM 请求
logger.debug(f"llm request -> {resp.prompt}")
event.set_extra("provider_request", resp)
_t = False
async for _ in self.llm_request_sub_stage.process(event):
+19 -3
View File
@@ -7,14 +7,17 @@ from ..context import PipelineContext
from astrbot.core.platform.astr_message_event import AstrMessageEvent
from astrbot.core.message.message_event_result import MessageChain
from astrbot.core import logger
from astrbot.core.message.message_event_result import BaseMessageComponent, Plain
from astrbot.core.message.message_event_result import BaseMessageComponent
from astrbot.core.star.star_handler import star_handlers_registry, EventType
from astrbot.core.message.components import Plain, Reply, At
@register_stage
class RespondStage(Stage):
async def initialize(self, ctx: PipelineContext):
self.ctx = ctx
self.reply_with_mention = ctx.astrbot_config['platform_settings']['reply_with_mention']
self.reply_with_quote = ctx.astrbot_config['platform_settings']['reply_with_quote']
# 分段回复
self.enable_seg: bool = ctx.astrbot_config['platform_settings']['segmented_reply']['enable']
self.only_llm_result = ctx.astrbot_config['platform_settings']['segmented_reply']['only_llm_result']
@@ -60,11 +63,24 @@ class RespondStage(Stage):
await event._pre_send()
if self.enable_seg and ((self.only_llm_result and result.is_llm_result()) or not self.only_llm_result):
decorated_comps = []
if self.reply_with_mention:
for comp in result.chain:
if isinstance(comp, At):
decorated_comps.append(comp)
result.chain.remove(comp)
break
if self.reply_with_quote:
for comp in result.chain:
if isinstance(comp, Reply):
decorated_comps.append(comp)
result.chain.remove(comp)
break
# 分段回复
for comp in result.chain:
i = await self._calc_comp_interval(comp)
await asyncio.sleep(i)
await event.send(MessageChain([comp]))
await event.send(MessageChain([*decorated_comps, comp]))
else:
await event.send(result)
await event._post_send()
+39 -11
View File
@@ -7,7 +7,7 @@ from ..context import PipelineContext
from astrbot.core.platform.astr_message_event import AstrMessageEvent
from astrbot.core.platform.message_type import MessageType
from astrbot.core import logger
from astrbot.core.message.components import Plain, Image, At, Reply, Record
from astrbot.core.message.components import Plain, Image, At, Reply, Record, File, Node
from astrbot.core import html_renderer
from astrbot.core.star.star_handler import star_handlers_registry, EventType
@@ -25,12 +25,16 @@ class ResultDecorateStage(Stage):
self.t2i_word_threshold = 50
except BaseException:
self.t2i_word_threshold = 150
self.forward_threshold = ctx.astrbot_config['platform_settings']['forward_threshold']
# 分段回复
self.words_count_threshold = int(ctx.astrbot_config['platform_settings']['segmented_reply']['words_count_threshold'])
self.enable_segmented_reply = ctx.astrbot_config['platform_settings']['segmented_reply']['enable']
self.only_llm_result = ctx.astrbot_config['platform_settings']['segmented_reply']['only_llm_result']
self.regex = ctx.astrbot_config['platform_settings']['segmented_reply']['regex']
self.content_cleanup_rule = ctx.astrbot_config['platform_settings']['segmented_reply']['content_cleanup_rule']
# exception
self.content_safe_check_reply = ctx.astrbot_config['content_safety']['also_use_in_response']
@@ -82,12 +86,16 @@ class ResultDecorateStage(Stage):
# 不分段回复
new_chain.append(comp)
continue
split_response = re.findall(self.regex, comp.text)
split_response = []
for line in comp.text.split("\n"):
split_response.extend(re.findall(self.regex, line))
if not split_response:
new_chain.append(comp)
continue
for seg in split_response:
if seg:
if self.content_cleanup_rule:
seg = re.sub(self.content_cleanup_rule, "", seg)
if seg.strip():
new_chain.append(Plain(seg))
else:
# 非 Plain 类型的消息段不分段
@@ -137,12 +145,32 @@ class ResultDecorateStage(Stage):
if url:
result.chain = [Image.fromURL(url)]
# at 回复
if self.reply_with_mention and event.get_message_type() != MessageType.FRIEND_MESSAGE:
result.chain.insert(0, At(qq=event.get_sender_id(), name=event.get_sender_name()))
if len(result.chain) > 1 and isinstance(result.chain[1], Plain):
result.chain[1].text = "\n" + result.chain[1].text
# 触发转发消息
has_forwarded = False
if event.get_platform_name() == 'aiocqhttp':
word_cnt = 0
for comp in result.chain:
if isinstance(comp, Plain):
word_cnt += len(comp.text)
if word_cnt > self.forward_threshold:
node = Node(
uin=event.get_self_id(),
name="AstrBot",
content=[
*result.chain
]
)
result.chain = [node]
has_forwarded = True
# 引用回复
if self.reply_with_quote:
result.chain.insert(0, Reply(id=event.message_obj.message_id))
if not has_forwarded:
# at 回复
if self.reply_with_mention and event.get_message_type() != MessageType.FRIEND_MESSAGE:
result.chain.insert(0, At(qq=event.get_sender_id(), name=event.get_sender_name()))
if len(result.chain) > 1 and isinstance(result.chain[1], Plain):
result.chain[1].text = "\n" + result.chain[1].text
# 引用回复
if self.reply_with_quote:
if not any(isinstance(item, File) for item in result.chain):
result.chain.insert(0, Reply(id=event.message_obj.message_id))
+2 -2
View File
@@ -12,14 +12,14 @@ class PipelineScheduler():
async def initialize(self):
for stage in registered_stages:
logger.debug(f"初始化阶段 {stage.__class__ .__name__}")
# logger.debug(f"初始化阶段 {stage.__class__ .__name__}")
await stage.initialize(self.ctx)
async def _process_stages(self, event: AstrMessageEvent, from_stage=0):
for i in range(from_stage, len(registered_stages)):
stage = registered_stages[i]
logger.debug(f"执行阶段 {stage.__class__ .__name__}")
# logger.debug(f"执行阶段 {stage.__class__ .__name__}")
coro = stage.process(event)
if isinstance(coro, AsyncGenerator):
async for _ in coro:
@@ -2,6 +2,7 @@ import os
import time
import asyncio
import logging
import uuid
from typing import Awaitable, Any
from aiocqhttp import CQHttp, Event
from astrbot.api.platform import Platform, AstrBotMessage, MessageMember, MessageType, PlatformMetadata
@@ -46,18 +47,81 @@ class AiocqhttpAdapter(Platform):
await super().send_by_session(session, message_chain)
async def convert_message(self, event: Event) -> AstrBotMessage:
logger.debug(f"[aiocqhttp] RawMessage {event}")
if event['post_type'] == 'message':
abm = await self._convert_handle_message_event(event)
elif event['post_type'] == 'notice':
abm = await self._convert_handle_notice_event(event)
elif event['post_type'] == 'request':
abm = await self._convert_handle_request_event(event)
return abm
async def _convert_handle_request_event(self, event: Event) -> AstrBotMessage:
'''OneBot V11 请求类事件'''
abm = AstrBotMessage()
abm.self_id = str(event.self_id)
abm.tag = "aiocqhttp"
abm.sender = MessageMember(
user_id=event.user_id,
nickname=event.user_id
)
abm.type = MessageType.OTHER_MESSAGE
if 'group_id' in event and event['group_id']:
abm.type = MessageType.GROUP_MESSAGE
abm.group_id = str(event.group_id)
else:
abm.type = MessageType.FRIEND_MESSAGE
if self.unique_session and abm.type == MessageType.GROUP_MESSAGE:
abm.session_id = abm.sender.user_id + "_" + str(event.group_id)
abm.message_str = ''
abm.message = []
abm.timestamp = int(time.time())
abm.message_id = uuid.uuid4().hex
abm.raw_message = event
return abm
async def _convert_handle_notice_event(self, event: Event) -> AstrBotMessage:
'''OneBot V11 通知类事件'''
abm = AstrBotMessage()
abm.self_id = str(event.self_id)
abm.sender = MessageMember(
user_id=event.user_id,
nickname=event.user_id
)
abm.type = MessageType.OTHER_MESSAGE
if 'group_id' in event and event['group_id']:
abm.group_id = str(event.group_id)
abm.type = MessageType.GROUP_MESSAGE
else:
abm.type = MessageType.FRIEND_MESSAGE
if self.unique_session and abm.type == MessageType.GROUP_MESSAGE:
abm.session_id = abm.sender.user_id + "_" + str(event.group_id) # 也保留群组 id
else:
abm.session_id = str(event.group_id) if abm.type == MessageType.GROUP_MESSAGE else abm.sender.user_id
abm.message_str = ""
abm.message = []
abm.raw_message = event
abm.timestamp = int(time.time())
abm.message_id = uuid.uuid4().hex
abm.sender = MessageMember(str(event.sender['user_id']), event.sender['nickname'])
if 'sub_type' in event:
if event['sub_type'] == 'poke' and 'target_id' in event:
abm.message.append(Poke(qq=str(event['target_id']), type='poke')) # noqa: F405
return abm
async def _convert_handle_message_event(self, event: Event) -> AstrBotMessage:
'''OneBot V11 消息类事件'''
abm = AstrBotMessage()
abm.self_id = str(event.self_id)
abm.sender = MessageMember(str(event.sender['user_id']), event.sender['nickname'])
if event['message_type'] == 'group':
abm.type = MessageType.GROUP_MESSAGE
abm.group_id = str(event.group_id)
elif event['message_type'] == 'private':
abm.type = MessageType.FRIEND_MESSAGE
if self.unique_session and abm.type == MessageType.GROUP_MESSAGE:
abm.session_id = abm.sender.user_id + "_" + str(event.group_id) # 也保留群组 id
else:
@@ -75,7 +139,8 @@ class AiocqhttpAdapter(Platform):
except BaseException as e:
logger.error(f"回复消息失败: {e}")
return
logger.debug(f"aiocqhttp: 收到消息: {event.message}")
# 按消息段类型类型适配
for m in event.message:
t = m['type']
a = None
@@ -118,6 +183,7 @@ class AiocqhttpAdapter(Platform):
abm.timestamp = int(time.time())
abm.message_str = message_str
abm.raw_message = event
return abm
def run(self) -> Awaitable[Any]:
@@ -127,6 +193,19 @@ class AiocqhttpAdapter(Platform):
self.port = 6199
self.bot = CQHttp(use_ws_reverse=True, import_name='aiocqhttp', api_timeout_sec=180)
@self.bot.on_request()
async def request(event: Event):
abm = await self.convert_message(event)
if abm:
await self.handle_msg(abm)
@self.bot.on_notice()
async def notice(event: Event):
abm = await self.convert_message(event)
if abm:
await self.handle_msg(abm)
@self.bot.on_message('group')
async def group(event: Event):
abm = await self.convert_message(event)
@@ -134,6 +134,6 @@ class GewechatPlatformEvent(AstrMessageEvent):
elif isinstance(comp, At):
pass
else:
logger.error(f"gewechat 暂不支持发送消息类型: {comp.type}")
logger.debug(f"gewechat 忽略: {comp.type}")
await super().send(message)
@@ -5,7 +5,7 @@ import botpy.types.message
from astrbot.core.utils.io import file_to_base64, download_image_by_url
from astrbot.api.event import AstrMessageEvent, MessageChain
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
from astrbot.api.message_components import Plain, Image, Reply
from astrbot.api.message_components import Plain, Image
from botpy import Client
from botpy.http import Route
from astrbot.api import logger
@@ -33,19 +33,6 @@ class QQOfficialMessageEvent(AstrMessageEvent):
if not plain_text and not image_base64 and not image_path:
return
ref = None
for i in self.send_buffer.chain:
if isinstance(i, Reply):
try:
ref = self.message_obj.raw_message.message_reference
ref = botpy.types.message.Reference(
message_id=ref.message_id,
ignore_get_message_error=False
)
except BaseException as _:
pass
break
payload = {
'content': plain_text,
'msg_id': self.message_obj.message_id,
@@ -53,30 +40,22 @@ class QQOfficialMessageEvent(AstrMessageEvent):
match type(source):
case botpy.message.GroupMessage:
if ref:
payload['message_reference'] = ref
if image_base64:
media = await self.upload_group_and_c2c_image(image_base64, 1, group_openid=source.group_openid)
payload['media'] = media
payload['msg_type'] = 7
await self.bot.api.post_group_message(group_openid=source.group_openid, **payload)
case botpy.message.C2CMessage:
if ref:
payload['message_reference'] = ref
if image_base64:
media = await self.upload_group_and_c2c_image(image_base64, 1, openid=source.author.user_openid)
payload['media'] = media
payload['msg_type'] = 7
await self.bot.api.post_c2c_message(openid=source.author.user_openid, **payload)
case botpy.message.Message:
if ref:
payload['message_reference'] = ref
if image_path:
payload['file_image'] = image_path
await self.bot.api.post_message(channel_id=source.channel_id, **payload)
case botpy.message.DirectMessage:
if ref:
payload['message_reference'] = ref
if image_path:
payload['file_image'] = image_path
await self.bot.api.post_dms(guild_id=source.guild_id, **payload)
@@ -119,5 +98,5 @@ class QQOfficialMessageEvent(AstrMessageEvent):
image_base64 = file_to_base64(i.file).replace("base64://", "")
image_file_path = i.file
else:
logger.error(f"qq_official 暂不支持发送消息类型 {i.type}")
logger.debug(f"qq_official 忽略 {i.type}")
return plain_text, image_base64, image_file_path
@@ -39,6 +39,6 @@ class WebChatMessageEvent(AstrMessageEvent):
f.write(f2.read())
web_chat_back_queue.put_nowait((f"[IMAGE]{filename}", cid))
else:
logger.error(f"webchat 暂不支持发送消息类型: {comp.type}")
logger.debug(f"webchat 忽略: {comp.type}")
web_chat_back_queue.put_nowait(None)
await super().send(message)
+5 -2
View File
@@ -31,6 +31,9 @@ class ProviderDify(Provider):
raise Exception("Dify API 类型不能为空。")
self.model_name = "dify"
self.workflow_output_key = provider_config.get("dify_workflow_output_key", "astrbot_wf_output")
self.dify_query_input_key = provider_config.get("dify_query_input_key", "astrbot_text_query")
if not self.dify_query_input_key:
self.dify_query_input_key = "astrbot_text_query"
self.timeout = provider_config.get("timeout", 120)
if isinstance(self.timeout, str):
self.timeout = int(self.timeout)
@@ -95,7 +98,7 @@ class ProviderDify(Provider):
case "workflow":
async for chunk in self.api_client.workflow_run(
inputs={
"astrbot_text_query": prompt,
self.dify_query_input_key: prompt,
"astrbot_session_id": session_id,
**session_var
},
@@ -121,7 +124,7 @@ class ProviderDify(Provider):
return LLMResponse(role="assistant", completion_text=result)
async def forget(self, session_id):
self.conversation_ids.pop(session_id, None)
self.conversation_ids[session_id] = ""
return True
async def get_current_key(self):
+19 -3
View File
@@ -1,19 +1,23 @@
import re
import inspect
from typing import List
from . import HandlerFilter
from astrbot.core.platform.astr_message_event import AstrMessageEvent
from astrbot.core.config import AstrBotConfig
from astrbot.core.utils.param_validation_mixin import ParameterValidationMixin
from .custom_filter import CustomFilter
from ..star_handler import StarHandlerMetadata
# 标准指令受到 wake_prefix 的制约。
class CommandFilter(HandlerFilter, ParameterValidationMixin):
'''标准指令过滤器'''
def __init__(self, command_name: str, handler_md: StarHandlerMetadata = None):
def __init__(self, command_name: str, alias: set = None, handler_md: StarHandlerMetadata = None):
self.command_name = command_name
self.alias = alias if alias else set()
if handler_md:
self.init_handler_md(handler_md)
self.custom_filter_list: List[CustomFilter] = []
def print_types(self):
result = ""
@@ -42,10 +46,22 @@ class CommandFilter(HandlerFilter, ParameterValidationMixin):
def get_handler_md(self) -> StarHandlerMetadata:
return self.handler_md
def add_custom_filter(self, custom_filter: CustomFilter):
self.custom_filter_list.append(custom_filter)
def custom_filter_ok(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool:
for custom_filter in self.custom_filter_list:
if not custom_filter.filter(event, cfg):
return False
return True
def filter(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool:
if not event.is_at_or_wake_command:
return False
if not self.custom_filter_ok(event, cfg):
return False
if event.get_extra("parsing_command"):
message_str = event.get_extra("parsing_command").strip()
else:
@@ -53,7 +69,7 @@ class CommandFilter(HandlerFilter, ParameterValidationMixin):
# 分割为列表(每个参数之间可能会有多个空格)
ls = re.split(r"\s+", message_str)
if self.command_name != ls[0]:
if self.command_name != ls[0] and ls[0] not in self.alias:
return False
# if len(self.handler_params) == 0 and len(ls) > 1:
# # 一定程度避免 LLM 聊天时误判为指令
+50 -21
View File
@@ -6,36 +6,61 @@ from . import HandlerFilter
from .command import CommandFilter
from astrbot.core.platform.astr_message_event import AstrMessageEvent
from astrbot.core.config import AstrBotConfig
from .custom_filter import CustomFilter
from ..star_handler import StarHandlerMetadata
# 指令组受到 wake_prefix 的制约。
class CommandGroupFilter(HandlerFilter):
def __init__(self, group_name: str):
def __init__(self, group_name: str, alias: set = None):
self.group_name = group_name
self.alias = alias if alias else set()
self.sub_command_filters: List[Union[CommandFilter, CommandGroupFilter]] = []
self.custom_filter_list: List[CustomFilter] = []
def add_sub_command_filter(self, sub_command_filter: Union[CommandFilter, CommandGroupFilter]):
self.sub_command_filters.append(sub_command_filter)
def add_custom_filter(self, custom_filter: CustomFilter):
self.custom_filter_list.append(custom_filter)
# 以树的形式打印出来
def print_cmd_tree(self, sub_command_filters: List[Union[CommandFilter, CommandGroupFilter]], prefix: str = "") -> str:
def print_cmd_tree(self,
sub_command_filters: List[Union[CommandFilter, CommandGroupFilter]],
prefix: str = "",
event: AstrMessageEvent = None,
cfg: AstrBotConfig = None,
) -> str:
result = ""
for sub_filter in sub_command_filters:
if isinstance(sub_filter, CommandFilter):
cmd_th = sub_filter.print_types()
result += f"{prefix}├── {sub_filter.command_name}"
if cmd_th:
result += f" ({cmd_th})"
else:
result += " (无参数指令)"
result += "\n"
custom_filter_pass = True
if event and cfg:
custom_filter_pass = sub_filter.custom_filter_ok(event, cfg)
if custom_filter_pass:
cmd_th = sub_filter.print_types()
result += f"{prefix}├── {sub_filter.command_name}"
if cmd_th:
result += f" ({cmd_th})"
else:
result += " (无参数指令)"
result += "\n"
elif isinstance(sub_filter, CommandGroupFilter):
result += f"{prefix}├── {sub_filter.group_name}"
result += "\n"
result += sub_filter.print_cmd_tree(sub_filter.sub_command_filters, prefix+"")
custom_filter_pass = True
if event and cfg:
custom_filter_pass = sub_filter.custom_filter_ok(event, cfg)
if custom_filter_pass:
result += f"{prefix}├── {sub_filter.group_name}"
result += "\n"
result += sub_filter.print_cmd_tree(sub_filter.sub_command_filters, prefix+"", event=event, cfg=cfg)
return result
def custom_filter_ok(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool:
for custom_filter in self.custom_filter_list:
if not custom_filter.filter(event, cfg):
return False
return True
def filter(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> Tuple[bool, StarHandlerMetadata]:
if not event.is_at_or_wake_command:
return False, None
@@ -46,8 +71,8 @@ class CommandGroupFilter(HandlerFilter):
message_str = event.get_message_str().strip()
ls = re.split(r"\s+", message_str)
if ls[0] != self.group_name:
if ls[0] != self.group_name and ls[0] not in self.alias:
return False, None
# 改写 message_str
ls = ls[1:]
@@ -56,12 +81,16 @@ class CommandGroupFilter(HandlerFilter):
parsing_command = " ".join(ls)
parsing_command = parsing_command.strip()
event.set_extra("parsing_command", parsing_command)
# 判断当前指令组的自定义过滤器
if not self.custom_filter_ok(event, cfg):
return False, None
if parsing_command == "":
# 当前还是指令组
tree = self.group_name + "\n" + self.print_cmd_tree(self.sub_command_filters)
tree = self.group_name + "\n" + self.print_cmd_tree(self.sub_command_filters, event=event, cfg=cfg)
raise ValueError(f"指令组 {self.group_name} 未填写完全。这个指令组下有如下指令:\n"+tree)
child_command_handler_md = None
for sub_filter in self.sub_command_filters:
if isinstance(sub_filter, CommandFilter):
@@ -73,5 +102,5 @@ class CommandGroupFilter(HandlerFilter):
if ok:
child_command_handler_md = handler
return True, child_command_handler_md
tree = self.group_name + "\n" + self.print_cmd_tree(self.sub_command_filters)
tree = self.group_name + "\n" + self.print_cmd_tree(self.sub_command_filters, event=event, cfg=cfg)
raise ValueError(f"指令组 {self.group_name} 下没有找到对应的指令。这个指令组下有如下指令:\n"+tree)
+53
View File
@@ -0,0 +1,53 @@
from abc import abstractmethod, ABCMeta
from . import HandlerFilter
from astrbot.core.platform.astr_message_event import AstrMessageEvent
from astrbot.core.config import AstrBotConfig
class CustomFilterMeta(ABCMeta):
def __and__(cls, other):
if not issubclass(other, CustomFilter):
raise TypeError("Operands must be subclasses of CustomFilter.")
return CustomFilterAnd(cls(), other())
def __or__(cls, other):
if not issubclass(other, CustomFilter):
raise TypeError("Operands must be subclasses of CustomFilter.")
return CustomFilterOr(cls(), other())
class CustomFilter(HandlerFilter, metaclass=CustomFilterMeta):
def __init__(self, raise_error: bool = True, **kwargs):
self.raise_error = raise_error
@abstractmethod
def filter(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool:
''' 一个用于重写的自定义Filter '''
raise NotImplementedError
def __or__(self, other):
return CustomFilterOr(self, other)
def __and__(self, other):
return CustomFilterAnd(self, other)
class CustomFilterOr(CustomFilter):
def __init__(self, filter1: CustomFilter, filter2: CustomFilter):
super().__init__()
if not isinstance(filter1, (CustomFilter, CustomFilterAnd, CustomFilterOr)):
raise ValueError("CustomFilter lass can only operate with other CustomFilter.")
self.filter1 = filter1
self.filter2 = filter2
def filter(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool:
return self.filter1.filter(event, cfg) or self.filter2.filter(event, cfg)
class CustomFilterAnd(CustomFilter):
def __init__(self, filter1: CustomFilter, filter2: CustomFilter):
super().__init__()
if not isinstance(filter1, (CustomFilter, CustomFilterAnd, CustomFilterOr)):
raise ValueError("CustomFilter lass can only operate with other CustomFilter.")
self.filter1 = filter1
self.filter2 = filter2
def filter(self, event: AstrMessageEvent, cfg: AstrBotConfig) -> bool:
return self.filter1.filter(event, cfg) and self.filter2.filter(event, cfg)
+2
View File
@@ -6,6 +6,7 @@ from .star_handler import (
register_platform_adapter_type,
register_regex,
register_permission_type,
register_custom_filter,
register_on_llm_request,
register_on_llm_response,
register_llm_tool,
@@ -21,6 +22,7 @@ __all__ = [
'register_platform_adapter_type',
'register_regex',
'register_permission_type',
'register_custom_filter',
'register_on_llm_request',
'register_on_llm_response',
'register_llm_tool',
+68 -8
View File
@@ -7,6 +7,7 @@ from ..filter.command_group import CommandGroupFilter
from ..filter.event_message_type import EventMessageTypeFilter, EventMessageType
from ..filter.platform_adapter_type import PlatformAdapterTypeFilter, PlatformAdapterType
from ..filter.permission import PermissionTypeFilter, PermissionType
from ..filter.custom_filter import CustomFilterAnd, CustomFilterOr
from ..filter.regex import RegexFilter
from typing import Awaitable
from astrbot.core.provider.func_tool_manager import SUPPORTED_TYPES
@@ -50,7 +51,7 @@ def get_handler_or_create(
star_handlers_registry.append(md)
return md
def register_command(command_name: str = None, *args, **kwargs):
def register_command(command_name: str = None, sub_command: str = None, alias: set = None, **kwargs):
'''注册一个 Command.
'''
@@ -60,11 +61,11 @@ def register_command(command_name: str = None, *args, **kwargs):
add_to_event_filters = False
if isinstance(command_name, RegisteringCommandable):
# 子指令
new_command = CommandFilter(args[0], None)
new_command = CommandFilter(sub_command, alias, None)
command_name.parent_group.add_sub_command_filter(new_command)
else:
# 裸指令
new_command = CommandFilter(command_name, None)
new_command = CommandFilter(command_name, alias, None)
add_to_event_filters = True
def decorator(awaitable):
@@ -80,7 +81,65 @@ def register_command(command_name: str = None, *args, **kwargs):
return decorator
def register_command_group(command_group_name: str = None, *args, **kwargs):
def register_custom_filter(custom_type_filter, *args, **kwargs):
'''注册一个自定义的 CustomFilter
Args:
custom_type_filter: 在裸指令时为CustomFilter对象
在指令组时为父指令的RegisteringCommandable对象,即self或者command_group的返回
raise_error: 如果没有权限,是否抛出错误到消息平台,并且停止事件传播。默认为 True
'''
add_to_event_filters = False
raise_error = True
# 判断是否是指令组,指令组则添加到指令组的CommandGroupFilter对象中在waking_check的时候一起判断
if isinstance(custom_type_filter, RegisteringCommandable):
# 子指令, 此时函数为RegisteringCommandable对象的方法,首位参数为RegisteringCommandable对象的self。
parent_register_commandable = custom_type_filter
custom_filter = args[0]
if len(args) > 1:
raise_error = args[1]
else:
# 裸指令
add_to_event_filters = True
custom_filter = custom_type_filter
if args:
raise_error = args[0]
if not isinstance(custom_filter, (CustomFilterAnd, CustomFilterOr)):
custom_filter = custom_filter(raise_error)
def decorator(awaitable):
# 裸指令,子指令与指令组的区分,指令组会因为标记跳过wake。
if not add_to_event_filters and isinstance(awaitable, RegisteringCommandable) or \
(add_to_event_filters and isinstance(awaitable, RegisteringCommandable)):
# 指令组 与 根指令组,添加到本层的grouphandle中一起判断
awaitable.parent_group.add_custom_filter(custom_filter)
else:
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent, **kwargs)
if not add_to_event_filters and not isinstance(awaitable, RegisteringCommandable):
# 底层子指令
handle_full_name = get_handler_full_name(awaitable)
for sub_handle in parent_register_commandable.parent_group.sub_command_filters:
# 所有符合fullname一致的子指令handle添加自定义过滤器。
# 不确定是否会有多个子指令有一样的fullname,比如一个方法添加多个command装饰器?
sub_handle_md = sub_handle.get_handler_md()
if sub_handle_md and sub_handle_md.handler_full_name == handle_full_name:
sub_handle.add_custom_filter(custom_filter)
else:
# 裸指令
handler_md = get_handler_or_create(awaitable, EventType.AdapterMessageEvent, **kwargs)
handler_md.event_filters.append(custom_filter)
return awaitable
return decorator
def register_command_group(
command_group_name: str = None, sub_command: str = None, alias: set = None, **kwargs
):
'''注册一个 CommandGroup
'''
@@ -90,11 +149,11 @@ def register_command_group(command_group_name: str = None, *args, **kwargs):
add_to_event_filters = False
if isinstance(command_group_name, RegisteringCommandable):
# 子指令组
new_group = CommandGroupFilter(args[0])
new_group = CommandGroupFilter(sub_command, alias)
command_group_name.parent_group.add_sub_command_filter(new_group)
else:
# 根指令组
new_group = CommandGroupFilter(command_group_name)
new_group = CommandGroupFilter(command_group_name, alias)
add_to_event_filters = True
def decorator(obj):
@@ -102,7 +161,7 @@ def register_command_group(command_group_name: str = None, *args, **kwargs):
# 根指令组
handler_md = get_handler_or_create(obj, EventType.AdapterMessageEvent, **kwargs)
handler_md.event_filters.append(new_group)
return RegisteringCommandable(new_group)
return decorator
@@ -111,7 +170,8 @@ class RegisteringCommandable():
'''用于指令组级联注册'''
group = register_command_group
command = register_command
custom_filter = register_custom_filter
def __init__(self, parent_group: CommandGroupFilter):
self.parent_group = parent_group
+1 -1
View File
@@ -22,7 +22,7 @@ class HtmlRenderer:
@return: 图片 URL 或者文件路径,取决于 return_url 参数。
@example: 参见 https://astrbot.soulter.top 插件开发部分。
@example: 参见 https://astrbot.app 插件开发部分。
'''
local = locals()
local.pop('self')
+12
View File
@@ -0,0 +1,12 @@
# What's Changed
1. ✨ 新增: 管理面板支持搜索插件
2. ✨ 新增: 支持传递 OneBot 的 notice, request 事件类型,如戳一戳,进退群请求等
3. ✨ 新增: 插件支持自定义过滤算子 by @AraragiEro
4. ✨ 新增: 添加命令和命令组的别名支持 by @Cvandia
4. ✨ 新增: 提供了一个方法以删除分段回复后的某些字符,如末尾的标点符号。 by @Soulter and @Nothingness-Void
5. ⚡ 优化: 优化了分段回复和回复时at,引用都打开时的一些体验性问题
7. 🐛 修复: 分段回复导致了不完全的非 LLM 输出 #503
8. 🐛 修复: 添加 no_proxy 环境变量以支持本地请求, 修复在代理状态下时的 502 错误当通过 LMStudio, Ollama 本地部署 LLM 时 #504 #514
9. 💡🐛 修复: 修复转发消息的字数阈值功能 #510
10. 💡🐛 修复: 修复 Dify 下无法主动回复的问题 #494
@@ -4,6 +4,7 @@ const props = defineProps({
link: String,
logo: String,
has_update: Boolean,
activated: Boolean,
});
const open = (link: string | undefined) => {
@@ -16,8 +17,9 @@ const open = (link: string | undefined) => {
<v-card-item style="padding: 10px 12px">
<div class="d-sm-flex align-center justify-space-between">
<img v-if="logo" :src="logo" alt="logo" style="width: 40px; height: 40px; margin-right: 8px;">
<v-card-title style="font-size: 16px;">{{ props.title }}</v-card-title>
<v-card-title style="font-size: 15px; max-width: 70%">{{ props.title }}</v-card-title>
<v-spacer></v-spacer>
<v-icon color="success" v-if="!activated">mdi-cancel</v-icon>
<v-icon color="success" v-if="has_update">mdi-arrow-up-bold</v-icon>
<v-btn size="small" text="Read" variant="flat" border @click="open(props.link)">帮助</v-btn>
</div>
@@ -21,7 +21,7 @@ const sidebarMenu = shallowRef(sidebarItems);
</div>
<div style="position: absolute; bottom: 32px; width: 100%" class="text-center">
<v-list-item v-if="!customizer.mini_sidebar" href="https://astrbot.soulter.top/">
<v-list-item v-if="!customizer.mini_sidebar" href="https://astrbot.app/">
<v-btn variant="plain" size="small">
🤔 初次使用点击查看文档
</v-btn>
+1 -1
View File
@@ -91,7 +91,7 @@ import config from '@/config';
<div style="margin-left: 16px; padding-bottom: 16px">
<small>不了解配置请见 <a
href="https://astrbot.soulter.top/">官方文档</a>
href="https://astrbot.app/">官方文档</a>
<a
href="https://qm.qq.com/cgi-bin/qm/qr?k=EYGsuUTfe00_iOu9JTXS7_TEpMkXOvwv&jump_from=webapi&authKey=uUEMKCROfsseS+8IzqPjzV3y1tzy4AkykwTib2jNkOFdzezF9s9XknqnIaf3CDft">加群询问</a></small>
</div>
+91 -50
View File
@@ -10,8 +10,8 @@ import { max } from 'date-fns';
<template>
<v-row>
<v-alert style="margin: 16px" text="1. 如果因为网络问题安装失败,点击设置页选择 GitHub 加速地址。或前往仓库下载压缩包然后本地上传。" title="💡提示"
type="info" color="primary" variant="tonal">
<v-alert style="margin: 16px" text="1. 如果因为网络问题安装失败,点击设置页选择 GitHub 加速地址。或前往仓库下载压缩包然后本地上传。" title="💡提示" type="info"
color="primary" variant="tonal">
</v-alert>
<v-col cols="12" md="12">
<div style="background-color: white; width: 100%; padding: 16px; border-radius: 10px;">
@@ -44,14 +44,14 @@ import { max } from 'date-fns';
</v-dialog>
</div>
</div>
</v-col>
</v-col>
<v-col cols="12" md="6" lg="3" v-for="extension in extension_data.data">
<ExtensionCard :key="extension.name" :title="extension.name" :link="extension.repo" :logo="extension?.logo"
:has_update="extension.has_update" style="margin-bottom: 4px;">
:has_update="extension.has_update" style="margin-bottom: 4px;" :activated="extension.activated">
<div style="min-height: 140px; max-height: 140px; overflow: auto;">
<div>
<span style="font-weight: bold ;">By @{{ extension.author }}</span>
<span> | 插件有 {{ extension.handlers.length }} 个行为</span>
<span> | {{ extension.handlers.length }} 个行为</span>
</div>
<span> 当前: <v-chip size="small" color="primary">{{ extension.version }}</v-chip>
<span v-if="extension.online_version">
@@ -82,51 +82,88 @@ import { max } from 'date-fns';
</div>
</ExtensionCard>
</v-col>
<v-col cols="12" md="12">
<div style="background-color: white; width: 100%; padding: 16px; border-radius: 10px;">
<div style="display: flex; align-items: center;">
<h3>🧩 插件市场</h3>
<small style="margin-left: 16px;">如无法显示请打开 <a
href="https://soulter.github.io/AstrBot_Plugins_Collection/plugins.json">链接</a> 复制想安装插件对应的 `repo`
链接然后点击右下角 + 号安装或打开链接下载压缩包安装</small>
<v-btn icon @click="isListView = !isListView" size="small" style="margin-left: auto;" variant="plain">
<v-icon>{{ isListView ? 'mdi-view-grid' : 'mdi-view-list' }}</v-icon>
</v-btn>
</div>
</div>
</v-col>
<v-col cols="12" md="12" v-if="announcement">
<v-banner color="success" lines="one" :text="announcement" :stacked="false">
</v-banner>
</v-col>
<template v-if="isListView">
<v-col cols="12" md="12">
<v-data-table :headers="pluginMarketHeaders" :items="pluginMarketData" item-key="name">
<template v-slot:item.actions="{ item }">
<v-btn v-if="!item.installed" class="text-none mr-2" size="small" text="Read" variant="flat" border
@click="extension_url = item.repo; newExtension()">安装</v-btn>
<v-btn v-else class="text-none mr-2" size="small" text="Read" variant="flat" border disabled>已安装</v-btn>
</template>
</v-data-table>
</v-col>
</template>
<template v-else>
<v-col cols="12" md="6" lg="3" v-for="plugin in pluginMarketData">
<ExtensionCard :key="plugin.name" :title="plugin.name" :link="plugin.repo" style="margin-bottom: 4px;">
<div style="min-height: 130px; max-height: 130px; overflow: hidden;">
<p style="font-weight: bold;">By @{{ plugin.author }}</p>
{{ plugin.desc }}
</div>
<div class="d-flex align-center gap-2">
<v-btn v-if="!plugin.installed" class="text-none mr-2" size="small" text="Read" variant="flat" border
@click="extension_url = plugin.repo; newExtension()">安装</v-btn>
<v-btn v-else class="text-none mr-2" size="small" text="Read" variant="flat" border disabled>已安装</v-btn>
</div>
</ExtensionCard>
</v-col>
</template>
<v-col cols="12" md="12">
<v-card>
<v-card-title class="d-flex align-center pe-2">
🧩 插件市场
<v-btn icon size="small" style="margin-left: 8px" variant="plain">
<v-icon size="small">mdi-help</v-icon>
<v-tooltip activator="parent" location="start">
如无法显示请打开 <a href="https://soulter.github.io/AstrBot_Plugins_Collection/plugins.json">链接</a> 复制想安装插件对应的
`repo`
链接然后点击右下角 + 号安装或打开链接下载压缩包安装
</v-tooltip>
</v-btn>
<v-btn icon @click="isListView = !isListView" size="small" style="margin-left: auto;" variant="plain">
<v-icon>{{ isListView ? 'mdi-view-grid' : 'mdi-view-list' }}</v-icon>
</v-btn>
<v-spacer></v-spacer>
<v-text-field v-model="marketSearch" density="compact" label="Search" prepend-inner-icon="mdi-magnify"
variant="solo-filled" flat hide-details single-line></v-text-field>
</v-card-title>
<v-divider></v-divider>
<template v-if="isListView">
<v-col cols="12" md="12">
<v-data-table :headers="pluginMarketHeaders" :items="pluginMarketData" item-key="name"
v-model:search="marketSearch" :filter-keys="['name']">
<template v-slot:item.name="{ item }">
<span v-if="item?.repo"><a :href="item?.repo" style="color: #000; text-decoration:none">{{ item.name }}</a></span>
<span v-else>{{ item.name}}</span>
</template>
<template v-slot:item.author="{ item }">
<span v-if="item?.social_link"><a :href="item?.social_link">{{ item.author}}</a></span>
<span v-else>{{ item.author}}</span>
</template>
<template v-slot:item.tags="{ item }">
<span v-if="item.tags.length === 0"></span>
<v-chip v-for="tag in item.tags" :key="tag" color="primary" size="small">{{ tag }}</v-chip>
</template>
<template v-slot:item.actions="{ item }">
<v-btn v-if="!item.installed" class="text-none mr-2" size="small" text="Read" variant="flat" border
@click="extension_url = item.repo; newExtension()">安装</v-btn>
<v-btn v-else class="text-none mr-2" size="small" text="Read" variant="flat" border disabled>已安装</v-btn>
</template>
</v-data-table>
</v-col>
</template>
<template v-else>
<v-row style="margin: 8px;">
<v-col cols="12" md="6" lg="3" v-for="plugin in pluginMarketData">
<ExtensionCard :key="plugin.name" :title="plugin.name" :link="plugin.repo" style="margin-bottom: 4px;">
<div style="min-height: 130px; max-height: 130px; overflow: hidden;">
<p style="font-weight: bold;">By @{{ plugin.author }}</p>
{{ plugin.desc }}
</div>
<div class="d-flex align-center gap-2">
<v-btn v-if="!plugin.installed" class="text-none mr-2" size="small" text="Read" variant="flat"
border @click="extension_url = plugin.repo; newExtension()">安装</v-btn>
<v-btn v-else class="text-none mr-2" size="small" text="Read" variant="flat" border
disabled>已安装</v-btn>
</div>
</ExtensionCard>
</v-col>
</v-row>
</template>
</v-card>
</v-col>
<v-col style="margin-bottom: 16px;" cols="12" md="12">
<small><a href="https://astrbot.app/dev/plugin.html">插件开发文档</a></small> |
@@ -314,7 +351,7 @@ export default {
"config": {}
},
upload_file: null,
pluginMarketData: {},
pluginMarketData: [],
loadingDialog: {
show: false,
title: "加载中...",
@@ -331,13 +368,15 @@ export default {
{ title: '具体类型', key: 'type' },
{ title: '触发方式', key: 'cmd' },
],
isListView: false,
isListView: true,
pluginMarketHeaders: [
{ title: '名称', value: 'name' },
{ title: '描述', value: 'desc' },
{ title: '作者', value: 'author' },
{ title: '操作', value: 'actions', sortable: false }
{ title: '名称', key: 'name', maxWidth: '150px' },
{ title: '描述', key: 'desc', maxWidth: '250px' },
{ title: '作者', key: 'author', maxWidth: '60px' },
{ title: '标签', key: 'tags', maxWidth: '60px' },
{ title: '操作', key: 'actions', sortable: false }
],
marketSearch: "",
alreadyCheckUpdate: false
}
},
@@ -562,6 +601,8 @@ export default {
"repo": res.data.data[key].repo,
"installed": false,
"version": res.data.data[key]?.version ? res.data.data[key].version : "未知",
"social_link": res.data.data[key]?.social_link,
"tags": res.data.data[key]?.tags ? res.data.data[key].tags : []
})
}
this.pluginMarketData = data;
+25 -16
View File
@@ -6,8 +6,7 @@ import astrbot.api.star as star
import astrbot.api.event.filter as filter
from astrbot.api.event import AstrMessageEvent, MessageEventResult
from astrbot.api import sp
from astrbot.api.platform import MessageType
from astrbot.api.provider import Personality, ProviderRequest, LLMResponse
from astrbot.api.provider import ProviderRequest
from astrbot.core.provider.sources.dify_source import ProviderDify
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
from astrbot.core.star.star_handler import star_handlers_registry, StarHandlerMetadata
@@ -40,7 +39,7 @@ class Main(star.Star):
async def _query_astrbot_notice(self):
try:
async with aiohttp.ClientSession(trust_env=True) as session:
async with session.get("https://astrbot.soulter.top/notice.json", timeout=2) as resp:
async with session.get("https://astrbot.app/notice.json", timeout=2) as resp:
return (await resp.json())["notice"]
except BaseException:
return ""
@@ -775,20 +774,30 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
logger.error("未找到任何 LLM 提供商。请先配置。无法主动回复")
return
try:
session_curr_cid = await self.context.conversation_manager.get_curr_conversation_id(event.unified_msg_origin)
if not session_curr_cid:
logger.error("当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /switch 序号 切换或者 /new 创建一个会话。")
return
conv = await self.context.conversation_manager.get_conversation(
event.unified_msg_origin,
session_curr_cid
)
conv = None
history = []
if conv:
history = json.loads(conv.history)
if provider.meta().type != 'dify':
# Dify 自己有维护对话,不需要 bot 端维护。
session_curr_cid = await self.context.conversation_manager.get_curr_conversation_id(event.unified_msg_origin)
if not session_curr_cid:
logger.error("当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /switch 序号 切换或者 /new 创建一个会话。")
return
conv = await self.context.conversation_manager.get_conversation(
event.unified_msg_origin,
session_curr_cid
)
history = []
if conv:
history = json.loads(conv.history)
else:
assert isinstance(provider, ProviderDify)
cid = provider.conversation_ids.get(event.unified_msg_origin, None)
if cid is None:
logger.error("[Dify] 当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /switch 序号 切换或者 /new 创建一个会话。")
return
prompt = self.ltm.ar_prompt
if not prompt:
prompt = event.message_str
+2 -2
View File
@@ -75,10 +75,10 @@ class Main(star.Star):
@llm_tool("web_search")
async def search_from_search_engine(self, event: AstrMessageEvent, query: str) -> str:
'''Search the web for answers to the user's query
'''搜索网络以回答用户的问题。当用户需要搜索网络以获取即时性的信息时调用此工具。
Args:
query(string): A search query which will be used to fetch the most relevant snippets regarding the user's query
query(string): 和用户的问题最相关的搜索关键词,用于在 Google 上搜索。
'''
logger.info("web_searcher - search_from_search_engine: " + query)
results = []