Compare commits

...

9 Commits

Author SHA1 Message Date
Soulter a4f47da35c feat: 支持插件市场 2024-10-07 16:26:39 +08:00
Soulter 29364000e2 chore: update version 2024-10-07 15:19:50 +08:00
Soulter ceecca44a4 fix: active message 2024-10-07 15:18:08 +08:00
Soulter 50f62e66b0 perf: 文转图渲染失败时发送纯文本 2024-10-06 00:20:42 +08:00
Soulter ab39dfd254 Merge pull request #214 from Soulter/dev
通过 Commit Hash 更新和仪表盘 UI 优化
2024-10-05 10:52:43 +08:00
Soulter 708fad18b6 Merge pull request #213 from lumenmai/identifier
添加可识别群员身份功能
2024-10-05 10:46:05 +08:00
Soulter 526ba34d87 remove: .idea 2024-10-05 10:35:03 +08:00
lumenmai 5d4882dee9 添加可识别群员身份功能 2024-10-05 00:28:20 +08:00
Soulter 48c4361d37 feat: 支持通过 commit hash 更新到指定 commit
perf: 仪表盘顶部导航栏优化
2024-10-04 15:09:07 +08:00
28 changed files with 130 additions and 272 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
__pycache__
botpy.log
.vscode
data.db
data_v2.db
configs/session
configs/config.yaml
**/.DS_Store
+8
View File
@@ -110,6 +110,7 @@ class MessageHandler():
self.rate_limit_helper = RateLimitHelper(context)
self.content_safety_helper = ContentSafetyHelper(context)
self.llm_wake_prefix = self.context.config_helper.llm_settings.wake_prefix
self.llm_identifier = self.context.config_helper.llm_settings.identifier
if self.llm_wake_prefix:
self.llm_wake_prefix = self.llm_wake_prefix.strip()
self.provider = self.context.llms[0].llm_instance if len(self.context.llms) > 0 else None
@@ -235,6 +236,13 @@ class MessageHandler():
else:
# normal chat
tool_use_flag = False
# add user info to the prompt
if self.llm_identifier:
user_id = message.message_obj.sender.user_id
user_nickname = message.message_obj.sender.nickname
user_info = f"[User ID: {user_id}, Nickname: {user_nickname}]\n"
msg_plain = user_info + msg_plain
llm_result = await provider.text_chat(
prompt=msg_plain,
session_id=message.session_id,
+1
View File
@@ -0,0 +1 @@
import{q as e,o as a,c as t,w as o,d as s,x as n,U as r,X as c}from"./index-b50bcc8e.js";const f=e({__name:"BlankLayout",setup(p){return(u,_)=>(a(),t(r,null,{default:o(()=>[s(n(c))]),_:1}))}});export{f as default};
-1
View File
@@ -1 +0,0 @@
import{q as e,o as a,b as t,w as o,c as s,x as n,X as r,Z as c}from"./index-7e5a38e4.js";const f=e({__name:"BlankLayout",setup(p){return(u,_)=>(a(),t(r,null,{default:o(()=>[s(n(c))]),_:1}))}});export{f as default};
-1
View File
@@ -1 +0,0 @@
import{q as C,o as l,b as n,w as t,c as o,a0 as k,u as i,H as x,a as U,t as m,a1 as v,A as f,I as w,G as N,l as s,n as _,Q as S,R as B,F as r,a4 as D,M as y,a5 as T,e as $,m as F,g as b}from"./index-7e5a38e4.js";const A={class:"d-sm-flex align-center justify-space-between"},I=C({__name:"UiParentCard",props:{title:String},setup(d){const c=d;return(p,u)=>(l(),n(N,{variant:"outlined",elevation:"0",class:"withbg"},{default:t(()=>[o(k,null,{default:t(()=>[i("div",A,[o(x,null,{default:t(()=>[U(m(c.title),1)]),_:1}),v(p.$slots,"action")])]),_:3}),o(f),o(w,null,{default:t(()=>[v(p.$slots,"default")]),_:3})]),_:3}))}}),q={__name:"ConfigDetailCard",props:{config:Array},setup(d){return(c,p)=>(l(!0),s(r,null,_(d.config,u=>(l(),n(I,{key:u.name,title:u.name,style:{"margin-bottom":"16px"}},{default:t(()=>[S(i("a",null,"No data",512),[[B,d.config.length===0]]),(l(!0),s(r,null,_(u.body,e=>(l(),s(r,null,[e.config_type==="item"?(l(),s(r,{key:0},[e.val_type==="bool"?(l(),n(D,{key:0,modelValue:e.value,"onUpdate:modelValue":a=>e.value=a,label:e.name,hint:e.description,color:"primary",inset:""},null,8,["modelValue","onUpdate:modelValue","label","hint"])):e.val_type==="str"?(l(),n(y,{key:1,modelValue:e.value,"onUpdate:modelValue":a=>e.value=a,label:e.name,hint:e.description,style:{"margin-bottom":"8px"},variant:"outlined"},null,8,["modelValue","onUpdate:modelValue","label","hint"])):e.val_type==="int"?(l(),n(y,{key:2,modelValue:e.value,"onUpdate:modelValue":a=>e.value=a,label:e.name,hint:e.description,style:{"margin-bottom":"8px"},variant:"outlined"},null,8,["modelValue","onUpdate:modelValue","label","hint"])):e.val_type==="list"?(l(),s(r,{key:3},[i("span",null,m(e.name),1),o(T,{modelValue:e.value,"onUpdate:modelValue":a=>e.value=a,chips:"",clearable:"",label:"请添加",multiple:"","prepend-icon":"mdi-tag-multiple-outline"},{selection:t(({attrs:a,item:V,select:g,selected:h})=>[o($,F(a,{"model-value":h,closable:"",onClick:g,"onClick:close":P=>c.remove(V)}),{default:t(()=>[i("strong",null,m(V),1)]),_:2},1040,["model-value","onClick","onClick:close"])]),_:2},1032,["modelValue","onUpdate:modelValue"])],64)):b("",!0)],64)):e.config_type==="divider"?(l(),n(f,{key:1,style:{"margin-top":"8px","margin-bottom":"8px"}})):b("",!0)],64))),256))]),_:2},1032,["title"]))),128))}};export{q as _};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
import{aj as L,q as $,B as J,o as q,l as X,c as G,w as Q,Q as R,R as O,x as S,u as m,ak as Z,al as t0,am as r0,an as e0}from"./index-7e5a38e4.js";const E={Sidebar_drawer:!0,Customizer_drawer:!1,mini_sidebar:!1,fontTheme:"Roboto",inputBg:!1},i0=L({id:"customizer",state:()=>({Sidebar_drawer:E.Sidebar_drawer,Customizer_drawer:E.Customizer_drawer,mini_sidebar:E.mini_sidebar,fontTheme:"Poppins",inputBg:E.inputBg}),getters:{},actions:{SET_SIDEBAR_DRAWER(){this.Sidebar_drawer=!this.Sidebar_drawer},SET_MINI_SIDEBAR(p){this.mini_sidebar=p},SET_FONT(p){this.fontTheme=p}}}),s0={class:"logo",style:{display:"flex","align-items":"center"}},a0={style:{"font-size":"24px","font-weight":"1000"}},f0={style:{"font-size":"20px","font-weight":"1000"}},o0={style:{"font-size":"20px"}},l0=$({__name:"LogoDark",setup(p){J("rgb(var(--v-theme-primary))"),J("rgb(var(--v-theme-secondary))");const d=i0();return(M,y)=>(q(),X("div",s0,[G(S(Z),{to:"/",style:{"text-decoration":"none",color:"black"}},{default:Q(()=>[R(m("span",a0,"AstrBot 仪表盘",512),[[O,!S(d).mini_sidebar]]),R(m("span",f0,"Astr",512),[[O,S(d).mini_sidebar]]),R(m("span",o0,"Bot",512),[[O,S(d).mini_sidebar]])]),_:1})]))}});var P={exports:{}};const n0={},h0=Object.freeze(Object.defineProperty({__proto__:null,default:n0},Symbol.toStringTag,{value:"Module"})),H=t0(h0);/**
import{aj as L,q as $,B as J,o as q,l as X,d as G,w as Z,a0 as O,a1 as R,x as S,u as m,ak as Q,al as t0,am as r0,an as e0}from"./index-b50bcc8e.js";const E={Sidebar_drawer:!0,Customizer_drawer:!1,mini_sidebar:!1,fontTheme:"Roboto",inputBg:!1},i0=L({id:"customizer",state:()=>({Sidebar_drawer:E.Sidebar_drawer,Customizer_drawer:E.Customizer_drawer,mini_sidebar:E.mini_sidebar,fontTheme:"Poppins",inputBg:E.inputBg}),getters:{},actions:{SET_SIDEBAR_DRAWER(){this.Sidebar_drawer=!this.Sidebar_drawer},SET_MINI_SIDEBAR(p){this.mini_sidebar=p},SET_FONT(p){this.fontTheme=p}}}),s0={class:"logo",style:{display:"flex","align-items":"center"}},a0={style:{"font-size":"24px","font-weight":"1000"}},f0={style:{"font-size":"20px","font-weight":"1000"}},o0={style:{"font-size":"20px"}},l0=$({__name:"LogoDark",setup(p){J("rgb(var(--v-theme-primary))"),J("rgb(var(--v-theme-secondary))");const d=i0();return(M,y)=>(q(),X("div",s0,[G(S(Q),{to:"/",style:{"text-decoration":"none",color:"black"}},{default:Z(()=>[O(m("span",a0,"AstrBot 仪表盘",512),[[R,!S(d).mini_sidebar]]),O(m("span",f0,"Astr",512),[[R,S(d).mini_sidebar]]),O(m("span",o0,"Bot",512),[[R,S(d).mini_sidebar]])]),_:1})]))}});var P={exports:{}};const n0={},h0=Object.freeze(Object.defineProperty({__proto__:null,default:n0},Symbol.toStringTag,{value:"Module"})),H=t0(h0);/**
* [js-md5]{@link https://github.com/emn178/js-md5}
*
* @namespace md5
+2 -2
View File
@@ -11,8 +11,8 @@
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap"
/>
<title>AstrBot - 仪表盘</title>
<script type="module" crossorigin src="/assets/index-7e5a38e4.js"></script>
<link rel="stylesheet" href="/assets/index-0f1523f3.css">
<script type="module" crossorigin src="/assets/index-b50bcc8e.js"></script>
<link rel="stylesheet" href="/assets/index-86dd25ba.css">
</head>
<body>
<div id="app"></div>
+4
View File
@@ -5,6 +5,9 @@ from type.astrbot_message import AstrBotMessage
from type.command import CommandResult
from type.astrbot_message import MessageType
class T2IException(Exception):
def __init__(self, message: str = "文本转图片时发生错误") -> None:
super().__init__(message)
class Platform():
def __init__(self, platform_name: str, context) -> None:
@@ -85,6 +88,7 @@ class Platform():
else:
rendered_images.append(Image.fromFileSystem(p))
return rendered_images
return message_result
async def record_metrics(self):
self.context.metrics_uploader.increment_platform_stat(self.PLATFORM_NAME)
+29 -40
View File
@@ -4,7 +4,7 @@ import traceback
import logging
from aiocqhttp import CQHttp, Event
from aiocqhttp.exceptions import ActionFailed
from . import Platform
from . import Platform, T2IException
from type.astrbot_message import *
from type.message_event import *
from type.command import *
@@ -25,13 +25,11 @@ class AIOCQHTTP(Platform):
assert isinstance(platform_config, AiocqhttpPlatformConfig), "aiocqhttp: 无法识别的配置类型。"
self.message_handler = message_handler
self.waiting = {}
self.context = context
self.config = platform_config
self.unique_session = context.config_helper.platform_settings.unique_session
self.host = platform_config.ws_reverse_host
self.port = platform_config.ws_reverse_port
self.admins = context.config_helper.admins_id
def convert_message(self, event: Event) -> AstrBotMessage:
@@ -134,13 +132,6 @@ class AIOCQHTTP(Platform):
ok, reason = await self.pre_check(message)
if not ok:
return
# 解析 role
sender_id = str(message.sender.user_id)
if sender_id in self.admins:
role = 'admin'
else:
role = 'member'
# parse unified message origin
unified_msg_origin = None
@@ -157,7 +148,6 @@ class AIOCQHTTP(Platform):
self.context,
"aiocqhttp",
message.session_id,
role,
unified_msg_origin,
reason == "command") # only_command
@@ -169,13 +159,8 @@ class AIOCQHTTP(Platform):
if message_result.callback:
message_result.callback()
# 如果是等待回复的消息
if message.session_id in self.waiting and self.waiting[message.session_id] == '':
self.waiting[message.session_id] = message
return message_result
async def reply_msg(self,
message: AstrBotMessage,
result_message: list,
@@ -183,36 +168,35 @@ class AIOCQHTTP(Platform):
"""
回复用户唤醒机器人的消息。(被动回复)
"""
res = result_message
if isinstance(res, str):
res = [Plain(text=res), ]
try:
await self._reply(message, result_message, use_t2i)
except T2IException as e:
logger.error(traceback.format_exc())
logger.warning(f"文本转图片时发生错误,将使用纯文本发送。")
await self._reply(message, result_message, False)
return result_message
# if image mode, put all Plain texts into a new picture.
if (use_t2i or (use_t2i == None and self.context.config_helper.t2i)) and isinstance(result_message, list):
rendered_images = await self.convert_to_t2i_chain(res)
if rendered_images:
try:
await self._reply(message, rendered_images)
return rendered_images
except BaseException as e:
logger.warn(traceback.format_exc())
logger.warn(f"以文本转图片的形式回复消息时发生错误: {e},将尝试默认方式。")
await self._reply(message, res)
return res
async def _reply(self, message: Union[AstrBotMessage, Dict], message_chain: List[BaseMessageComponent]):
async def _reply(self, message: Union[AstrBotMessage, Dict], message_chain: List[BaseMessageComponent], use_t2i: bool = None):
await self.record_metrics()
if isinstance(message_chain, str):
message_chain = [Plain(text=message_chain), ]
# 文转图处理
if (use_t2i or (use_t2i == None and self.context.config_helper.t2i)) and isinstance(message_chain, list):
try:
message_chain = await self.convert_to_t2i_chain(message_chain)
if not message_chain: raise T2IException()
except BaseException as e:
raise T2IException()
# log
if isinstance(message, AstrBotMessage):
logger.info(
f"{message.sender.nickname}/{message.sender.user_id} <- {self.parse_message_outline(message_chain)}")
else:
logger.info(f"回复消息: {message_chain}")
# 解析成 OneBot json 格式并发送
ret = []
image_idx = []
for idx, segment in enumerate(message_chain):
@@ -232,10 +216,11 @@ class AIOCQHTTP(Platform):
# ENOENT
if not image_idx:
raise e
logger.warn("回复失败。检测到失败原因为文件未找到,猜测用户的协议端与 AstrBot 位于不同的文件系统上。尝试采用上传图片的方式发图。")
logger.warning("回复失败。检测到失败原因为文件未找到,猜测用户的协议端与 AstrBot 位于不同的文件系统上。尝试采用上传图片的方式发图。")
for idx in image_idx:
if ret[idx]['data']['file'].startswith('file://'):
logger.info(f"正在上传图片: {ret[idx]['data']['path']}")
# 除了上传到图床,想不到更好的办法。
image_url = await self.context.image_uploader.upload_image(ret[idx]['data']['path'])
logger.info(f"上传成功。")
ret[idx]['data']['file'] = image_url
@@ -267,8 +252,12 @@ class AIOCQHTTP(Platform):
- 要发给某个群聊,请添加 key `group_id`,值为 int 类型的 qq 群号;
'''
await self._reply(target, result_message.message_chain)
try:
await self._reply(target, result_message.message_chain, result_message.is_use_t2i)
except T2IException as e:
logger.error(traceback.format_exc())
logger.warning(f"文本转图片时发生错误,将使用纯文本发送。")
await self._reply(target, result_message.message_chain, False)
async def send_msg_new(self, message_type: MessageType, target: str, result_message: CommandResult):
if message_type == MessageType.GROUP_MESSAGE:
+37 -60
View File
@@ -11,7 +11,7 @@ from nakuru import (
)
from typing import Union, List, Dict
from type.types import Context
from . import Platform
from . import Platform, T2IException
from type.astrbot_message import *
from type.message_event import *
from type.command import *
@@ -40,11 +40,9 @@ class QQNakuru(Platform):
asyncio.set_event_loop(self.loop)
self.message_handler = message_handler
self.waiting = {}
self.context = context
self.unique_session = context.config_helper.platform_settings.unique_session
self.config = platform_config
self.admins = context.config_helper.admins_id
self.client = CQHTTP(
host=self.config.host,
@@ -113,13 +111,6 @@ class QQNakuru(Platform):
session_id = message.raw_message.user_id
message.session_id = session_id
# 解析 role
sender_id = str(message.raw_message.user_id)
if sender_id in self.admins:
role = 'admin'
else:
role = 'member'
# parse unified message origin
unified_msg_origin = None
@@ -141,7 +132,6 @@ class QQNakuru(Platform):
self.context,
"nakuru",
session_id,
role,
unified_msg_origin,
reason == 'command') # only_command
@@ -153,49 +143,47 @@ class QQNakuru(Platform):
if message_result.callback:
message_result.callback()
# 如果是等待回复的消息
if session_id in self.waiting and self.waiting[session_id] == '':
self.waiting[session_id] = message
async def reply_msg(self,
message: AstrBotMessage,
result_message: List[BaseMessageComponent],
use_t2i: bool = None):
"""
回复用户唤醒机器人的消息。(被动回复)
"""
source = message.raw_message
res = result_message
"""
assert isinstance(message.raw_message, (GroupMessage, FriendMessage, GuildMessage))
try:
await self._reply(message, result_message, use_t2i)
except T2IException as e:
logger.error(traceback.format_exc())
logger.warning(f"文本转图片时发生错误,将使用纯文本发送。")
await self._reply(message, result_message, False)
return result_message
assert isinstance(source,
(GroupMessage, FriendMessage, GuildMessage))
logger.info(
f"{message.sender.nickname}/{message.sender.user_id} <- {self.parse_message_outline(res)}")
if isinstance(res, str):
res = [Plain(text=res), ]
# if image mode, put all Plain texts into a new picture.
if use_t2i or (use_t2i == None and self.context.config_helper.t2i) and isinstance(result_message, list):
rendered_images = await self.convert_to_t2i_chain(res)
if rendered_images:
try:
await self._reply(source, rendered_images)
return
except BaseException as e:
logger.warn(traceback.format_exc())
logger.warn(f"以文本转图片的形式回复消息时发生错误: {e},将尝试默认方式。")
await self._reply(source, res)
async def _reply(self, source, message_chain: List[BaseMessageComponent]):
async def _reply(self, message: Union[AstrBotMessage, Dict], message_chain: List[BaseMessageComponent], use_t2i: bool = None):
await self.record_metrics()
if isinstance(message_chain, str):
message_chain = [Plain(text=message_chain), ]
# 文转图处理
if (use_t2i or (use_t2i == None and self.context.config_helper.t2i)) and isinstance(message_chain, list):
try:
message_chain = await self.convert_to_t2i_chain(message_chain)
if not message_chain: raise T2IException()
except BaseException as e:
raise T2IException()
# log
if isinstance(message, AstrBotMessage):
logger.info(
f"{message.sender.nickname}/{message.sender.user_id} <- {self.parse_message_outline(message_chain)}")
else:
logger.info(f"回复消息: {message_chain}")
source = message.raw_message
is_dict = isinstance(source, dict)
# 发消息
typ = None
if is_dict:
if "group_id" in source:
@@ -250,7 +238,13 @@ class QQNakuru(Platform):
guild_id 不是频道号。
'''
await self._reply(target, result_message.message_chain)
try:
await self._reply(target, result_message.message_chain, result_message.is_use_t2i)
except T2IException as e:
logger.error(traceback.format_exc())
logger.warning(f"文本转图片时发生错误,将使用纯文本发送。")
await self._reply(target, result_message.message_chain, False)
return result_message
async def send_msg_new(self, message_type: MessageType, target: str, result_message: CommandResult):
'''
@@ -290,21 +284,4 @@ class QQNakuru(Platform):
)
abm.tag = "nakuru"
abm.message = message.message
return abm
def wait_for_message(self, group_id) -> Union[GroupMessage, FriendMessage, GuildMessage]:
'''
等待下一条消息,超时 300s 后抛出异常
'''
self.waiting[group_id] = ''
cnt = 0
while True:
if group_id in self.waiting and self.waiting[group_id] != '':
# 去掉
ret = self.waiting[group_id]
del self.waiting[group_id]
return ret
cnt += 1
if cnt > 300:
raise Exception("等待消息超时。")
time.sleep(1)
return abm
+8 -33
View File
@@ -63,10 +63,8 @@ class QQOfficial(Platform):
asyncio.set_event_loop(self.loop)
self.message_handler = message_handler
self.waiting: dict = {}
self.context = context
self.config = platform_config
self.admins = context.config_helper.admins_id
self.appid = platform_config.appid
self.secret = platform_config.secret
@@ -201,15 +199,8 @@ class QQOfficial(Platform):
session_id = str(message.raw_message.author.id)
message.session_id = session_id
# 解析出 role
sender_id = message.sender.user_id
if sender_id in self.admins:
role = 'admin'
else:
role = 'member'
# construct astrbot message event
ame = AstrMessageEvent.from_astrbot_message(message, self.context, "qqofficial", session_id, role)
ame = AstrMessageEvent.from_astrbot_message(message, self.context, "qqofficial", session_id)
message_result = await self.message_handler.handle(ame)
if not message_result:
@@ -219,10 +210,6 @@ class QQOfficial(Platform):
if message_result.callback:
message_result.callback()
# 如果是等待回复的消息
if session_id in self.waiting and self.waiting[session_id] == '':
self.waiting[session_id] = message
return ret
async def reply_msg(self,
@@ -241,10 +228,15 @@ class QQOfficial(Platform):
plain_text = ''
image_path = ''
msg_ref = None
rendered_images = []
rendered_images = None
if use_t2i or (use_t2i == None and self.context.config_helper.t2i) and isinstance(result_message, list):
rendered_images = await self.convert_to_t2i_chain(result_message)
try:
rendered_images = await self.convert_to_t2i_chain(result_message)
except BaseException as e:
logger.warning(traceback.format_exc())
logger.warning(f"文本转图片时发生错误: {e},将尝试默认方式。")
rendered_images = None
if isinstance(result_message, list):
plain_text, image_path = await self._parse_to_qqofficial(result_message, message.type == MessageType.GROUP_MESSAGE)
@@ -386,20 +378,3 @@ class QQOfficial(Platform):
async def send_msg_new(self, message_type: MessageType, target: str, result_message: CommandResult):
raise NotImplementedError("qqofficial 不支持此方法。")
def wait_for_message(self, channel_id: int) -> AstrBotMessage:
'''
等待指定 channel_id 的下一条信息,超时 300s 后抛出异常
'''
self.waiting[channel_id] = ''
cnt = 0
while True:
if channel_id in self.waiting and self.waiting[channel_id] != '':
# 去掉
ret = self.waiting[channel_id]
del self.waiting[channel_id]
return ret
cnt += 1
if cnt > 300:
raise Exception("等待消息超时。")
time.sleep(1)
-2
View File
@@ -99,8 +99,6 @@ class PluginManager():
if "no such option: --break-system-package" in err:
self.update_plugin_dept(path, break_system_package=False)
break
else:
logger.error("可能发生插件依赖安装失败导致插件无法被正常载入。请手动更新插件依赖。路径: " + path)
if output == '' and process.poll() is not None:
break
if output:
+4 -75
View File
@@ -1,80 +1,6 @@
VERSION = '3.3.16'
VERSION = '3.3.17'
DB_PATH = 'data/data_v2.db'
DEFAULT_CONFIG = {
"qqbot": {
"enable": False,
"appid": "",
"token": "",
},
"gocqbot": {
"enable": False,
},
"uniqueSessionMode": False,
"dump_history_interval": 10,
"limit": {
"time": 60,
"count": 30,
},
"notice": "",
"direct_message_mode": True,
"reply_prefix": "",
"baidu_aip": {
"enable": False,
"app_id": "",
"api_key": "",
"secret_key": ""
},
"openai": {
"key": [],
"api_base": "",
"chatGPTConfigs": {
"model": "gpt-4o",
"max_tokens": 6000,
"temperature": 0.9,
"top_p": 1,
"frequency_penalty": 0,
"presence_penalty": 0,
},
"total_tokens_limit": 10000,
},
"qq_forward_threshold": 200,
"qq_welcome": "",
"qq_pic_mode": True,
"gocq_host": "127.0.0.1",
"gocq_http_port": 5700,
"gocq_websocket_port": 6700,
"gocq_react_group": True,
"gocq_react_guild": True,
"gocq_react_friend": True,
"gocq_react_group_increase": True,
"other_admins": [],
"CHATGPT_BASE_URL": "",
"qqbot_secret": "",
"qqofficial_enable_group_message": False,
"admin_qq": "",
"nick_qq": ["/", "!"],
"admin_qqchan": "",
"llm_env_prompt": "",
"llm_wake_prefix": "",
"default_personality_str": "",
"openai_image_generate": {
"model": "dall-e-3",
"size": "1024x1024",
"style": "vivid",
"quality": "standard",
},
"http_proxy": "",
"https_proxy": "",
"dashboard_username": "",
"dashboard_password": "",
"aiocqhttp": {
"enable": False,
"ws_reverse_host": "",
"ws_reverse_port": 0,
}
}
# 新版本配置文件,摈弃旧版本令人困惑的配置项 :D
DEFAULT_CONFIG_VERSION_2 = {
"config_version": 2,
@@ -146,6 +72,7 @@ DEFAULT_CONFIG_VERSION_2 = {
"llm_settings": {
"wake_prefix": "",
"web_search": False,
"identifier": False,
},
"content_safety": {
"baidu_aip": {
@@ -231,6 +158,7 @@ MAPPINGS_1_2 = [
[["nick_qq"], ["wake_prefix"]],
]
# 配置项的中文描述、值类型
CONFIG_METADATA_2 = {
"config_version": {"description": "配置版本", "type": "int"},
"platform": {
@@ -312,6 +240,7 @@ CONFIG_METADATA_2 = {
"items": {
"wake_prefix": {"description": "LLM 聊天额外唤醒前缀", "type": "string"},
"web_search": {"description": "启用网页搜索(能访问 Google 时效果最佳)", "type": "bool"},
"identifier": {"description": "启动识别群员(略微增加token开销)", "type": "bool"},
}
},
"content_safety": {
+8 -1
View File
@@ -47,10 +47,17 @@ class AstrMessageEvent():
context: Context,
platform_name: str,
session_id: str,
role: str = "member",
unified_msg_origin: str = None,
only_command: bool = False):
# 解析 role
sender_id = str(message.sender.user_id)
if sender_id in context.config_helper.admins_id:
role = 'admin'
else:
role = 'member'
ame = AstrMessageEvent(message.message_str,
message,
context.find_platform(platform_name),
+10 -43
View File
@@ -3,7 +3,7 @@ import json
import shutil
import logging
from util.io import on_error
from type.config import DEFAULT_CONFIG, DEFAULT_CONFIG_VERSION_2, MAPPINGS_1_2
from type.config import DEFAULT_CONFIG_VERSION_2, MAPPINGS_1_2
from dataclasses import dataclass, field, asdict
from typing import List, Dict, Optional
@@ -90,6 +90,7 @@ class LLMConfig:
class LLMSettings:
wake_prefix: str = ""
web_search: bool = False
identifier: bool = False
@dataclass
class BaiduAIPConfig:
@@ -175,7 +176,7 @@ class AstrBotConfig():
self.https_proxy=data.get("https_proxy", "")
self.http_proxy=data.get("http_proxy", "")
self.dashboard=DashboardConfig(**data.get("dashboard", {}))
self.wake_prefix=data.get("wake_prefix", [])
self.wake_prefix=data.get("wake_prefix", ["/"])
self.log_level=data.get("log_level", "INFO")
self.t2i_endpoint=data.get("t2i_endpoint", "")
@@ -228,22 +229,15 @@ class AstrBotConfig():
config = self.migrate_config_1_2(config)
self.flush_config(config)
_tag = False
for key, val in DEFAULT_CONFIG_VERSION_2.items():
if key not in config:
config[key] = val
_tag = True
if _tag:
with open(ASTRBOT_CONFIG_PATH, "w", encoding="utf-8-sig") as f:
json.dump(config, f, indent=2, ensure_ascii=False)
f.flush()
# 加载配置到对象
self.load_from_dict(config)
# 保存到文件
# 这一步操作是为了保证配置文件中的字段的完整性。
# 在版本变动新增配置项时,将对象中新增的配置项的默认值写入文件。
self.save_config()
def get(self, key: str, default=None):
'''
从文件系统中直接获取配置
'''
'''从文件系统中直接获取配置'''
with open(ASTRBOT_CONFIG_PATH, "r", encoding="utf-8-sig") as f:
d = json.load(f)
if key in d:
@@ -252,9 +246,7 @@ class AstrBotConfig():
return default
def get_all(self):
'''
从文件系统中获取所有配置
'''
'''从文件系统中获取所有配置'''
with open(ASTRBOT_CONFIG_PATH, "r", encoding="utf-8-sig") as f:
conf_str = f.read()
if conf_str.startswith(u'/ufeff'): # remove BOM
@@ -274,31 +266,6 @@ class AstrBotConfig():
def to_dict(self) -> Dict:
return asdict(self)
def put_by_dot_str(self, key: str, value):
'''根据点分割的字符串,将值写入配置文件'''
with open(ASTRBOT_CONFIG_PATH, "r", encoding="utf-8-sig") as f:
d = json.load(f)
_d = d
_ks = key.split(".")
for i in range(len(_ks)):
if i == len(_ks) - 1:
_d[_ks[i]] = value
else:
_d = _d[_ks[i]]
with open(ASTRBOT_CONFIG_PATH, "w", encoding="utf-8-sig") as f:
json.dump(d, f, indent=2, ensure_ascii=False)
f.flush()
def update_by_path(self, path: List):
'''根据路径更新配置文件。
这个方法首先会更新缓存在内存中的配置,然后再写入文件。
'''
for key in path:
if key not in self:
raise KeyError(f"Key {key} not found in config.")
def check_exist(self) -> bool:
return os.path.exists(ASTRBOT_CONFIG_PATH)
+7 -2
View File
@@ -58,14 +58,19 @@ class AstrBotUpdator(RepoZipUpdator):
if self.compare_version(VERSION, latest_version) >= 0:
raise Exception("当前已经是最新版本。")
file_url = update_data[0]['zipball_url']
else:
elif str(version).startswith("v"):
# 更新到指定版本
print(f"请求更新到指定版本: {version}")
logger.info(f"正在更新到指定版本: {version}")
for data in update_data:
if data['tag_name'] == version:
file_url = data['zipball_url']
if not file_url:
raise Exception(f"未找到版本号为 {version} 的更新文件。")
else:
if len(str(version)) != 40:
raise Exception("commit hash 长度不正确,应为 40")
logger.info(f"正在尝试更新到指定 commit: {version}")
file_url = "https://github.com/Soulter/AstrBot/archive/" + version + ".zip"
try:
await download_file(file_url, "temp.zip")