Skip to content

Commit 3141ed5

Browse files
committed
Merge branch 'feat/optional-backend' into master
2 parents 63ff234 + 48c2d98 commit 3141ed5

27 files changed

Lines changed: 2150 additions & 1255 deletions

File tree

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.12
1+
3.12

astrbot/cli/commands/cmd_init.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@ async def initialize_astrbot(astrbot_root: Path) -> None:
3434
for name, path in paths.items():
3535
path.mkdir(parents=True, exist_ok=True)
3636
click.echo(f"{'Created' if not path.exists() else 'Directory exists'}: {path}")
37-
38-
await check_dashboard(astrbot_root / "data")
37+
if click.confirm(
38+
"是否需要集成式 WebUI?(个人电脑推荐,服务器不推荐)",
39+
default=True,
40+
):
41+
await check_dashboard(astrbot_root / "data")
42+
else:
43+
click.echo("你可以使用在线面版(v4.14.4+),填写后端地址的方式来控制。")
3944

4045

4146
@click.command()

astrbot/cli/commands/cmd_run.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ async def run_astrbot(astrbot_root: Path) -> None:
1515
from astrbot.core import LogBroker, LogManager, db_helper, logger
1616
from astrbot.core.initial_loader import InitialLoader
1717

18-
await check_dashboard(astrbot_root / "data")
18+
if os.environ.get("DASHBOARD_ENABLE") == "True":
19+
await check_dashboard(astrbot_root / "data")
1920

2021
log_broker = LogBroker()
2122
LogManager.set_queue_handler(logger, log_broker)
@@ -27,9 +28,17 @@ async def run_astrbot(astrbot_root: Path) -> None:
2728

2829

2930
@click.option("--reload", "-r", is_flag=True, help="插件自动重载")
30-
@click.option("--port", "-p", help="Astrbot Dashboard端口", required=False, type=str)
31+
@click.option(
32+
"--host", "-H", help="Astrbot Dashboard Host,默认::", required=False, type=str
33+
)
34+
@click.option(
35+
"--port", "-p", help="Astrbot Dashboard端口,默认6185", required=False, type=str
36+
)
37+
@click.option(
38+
"--backend-only", is_flag=True, default=False, help="禁用WEBUI,仅启动后端"
39+
)
3140
@click.command()
32-
def run(reload: bool, port: str) -> None:
41+
def run(reload: bool, host: str, port: str, backend_only: bool) -> None:
3342
"""运行 AstrBot"""
3443
try:
3544
os.environ["ASTRBOT_CLI"] = "1"
@@ -43,8 +52,11 @@ def run(reload: bool, port: str) -> None:
4352
os.environ["ASTRBOT_ROOT"] = str(astrbot_root)
4453
sys.path.insert(0, str(astrbot_root))
4554

46-
if port:
55+
if port is not None:
4756
os.environ["DASHBOARD_PORT"] = port
57+
if host is not None:
58+
os.environ["DASHBOARD_HOST"] = host
59+
os.environ["DASHBOARD_ENABLE"] = str(not backend_only)
4860

4961
if reload:
5062
click.echo("启用插件自动重载")

astrbot/core/config/default.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@
195195
"username": "astrbot",
196196
"password": "77b90590a8945a7d36c963981a307dc9",
197197
"jwt_secret": "",
198-
"host": "0.0.0.0",
198+
"host": "::",
199199
"port": 6185,
200200
"disable_access_log": True,
201201
"ssl": {

astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ async def _convert_handle_message_event(
419419
def run(self) -> Awaitable[Any]:
420420
if not self.host or not self.port:
421421
logger.warning(
422-
"aiocqhttp: 未配置 ws_reverse_host 或 ws_reverse_port,将使用默认值:http://0.0.0.0:6199",
422+
"aiocqhttp: 未配置 ws_reverse_host 或 ws_reverse_port,将使用默认值:http://[::]:6199",
423423
)
424424
self.host = "0.0.0.0"
425425
self.port = 6199

astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def __init__(
2121
self.secret = config["secret"]
2222
self.port = config.get("port", 6196)
2323
self.is_sandbox = config.get("is_sandbox", False)
24-
self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
24+
self.callback_server_host = config.get("callback_server_host", "::")
2525

2626
if isinstance(self.port, str):
2727
self.port = int(self.port)

astrbot/core/platform/sources/wecom/wecom_adapter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class WecomServer:
4343
def __init__(self, event_queue: asyncio.Queue, config: dict) -> None:
4444
self.server = quart.Quart(__name__)
4545
self.port = int(cast(str, config.get("port")))
46-
self.callback_server_host = config.get("callback_server_host", "0.0.0.0")
46+
self.callback_server_host = config.get("callback_server_host", "::")
4747
self.server.add_url_rule(
4848
"/callback/command",
4949
view_func=self.verify,
@@ -407,7 +407,7 @@ async def convert_wechat_kf_message(self, msg: dict) -> AstrBotMessage | None:
407407
abm.message = [Image(file=path, url=path)]
408408
elif msgtype == "voice":
409409
media_id = msg.get("voice", {}).get("media_id", "")
410-
resp: Response = await asyncio.get_event_loop().run_in_executor(
410+
resp = await asyncio.get_event_loop().run_in_executor(
411411
None,
412412
self.client.media.download,
413413
media_id,

astrbot/core/utils/io.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import asyncio
12
import base64
23
import logging
34
import os
@@ -7,6 +8,7 @@
78
import time
89
import uuid
910
import zipfile
11+
from ipaddress import IPv4Address, IPv6Address, ip_address
1012
from pathlib import Path
1113

1214
import aiohttp
@@ -206,18 +208,53 @@ def file_to_base64(file_path: str) -> str:
206208
return "base64://" + base64_str
207209

208210

209-
def get_local_ip_addresses():
211+
def get_local_ip_addresses() -> list[IPv4Address | IPv6Address]:
210212
net_interfaces = psutil.net_if_addrs()
211-
network_ips = []
213+
network_ips: list[IPv4Address | IPv6Address] = []
212214

213-
for interface, addrs in net_interfaces.items():
215+
for _, addrs in net_interfaces.items():
214216
for addr in addrs:
215-
if addr.family == socket.AF_INET: # 使用 socket.AF_INET 代替 psutil.AF_INET
216-
network_ips.append(addr.address)
217+
if addr.family == socket.AF_INET:
218+
network_ips.append(ip_address(addr.address))
219+
elif addr.family == socket.AF_INET6:
220+
# 过滤掉 IPv6 的 link-local 地址(fe80:...)
221+
ip = ip_address(addr.address.split("%")[0]) # 处理带 zone index 的情况
222+
if not ip.is_link_local:
223+
network_ips.append(ip)
217224

218225
return network_ips
219226

220227

228+
async def get_public_ip_address() -> list[IPv4Address | IPv6Address]:
229+
urls = [
230+
"https://api64.ipify.org",
231+
"https://ident.me",
232+
"https://ifconfig.me",
233+
"https://icanhazip.com",
234+
]
235+
found_ips: dict[int, IPv4Address | IPv6Address] = {}
236+
237+
async def fetch(session: aiohttp.ClientSession, url: str):
238+
try:
239+
async with session.get(url, timeout=3) as resp:
240+
if resp.status == 200:
241+
raw_ip = (await resp.text()).strip()
242+
ip = ip_address(raw_ip)
243+
if ip.version not in found_ips:
244+
found_ips[ip.version] = ip
245+
except Exception as e:
246+
# Ignore errors from individual services so that a single failing
247+
# endpoint does not prevent discovering the public IP from others.
248+
logger.debug("Failed to fetch public IP from %s: %s", url, e)
249+
250+
async with aiohttp.ClientSession() as session:
251+
tasks = [fetch(session, url) for url in urls]
252+
await asyncio.gather(*tasks)
253+
254+
# 返回找到的所有 IP 对象列表
255+
return list(found_ips.values())
256+
257+
221258
async def get_dashboard_version():
222259
dist_dir = os.path.join(get_astrbot_data_path(), "dist")
223260
if os.path.exists(dist_dir):

astrbot/dashboard/routes/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@
99
from .cron import CronRoute
1010
from .file import FileRoute
1111
from .knowledge_base import KnowledgeBaseRoute
12+
from .live_chat import LiveChatRoute
1213
from .log import LogRoute
1314
from .open_api import OpenApiRoute
1415
from .persona import PersonaRoute
1516
from .platform import PlatformRoute
1617
from .plugin import PluginRoute
18+
from .response import Response
19+
from .route import RouteContext
1720
from .session_management import SessionManagementRoute
1821
from .skills import SkillsRoute
1922
from .stat import StatRoute
2023
from .static_file import StaticFileRoute
2124
from .subagent import SubAgentRoute
25+
from .t2i import T2iRoute
2226
from .tools import ToolsRoute
2327
from .update import UpdateRoute
2428

@@ -46,4 +50,8 @@
4650
"ToolsRoute",
4751
"SkillsRoute",
4852
"UpdateRoute",
53+
"T2iRoute",
54+
"LiveChatRoute",
55+
"Response",
56+
"RouteContext",
4957
]

astrbot/dashboard/routes/route.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from dataclasses import dataclass
1+
from dataclasses import asdict, dataclass
22

33
from quart import Quart
44

@@ -57,3 +57,7 @@ def ok(self, data: dict | list | None = None, message: str | None = None):
5757
self.data = data
5858
self.message = message
5959
return self
60+
61+
def to_json(self):
62+
# Return a plain dict so callers can safely wrap with jsonify()
63+
return asdict(self)

0 commit comments

Comments
 (0)