Merge remote-tracking branch 'origin' into dev

# Conflicts:
#	astrbot/builtin_stars/astrbot/main.py
#	astrbot/cli/__main__.py
#	astrbot/cli/commands/__init__.py
#	astrbot/core/provider/sources/openai_source.py
This commit is contained in:
LIghtJUNction
2026-05-22 15:19:54 +08:00
9 changed files with 155 additions and 22 deletions
+1 -1
View File
@@ -176,7 +176,7 @@ class Main(star.Star):
if not session_curr_cid:
logger.error(
"当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /switch 序号 切换或者 /new 创建一个会话。",
"当前未处于对话状态,无法主动回复,请确保 平台设置->会话隔离(unique_session) 未开启,并使用 /new 创建一个会话。",
)
return
+2 -2
View File
@@ -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()
+2 -1
View File
@@ -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"]
+17 -10
View File
@@ -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)
+38
View File
@@ -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}")
+1 -1
View File
@@ -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",
@@ -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"):
@@ -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)
+90
View File
@@ -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