Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b5d49cb41 | |||
| 3fd35b6058 | |||
| 5f86c4ab99 | |||
| c94a7f6629 | |||
| 7d6beb4141 | |||
| e2117e690a | |||
| fb791290e2 | |||
| 5dd1488b5d | |||
| 529cd64d82 | |||
| d2bd3e8da8 | |||
| e42ce7dd86 | |||
| 40709462ee | |||
| 2ad6c01a4d | |||
| 70c12e788e | |||
| 1713791c90 | |||
| 9aa23fd412 | |||
| e4ba09cd93 |
@@ -20,78 +20,89 @@ _✨ 易上手的多平台 LLM 聊天机器人及开发框架 ✨_
|
||||
[<img src="https://api.gitsponsors.com/api/badge/img?id=575865240" height="20">](https://api.gitsponsors.com/api/badge/link?p=XEpbdGxlitw/RbcwiTX93UMzNK/jgDYC8NiSzamIPMoKvG2lBFmyXhSS/b0hFoWlBBMX2L5X5CxTDsUdyvcIEHTOfnkXz47UNOZvMwyt5CzbYpq0SEzsSV1OJF1cCo90qC/ZyYKYOWedal3MhZ3ikw==)
|
||||
</a>
|
||||
|
||||
<a href="https://astrbot.lwl.lol/">查看文档</a> |
|
||||
<a href="https://astrbot.app/">查看文档</a> |
|
||||
<a href="https://github.com/Soulter/AstrBot/issues">问题提交</a>
|
||||
</div>
|
||||
|
||||
AstrBot 是一个松耦合、异步、支持多消息平台部署、具有易用的插件系统和完善的大语言模型(LLM)接入功能的聊天机器人及开发框架。
|
||||
|
||||
## ✨ 多消息平台部署
|
||||
## ✨ 主要功能
|
||||
|
||||
1. QQ 群、QQ 频道、微信个人号、Telegram。
|
||||
2. 内置 Web Chat,即使不部署到消息平台也能聊天。
|
||||
3. 支持文本转图片,Markdown 渲染。
|
||||
|
||||
## ✨ 多 LLM 配置
|
||||
1. **大语言模型对话**。支持各种大语言模型,包括 OpenAI API、Google Gemini、Llama、Deepseek、ChatGLM 等,支持接入本地部署的大模型,通过 Ollama、LLMTuner。具有多轮对话、人格情境、多模态能力,支持图片理解、语音转文字(Whisper)。
|
||||
2. **多消息平台接入**。支持接入 QQ(OneBot)、QQ 频道、微信(Gewechat、VChat)、Telegram。后续将支持钉钉、飞书、Discord、WhatsApp、小爱音响。支持速率限制、白名单、关键词过滤、百度内容审核。
|
||||
3. **Agent**。原生支持部分 Agent 能力,如代码执行器、自然语言待办、网页搜索。对接 [Dify 平台](https://astrbot.app/others/dify.html),便捷接入 Dify 智能助手、知识库和 Dify 工作流。
|
||||
4. **插件扩展**。深度优化的插件机制,支持[开发插件](https://astrbot.app/dev/plugin.html)扩展功能,极简开发。已支持安装多个插件。
|
||||
5. **可视化管理面板**。支持可视化修改配置、插件管理、日志查看等功能,降低配置难度。集成 WebChat,可在面板上与大模型对话。
|
||||
6. **高稳定性、高模块化**。基于事件总线和流水线的架构设计,高度模块化,低耦合。
|
||||
|
||||
1. 适配 OpenAI API,支持接入 Gemini、GPT、Llama、Claude、DeepSeek、GLM 等各种大语言模型。
|
||||
2. 支持 OneAPI 等分发平台。
|
||||
3. 支持 LLMTuner 载入微调模型。
|
||||
4. 支持 Ollama 载入自部署模型。
|
||||
4. 支持网页搜索(Web Search)、自然语言待办提醒。
|
||||
5. 支持 Whisper 语音转文字
|
||||
> [!TIP]
|
||||
> 管理面板在线体验 Demo: [https://demo.astrbot.app/](https://demo.astrbot.app/)
|
||||
>
|
||||
> 用户名: `astrbot`, 密码: `astrbot`。此 Demo 未配置 LLM,因此无法在聊天页使用大模型。
|
||||
|
||||
## ✨ 管理面板
|
||||
## ✨ 使用方式
|
||||
|
||||
1. 支持可视化修改配置
|
||||
2. 日志实时查看
|
||||
3. 简单的信息统计
|
||||
4. 插件管理
|
||||
#### Docker 部署
|
||||
|
||||
## ✨ 支持 Dify
|
||||
请参阅官方文档 [使用 Docker 部署 AstrBot](https://astrbot.app/deploy/astrbot/docker.html#%E4%BD%BF%E7%94%A8-docker-%E9%83%A8%E7%BD%B2-astrbot) 。
|
||||
|
||||
1. 对接了 LLMOps 平台 Dify,便捷接入 Dify 智能助手、知识库和 Dify 工作流
|
||||
#### Windows 一键安装器部署
|
||||
|
||||
## ✨ 代码执行器(Beta)
|
||||
需要电脑上安装有 Python(>3.10)。请参阅官方文档 [使用 Windows 一键安装器部署 AstrBot](https://astrbot.app/deploy/astrbot/windows.html) 。
|
||||
|
||||
基于 Docker 的沙箱化代码执行器(Beta 测试中)
|
||||
|
||||
> [!NOTE]
|
||||
> 文件输入/输出目前仅测试了 Napcat(QQ), Lagrange(QQ)
|
||||
|
||||
<div align='center'>
|
||||
|
||||
<img src="https://github.com/user-attachments/assets/4ee688d9-467d-45c8-99d6-368f9a8a92d8" width="600">
|
||||
|
||||
</div>
|
||||
|
||||
## ✨ 云部署
|
||||
#### Replit 部署
|
||||
|
||||
[](https://repl.it/github/Soulter/AstrBot)
|
||||
|
||||
#### CasaOS 部署
|
||||
|
||||
社区贡献的部署方式。
|
||||
|
||||
请参阅官方文档 [通过源码部署 AstrBot](https://astrbot.app/deploy/astrbot/casaos.html) 。
|
||||
|
||||
#### 手动部署
|
||||
|
||||
请参阅官方文档 [通过源码部署 AstrBot](https://astrbot.app/deploy/astrbot/cli.html) 。
|
||||
|
||||
|
||||
## ⚡ 消息平台支持情况
|
||||
|
||||
|
||||
| 平台 | 支持性 | 详情 | 消息类型 |
|
||||
| -------- | ------- | ------- | ------ |
|
||||
| QQ | ✔ | 私聊、群聊 | 文字、图片、语音 |
|
||||
| QQ 官方API | ✔ | 私聊、群聊,QQ 频道私聊、群聊 | 文字、图片 |
|
||||
| 微信 | ✔ | [Gewechat](https://github.com/Devo919/Gewechat)。微信个人号私聊、群聊 | 文字 |
|
||||
| [Telegram](https://github.com/Soulter/astrbot_plugin_telegram) | ✔ | 私聊、群聊 | 文字、图片 |
|
||||
| 微信对话开放平台 | 🚧 | 计划内 | - |
|
||||
| 飞书 | 🚧 | 计划内 | - |
|
||||
| Discord | 🚧 | 计划内 | - |
|
||||
| WhatsApp | 🚧 | 计划内 | - |
|
||||
| 小爱音响 | 🚧 | 计划内 | - |
|
||||
|
||||
## ❤️ 贡献
|
||||
|
||||
欢迎任何 Issues/Pull Requests!只需要将你的更改提交到此项目 :)
|
||||
|
||||
对于新功能的添加,请先通过 Issue 讨论。
|
||||
|
||||
## 🔭 展望
|
||||
|
||||
1. 更强大的 Agent 系统。
|
||||
2. 打造插件工作流平台。
|
||||
|
||||
## ✨ Support
|
||||
## 🌟 支持
|
||||
|
||||
- Star 这个项目!
|
||||
- 在[爱发电](https://afdian.com/a/soulter)支持我!
|
||||
- 在[微信](https://drive.soulter.top/f/pYfA/d903f4fa49a496fda3f16d2be9e023b5.png)支持我~
|
||||
|
||||
|
||||
|
||||
## ✨ Demo
|
||||
|
||||
> [!NOTE]
|
||||
> 代码执行器的文件输入/输出目前仅测试了 Napcat(QQ), Lagrange(QQ)
|
||||
|
||||
<div align='center'>
|
||||
|
||||
<img src="https://github.com/user-attachments/assets/4ee688d9-467d-45c8-99d6-368f9a8a92d8" width="600">
|
||||
|
||||
_✨基于 Docker 的沙箱化代码执行器(Beta 测试中)✨_
|
||||
|
||||
<img src="https://github.com/user-attachments/assets/0378f407-6079-4f64-ae4c-e97ab20611d2" height=500>
|
||||
|
||||
_✨ 多模态、网页搜索、长文本转图片(可配置) ✨_
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。
|
||||
"""
|
||||
|
||||
VERSION = "3.4.8"
|
||||
VERSION = "3.4.10"
|
||||
DB_PATH = "data/data_v3.db"
|
||||
|
||||
# 默认配置
|
||||
@@ -285,7 +285,7 @@ CONFIG_METADATA_2 = {
|
||||
"type": "openai_chat_completion",
|
||||
"enable": True,
|
||||
"key": ["ollama"], # ollama 的 key 默认是 ollama
|
||||
"api_base": "http://localhost:11434",
|
||||
"api_base": "http://localhost:11434/v1",
|
||||
"model_config": {
|
||||
"model": "llama3.1-8b",
|
||||
},
|
||||
@@ -396,7 +396,8 @@ CONFIG_METADATA_2 = {
|
||||
"api_base": {
|
||||
"description": "API Base URL",
|
||||
"type": "string",
|
||||
"hint": "API Base URL 请在在模型提供商处获得。支持 Ollama 开放的 API 地址。如果您确认填写正确但是使用时出现了 404 异常,可以尝试在地址末尾加上 `/v1`。",
|
||||
"hint": "API Base URL 请在在模型提供商处获得。如使用时出现了 404 报错,可以尝试在地址末尾加上 `/v1`。",
|
||||
"obvious_hint": True,
|
||||
},
|
||||
"base_model_path": {
|
||||
"description": "基座模型路径",
|
||||
|
||||
@@ -17,6 +17,13 @@ class LLMRequestSubStage(Stage):
|
||||
|
||||
async def initialize(self, ctx: PipelineContext) -> None:
|
||||
self.ctx = ctx
|
||||
self.bot_wake_prefixs = ctx.astrbot_config['wake_prefix'] # list
|
||||
self.provider_wake_prefix = ctx.astrbot_config['provider_settings']['wake_prefix'] # str
|
||||
|
||||
for bwp in self.bot_wake_prefixs:
|
||||
if self.provider_wake_prefix.startswith(bwp):
|
||||
logger.info(f"识别 LLM 聊天额外唤醒前缀 {self.provider_wake_prefix} 以机器人唤醒前缀 {bwp} 开头,已自动去除。")
|
||||
self.provider_wake_prefix = self.provider_wake_prefix[len(bwp):]
|
||||
|
||||
async def process(self, event: AstrMessageEvent, _nested: bool = False) -> Union[None, AsyncGenerator[None, None]]:
|
||||
req: ProviderRequest = None
|
||||
@@ -30,10 +37,10 @@ class LLMRequestSubStage(Stage):
|
||||
assert isinstance(req, ProviderRequest), "provider_request 必须是 ProviderRequest 类型。"
|
||||
else:
|
||||
req = ProviderRequest(prompt="", image_urls=[])
|
||||
if self.ctx.astrbot_config['provider_settings']['wake_prefix']:
|
||||
if not event.message_str.startswith(self.ctx.astrbot_config['provider_settings']['wake_prefix']):
|
||||
if self.provider_wake_prefix:
|
||||
if not event.message_str.startswith(self.provider_wake_prefix):
|
||||
return
|
||||
req.prompt = event.message_str[len(self.ctx.astrbot_config['provider_settings']['wake_prefix']):]
|
||||
req.prompt = event.message_str[len(self.provider_wake_prefix):]
|
||||
req.func_tool = self.ctx.plugin_manager.context.get_llm_tool_manager()
|
||||
for comp in event.message_obj.message:
|
||||
if isinstance(comp, Image):
|
||||
@@ -98,5 +105,5 @@ class LLMRequestSubStage(Stage):
|
||||
|
||||
except BaseException as e:
|
||||
logger.error(traceback.format_exc())
|
||||
event.set_result(MessageEventResult().message("AstrBot 请求 LLM 资源失败:" + str(e)))
|
||||
event.set_result(MessageEventResult().message(f"AstrBot 请求失败。\n错误类型: {type(e).__name__}\n错误信息: {str(e)}"))
|
||||
return
|
||||
@@ -28,7 +28,9 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
||||
elif segment.file and segment.file.startswith("http"):
|
||||
image_file_path = await download_image_by_url(segment.file)
|
||||
image_base64 = file_to_base64(image_file_path)
|
||||
d['data']['file'] = image_base64
|
||||
d['data'] = {
|
||||
'file': image_base64,
|
||||
}
|
||||
ret.append(d)
|
||||
return ret
|
||||
|
||||
|
||||
@@ -166,6 +166,21 @@ class SimpleGewechatClient():
|
||||
) as resp:
|
||||
json_blob = await resp.json()
|
||||
return json_blob['data']
|
||||
|
||||
async def logout(self):
|
||||
if self.appid:
|
||||
online = await self.check_online(self.appid)
|
||||
if online:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
f"{self.base_url}/login/logout",
|
||||
headers=self.headers,
|
||||
json={
|
||||
"appId": self.appid
|
||||
}
|
||||
) as resp:
|
||||
json_blob = await resp.json()
|
||||
logger.info(f"登出结果: {json_blob}")
|
||||
|
||||
async def login(self):
|
||||
if self.token is None:
|
||||
|
||||
@@ -68,6 +68,9 @@ class GewechatPlatformAdapter(Platform):
|
||||
|
||||
return self._run()
|
||||
|
||||
async def logout(self):
|
||||
await self.client.logout()
|
||||
|
||||
async def _run(self):
|
||||
await self.client.login()
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class SimpleGoogleGenAIClient():
|
||||
self.api_base = api_base[:-1]
|
||||
else:
|
||||
self.api_base = api_base
|
||||
self.client = aiohttp.ClientSession()
|
||||
self.client = aiohttp.ClientSession(trust_env=True)
|
||||
|
||||
async def models_list(self) -> List[str]:
|
||||
request_url = f"{self.api_base}/v1beta/models?key={self.api_key}"
|
||||
@@ -224,15 +224,24 @@ class ProviderGoogleGenAI(Provider):
|
||||
|
||||
try:
|
||||
llm_response = await self._query(payloads, func_tool)
|
||||
await self.save_history(contexts, new_record, session_id, llm_response)
|
||||
return llm_response
|
||||
except Exception as e:
|
||||
if "maximum context length" in str(e):
|
||||
logger.warning(f"请求失败:{e}。上下文长度超过限制。尝试弹出最早的记录然后重试。")
|
||||
self.pop_record(session_id)
|
||||
logger.warning(traceback.format_exc())
|
||||
|
||||
await self.save_history(contexts, new_record, session_id, llm_response)
|
||||
|
||||
return llm_response
|
||||
retry_cnt = 10
|
||||
while retry_cnt > 0:
|
||||
logger.warning(f"请求失败:{e}。上下文长度超过限制。尝试弹出最早的记录然后重试。")
|
||||
try:
|
||||
self.pop_record(session_id)
|
||||
llm_response = await self._query(payloads, func_tool)
|
||||
break
|
||||
except Exception as e:
|
||||
if "maximum context length" in str(e):
|
||||
retry_cnt -= 1
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise e
|
||||
|
||||
async def save_history(self, contexts: List, new_record: dict, session_id: str, llm_response: LLMResponse):
|
||||
if llm_response.role == "assistant" and session_id:
|
||||
|
||||
@@ -164,15 +164,25 @@ class ProviderOpenAIOfficial(Provider):
|
||||
|
||||
try:
|
||||
llm_response = await self._query(payloads, func_tool)
|
||||
await self.save_history(contexts, new_record, session_id, llm_response)
|
||||
return llm_response
|
||||
except Exception as e:
|
||||
if "maximum context length" in str(e):
|
||||
logger.warning(f"请求失败:{e}。上下文长度超过限制。尝试弹出最早的记录然后重试。")
|
||||
self.pop_record(session_id)
|
||||
logger.warning(traceback.format_exc())
|
||||
|
||||
await self.save_history(contexts, new_record, session_id, llm_response)
|
||||
retry_cnt = 10
|
||||
while retry_cnt > 0:
|
||||
logger.warning(f"请求失败:{e}。上下文长度超过限制。尝试弹出最早的记录然后重试。")
|
||||
try:
|
||||
self.pop_record(session_id)
|
||||
llm_response = await self._query(payloads, func_tool)
|
||||
break
|
||||
except Exception as e:
|
||||
if "maximum context length" in str(e):
|
||||
retry_cnt -= 1
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise e
|
||||
|
||||
return llm_response
|
||||
|
||||
async def save_history(self, contexts: List, new_record: dict, session_id: str, llm_response: LLMResponse):
|
||||
if llm_response.role == "assistant" and session_id:
|
||||
|
||||
@@ -60,15 +60,23 @@ class ProviderZhipu(ProviderOpenAIOfficial):
|
||||
"messages": context_query,
|
||||
**model_cfgs
|
||||
}
|
||||
llm_response = None
|
||||
try:
|
||||
llm_response = await self._query(payloads, func_tool)
|
||||
await self.save_history(contexts, new_record, session_id, llm_response)
|
||||
return llm_response
|
||||
except Exception as e:
|
||||
if "maximum context length" in str(e):
|
||||
logger.warning(f"请求失败:{e}。上下文长度超过限制。尝试弹出最早的记录然后重试。")
|
||||
self.pop_record(session_id)
|
||||
logger.warning(traceback.format_exc())
|
||||
|
||||
await self.save_history(contexts, new_record, session_id, llm_response)
|
||||
|
||||
return llm_response
|
||||
retry_cnt = 10
|
||||
while retry_cnt > 0:
|
||||
logger.warning(f"请求失败:{e}。上下文长度超过限制。尝试弹出最早的记录然后重试。")
|
||||
try:
|
||||
self.pop_record(session_id)
|
||||
llm_response = await self._query(payloads, func_tool)
|
||||
break
|
||||
except Exception as e:
|
||||
if "maximum context length" in str(e):
|
||||
retry_cnt -= 1
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
raise e
|
||||
@@ -20,6 +20,6 @@ class PermissionTypeFilter(HandlerFilter):
|
||||
if self.permission_type == PermissionType.ADMIN:
|
||||
if not event.is_admin():
|
||||
event.stop_event()
|
||||
raise ValueError("您没有权限执行此操作。")
|
||||
raise ValueError(f"您 (ID: {event.get_sender_id()}) 没有权限执行此操作。")
|
||||
|
||||
return True
|
||||
|
||||
@@ -8,12 +8,14 @@ class PlatformAdapterType(enum.Flag):
|
||||
AIOCQHTTP = enum.auto()
|
||||
QQOFFICIAL = enum.auto()
|
||||
VCHAT = enum.auto()
|
||||
ALL = AIOCQHTTP | QQOFFICIAL | VCHAT
|
||||
GEWECHAT = enum.auto()
|
||||
ALL = AIOCQHTTP | QQOFFICIAL | VCHAT | GEWECHAT
|
||||
|
||||
ADAPTER_NAME_2_TYPE = {
|
||||
"aiocqhttp": PlatformAdapterType.AIOCQHTTP,
|
||||
"qq_official": PlatformAdapterType.QQOFFICIAL,
|
||||
"vchat": PlatformAdapterType.VCHAT
|
||||
"vchat": PlatformAdapterType.VCHAT,
|
||||
"gewechat": PlatformAdapterType.GEWECHAT
|
||||
}
|
||||
|
||||
class PlatformAdapterTypeFilter(HandlerFilter):
|
||||
|
||||
@@ -11,7 +11,7 @@ class AstrBotUpdator(RepoZipUpdator):
|
||||
def __init__(self, repo_mirror: str = "") -> None:
|
||||
super().__init__(repo_mirror)
|
||||
self.MAIN_PATH = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
|
||||
self.ASTRBOT_RELEASE_API = "https://api.github.com/repos/Soulter/AstrBot/releases"
|
||||
self.ASTRBOT_RELEASE_API = "https://api.soulter.top/releases"
|
||||
|
||||
def terminate_child_processes(self):
|
||||
try:
|
||||
|
||||
+46
-10
@@ -70,7 +70,7 @@ async def download_image_by_url(url: str, post: bool = False, post_data: dict =
|
||||
下载图片, 返回 path
|
||||
'''
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
if post:
|
||||
async with session.post(url, json=post_data) as resp:
|
||||
if not path:
|
||||
@@ -91,7 +91,7 @@ async def download_image_by_url(url: str, post: bool = False, post_data: dict =
|
||||
# 关闭SSL验证
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.set_ciphers('DEFAULT')
|
||||
async with aiohttp.ClientSession(trust_env=False) as session:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
if post:
|
||||
async with session.get(url, ssl=ssl_context) as resp:
|
||||
return save_temp_img(await resp.read())
|
||||
@@ -101,34 +101,57 @@ async def download_image_by_url(url: str, post: bool = False, post_data: dict =
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
async def download_file(url: str, path: str):
|
||||
async def download_file(url: str, path: str, show_progress: bool = False):
|
||||
'''
|
||||
从指定 url 下载文件到指定路径 path
|
||||
'''
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, timeout=20) as resp:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.get(url, timeout=120) as resp:
|
||||
if resp.status != 200:
|
||||
raise Exception(f"下载文件失败: {resp.status}")
|
||||
total_size = int(resp.headers.get('content-length', 0))
|
||||
downloaded_size = 0
|
||||
start_time = time.time()
|
||||
if show_progress:
|
||||
print(f"文件大小: {total_size / 1024:.2f} KB | 文件地址: {url}")
|
||||
with open(path, 'wb') as f:
|
||||
while True:
|
||||
chunk = await resp.content.read(8192)
|
||||
if not chunk:
|
||||
break
|
||||
f.write(chunk)
|
||||
downloaded_size += len(chunk)
|
||||
if show_progress:
|
||||
elapsed_time = time.time() - start_time
|
||||
speed = downloaded_size / 1024 / elapsed_time # KB/s
|
||||
print(f"\r下载进度: {downloaded_size / total_size:.2%} 速度: {speed:.2f} KB/s", end='')
|
||||
except aiohttp.client.ClientConnectorSSLError:
|
||||
# 关闭SSL验证
|
||||
ssl_context = ssl.create_default_context()
|
||||
ssl_context.set_ciphers('DEFAULT')
|
||||
async with aiohttp.ClientSession(trust_env=False) as session:
|
||||
async with session.get(url, ssl=ssl_context, timeout=20) as resp:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, ssl=ssl_context, timeout=120) as resp:
|
||||
total_size = int(resp.headers.get('content-length', 0))
|
||||
downloaded_size = 0
|
||||
start_time = time.time()
|
||||
if show_progress:
|
||||
print(f"文件大小: {total_size / 1024:.2f} KB | 文件地址: {url}")
|
||||
with open(path, 'wb') as f:
|
||||
while True:
|
||||
chunk = await resp.content.read(8192)
|
||||
if not chunk:
|
||||
break
|
||||
f.write(chunk)
|
||||
|
||||
downloaded_size += len(chunk)
|
||||
if show_progress:
|
||||
elapsed_time = time.time() - start_time
|
||||
speed = downloaded_size / 1024 / elapsed_time # KB/s
|
||||
print(f"\r下载进度: {downloaded_size / total_size:.2%} 速度: {speed:.2f} KB/s", end='')
|
||||
if show_progress:
|
||||
print()
|
||||
|
||||
|
||||
def file_to_base64(file_path: str) -> str:
|
||||
with open(file_path, "rb") as f:
|
||||
data_bytes = f.read()
|
||||
@@ -147,9 +170,22 @@ def get_local_ip_addresses():
|
||||
s.close()
|
||||
return ip
|
||||
|
||||
async def get_dashboard_version():
|
||||
if os.path.exists("data/dist"):
|
||||
if os.path.exists("data/dist/assets/version"):
|
||||
with open("data/dist/assets/version", "r") as f:
|
||||
v = f.read().strip()
|
||||
return v
|
||||
return None
|
||||
|
||||
async def download_dashboard():
|
||||
'''下载管理面板文件'''
|
||||
dashboard_release_url = "https://astrbot-registry.lwl.lol/download/astrbot-dashboard/latest/dist.zip"
|
||||
await download_file(dashboard_release_url, "data/dashboard.zip")
|
||||
dashboard_release_url = "https://astrbot-registry.soulter.top/download/astrbot-dashboard/latest/dist.zip"
|
||||
try:
|
||||
await download_file(dashboard_release_url, "data/dashboard.zip", show_progress=True)
|
||||
except BaseException as _:
|
||||
dashboard_release_url = "https://github.com/Soulter/AstrBot/releases/latest/download/dist.zip"
|
||||
await download_file(dashboard_release_url, "data/dashboard.zip", show_progress=True)
|
||||
print("解压管理面板文件中...")
|
||||
with zipfile.ZipFile("data/dashboard.zip", "r") as z:
|
||||
z.extractall("data")
|
||||
@@ -30,7 +30,7 @@ class Metric():
|
||||
pass
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.post(base_url, json=payload, timeout=3) as response:
|
||||
if response.status != 200:
|
||||
pass
|
||||
|
||||
@@ -83,7 +83,7 @@ class LocalRenderStrategy(RenderStrategy):
|
||||
try:
|
||||
image_url = re.findall(IMAGE_REGEX, line)[0]
|
||||
print(image_url)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.get(image_url) as resp:
|
||||
image_res = Image.open(BytesIO(await resp.read()))
|
||||
images[i] = image_res
|
||||
|
||||
@@ -33,7 +33,7 @@ class NetworkRenderStrategy(RenderStrategy):
|
||||
}
|
||||
}
|
||||
if return_url:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.post(f"{self.BASE_RENDER_URL}/generate", json=post_data) as resp:
|
||||
ret = await resp.json()
|
||||
return f"{self.BASE_RENDER_URL}/{ret['data']['id']}"
|
||||
|
||||
@@ -29,7 +29,7 @@ class RepoZipUpdator():
|
||||
返回一个列表,每个元素是一个字典,包含版本号、发布时间、更新内容、commit hash等信息。
|
||||
'''
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.get(url) as response:
|
||||
result = await response.json()
|
||||
if not result:
|
||||
@@ -111,7 +111,7 @@ class RepoZipUpdator():
|
||||
releases = await self.fetch_release_info(url=release_url)
|
||||
if not releases:
|
||||
# download from the default branch directly.
|
||||
logger.warning(f"未在仓库 {author}/{repo} 中找到任何发布版本,正在从默认分支下载。")
|
||||
logger.info(f"未在仓库 {author}/{repo} 中找到任何发布版本,正在从默认分支下载。")
|
||||
release_url = f"https://github.com/{author}/{repo}/archive/refs/heads/master.zip"
|
||||
else:
|
||||
release_url = releases[0]['zipball_url']
|
||||
|
||||
@@ -182,8 +182,7 @@ class ChatRoute(Route):
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
except BaseException as e:
|
||||
logger.error(e)
|
||||
logger.error(f"与用户 {username} 断开聊天长连接。")
|
||||
logger.debug(f"用户 {username} 断开聊天长连接: {str(e)}。")
|
||||
self.curr_chat_sse.pop(username)
|
||||
return
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class PluginRoute(Route):
|
||||
async def get_online_plugins(self):
|
||||
url = "https://soulter.github.io/AstrBot_Plugins_Collection/plugins.json"
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.get(url) as response:
|
||||
result = await response.json()
|
||||
return Response().ok(result).__dict__
|
||||
|
||||
@@ -15,7 +15,6 @@ class StatRoute(Route):
|
||||
self.routes = {
|
||||
'/stat/get': ('GET', self.get_stat),
|
||||
'/stat/version': ('GET', self.get_version),
|
||||
'/stat/dashboard-version': ('GET', self.get_dashboard_version),
|
||||
'/stat/start-time': ('GET', self.get_start_time),
|
||||
'/stat/restart-core': ('GET', self.restart_core)
|
||||
}
|
||||
@@ -37,16 +36,6 @@ class StatRoute(Route):
|
||||
"version": VERSION
|
||||
}).__dict__
|
||||
|
||||
async def get_dashboard_version(self):
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get('https://api.github.com/repos/Soulter/Astrbot-dashboard/actions/artifacts') as resp:
|
||||
data = await resp.json()
|
||||
return Response().ok({
|
||||
"data": data,
|
||||
"mark": "unimplemented feature"
|
||||
}).__dict__
|
||||
|
||||
|
||||
async def get_start_time(self):
|
||||
return Response().ok({
|
||||
"start_time": self.core_lifecycle.start_time
|
||||
|
||||
@@ -4,6 +4,8 @@ from .route import Route, Response, RouteContext
|
||||
from quart import request
|
||||
from astrbot.core.updator import AstrBotUpdator
|
||||
from astrbot.core import logger, pip_installer
|
||||
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
|
||||
from astrbot.core.config.default import VERSION
|
||||
|
||||
class UpdateRoute(Route):
|
||||
def __init__(self, context: RouteContext, astrbot_updator: AstrBotUpdator) -> None:
|
||||
@@ -11,21 +13,34 @@ class UpdateRoute(Route):
|
||||
self.routes = {
|
||||
'/update/check': ('GET', self.check_update),
|
||||
'/update/do': ('POST', self.update_project),
|
||||
'/update/dashboard': ('POST', self.update_dashboard),
|
||||
'/update/pip-install': ('POST', self.install_pip_package)
|
||||
}
|
||||
self.astrbot_updator = astrbot_updator
|
||||
self.register_routes()
|
||||
|
||||
async def check_update(self):
|
||||
type_ = request.args.get('type', None)
|
||||
|
||||
try:
|
||||
ret = await self.astrbot_updator.check_update(None, None)
|
||||
return Response(
|
||||
status="success",
|
||||
message=str(ret) if ret is not None else "已经是最新版本了。",
|
||||
data={
|
||||
"has_new_version": ret is not None
|
||||
}
|
||||
).__dict__
|
||||
dv = await get_dashboard_version()
|
||||
if type_ == 'dashboard':
|
||||
return Response().ok({
|
||||
"has_new_version": dv != f"v{VERSION}",
|
||||
"current_version": dv
|
||||
}).__dict__
|
||||
else:
|
||||
ret = await self.astrbot_updator.check_update(None, None)
|
||||
return Response(
|
||||
status="success",
|
||||
message=str(ret) if ret is not None else "已经是最新版本了。",
|
||||
data={
|
||||
"version": f"v{VERSION}",
|
||||
"has_new_version": ret is not None,
|
||||
"dashboard_version": dv,
|
||||
"dashboard_has_new_version": dv != f"v{VERSION}"
|
||||
}
|
||||
).__dict__
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
return Response().error(e.__str__()).__dict__
|
||||
@@ -41,6 +56,13 @@ class UpdateRoute(Route):
|
||||
latest = False
|
||||
try:
|
||||
await self.astrbot_updator.update(latest=latest, version=version)
|
||||
|
||||
if latest:
|
||||
try:
|
||||
await download_dashboard()
|
||||
except Exception as e:
|
||||
logger.error(f"下载管理面板文件失败: {e}。")
|
||||
|
||||
if reboot:
|
||||
threading.Thread(target=self.astrbot_updator._reboot, args=(2, )).start()
|
||||
return Response().ok(None, "更新成功,AstrBot 将在 2 秒内全量重启以应用新的代码。").__dict__
|
||||
@@ -50,6 +72,18 @@ class UpdateRoute(Route):
|
||||
logger.error(f"/api/update_project: {traceback.format_exc()}")
|
||||
return Response().error(e.__str__()).__dict__
|
||||
|
||||
async def update_dashboard(self):
|
||||
try:
|
||||
try:
|
||||
await download_dashboard()
|
||||
except Exception as e:
|
||||
logger.error(f"下载管理面板文件失败: {e}。")
|
||||
return Response().error(f"下载管理面板文件失败: {e}").__dict__
|
||||
return Response().ok(None, "更新成功。刷新页面即可应用新版本面板。").__dict__
|
||||
except Exception as e:
|
||||
logger.error(f"/api/update_dashboard: {traceback.format_exc()}")
|
||||
return Response().error(e.__str__()).__dict__
|
||||
|
||||
async def install_pip_package(self):
|
||||
data = await request.json
|
||||
package = data.get('package', '')
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# What's Changed
|
||||
|
||||
- 修复 LLM 请求报错信息被覆盖的问题,增强 LLM 请求错误处理 #243
|
||||
- 修复 Napcat 接口更新导致 QQ 图片发送失败的问题 #246
|
||||
- 修复某些请求不能正确应用代理的问题
|
||||
- 针对 api_base 的明显提示,修改 ollama 模板的 api_base #247
|
||||
- 支持登出 gewechat,在webchat等地方使用 `/gewe_logout` 指令,这在微信上显示账号下线但是 gewe 仍显示设备在线时很好用
|
||||
- 添加gewechat适配器过滤器
|
||||
- help显示AstrBot和webui版本
|
||||
- 优化webui和主程序更新的协调
|
||||
- 下载管理面板时显示提示、下载进度和下载速度
|
||||
- 管理面板前端更新功能入口移入右上角更新按钮,以便统一管理 #245
|
||||
@@ -0,0 +1,6 @@
|
||||
# What's Changed
|
||||
|
||||
- AstrBot 新域名:astrbot.app
|
||||
- LLM额外唤醒词与机器人唤醒词冲突时的处理
|
||||
- 调整部分日志的严重级别
|
||||
- 下载管理面板时显示提示、下载进度和下载速度
|
||||
Generated
-9998
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,9 @@ let newUsername = ref('');
|
||||
let status = ref('');
|
||||
let updateStatus = ref('')
|
||||
let hasNewVersion = ref(false);
|
||||
let botCurrVersion = ref('');
|
||||
let dashboardHasNewVersion = ref(false);
|
||||
let dashboardCurrentVersion = ref('');
|
||||
let version = ref('');
|
||||
|
||||
const open = (link: string) => {
|
||||
@@ -64,6 +67,9 @@ function checkUpdate() {
|
||||
.then((res) => {
|
||||
hasNewVersion.value = res.data.data.has_new_version;
|
||||
updateStatus.value = res.data.message;
|
||||
botCurrVersion.value = res.data.data.version;
|
||||
dashboardCurrentVersion.value = res.data.data.dashboard_version;
|
||||
dashboardHasNewVersion.value = res.data.data.dashboard_has_new_version;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response.status == 401) {
|
||||
@@ -84,7 +90,24 @@ function switchVersion(version: string) {
|
||||
})
|
||||
.then((res) => {
|
||||
updateStatus.value = res.data.message;
|
||||
if (res.data.status == 'success') {
|
||||
if (res.data.status == 'ok') {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
updateStatus.value = err
|
||||
});
|
||||
}
|
||||
|
||||
function updateDashboard() {
|
||||
updateStatus.value = '正在更新...';
|
||||
axios.post('/api/update/dashboard')
|
||||
.then((res) => {
|
||||
updateStatus.value = res.data.message;
|
||||
if (res.data.status == 'ok') {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
@@ -106,8 +129,8 @@ commonStore.getStartTime();
|
||||
<template>
|
||||
<v-app-bar elevation="0" height="70">
|
||||
|
||||
<v-btn style="margin-left: 22px;" class="hidden-md-and-down text-secondary" color="lightsecondary" icon rounded="sm" variant="flat"
|
||||
@click.stop="customizer.SET_MINI_SIDEBAR(!customizer.mini_sidebar)" size="small">
|
||||
<v-btn style="margin-left: 22px;" class="hidden-md-and-down text-secondary" color="lightsecondary" icon rounded="sm"
|
||||
variant="flat" @click.stop="customizer.SET_MINI_SIDEBAR(!customizer.mini_sidebar)" size="small">
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
<v-btn class="hidden-lg-and-up text-secondary ms-3" color="lightsecondary" icon rounded="sm" variant="flat"
|
||||
@@ -136,11 +159,16 @@ commonStore.getStartTime();
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">更新项目</span>
|
||||
<span class="text-h5">更新 AstrBot</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<h3 class="mb-4">升级到最新版本</h3>
|
||||
<h3 class="mb-4">升级到项目最新版本</h3>
|
||||
<small>当前版本 {{ botCurrVersion }}</small>
|
||||
<div class="mb-4">
|
||||
<small>会同时尝试更新机器人主程序和管理面板。如果您正在使用 Docker 部署,也可以重新拉取镜像或者使用 <a
|
||||
href="https://containrrr.dev/watchtower/usage-overview/">watchtower</a> 来自动监控拉取。</small>
|
||||
</div>
|
||||
<p>{{ updateStatus }}</p>
|
||||
<v-btn class="mt-4 mb-4" @click="switchVersion('latest')" color="primary" style="border-radius: 10px;"
|
||||
:disabled="!hasNewVersion">
|
||||
@@ -148,7 +176,11 @@ commonStore.getStartTime();
|
||||
</v-btn>
|
||||
<v-divider></v-divider>
|
||||
<div style="margin-top: 16px;">
|
||||
<h3 class="mb-4">切换到指定版本或指定提交</h3>
|
||||
<h3 class="mb-4">切换到项目指定版本或指定提交</h3>
|
||||
<div class="mb-4">
|
||||
<small>跳到旧版本不会重新下载管理面板文件,这可能会造成部分数据显示错误。您可在 <a href="https://github.com/Soulter/AstrBot/releases">此处</a>
|
||||
找到对应的面板文件 dist.zip,解压后替换 data/dist 文件夹即可。</small>
|
||||
</div>
|
||||
<v-text-field label="输入版本号或 master 分支下的 commit hash。" v-model="version" required
|
||||
variant="outlined"></v-text-field>
|
||||
<div class="mb-4">
|
||||
@@ -160,7 +192,29 @@ commonStore.getStartTime();
|
||||
<v-btn color="error" style="border-radius: 10px;" @click="switchVersion(version)">
|
||||
确定切换
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<v-divider></v-divider>
|
||||
<div style="margin-top: 16px;">
|
||||
<h3 class="mb-4">更新管理面板到最新版本</h3>
|
||||
<div class="mb-4">
|
||||
<small>当前版本 {{ dashboardCurrentVersion }}</small>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<p v-if="dashboardHasNewVersion">
|
||||
有新版本!
|
||||
</p>
|
||||
<p v-else="dashboardHasNewVersion">
|
||||
已经是最新版本了。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<v-btn color="primary" style="border-radius: 10px;" @click="updateDashboard()">
|
||||
下载并更新
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
@@ -190,8 +244,7 @@ commonStore.getStartTime();
|
||||
<v-text-field label="原密码*" type="password" v-model="password" required
|
||||
variant="outlined"></v-text-field>
|
||||
|
||||
<v-text-field label="新用户名" v-model="newUsername" required
|
||||
variant="outlined"></v-text-field>
|
||||
<v-text-field label="新用户名" v-model="newUsername" required variant="outlined"></v-text-field>
|
||||
|
||||
<v-text-field label="新密码" type="password" v-model="newPassword" required
|
||||
variant="outlined"></v-text-field>
|
||||
|
||||
@@ -27,10 +27,10 @@ const sidebarMenu = shallowRef(sidebarItems);
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
<small style="display: block;" v-if="buildVer">构建: {{ buildVer }}</small>
|
||||
<small style="display: block;" v-else="buildVer">构建: embedded</small>
|
||||
<small style="display: block;" v-else>构建: embedded</small>
|
||||
<v-tooltip text="使用 /dashbord_update 指令更新管理面板">
|
||||
<template v-slot:activator="{ props }">
|
||||
<small v-bind="props" v-if="buildVer != version" style="display: block; margin-top: 4px;">面板有更新</small>
|
||||
<small v-bind="props" v-if="hasWebUIUpdate" style="display: block; margin-top: 4px;">面板有更新</small>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
|
||||
@@ -50,19 +50,12 @@ export default {
|
||||
},
|
||||
data: () => ({
|
||||
version: "",
|
||||
buildVer: ""
|
||||
buildVer: "",
|
||||
hasWebUIUpdate: false,
|
||||
}),
|
||||
mounted() {
|
||||
this.get_version()
|
||||
fetch('/assets/version').then((res) => {
|
||||
return res.text()
|
||||
}).then((res) => {
|
||||
if (res.length > 10) {
|
||||
// 不是版本,不显示 😎
|
||||
return
|
||||
}
|
||||
this.buildVer = res.replace(/\s+/g, '')
|
||||
})
|
||||
this.check_webui_update()
|
||||
},
|
||||
methods: {
|
||||
get_version() {
|
||||
@@ -73,6 +66,16 @@ export default {
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
},
|
||||
check_webui_update() {
|
||||
axios.get('/api/update/check?type=dashboard')
|
||||
.then((res) => {
|
||||
this.hasWebUIUpdate = res.data.data.has_new_version;
|
||||
this.buildVer = res.data.data.current_version;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ from astrbot.dashboard import AstrBotDashBoardLifecycle
|
||||
from astrbot.core import db_helper
|
||||
from astrbot.core import logger, LogManager, LogBroker
|
||||
from astrbot.core.config.default import VERSION
|
||||
from astrbot.core.utils.io import download_dashboard
|
||||
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
|
||||
|
||||
# add parent path to sys.path
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
@@ -37,22 +37,22 @@ def check_env():
|
||||
|
||||
async def check_dashboard_files():
|
||||
'''下载管理面板文件'''
|
||||
if os.path.exists("data/dist"):
|
||||
if os.path.exists("data/dist/assets/version"):
|
||||
with open("data/dist/assets/version", "r") as f:
|
||||
v = f.read().strip()
|
||||
if v != f"v{VERSION}":
|
||||
logger.warning("检测到管理面板有更新。可以使用 /dashboard update 命令更新。")
|
||||
else:
|
||||
logger.info("管理面板文件已是最新。")
|
||||
|
||||
v = await get_dashboard_version()
|
||||
if v is not None:
|
||||
# has file
|
||||
if v == f"v{VERSION}":
|
||||
logger.info("管理面板文件已是最新。")
|
||||
else:
|
||||
logger.warning("检测到管理面板有更新。可以使用 /dashboard_update 命令更新。")
|
||||
return
|
||||
|
||||
logger.info("开始下载管理面板文件...")
|
||||
logger.info("开始下载管理面板文件...高峰期(晚上)可能导致较慢的速度。如多次下载失败,请前往 https://github.com/Soulter/AstrBot/releases/latest 下载 dist.zip,并将其中的 dist 文件夹解压至 data 目录下。")
|
||||
|
||||
try:
|
||||
await download_dashboard()
|
||||
except Exception as e:
|
||||
logger.critical(f"下载管理面板文件失败: {e}")
|
||||
logger.critical(f"下载管理面板文件失败: {e}。")
|
||||
return
|
||||
|
||||
logger.info("管理面板下载完成。")
|
||||
|
||||
@@ -6,7 +6,8 @@ import astrbot.api.event.filter as filter
|
||||
from astrbot.api.event import AstrMessageEvent, MessageEventResult
|
||||
from astrbot.api import sp
|
||||
from astrbot.api.provider import Personality, ProviderRequest
|
||||
from astrbot.core.utils.io import download_dashboard
|
||||
from astrbot.core.utils.io import download_dashboard, get_dashboard_version
|
||||
from astrbot.core.config.default import VERSION
|
||||
|
||||
from typing import Union
|
||||
|
||||
@@ -23,7 +24,7 @@ class Main(star.Star):
|
||||
|
||||
async def _query_astrbot_notice(self):
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.get("https://astrbot.soulter.top/notice.json", timeout=2) as resp:
|
||||
return (await resp.json())["notice"]
|
||||
except BaseException:
|
||||
@@ -36,9 +37,12 @@ class Main(star.Star):
|
||||
notice = await self._query_astrbot_notice()
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
dashboard_version = await get_dashboard_version()
|
||||
|
||||
msg = "已注册的 AstrBot 内置指令:\n"
|
||||
msg += f"""[System]
|
||||
msg = f"""AstrBot v{VERSION}(WebUI: {dashboard_version})
|
||||
已注册的 AstrBot 内置指令:
|
||||
[System]
|
||||
/plugin: 查看注册的插件、插件帮助
|
||||
/t2i: 开启/关闭文本转图片模式
|
||||
/sid: 获取当前会话的 ID
|
||||
@@ -414,6 +418,16 @@ UID: {user_id} 此 ID 可用于设置管理员。/op <UID> 授权管理员, /deo
|
||||
del session_var[key]
|
||||
sp.put("session_variables", session_vars)
|
||||
yield event.plain_result(f"会话 {session_id} 变量 {key} 移除成功。")
|
||||
|
||||
@filter.command("gewe_logout")
|
||||
async def gewe_logout(self, event: AstrMessageEvent):
|
||||
platforms = self.context.platform_manager.platform_insts
|
||||
for platform in platforms:
|
||||
if platform.meta().name == "gewechat":
|
||||
yield event.plain_result("正在登出 gewechat")
|
||||
await platform.logout()
|
||||
yield event.plain_result("已登出 gewechat")
|
||||
return
|
||||
|
||||
@filter.command_group("kdb")
|
||||
def kdb(self):
|
||||
|
||||
@@ -127,7 +127,7 @@ class Main(star.Star):
|
||||
|
||||
s3_file_url = f"{S3_URL}/{uuid.uuid4().hex}{ext}"
|
||||
|
||||
async with aiohttp.ClientSession(headers = {"Accept": "application/json"}) as session:
|
||||
async with aiohttp.ClientSession(headers = {"Accept": "application/json"}, trust_env=True) as session:
|
||||
async with session.put(s3_file_url, data=file) as resp:
|
||||
if resp.status != 200:
|
||||
raise Exception(f"Failed to upload image: {resp.status}")
|
||||
@@ -140,7 +140,7 @@ class Main(star.Star):
|
||||
docker = aiodocker.Docker()
|
||||
await docker.version()
|
||||
return True
|
||||
except aiodocker.exceptions.DockerError as e:
|
||||
except BaseException as e:
|
||||
logger.info(f"检查 Docker 可用性: {e}")
|
||||
return False
|
||||
|
||||
@@ -159,7 +159,7 @@ class Main(star.Star):
|
||||
|
||||
async def download_image(self, image_url: str, workplace_path: str, filename: str) -> str:
|
||||
'''Download image from url to workplace_path'''
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.get(image_url) as resp:
|
||||
if resp.status != 200:
|
||||
return ""
|
||||
|
||||
@@ -39,7 +39,7 @@ class Main(star.Star):
|
||||
'''获取网页内容'''
|
||||
header = HEADERS
|
||||
header.update({'User-Agent': random.choice(USER_AGENTS)})
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
async with session.get(url, headers=header, timeout=6) as response:
|
||||
html = await response.text(encoding="utf-8")
|
||||
doc = Document(html)
|
||||
|
||||
Reference in New Issue
Block a user