✨ feat: supports CLI mode · AstrBotDevs/AstrBot@b4f12fc · GitHub
Skip to content

Commit b4f12fc

Browse files
LIghtJUNctionSoulter
authored andcommitted
✨ feat: supports CLI mode
Squashed by: STEP1 - 新增CLI命令行程序 🎨 style: improve code style and some typo fixes remove: llms.txt
1 parent 56001ed commit b4f12fc

5 files changed

Lines changed: 919 additions & 582 deletions

File tree

‎.python-version‎

Lines changed: 1 addition & 0 deletions

‎astrbot/cli/__main__.py‎

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import asyncio
2+
import os
3+
import shutil
4+
import sys
5+
import click
6+
from pathlib import Path
7+
from astrbot.core.config.default import VERSION
8+
9+
10+
logo_tmpl = r"""
11+
___ _______.___________..______ .______ ______ .___________.
12+
/ \ / | || _ \ | _ \ / __ \ | |
13+
/ ^ \ | (----`---| |----`| |_) | | |_) | | | | | `---| |----`
14+
/ /_\ \ \ \ | | | / | _ < | | | | | |
15+
/ _____ \ .----) | | | | |\ \----.| |_) | | `--' | | |
16+
/__/ \__\ |_______/ |__| | _| `._____||______/ \______/ |__|
17+
18+
"""
19+
20+
21+
# utils
22+
def _get_astrbot_root(path: str | None) -> Path:
23+
"""获取astrbot根目录"""
24+
match path:
25+
case None:
26+
match ASTRBOT_ROOT := os.getenv("ASTRBOT_ROOT"):
27+
case None:
28+
astrbot_root = Path.cwd() / "data"
29+
case _:
30+
astrbot_root = Path(ASTRBOT_ROOT).resolve()
31+
case str():
32+
astrbot_root = Path(path).resolve()
33+
34+
dot_astrbot = astrbot_root / ".astrbot"
35+
if not dot_astrbot.exists():
36+
if click.confirm(
37+
f"运行前必须先执行初始化!请检查当前目录是否正确,回车以继续: {astrbot_root}",
38+
default=True,
39+
abort=True,
40+
):
41+
dot_astrbot.touch()
42+
astrbot_root.mkdir(parents=True, exist_ok=True)
43+
click.echo(f"Created {dot_astrbot}")
44+
45+
return astrbot_root
46+
47+
48+
# 通过类型来验证先后,必须先获取 Path 对象才能对该目录进行检查
49+
def _check_astrbot_root(astrbot_root: Path) -> None:
50+
"""验证"""
51+
dot_astrbot = astrbot_root / ".astrbot"
52+
if not astrbot_root.exists():
53+
click.echo(f"AstrBot root directory does not exist: {astrbot_root}")
54+
click.echo("Please run 'astrbot init' to create the directory.")
55+
sys.exit(1)
56+
else:
57+
click.echo(f"AstrBot root directory exists: {astrbot_root}")
58+
if not dot_astrbot.exists():
59+
click.echo(
60+
"如果你确认这是astrbot_root,你需要在当前目录下创建一个 .astrbot 文件标记该目录为 AstrBot 的数据目录。"
61+
)
62+
if click.confirm(
63+
f"请检查当前目录是否正确,确认正确请回车: {astrbot_root}",
64+
default=True,
65+
abort=True,
66+
):
67+
dot_astrbot.touch()
68+
click.echo(f"Created {dot_astrbot}")
69+
else:
70+
click.echo(f"Welcome back! AstrBot root directory: {astrbot_root}")
71+
72+
73+
async def _check_dashboard(astrbot_root: Path) -> None:
74+
"""检查是否安装了dashboard"""
75+
try:
76+
from ..core.utils.io import get_dashboard_version, download_dashboard
77+
except ImportError:
78+
from astrbot.core.utils.io import get_dashboard_version, download_dashboard
79+
80+
try:
81+
# 添加 create=True 参数以确保在初始化时不会抛出异常
82+
dashboard_version = await get_dashboard_version()
83+
match dashboard_version:
84+
case None:
85+
click.echo("未安装管理面板")
86+
if click.confirm(
87+
"是否安装管理面板?",
88+
default=True,
89+
abort=True,
90+
):
91+
click.echo("正在安装管理面板...")
92+
# 确保使用 create=True 参数
93+
await download_dashboard(
94+
path="data/dashboard.zip", extract_path=str(astrbot_root)
95+
)
96+
click.echo("管理面板安装完成")
97+
98+
case str():
99+
if dashboard_version == f"v{VERSION}":
100+
click.echo("无需更新")
101+
else:
102+
try:
103+
version = dashboard_version.split("v")[1]
104+
click.echo(f"管理面板版本: {version}")
105+
# 确保使用 create=True 参数
106+
await download_dashboard(
107+
path="data/dashboard.zip", extract_path=str(astrbot_root)
108+
)
109+
except Exception as e:
110+
click.echo(f"下载管理面板失败: {e}")
111+
return
112+
except FileNotFoundError:
113+
click.echo("初始化管理面板目录...")
114+
# 初始化模式下,下载到指定位置
115+
try:
116+
await download_dashboard(
117+
path=str(astrbot_root / "dashboard.zip"), extract_path=str(astrbot_root)
118+
)
119+
click.echo("管理面板初始化完成")
120+
except Exception as e:
121+
click.echo(f"下载管理面板失败: {e}")
122+
return
123+
124+
125+
@click.group(name="astrbot")
126+
def cli() -> None:
127+
"""The AstrBot CLI"""
128+
click.echo(logo_tmpl)
129+
click.echo("Welcome to AstrBot CLI!")
130+
click.echo(f"AstrBot version: {VERSION}")
131+
132+
133+
# region init
134+
@cli.command()
135+
@click.option("--path", "-p", help="astrbot 数据目录")
136+
@click.option("--force", "-f", is_flag=True, help="强制初始化")
137+
def init(path: str | None, force: bool) -> None:
138+
"""Initialize AstrBot"""
139+
click.echo("Initializing AstrBot...")
140+
astrbot_root = _get_astrbot_root(path)
141+
if force:
142+
if click.confirm(
143+
"强制初始化会删除当前目录下的所有文件,是否继续?",
144+
default=False,
145+
abort=True,
146+
):
147+
click.echo("正在删除当前目录下的所有文件...")
148+
shutil.rmtree(astrbot_root, ignore_errors=True)
149+
150+
_check_astrbot_root(astrbot_root)
151+
152+
click.echo(f"AstrBot root directory: {astrbot_root}")
153+
154+
if not astrbot_root.exists():
155+
# 创建目录
156+
astrbot_root.mkdir(parents=True, exist_ok=True)
157+
click.echo(f"Created directory: {astrbot_root}")
158+
else:
159+
click.echo(f"Directory already exists: {astrbot_root}")
160+
161+
config_path: Path = astrbot_root / "config"
162+
plugins_path: Path = astrbot_root / "plugins"
163+
temp_path: Path = astrbot_root / "temp"
164+
config_path.mkdir(parents=True, exist_ok=True)
165+
plugins_path.mkdir(parents=True, exist_ok=True)
166+
temp_path.mkdir(parents=True, exist_ok=True)
167+
168+
click.echo(f"Created directories: {config_path}, {plugins_path}, {temp_path}")
169+
170+
# 检查是否安装了dashboard
171+
asyncio.run(_check_dashboard(astrbot_root))
172+
173+
174+
# region run
175+
@cli.command()
176+
@click.option("--path", "-p", help="astrbot 数据目录")
177+
def run(path: str | None = None) -> None:
178+
"""Run AstrBot"""
179+
# 解析为绝对路径
180+
try:
181+
from ..core.log import LogBroker
182+
from ..core import db_helper
183+
from ..core.initial_loader import InitialLoader
184+
except ImportError:
185+
from astrbot.core.log import LogBroker
186+
from astrbot.core import db_helper
187+
from astrbot.core.initial_loader import InitialLoader
188+
189+
astrbot_root = _get_astrbot_root(path)
190+
191+
_check_astrbot_root(astrbot_root)
192+
193+
asyncio.run(_check_dashboard(astrbot_root))
194+
195+
log_broker = LogBroker()
196+
db = db_helper
197+
198+
core_lifecycle = InitialLoader(db, log_broker)
199+
try:
200+
asyncio.run(core_lifecycle.start())
201+
except KeyboardInterrupt:
202+
click.echo("接收到退出信号,正在关闭AstrBot...")
203+
except Exception as e:
204+
click.echo(f"运行时出现错误: {e}")
205+
206+
207+
# region Basic
208+
@cli.command(name="version")
209+
def version() -> None:
210+
"""Show the version of AstrBot"""
211+
click.echo(f"AstrBot version: {VERSION}")
212+
213+
214+
@cli.command()
215+
@click.argument("command_name", required=False, type=str)
216+
def help(command_name: str | None) -> None:
217+
"""Show help information for commands
218+
219+
If COMMAND_NAME is provided, show detailed help for that command.
220+
Otherwise, show general help information.
221+
"""
222+
ctx = click.get_current_context()
223+
if command_name:
224+
# 查找指定命令
225+
command = cli.get_command(ctx, command_name)
226+
if command:
227+
# 显示特定命令的帮助信息
228+
click.echo(command.get_help(ctx))
229+
else:
230+
click.echo(f"Unknown command: {command_name}")
231+
sys.exit(1)
232+
else:
233+
# 显示通用帮助信息
234+
click.echo(cli.get_help(ctx))
235+
236+
237+
if __name__ == "__main__":
238+
cli()

‎astrbot/core/utils/io.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,20 +209,20 @@ async def get_dashboard_version():
209209
return None
210210

211211

212-
async def download_dashboard():
212+
async def download_dashboard(path: str = "data/dashboard.zip", extract_path: str = "data"):
213213
"""下载管理面板文件"""
214214
dashboard_release_url = "https://astrbot-registry.soulter.top/download/astrbot-dashboard/latest/dist.zip"
215215
try:
216216
await download_file(
217-
dashboard_release_url, "data/dashboard.zip", show_progress=True
217+
dashboard_release_url, path, show_progress=True
218218
)
219219
except BaseException as _:
220220
dashboard_release_url = (
221221
"https://github.com/Soulter/AstrBot/releases/latest/download/dist.zip"
222222
)
223223
await download_file(
224-
dashboard_release_url, "data/dashboard.zip", show_progress=True
224+
dashboard_release_url, path, show_progress=True
225225
)
226226
print("解压管理面板文件中...")
227-
with zipfile.ZipFile("data/dashboard.zip", "r") as z:
228-
z.extractall("data")
227+
with zipfile.ZipFile(path, "r") as z:
228+
z.extractall(extract_path)

‎pyproject.toml‎

Lines changed: 7 additions & 0 deletions

0 commit comments

Comments
 (0)