diff --git a/astrbot/builtin_stars/astrbot/main.py b/astrbot/builtin_stars/astrbot/main.py index 06108636b..e4f840bcc 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/cli/__main__.py b/astrbot/cli/__main__.py index afdedb99d..51a08b22c 100644 --- a/astrbot/cli/__main__.py +++ b/astrbot/cli/__main__.py @@ -9,7 +9,7 @@ import click from click.shell_completion import get_completion_class from . import __version__ -from .commands import bk, conf, init, plug, run, uninstall +from .commands import bk, conf, init, password, plug, run, uninstall from .i18n import t @@ -127,6 +127,7 @@ cli.add_command(plug) cli.add_command(conf) cli.add_command(uninstall) cli.add_command(bk) +cli.add_command(password) @click.command() @@ -172,6 +173,5 @@ def version_cmd() -> None: cli.add_command(version_cmd) - if __name__ == "__main__": cli() diff --git a/astrbot/cli/commands/__init__.py b/astrbot/cli/commands/__init__.py index c5d5944bb..5bbcb11c6 100644 --- a/astrbot/cli/commands/__init__.py +++ b/astrbot/cli/commands/__init__.py @@ -1,8 +1,9 @@ from .cmd_bk import bk 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 from .cmd_uninstall import uninstall -__all__ = ["bk", "conf", "init", "plug", "run", "uninstall"] +__all__ = ["bk", "conf", "init", "password", "plug", "run", "uninstall"] diff --git a/astrbot/cli/commands/cmd_conf.py b/astrbot/cli/commands/cmd_conf.py index 6d50d19af..8f49e012f 100644 --- a/astrbot/cli/commands/cmd_conf.py +++ b/astrbot/cli/commands/cmd_conf.py @@ -214,6 +214,22 @@ def set_dashboard_credentials( ) +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. @@ -245,16 +261,7 @@ def set_config(key: str, value: str) -> None: 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/astrbot/core/config/default.py b/astrbot/core/config/default.py index f5f8293b6..431235ff0 100644 --- a/astrbot/core/config/default.py +++ b/astrbot/core/config/default.py @@ -1050,7 +1050,7 @@ CONFIG_METADATA_2: Any = { "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 8e47e2883..bb77264ee 100644 --- a/astrbot/core/pipeline/result_decorate/stage.py +++ b/astrbot/core/pipeline/result_decorate/stage.py @@ -373,7 +373,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 312ae962b..0e626f9f7 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -1153,9 +1153,9 @@ class ProviderOpenAIOfficial(Provider): or ("tool" in str(e).lower() and "support" in str(e).lower()) or ("function" in str(e).lower() and "support" in str(e).lower()) ): - # openai, ollama, gemini openai, siliconcloud 的错误提示与 code 不统一,只能通过字符串匹配 - logger.info( - f"{self.get_model()} 不支持函数工具调用,已自动去除,不影响使用。", + # openai, ollama, gemini openai, siliconcloud 的错误提示与 code 不统一,只能通过字符串匹配 + logger.warning( + f"{self.get_model()} 不支持函数工具调用,已自动去除,不影响使用。如需永久关闭,可前往 WebUI 中关闭工具调用。", ) payloads.pop("tools", None) return ( @@ -1169,9 +1169,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) 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