From d15606d2020b1ed0a56b32b980f26ef1caaccb84 Mon Sep 17 00:00:00 2001 From: Weilong Liao <37870767+Soulter@users.noreply.github.com> Date: Thu, 21 May 2026 21:37:10 +0800 Subject: [PATCH 1/2] feat(password): add command to change AstrBot dashboard password (#8272) closes: #8268 --- astrbot/cli/__main__.py | 3 +- astrbot/cli/commands/__init__.py | 3 +- astrbot/cli/commands/cmd_conf.py | 27 +++++---- astrbot/cli/commands/cmd_password.py | 38 ++++++++++++ tests/test_cli_password.py | 90 ++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 astrbot/cli/commands/cmd_password.py create mode 100644 tests/test_cli_password.py diff --git a/astrbot/cli/__main__.py b/astrbot/cli/__main__.py index 6d48ec28d..3dc0d0e41 100644 --- a/astrbot/cli/__main__.py +++ b/astrbot/cli/__main__.py @@ -5,7 +5,7 @@ import sys import click from . import __version__ -from .commands import conf, init, plug, run +from .commands import conf, init, password, plug, run logo_tmpl = r""" ___ _______.___________..______ .______ ______ .___________. @@ -54,6 +54,7 @@ cli.add_command(run) cli.add_command(help) cli.add_command(plug) cli.add_command(conf) +cli.add_command(password) if __name__ == "__main__": cli() diff --git a/astrbot/cli/commands/__init__.py b/astrbot/cli/commands/__init__.py index 1d3e0bca2..d1765e5c2 100644 --- a/astrbot/cli/commands/__init__.py +++ b/astrbot/cli/commands/__init__.py @@ -1,6 +1,7 @@ from .cmd_conf import conf from .cmd_init import init +from .cmd_password import password from .cmd_plug import plug from .cmd_run import run -__all__ = ["conf", "init", "plug", "run"] +__all__ = ["conf", "init", "password", "plug", "run"] diff --git a/astrbot/cli/commands/cmd_conf.py b/astrbot/cli/commands/cmd_conf.py index cf583b9d5..ac626e0d1 100644 --- a/astrbot/cli/commands/cmd_conf.py +++ b/astrbot/cli/commands/cmd_conf.py @@ -137,6 +137,22 @@ def _get_nested_item(obj: dict[str, Any], path: str) -> Any: return obj +def _set_dashboard_password(config: dict[str, Any], raw_password: str) -> None: + """Set dashboard password hashes and clear password migration flags.""" + _set_nested_item( + config, + "dashboard.pbkdf2_password", + hash_dashboard_password(raw_password), + ) + _set_nested_item( + config, + "dashboard.password", + hash_legacy_dashboard_password(raw_password), + ) + _set_nested_item(config, "dashboard.password_storage_upgraded", True) + _set_nested_item(config, "dashboard.password_change_required", False) + + @click.group(name="conf") def conf() -> None: """Configuration management commands @@ -171,16 +187,7 @@ def set_config(key: str, value: str) -> None: old_value = _get_nested_item(config, key) validated_value = CONFIG_VALIDATORS[key](value) if key == "dashboard.password": - _set_nested_item( - config, - "dashboard.pbkdf2_password", - hash_dashboard_password(validated_value), - ) - _set_nested_item( - config, - "dashboard.password", - hash_legacy_dashboard_password(validated_value), - ) + _set_dashboard_password(config, validated_value) else: _set_nested_item(config, key, validated_value) _save_config(config) diff --git a/astrbot/cli/commands/cmd_password.py b/astrbot/cli/commands/cmd_password.py new file mode 100644 index 000000000..ab0ced0c9 --- /dev/null +++ b/astrbot/cli/commands/cmd_password.py @@ -0,0 +1,38 @@ +import click + +from .cmd_conf import ( + _load_config, + _save_config, + _set_dashboard_password, + _set_nested_item, + _validate_dashboard_password, + _validate_dashboard_username, +) + + +@click.command(name="password") +@click.option( + "--username", + help="Optional dashboard username to set together with the new password.", +) +def password(username: str | None) -> None: + """Change the AstrBot dashboard password.""" + config = _load_config() + + new_password = click.prompt( + "New dashboard password", + hide_input=True, + confirmation_prompt=True, + ) + validated_password = _validate_dashboard_password(new_password) + + if username is not None: + validated_username = _validate_dashboard_username(username.strip()) + _set_nested_item(config, "dashboard.username", validated_username) + + _set_dashboard_password(config, validated_password) + _save_config(config) + + click.echo("Dashboard password updated.") + if username is not None: + click.echo(f"Dashboard username updated: {validated_username}") diff --git a/tests/test_cli_password.py b/tests/test_cli_password.py new file mode 100644 index 000000000..e934220eb --- /dev/null +++ b/tests/test_cli_password.py @@ -0,0 +1,90 @@ +import copy +import json + +from click.testing import CliRunner + +from astrbot.cli.commands.cmd_conf import conf +from astrbot.cli.commands.cmd_password import password +from astrbot.core.config.default import DEFAULT_CONFIG +from astrbot.core.utils.auth_password import verify_dashboard_password + + +def _write_config(root): + (root / ".astrbot").touch() + data_dir = root / "data" + data_dir.mkdir() + config = copy.deepcopy(DEFAULT_CONFIG) + config["dashboard"]["password_change_required"] = True + config["dashboard"]["password_storage_upgraded"] = False + config_path = data_dir / "cmd_config.json" + config_path.write_text( + json.dumps(config, ensure_ascii=False, indent=2), + encoding="utf-8-sig", + ) + return config_path + + +def _read_config(config_path): + return json.loads(config_path.read_text(encoding="utf-8-sig")) + + +def test_password_command_changes_dashboard_password(monkeypatch, tmp_path): + config_path = _write_config(tmp_path) + monkeypatch.chdir(tmp_path) + + runner = CliRunner() + result = runner.invoke( + password, + input="AstrbotChanged123\nAstrbotChanged123\n", + ) + + assert result.exit_code == 0 + config = _read_config(config_path) + dashboard_config = config["dashboard"] + assert verify_dashboard_password( + dashboard_config["pbkdf2_password"], + "AstrbotChanged123", + ) + assert verify_dashboard_password( + dashboard_config["password"], + "AstrbotChanged123", + ) + assert dashboard_config["password_storage_upgraded"] is True + assert dashboard_config["password_change_required"] is False + + +def test_password_command_can_update_dashboard_username(monkeypatch, tmp_path): + config_path = _write_config(tmp_path) + monkeypatch.chdir(tmp_path) + + runner = CliRunner() + result = runner.invoke( + password, + ["--username", "astrbot-admin"], + input="AstrbotChanged123\nAstrbotChanged123\n", + ) + + assert result.exit_code == 0 + config = _read_config(config_path) + assert config["dashboard"]["username"] == "astrbot-admin" + + +def test_conf_set_dashboard_password_updates_password_state(monkeypatch, tmp_path): + config_path = _write_config(tmp_path) + monkeypatch.chdir(tmp_path) + + runner = CliRunner() + result = runner.invoke( + conf, + ["set", "dashboard.password", "AstrbotChanged123"], + ) + + assert result.exit_code == 0 + config = _read_config(config_path) + dashboard_config = config["dashboard"] + assert verify_dashboard_password( + dashboard_config["pbkdf2_password"], + "AstrbotChanged123", + ) + assert dashboard_config["password_storage_upgraded"] is True + assert dashboard_config["password_change_required"] is False From 0711172fa7fb1ceabcc0bc8034d4740c385a76de Mon Sep 17 00:00:00 2001 From: lingyun14 Date: Thu, 21 May 2026 21:45:57 +0800 Subject: [PATCH 2/2] Fix/stale command hints (#8245) * Update stage.py * fix: remove stale slash command hints * fix: remove stale slash command hints * fix: remove stale slash command hints * Update openai_source.py --- astrbot/builtin_stars/astrbot/main.py | 2 +- astrbot/core/config/default.py | 2 +- astrbot/core/pipeline/result_decorate/stage.py | 2 +- astrbot/core/provider/sources/openai_source.py | 7 ++----- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/astrbot/builtin_stars/astrbot/main.py b/astrbot/builtin_stars/astrbot/main.py index c1500a5d1..3d800edd2 100644 --- a/astrbot/builtin_stars/astrbot/main.py +++ b/astrbot/builtin_stars/astrbot/main.py @@ -176,7 +176,7 @@ class Main(star.Star): if not session_curr_cid: logger.error( - "当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /switch 序号 切换或者 /new 创建一个会话。", + "当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /new 创建一个会话。", ) return diff --git a/astrbot/core/config/default.py b/astrbot/core/config/default.py index ce79559bd..dec98692b 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -1068,7 +1068,7 @@ CONFIG_METADATA_2 = { "id_whitelist": { "type": "list", "items": {"type": "string"}, - "hint": "只处理填写的 ID 发来的消息事件,为空时不启用。可使用 /sid 指令获取在平台上的会话 ID(类似 abc:GroupMessage:123)。管理员可使用 /wl 添加白名单", + "hint": "只处理填写的 ID 发来的消息事件,为空时不启用。可使用 /sid 指令获取在平台上的会话 ID(类似 abc:GroupMessage:123)。管理员可在 WebUI 的平台设置中管理白名单", }, "id_whitelist_log": { "type": "bool", diff --git a/astrbot/core/pipeline/result_decorate/stage.py b/astrbot/core/pipeline/result_decorate/stage.py index 7e5d2287f..c2d799162 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -368,7 +368,7 @@ class ResultDecorateStage(Stage): return if time.time() - render_start > 3: logger.warning( - "文本转图片耗时超过了 3 秒,如果觉得很慢可以使用 /t2i 关闭文本转图片模式。", + "文本转图片耗时超过了 3 秒,如果觉得很慢可以在 WebUI 中关闭文本转图片模式。", ) if url: if url.startswith("http"): diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index fbcef1a80..cd0aabeef 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -1138,8 +1138,8 @@ class ProviderOpenAIOfficial(Provider): or ("function" in str(e).lower() and "support" in str(e).lower()) ): # openai, ollama, gemini openai, siliconcloud 的错误提示与 code 不统一,只能通过字符串匹配 - logger.info( - f"{self.get_model()} 不支持函数工具调用,已自动去除,不影响使用。", + logger.warning( + f"{self.get_model()} 不支持函数工具调用,已自动去除,不影响使用。如需永久关闭,可前往 WebUI 中关闭工具调用。", ) payloads.pop("tools", None) return ( @@ -1153,9 +1153,6 @@ class ProviderOpenAIOfficial(Provider): ) # logger.error(f"发生了错误。Provider 配置如下: {self.provider_config}") - if "tool" in str(e).lower() and "support" in str(e).lower(): - logger.error("疑似该模型不支持函数调用工具调用。请输入 /tool off_all") - if is_connection_error(e): proxy = self.provider_config.get("proxy", "") log_connection_failure("OpenAI", e, proxy)