diff --git a/astrbot/core/agent/tool_image_cache.py b/astrbot/core/agent/tool_image_cache.py index 72e22dd52e..0c7bc3c31e 100644 --- a/astrbot/core/agent/tool_image_cache.py +++ b/astrbot/core/agent/tool_image_cache.py @@ -52,7 +52,6 @@ def __init__(self) -> None: self._initialized = True self._cache_dir = os.path.join(get_astrbot_temp_path(), self.CACHE_DIR_NAME) os.makedirs(self._cache_dir, exist_ok=True) - logger.debug(f"ToolImageCache initialized, cache dir: {self._cache_dir}") def _get_file_extension(self, mime_type: str) -> str: """Get file extension from MIME type.""" diff --git a/astrbot/core/config/astrbot_config.py b/astrbot/core/config/astrbot_config.py index 1dd222929c..63236c1d8b 100644 --- a/astrbot/core/config/astrbot_config.py +++ b/astrbot/core/config/astrbot_config.py @@ -103,7 +103,8 @@ def check_config_integrity(self, refer_conf: dict, conf: dict, path=""): for key, value in refer_conf.items(): if key not in conf: # 配置项不存在,插入默认值 - logger.info("检查到配置项不存在,已插入默认值") + path_ = path + "." + key if path else key + logger.info("Config key missing; added default.") new_conf[key] = value has_new = True elif conf[key] is None: @@ -132,12 +133,16 @@ def check_config_integrity(self, refer_conf: dict, conf: dict, path=""): # 检查是否存在参考配置中没有的配置项 for key in list(conf.keys()): if key not in refer_conf: - logger.info("检查到未知配置项,将从当前配置中删除") + path_ = path + "." + key if path else key + logger.info("Config key removed: %s", path_) has_new = True # 顺序不一致也算作变更 if list(conf.keys()) != list(new_conf.keys()): - logger.info("检查到配置项顺序不一致,已重新排序") + if path: + logger.info("Config key order fixed: %s", path) + else: + logger.info("Config key order fixed") has_new = True # 更新原始配置 diff --git a/astrbot/core/core_lifecycle.py b/astrbot/core/core_lifecycle.py index fe6b1c351d..e5e34d4b54 100644 --- a/astrbot/core/core_lifecycle.py +++ b/astrbot/core/core_lifecycle.py @@ -294,7 +294,7 @@ async def start(self) -> None: 用load加载事件总线和任务并初始化, 执行启动完成事件钩子 """ self._load() - logger.info("AstrBot 启动完成。") + logger.info("AstrBot started.") # 执行启动完成事件钩子 handlers = star_handlers_registry.get_handlers_by_event_type( diff --git a/astrbot/core/persona_mgr.py b/astrbot/core/persona_mgr.py index 6320ac3bbc..290501438b 100644 --- a/astrbot/core/persona_mgr.py +++ b/astrbot/core/persona_mgr.py @@ -35,7 +35,7 @@ def __init__(self, db_helper: BaseDatabase, acm: AstrBotConfigManager) -> None: async def initialize(self) -> None: self.personas = await self.get_all_personas() self.get_v3_persona_data() - logger.info(f"已加载 {len(self.personas)} 个人格。") + logger.info("Loaded %s personas.", len(self.personas)) async def get_persona(self, persona_id: str): """获取指定 persona 的信息""" diff --git a/astrbot/core/platform/manager.py b/astrbot/core/platform/manager.py index d592eb2fbf..22409c0f83 100644 --- a/astrbot/core/platform/manager.py +++ b/astrbot/core/platform/manager.py @@ -123,7 +123,9 @@ async def load_platform(self, platform_config: dict) -> None: return logger.info( - f"载入 {platform_config['type']}({platform_config['id']}) 平台适配器 ...", + "Loading IM platform adapter %s(%s) ...", + platform_config["type"], + platform_config["id"], ) match platform_config["type"]: case "aiocqhttp": @@ -201,7 +203,7 @@ async def load_platform(self, platform_config: dict) -> None: if platform_config["type"] not in platform_cls_map: logger.error( - f"未找到适用于 {platform_config['type']}({platform_config['id']}) 平台适配器,请检查是否已经安装或者名称填写错误", + f"Platform adapter not found: {platform_config['type']}({platform_config['id']}).", ) return cls_type = platform_cls_map[platform_config["type"]] diff --git a/astrbot/core/platform/register.py b/astrbot/core/platform/register.py index 62ec5070ab..4db1b98b6c 100644 --- a/astrbot/core/platform/register.py +++ b/astrbot/core/platform/register.py @@ -57,7 +57,7 @@ def decorator(cls): ) platform_registry.append(pm) platform_cls_map[adapter_name] = cls - logger.debug(f"平台适配器 {adapter_name} 已注册") + logger.debug("Platform adapter registered: %s", adapter_name) return cls return decorator diff --git a/astrbot/core/provider/manager.py b/astrbot/core/provider/manager.py index 0dfdbdcf6d..333cf6c906 100644 --- a/astrbot/core/provider/manager.py +++ b/astrbot/core/provider/manager.py @@ -570,7 +570,9 @@ async def load_provider(self, provider_config: dict) -> None: return logger.info( - f"载入 {provider_config['type']}({provider_config['id']}) 服务提供商 ...", + "Loading model %s(%s) ...", + provider_config["type"], + provider_config["id"], ) # 动态导入 @@ -591,7 +593,7 @@ async def load_provider(self, provider_config: dict) -> None: if provider_config["type"] not in provider_cls_map: logger.error( - f"未找到适用于 {provider_config['type']}({provider_config['id']}) 的提供商适配器,请检查是否已经安装或者名称填写错误。已跳过。", + f"Provider adapter not found: {provider_config['type']}({provider_config['id']}). Skipped.", exc_info=True, ) return @@ -625,7 +627,7 @@ async def load_provider(self, provider_config: dict) -> None: ): self.curr_stt_provider_inst = inst logger.info( - f"已选择 {provider_config['type']}({provider_config['id']}) 作为当前语音转文本提供商适配器。", + f"Selected {provider_config['type']}({provider_config['id']}) as default STT provider", ) if not self.curr_stt_provider_inst: self.curr_stt_provider_inst = inst @@ -648,7 +650,7 @@ async def load_provider(self, provider_config: dict) -> None: ): self.curr_tts_provider_inst = inst logger.info( - f"已选择 {provider_config['type']}({provider_config['id']}) 作为当前文本转语音提供商适配器。", + f"Selected {provider_config['type']}({provider_config['id']}) as default TTS provider", ) if not self.curr_tts_provider_inst: self.curr_tts_provider_inst = inst @@ -674,7 +676,7 @@ async def load_provider(self, provider_config: dict) -> None: ): self.curr_provider_inst = inst logger.info( - f"已选择 {provider_config['type']}({provider_config['id']}) 作为当前提供商适配器。", + f"Selected {provider_config['type']}({provider_config['id']}) as default chat model provider", ) if not self.curr_provider_inst: self.curr_provider_inst = inst diff --git a/astrbot/core/provider/register.py b/astrbot/core/provider/register.py index 3ad83784ec..409f22f376 100644 --- a/astrbot/core/provider/register.py +++ b/astrbot/core/provider/register.py @@ -47,7 +47,7 @@ def decorator(cls): ) provider_registry.append(pm) provider_cls_map[provider_type_name] = pm - logger.debug(f"服务提供商 Provider {provider_type_name} 已注册") + logger.debug("Model provider registered: %s", provider_type_name) return cls return decorator diff --git a/astrbot/core/star/star_manager.py b/astrbot/core/star/star_manager.py index fc1fa075eb..dcaed8eba0 100644 --- a/astrbot/core/star/star_manager.py +++ b/astrbot/core/star/star_manager.py @@ -570,7 +570,7 @@ def _validate_astrbot_version_specifier( except InvalidSpecifier: return ( False, - "astrbot_version 格式无效,请使用 PEP 440 版本范围格式,例如 >=4.16,<5。", + "Invalid astrbot_version. Use a PEP 440 range, e.g. >=4.16,<5.", ) try: @@ -578,13 +578,13 @@ def _validate_astrbot_version_specifier( except InvalidVersion: return ( False, - f"AstrBot 当前版本 {VERSION} 无法被解析,无法校验插件版本范围。", + f"Invalid current AstrBot version: {VERSION}. Cannot check plugin version range.", ) if not specifier.contains(current_version, prereleases=True): return ( False, - f"当前 AstrBot 版本为 {VERSION},不满足插件要求的 astrbot_version: {normalized_spec}", + f"AstrBot {VERSION} does not satisfy plugin astrbot_version: {normalized_spec}", ) return True, None @@ -874,7 +874,7 @@ async def load( if specified_dir_name and root_dir_name != specified_dir_name: continue - logger.info(f"正在载入插件 {root_dir_name} ...") + logger.info("Loading plugin %s ...", root_dir_name) # 尝试导入模块 try: @@ -993,7 +993,7 @@ async def load( setattr(metadata.star_cls, "author", p_author) setattr(metadata.star_cls, "plugin_id", plugin_id) else: - logger.info(f"插件 {metadata.name} 已被禁用。") + logger.info("Plugin %s is disabled.", metadata.name) metadata.module = module metadata.root_dir_name = root_dir_name diff --git a/astrbot/core/utils/io.py b/astrbot/core/utils/io.py index b565926749..e118df17ea 100644 --- a/astrbot/core/utils/io.py +++ b/astrbot/core/utils/io.py @@ -136,12 +136,14 @@ async def download_file(url: str, path: str, show_progress: bool = False) -> Non ) as session: async with session.get(url, timeout=1800) as resp: if resp.status != 200: - raise Exception(f"下载文件失败: {resp.status}") + logger.error( + f"Failed to download file from {url}. HTTP status code: {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}") + print(f"Downloading: {url} | Size: {total_size / 1024:.2f} KB") with open(path, "wb") as f: while True: chunk = await resp.content.read(8192) @@ -157,13 +159,14 @@ async def download_file(url: str, path: str, show_progress: bool = False) -> Non ) speed = downloaded_size / 1024 / elapsed_time # KB/s print( - f"\r下载进度: {downloaded_size / total_size:.2%} 速度: {speed:.2f} KB/s", + f"\rProgress: {downloaded_size / total_size:.2%} Speed: {speed:.2f} KB/s", end="", ) except (aiohttp.ClientConnectorSSLError, aiohttp.ClientConnectorCertificateError): # 关闭SSL验证(仅在证书验证失败时作为fallback) logger.warning( - "SSL 证书验证失败,已关闭 SSL 验证(不安全,仅用于临时下载)。请检查目标服务器的证书配置。" + f"SSL certificate verification failed for {url}. " + "Falling back to unverified connection (CERT_NONE). " ) logger.warning( f"SSL certificate verification failed for {url}. " @@ -180,7 +183,7 @@ async def download_file(url: str, path: str, show_progress: bool = False) -> Non downloaded_size = 0 start_time = time.time() if show_progress: - print(f"文件大小: {total_size / 1024:.2f} KB | 文件地址: {url}") + print(f"Size: {total_size / 1024:.2f} KB | URL: {url}") with open(path, "wb") as f: while True: chunk = await resp.content.read(8192) @@ -192,7 +195,7 @@ async def download_file(url: str, path: str, show_progress: bool = False) -> Non 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", + f"\rProgress: {downloaded_size / total_size:.2%} Speed: {speed:.2f} KB/s", end="", ) if show_progress: @@ -252,7 +255,7 @@ async def download_dashboard( ver_name = "latest" if latest else version dashboard_release_url = f"https://astrbot-registry.soulter.top/download/astrbot-dashboard/{ver_name}/dist.zip" logger.info( - f"准备下载指定发行版本的 AstrBot WebUI 文件: {dashboard_release_url}", + f"Downloading AstrBot WebUI from {dashboard_release_url}", ) try: await download_file( @@ -274,7 +277,7 @@ async def download_dashboard( ) else: url = f"https://github.com/AstrBotDevs/astrbot-release-harbour/releases/download/release-{version}/dist.zip" - logger.info(f"准备下载指定版本的 AstrBot WebUI: {url}") + logger.info(f"Downloading AstrBot WebUI from {url}") if proxy: url = f"{proxy}/{url}" await download_file(url, str(zip_path), show_progress=True) diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index b3dc759458..280c83fe5f 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -322,7 +322,7 @@ def _resolve_dashboard_ssl_config( if not cert_file or not key_file: logger.warning( - "dashboard.ssl.enable 已启用,但未同时配置 cert_file 和 key_file,SSL 配置将不会生效。", + "dashboard.ssl.enable is set, but cert_file or key_file is missing. SSL disabled.", ) return False, {} @@ -330,12 +330,12 @@ def _resolve_dashboard_ssl_config( key_path = Path(key_file).expanduser() if not cert_path.is_file(): logger.warning( - f"dashboard.ssl.enable 已启用,但 SSL 证书文件不存在: {cert_path},SSL 配置将不会生效。", + f"dashboard.ssl.enable is set, but cert file is missing: {cert_path}. SSL disabled.", ) return False, {} if not key_path.is_file(): logger.warning( - f"dashboard.ssl.enable 已启用,但 SSL 私钥文件不存在: {key_path},SSL 配置将不会生效。", + f"dashboard.ssl.enable is set, but key file is missing: {key_path}. SSL disabled.", ) return False, {} @@ -348,7 +348,7 @@ def _resolve_dashboard_ssl_config( ca_path = Path(ca_certs).expanduser() if not ca_path.is_file(): logger.warning( - f"dashboard.ssl.enable 已启用,但 SSL CA 证书文件不存在: {ca_path},SSL 配置将不会生效。", + f"dashboard.ssl.enable is set, but CA cert file is missing: {ca_path}. SSL disabled.", ) return False, {} resolved_ssl_config["ca_certs"] = str(ca_path.resolve()) @@ -385,13 +385,13 @@ def run(self): scheme = "https" if ssl_enable else "http" if not enable: - logger.info("WebUI 已被禁用") + logger.info("WebUI disabled.") return None - logger.info(f"正在启动 WebUI, 监听地址: {scheme}://{host}:{port}") + logger.info("Starting WebUI at %s://%s:%s", scheme, host, port) if host == "0.0.0.0": logger.info( - "提示: WebUI 将监听所有网络接口,请注意安全。(可在 data/cmd_config.json 中配置 dashboard.host 以修改 host)", + "WebUI listens on all interfaces. Check security. Set dashboard.host in data/cmd_config.json to change it.", ) if host not in ["localhost", "127.0.0.1"]: @@ -415,16 +415,16 @@ def run(self): raise Exception(f"端口 {port} 已被占用") - parts = [f"\n ✨✨✨\n AstrBot v{VERSION} WebUI 已启动,可访问\n\n"] - parts.append(f" ➜ 本地: {scheme}://localhost:{port}\n") + parts = [f"\n ✨✨✨\n AstrBot v{VERSION} WebUI is ready\n\n"] + parts.append(f" ➜ Local: {scheme}://localhost:{port}\n") for ip in ip_addr: - parts.append(f" ➜ 网络: {scheme}://{ip}:{port}\n") - parts.append(" ➜ 默认用户名和密码: astrbot\n ✨✨✨\n") + parts.append(f" ➜ Network: {scheme}://{ip}:{port}\n") + parts.append(" ➜ Default username/password: astrbot / astrbot\n ✨✨✨\n") display = "".join(parts) if not ip_addr: display += ( - "可在 data/cmd_config.json 中配置 dashboard.host 以便远程访问。\n" + "Set dashboard.host in data/cmd_config.json to enable remote access.\n" ) logger.info(display) diff --git a/docs/.gitignore b/docs/.gitignore index 3562259c05..3501354b26 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -4,3 +4,5 @@ venv/ node_modules/ .vitepress/cache *dist +scripts/deploy-cli.sh +scripts/deploy-cli.ps1 diff --git a/docs/package.json b/docs/package.json index 8d39e96738..48fdb55028 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "scripts": { "docs:dev": "vitepress dev --host", - "docs:build": "vitepress build", + "docs:build": "node scripts/copy-deploy-cli.mjs && vitepress build", "docs:preview": "vitepress preview" }, "devDependencies": { diff --git a/docs/scripts/copy-deploy-cli.mjs b/docs/scripts/copy-deploy-cli.mjs new file mode 100644 index 0000000000..087b1ae3c7 --- /dev/null +++ b/docs/scripts/copy-deploy-cli.mjs @@ -0,0 +1,21 @@ +import { chmod, copyFile, mkdir } from "node:fs/promises"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(scriptDir, "../.."); + +const files = [ + { name: "deploy-cli.sh", mode: 0o755 }, + { name: "deploy-cli.ps1", mode: 0o644 }, +]; + +await mkdir(scriptDir, { recursive: true }); + +for (const file of files) { + const source = resolve(repoRoot, "scripts", file.name); + const target = resolve(scriptDir, file.name); + await copyFile(source, target); + await chmod(target, file.mode); + console.log(`Copied ${file.name} to docs/scripts/`); +} diff --git a/docs/zh/deploy/astrbot/cli.md b/docs/zh/deploy/astrbot/cli.md index 623eb583fb..617c230289 100644 --- a/docs/zh/deploy/astrbot/cli.md +++ b/docs/zh/deploy/astrbot/cli.md @@ -5,13 +5,12 @@ > > 以下教程默认您的设备上已经安装 Python,并且版本 `>=3.10` - ## 下载/克隆仓库 如果你的电脑上安装了 `git`,你可以通过以下命令来下载源码: ```bash -git clone https://github.com/AstrBotDevs/AstrBot.git +git clone https://github.com/AstrBotDevs/AstrBot # 上面的代码默认会拉取最新的提交的源码,如果你需要拉取最新稳定发行版本的源码,可以使用以下命令: # git clone --depth=1 --branch $(git ls-remote --tags --sort='-v:refname' https://github.com/AstrBotDevs/AstrBot.git | head -n1 | awk -F/ '{print $3}') https://github.com/AstrBotDevs/AstrBot.git cd AstrBot diff --git a/main.py b/main.py index 14e0c23a81..299d4b5ec6 100644 --- a/main.py +++ b/main.py @@ -70,9 +70,9 @@ async def check_dashboard_files(webui_dir: str | None = None): # 指定webui目录 if webui_dir: if os.path.exists(webui_dir): - logger.info(f"使用指定的 WebUI 目录: {webui_dir}") + logger.info("Using WebUI directory: %s", webui_dir) return webui_dir - logger.warning(f"指定的 WebUI 目录 {webui_dir} 不存在,将使用默认逻辑。") + logger.warning("WebUI directory not found: %s. Using default.", webui_dir) data_dist_path = os.path.join(get_astrbot_data_path(), "dist") if os.path.exists(data_dist_path): @@ -80,15 +80,17 @@ async def check_dashboard_files(webui_dir: str | None = None): if v is not None: # 存在文件 if v == f"v{VERSION}": - logger.info("WebUI 版本已是最新。") + logger.info("WebUI is up to date.") else: logger.warning( - f"检测到 WebUI 版本 ({v}) 与当前 AstrBot 版本 (v{VERSION}) 不符。", + "WebUI version mismatch: %s, expected v%s.", + v, + VERSION, ) return data_dist_path logger.info( - "开始下载管理面板文件...高峰期(晚上)可能导致较慢的速度。如多次下载失败,请前往 https://github.com/AstrBotDevs/AstrBot/releases/latest 下载 dist.zip,并将其中的 dist 文件夹解压至 data 目录下。", + "Downloading WebUI. If it fails, download dist.zip from https://github.com/AstrBotDevs/AstrBot/releases/latest and extract dist to data/.", ) try: @@ -126,7 +128,7 @@ async def main_async(webui_dir_arg: str | None) -> None: parser.add_argument( "--webui-dir", type=str, - help="指定 WebUI 静态文件目录路径", + help="Specify the directory path for WebUI static files", default=None, ) args = parser.parse_args() diff --git a/scripts/deploy-cli.ps1 b/scripts/deploy-cli.ps1 new file mode 100644 index 0000000000..8b7545b56c --- /dev/null +++ b/scripts/deploy-cli.ps1 @@ -0,0 +1,86 @@ +# deploy-cli.ps1 - Install astrbot with uv on Windows PowerShell. + +#Requires -Version 7.0 + +$ErrorActionPreference = 'Stop' + +$UseColor = [string]::IsNullOrEmpty($env:NO_COLOR) -and [Console]::IsOutputRedirected -eq $false + +function Write-Status { + param( + [string]$Prefix, + [string]$Message, + [string]$Color + ) + + if ($UseColor) { + Write-Host "$Prefix $Message" -ForegroundColor $Color + } else { + Write-Host "$Prefix $Message" + } +} + +function Info { Write-Status "[INFO]" "$args" "Cyan" } +function Warn { Write-Status "[WARN]" "$args" "Yellow" } +function Ok { Write-Status "[OK]" "$args" "Green" } +function Err { Write-Status "[ERROR]" "$args" "Red" } + +function Test-Command { + param([string]$Name) + $null = Get-Command $Name -ErrorAction SilentlyContinue + return $? +} + +function Update-UvPath { + $candidateDirs = @() + + if ($HOME) { + $candidateDirs += Join-Path $HOME ".local\bin" + } + if ($env:USERPROFILE) { + $candidateDirs += Join-Path $env:USERPROFILE ".local\bin" + } + if ($env:LOCALAPPDATA) { + $candidateDirs += Join-Path $env:LOCALAPPDATA "uv" + } + + foreach ($dir in $candidateDirs) { + if ((Test-Path $dir) -and (($env:PATH -split ';') -notcontains $dir)) { + $env:PATH = "$dir;$env:PATH" + } + } +} + +function Install-Uv { + Info "uv was not found. Installing uv..." + + $tempScript = [System.IO.Path]::GetTempFileName() + ".ps1" + try { + Invoke-WebRequest -Uri "https://astral.sh/uv/install.ps1" -OutFile $tempScript -UseBasicParsing + & $tempScript + Update-UvPath + } catch { + Err "Failed to install uv." + Err "Please install uv manually: https://docs.astral.sh/uv/getting-started/installation/" + exit 1 + } finally { + Remove-Item $tempScript -ErrorAction SilentlyContinue + } +} + +if (-not (Test-Command "uv")) { + Install-Uv +} + +Update-UvPath + +if (-not (Test-Command "uv")) { + Err "uv was not found after installation." + Err "Please install uv manually: https://docs.astral.sh/uv/getting-started/installation/" + exit 1 +} + +Ok (& uv --version) +Info "Installing AstrBot with Python 3.12..." +uv tool install --python 3.12 astrbot +Ok "AstrBot has been installed." diff --git a/scripts/deploy-cli.sh b/scripts/deploy-cli.sh new file mode 100644 index 0000000000..16caa08e83 --- /dev/null +++ b/scripts/deploy-cli.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +# deploy-cli.sh - Install astrbot with uv on Linux / macOS / WSL. + +set -euo pipefail + +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + CYAN='\033[0;36m' + NC='\033[0m' +else + RED='' + GREEN='' + YELLOW='' + CYAN='' + NC='' +fi + +info() { echo -e "${CYAN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +ok() { echo -e "${GREEN}[OK]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*" >&2; } + +has() { + command -v "$1" >/dev/null 2>&1 +} + +refresh_uv_path() { + export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH" +} + +install_uv() { + info "uv was not found. Installing uv..." + + if has curl; then + curl -fsSL https://astral.sh/uv/install.sh | sh + refresh_uv_path + return + fi + + if has wget; then + wget -qO- https://astral.sh/uv/install.sh | sh + refresh_uv_path + return + fi + + err "curl or wget is required to install uv." + exit 1 +} + +if has uv; then + UV_BIN="uv" +else + install_uv + UV_BIN="uv" +fi + +if ! has "$UV_BIN"; then + err "uv was not found after installation." + err "Please install uv manually: https://docs.astral.sh/uv/getting-started/installation/" + exit 1 +fi + +ok "$("$UV_BIN" --version)" +info "Installing AstrBot with Python 3.12..." +"$UV_BIN" tool install --python 3.12 astrbot +ok "AstrBot has been installed." diff --git a/tests/test_dashboard.py b/tests/test_dashboard.py index 6dc352057c..63ffbf4d8d 100644 --- a/tests/test_dashboard.py +++ b/tests/test_dashboard.py @@ -124,6 +124,12 @@ async def test_dashboard_ssl_missing_cert_and_key_falls_back_to_http( async def fake_serve(app, config, shutdown_trigger): return config + def capture(messages): + def append(message, *args): + messages.append(message % args if args else message) + + return append + try: core_lifecycle_td.astrbot_config["dashboard"]["ssl"] = { "enable": True, @@ -134,21 +140,22 @@ async def fake_serve(app, config, shutdown_trigger): monkeypatch.setattr("astrbot.dashboard.server.serve", fake_serve) monkeypatch.setattr( "astrbot.dashboard.server.logger.warning", - lambda message: warning_messages.append(message), + capture(warning_messages), ) monkeypatch.setattr( "astrbot.dashboard.server.logger.info", - lambda message: info_messages.append(message), + capture(info_messages), ) config = await server.run() assert getattr(config, "certfile", None) is None assert getattr(config, "keyfile", None) is None - assert any("cert_file 和 key_file" in message for message in warning_messages) assert any( - "正在启动 WebUI, 监听地址: http://" in message for message in info_messages + "cert_file or key_file is missing" in message + for message in warning_messages ) + assert any("Starting WebUI at http://" in message for message in info_messages) finally: core_lifecycle_td.astrbot_config["dashboard"] = original_dashboard_config diff --git a/tests/test_main.py b/tests/test_main.py index 0f20016aeb..8ed21f7b10 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -67,7 +67,9 @@ def test_check_env_appends_user_site_packages_after_runtime_paths(monkeypatch): monkeypatch.setattr(sys, "version_info", _version_info(3, 12)) monkeypatch.setattr("main.get_astrbot_root", lambda: astrbot_root) - monkeypatch.setattr("main.get_astrbot_site_packages_path", lambda: site_packages_path) + monkeypatch.setattr( + "main.get_astrbot_site_packages_path", lambda: site_packages_path + ) monkeypatch.setattr("main.get_astrbot_config_path", lambda: "/tmp/config") monkeypatch.setattr("main.get_astrbot_plugin_path", lambda: "/tmp/plugins") monkeypatch.setattr("main.get_astrbot_temp_path", lambda: "/tmp/temp") @@ -89,12 +91,16 @@ def test_check_env_does_not_append_duplicate_user_site_packages(monkeypatch): monkeypatch.setattr(sys, "version_info", _version_info(3, 12)) monkeypatch.setattr("main.get_astrbot_root", lambda: astrbot_root) - monkeypatch.setattr("main.get_astrbot_site_packages_path", lambda: site_packages_path) + monkeypatch.setattr( + "main.get_astrbot_site_packages_path", lambda: site_packages_path + ) monkeypatch.setattr("main.get_astrbot_config_path", lambda: "/tmp/config") monkeypatch.setattr("main.get_astrbot_plugin_path", lambda: "/tmp/plugins") monkeypatch.setattr("main.get_astrbot_temp_path", lambda: "/tmp/temp") monkeypatch.setattr("main.get_astrbot_knowledge_base_path", lambda: "/tmp/kb") - monkeypatch.setattr(sys, "path", [astrbot_root, *original_sys_path, site_packages_path]) + monkeypatch.setattr( + sys, "path", [astrbot_root, *original_sys_path, site_packages_path] + ) with mock.patch("os.makedirs"): check_env() @@ -176,7 +182,7 @@ async def test_check_dashboard_files_exists_but_version_mismatch(monkeypatch): await check_dashboard_files() mock_logger_warning.assert_called_once() call_args, _ = mock_logger_warning.call_args - assert "不符" in call_args[0] + assert "WebUI version mismatch" in call_args[0] @pytest.mark.asyncio diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index c49e97ca0f..c8f383a29f 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -303,14 +303,17 @@ def test_integrity_log_does_not_include_inserted_secret_value( with open(temp_config_path, "w", encoding="utf-8-sig") as f: json.dump(existing_config, f) - monkeypatch.setattr(astrbot_config.logger, "info", messages.append) + def capture_info(message, *args): + messages.append(message % args if args else message) + + monkeypatch.setattr(astrbot_config.logger, "info", capture_info) AstrBotConfig(config_path=temp_config_path, default_config=default_config) assert messages assert all("secret-value" not in message for message in messages) assert all("api_key" not in message for message in messages) - assert any("配置项不存在" in message for message in messages) + assert any("Config key missing" in message for message in messages) class TestConfigHotReload: