ForcomeBot 2.2.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- forcomebot-2.2.4.dist-info/METADATA +342 -0
- forcomebot-2.2.4.dist-info/RECORD +36 -0
- forcomebot-2.2.4.dist-info/WHEEL +4 -0
- forcomebot-2.2.4.dist-info/entry_points.txt +4 -0
- src/__init__.py +68 -0
- src/__main__.py +487 -0
- src/api/__init__.py +21 -0
- src/api/routes.py +775 -0
- src/api/websocket.py +280 -0
- src/auth/__init__.py +33 -0
- src/auth/database.py +87 -0
- src/auth/dingtalk.py +373 -0
- src/auth/jwt_handler.py +129 -0
- src/auth/middleware.py +260 -0
- src/auth/models.py +107 -0
- src/auth/routes.py +385 -0
- src/clients/__init__.py +7 -0
- src/clients/langbot.py +710 -0
- src/clients/qianxun.py +388 -0
- src/core/__init__.py +19 -0
- src/core/config_manager.py +411 -0
- src/core/log_collector.py +167 -0
- src/core/message_queue.py +364 -0
- src/core/state_store.py +242 -0
- src/handlers/__init__.py +8 -0
- src/handlers/message_handler.py +833 -0
- src/handlers/message_parser.py +325 -0
- src/handlers/scheduler.py +822 -0
- src/models.py +77 -0
- src/static/assets/index-B4i68B5_.js +50 -0
- src/static/assets/index-BPXisDkw.css +2 -0
- src/static/index.html +14 -0
- src/static/vite.svg +1 -0
- src/utils/__init__.py +13 -0
- src/utils/text_processor.py +166 -0
- src/utils/xml_parser.py +215 -0
src/__main__.py
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FORCOME 康康 - 千寻微信框架Pro与LangBot中间件
|
|
3
|
+
包入口点 - 支持 uvx ForcomeBot 和 python -m src 运行
|
|
4
|
+
"""
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import sys
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
import uvicorn
|
|
13
|
+
from fastapi import FastAPI, Request, WebSocket
|
|
14
|
+
from fastapi.responses import JSONResponse, RedirectResponse
|
|
15
|
+
from fastapi.staticfiles import StaticFiles
|
|
16
|
+
|
|
17
|
+
# 导入核心模块
|
|
18
|
+
from .core.config_manager import ConfigManager
|
|
19
|
+
from .core.state_store import StateStore
|
|
20
|
+
from .core.log_collector import LogCollector, log_system
|
|
21
|
+
from .core.message_queue import MessageQueue, MessagePriority
|
|
22
|
+
from .clients.qianxun import QianXunClient
|
|
23
|
+
from .clients.langbot import LangBotClient
|
|
24
|
+
from .handlers.message_handler import MessageHandler
|
|
25
|
+
from .handlers.scheduler import TaskScheduler
|
|
26
|
+
from .models import QianXunCallback
|
|
27
|
+
from .api import router as api_router, set_dependencies, websocket_endpoint, set_websocket_dependencies
|
|
28
|
+
from .web import router as admin_router, set_references
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def setup_logging(level: str = "INFO"):
|
|
32
|
+
"""配置日志"""
|
|
33
|
+
logging.basicConfig(
|
|
34
|
+
level=getattr(logging, level.upper(), logging.INFO),
|
|
35
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
36
|
+
datefmt="%Y-%m-%d %H:%M:%S"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# 创建 FastAPI 应用
|
|
41
|
+
app = FastAPI(
|
|
42
|
+
title="FORCOME 康康",
|
|
43
|
+
description="千寻微信框架Pro与LangBot中间件",
|
|
44
|
+
version="2.0.0"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 注册路由
|
|
48
|
+
app.include_router(api_router)
|
|
49
|
+
app.include_router(admin_router)
|
|
50
|
+
|
|
51
|
+
# 全局服务实例
|
|
52
|
+
config_manager: Optional[ConfigManager] = None
|
|
53
|
+
state_store: Optional[StateStore] = None
|
|
54
|
+
log_collector: Optional[LogCollector] = None
|
|
55
|
+
message_queue: Optional[MessageQueue] = None
|
|
56
|
+
qianxun_client: Optional[QianXunClient] = None
|
|
57
|
+
langbot_client: Optional[LangBotClient] = None
|
|
58
|
+
message_handler: Optional[MessageHandler] = None
|
|
59
|
+
scheduler: Optional[TaskScheduler] = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_config_path() -> Path:
|
|
63
|
+
"""获取配置文件路径
|
|
64
|
+
|
|
65
|
+
优先级:
|
|
66
|
+
1. 当前工作目录的 config.yaml
|
|
67
|
+
2. 用户目录的 ~/.forcome/config.yaml
|
|
68
|
+
"""
|
|
69
|
+
# 当前目录
|
|
70
|
+
cwd_config = Path.cwd() / "config.yaml"
|
|
71
|
+
if cwd_config.exists():
|
|
72
|
+
return cwd_config
|
|
73
|
+
|
|
74
|
+
# 用户目录
|
|
75
|
+
user_config_dir = Path.home() / ".forcome"
|
|
76
|
+
user_config = user_config_dir / "config.yaml"
|
|
77
|
+
if user_config.exists():
|
|
78
|
+
return user_config
|
|
79
|
+
|
|
80
|
+
# 如果都不存在,返回当前目录路径(后续会创建示例配置)
|
|
81
|
+
return cwd_config
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_data_dir() -> Path:
|
|
85
|
+
"""获取数据目录"""
|
|
86
|
+
config_path = get_config_path()
|
|
87
|
+
return config_path.parent / "data"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def create_example_config(config_path: Path):
|
|
91
|
+
"""创建示例配置文件"""
|
|
92
|
+
example_config = '''# FORCOME 康康 配置文件
|
|
93
|
+
# 首次运行自动生成,请根据实际情况修改
|
|
94
|
+
|
|
95
|
+
# 服务器配置
|
|
96
|
+
server:
|
|
97
|
+
host: "0.0.0.0"
|
|
98
|
+
port: 789
|
|
99
|
+
|
|
100
|
+
# 千寻框架配置
|
|
101
|
+
qianxun:
|
|
102
|
+
api_url: "http://127.0.0.1:7777/qianxun/httpapi"
|
|
103
|
+
|
|
104
|
+
# LangBot 配置
|
|
105
|
+
langbot:
|
|
106
|
+
ws_host: "127.0.0.1"
|
|
107
|
+
ws_port: 2280
|
|
108
|
+
access_token: ""
|
|
109
|
+
|
|
110
|
+
# 机器人配置
|
|
111
|
+
robot:
|
|
112
|
+
wxid: "" # 机器人微信ID,留空自动获取
|
|
113
|
+
|
|
114
|
+
# 消息过滤配置
|
|
115
|
+
filter:
|
|
116
|
+
ignore_wxids: [] # 忽略的wxid列表
|
|
117
|
+
reply_at_all: false # 是否回复@所有人
|
|
118
|
+
|
|
119
|
+
# 限流配置
|
|
120
|
+
rate_limit:
|
|
121
|
+
min_interval: 1 # 最小回复间隔(秒)
|
|
122
|
+
max_interval: 3 # 最大回复间隔(秒)
|
|
123
|
+
batch_min_interval: 2 # 群发最小间隔(秒)
|
|
124
|
+
batch_max_interval: 5 # 群发最大间隔(秒)
|
|
125
|
+
|
|
126
|
+
# 消息分段配置
|
|
127
|
+
message_split:
|
|
128
|
+
enabled: false
|
|
129
|
+
separator: "/!"
|
|
130
|
+
min_delay: 1
|
|
131
|
+
max_delay: 3
|
|
132
|
+
|
|
133
|
+
# 定时任务配置
|
|
134
|
+
scheduled_tasks: []
|
|
135
|
+
|
|
136
|
+
# 日志配置
|
|
137
|
+
logging:
|
|
138
|
+
level: "INFO"
|
|
139
|
+
'''
|
|
140
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
141
|
+
config_path.write_text(example_config, encoding="utf-8")
|
|
142
|
+
print(f"已创建示例配置文件: {config_path}")
|
|
143
|
+
print("请修改配置后重新运行")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def on_config_change(old_config: dict, new_config: dict):
|
|
147
|
+
"""配置变更回调"""
|
|
148
|
+
logger = logging.getLogger(__name__)
|
|
149
|
+
logger.info("检测到配置变更,正在应用...")
|
|
150
|
+
|
|
151
|
+
old_langbot = old_config.get('langbot', {})
|
|
152
|
+
new_langbot = new_config.get('langbot', {})
|
|
153
|
+
|
|
154
|
+
if (old_langbot.get('ws_host') != new_langbot.get('ws_host') or
|
|
155
|
+
old_langbot.get('ws_port') != new_langbot.get('ws_port') or
|
|
156
|
+
old_langbot.get('access_token') != new_langbot.get('access_token')):
|
|
157
|
+
|
|
158
|
+
logger.info("LangBot连接配置已变更,正在重新连接...")
|
|
159
|
+
if langbot_client:
|
|
160
|
+
await langbot_client.update_connection(
|
|
161
|
+
new_langbot.get('ws_host', '127.0.0.1'),
|
|
162
|
+
new_langbot.get('ws_port', 2280),
|
|
163
|
+
new_langbot.get('access_token', '')
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
old_qianxun = old_config.get('qianxun', {})
|
|
167
|
+
new_qianxun = new_config.get('qianxun', {})
|
|
168
|
+
|
|
169
|
+
if old_qianxun.get('api_url') != new_qianxun.get('api_url'):
|
|
170
|
+
logger.info("千寻API地址已变更,正在更新...")
|
|
171
|
+
if qianxun_client:
|
|
172
|
+
qianxun_client.update_api_url(new_qianxun.get('api_url', ''))
|
|
173
|
+
|
|
174
|
+
if scheduler:
|
|
175
|
+
scheduler.reload_tasks()
|
|
176
|
+
|
|
177
|
+
# 更新消息队列配置
|
|
178
|
+
if message_queue:
|
|
179
|
+
new_rate_limit = new_config.get('rate_limit', {})
|
|
180
|
+
message_queue.update_config(
|
|
181
|
+
min_interval=new_rate_limit.get('min_interval'),
|
|
182
|
+
max_interval=new_rate_limit.get('max_interval'),
|
|
183
|
+
batch_min_interval=new_rate_limit.get('batch_min_interval'),
|
|
184
|
+
batch_max_interval=new_rate_limit.get('batch_max_interval')
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if log_collector:
|
|
188
|
+
await log_system(log_collector, "配置已更新并生效")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@app.on_event("startup")
|
|
192
|
+
async def startup():
|
|
193
|
+
"""启动时初始化"""
|
|
194
|
+
global config_manager, state_store, log_collector, message_queue
|
|
195
|
+
global qianxun_client, langbot_client, message_handler, scheduler
|
|
196
|
+
|
|
197
|
+
logger = logging.getLogger(__name__)
|
|
198
|
+
|
|
199
|
+
config_path = get_config_path()
|
|
200
|
+
data_dir = get_data_dir()
|
|
201
|
+
|
|
202
|
+
# 初始化配置管理器
|
|
203
|
+
config_manager = ConfigManager(str(config_path))
|
|
204
|
+
try:
|
|
205
|
+
config_manager.load()
|
|
206
|
+
except Exception as e:
|
|
207
|
+
logger.error(f"加载配置失败: {e}")
|
|
208
|
+
sys.exit(1)
|
|
209
|
+
|
|
210
|
+
log_level = config_manager.get("logging.level", "INFO")
|
|
211
|
+
setup_logging(log_level)
|
|
212
|
+
|
|
213
|
+
logger.info("正在启动中间件...")
|
|
214
|
+
|
|
215
|
+
# 初始化状态存储器
|
|
216
|
+
state_store = StateStore(data_dir=str(data_dir))
|
|
217
|
+
await state_store.start()
|
|
218
|
+
|
|
219
|
+
# 初始化日志收集器
|
|
220
|
+
log_collector = LogCollector(max_logs=100)
|
|
221
|
+
|
|
222
|
+
# 初始化消息队列
|
|
223
|
+
rate_limit = config_manager.get_rate_limit_config()
|
|
224
|
+
message_queue = MessageQueue(
|
|
225
|
+
min_interval=rate_limit.get('min_interval', 1),
|
|
226
|
+
max_interval=rate_limit.get('max_interval', 3),
|
|
227
|
+
batch_min_interval=rate_limit.get('batch_min_interval', 2),
|
|
228
|
+
batch_max_interval=rate_limit.get('batch_max_interval', 5)
|
|
229
|
+
)
|
|
230
|
+
await message_queue.start()
|
|
231
|
+
logger.info("消息队列已启动")
|
|
232
|
+
|
|
233
|
+
# 初始化千寻客户端
|
|
234
|
+
qianxun_url = config_manager.get("qianxun.api_url", "http://127.0.0.1:7777/qianxun/httpapi")
|
|
235
|
+
qianxun_client = QianXunClient(qianxun_url)
|
|
236
|
+
logger.info(f"千寻框架API: {qianxun_url}")
|
|
237
|
+
|
|
238
|
+
# 初始化LangBot客户端
|
|
239
|
+
langbot_config = config_manager.get_langbot_config()
|
|
240
|
+
ws_host = langbot_config.get("ws_host", "127.0.0.1")
|
|
241
|
+
ws_port = langbot_config.get("ws_port", 2280)
|
|
242
|
+
access_token = langbot_config.get("access_token", "")
|
|
243
|
+
|
|
244
|
+
langbot_client = LangBotClient(ws_host, ws_port, access_token)
|
|
245
|
+
langbot_client.set_state_store(state_store)
|
|
246
|
+
|
|
247
|
+
# 初始化消息处理器
|
|
248
|
+
message_handler = MessageHandler(
|
|
249
|
+
qianxun_client,
|
|
250
|
+
langbot_client,
|
|
251
|
+
config_manager,
|
|
252
|
+
state_store,
|
|
253
|
+
log_collector
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# 初始化定时任务调度器
|
|
257
|
+
scheduler = TaskScheduler(
|
|
258
|
+
qianxun_client,
|
|
259
|
+
config_manager,
|
|
260
|
+
state_store,
|
|
261
|
+
log_collector,
|
|
262
|
+
message_queue # 传入消息队列
|
|
263
|
+
)
|
|
264
|
+
scheduler.start()
|
|
265
|
+
|
|
266
|
+
# 注册配置变更观察者
|
|
267
|
+
config_manager.register_observer(on_config_change)
|
|
268
|
+
|
|
269
|
+
# 设置API依赖
|
|
270
|
+
set_dependencies(
|
|
271
|
+
config_manager,
|
|
272
|
+
state_store,
|
|
273
|
+
log_collector,
|
|
274
|
+
qianxun_client,
|
|
275
|
+
langbot_client,
|
|
276
|
+
scheduler,
|
|
277
|
+
message_queue
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# 设置WebSocket依赖
|
|
281
|
+
set_websocket_dependencies(log_collector, langbot_client)
|
|
282
|
+
|
|
283
|
+
# 设置旧版Web管理界面的引用(兼容)
|
|
284
|
+
class CompatHandler:
|
|
285
|
+
def __init__(self, qianxun, config):
|
|
286
|
+
self.qianxun = qianxun
|
|
287
|
+
self.config = config
|
|
288
|
+
|
|
289
|
+
compat_handler = CompatHandler(qianxun_client, config_manager.config)
|
|
290
|
+
|
|
291
|
+
class CompatScheduler:
|
|
292
|
+
def __init__(self, real_scheduler, config_manager):
|
|
293
|
+
self._scheduler = real_scheduler
|
|
294
|
+
self._config_manager = config_manager
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def robot_wxid(self):
|
|
298
|
+
return self._scheduler.robot_wxid
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def scheduler(self):
|
|
302
|
+
return self._scheduler.scheduler
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def config(self):
|
|
306
|
+
return self._config_manager.config
|
|
307
|
+
|
|
308
|
+
@config.setter
|
|
309
|
+
def config(self, value):
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
def _setup_nickname_check_tasks(self):
|
|
313
|
+
self._scheduler.reload_tasks()
|
|
314
|
+
|
|
315
|
+
def _setup_scheduled_reminders(self):
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
compat_scheduler = CompatScheduler(scheduler, config_manager)
|
|
319
|
+
set_references(compat_handler, compat_scheduler, config_manager.config)
|
|
320
|
+
|
|
321
|
+
# 连接到LangBot
|
|
322
|
+
asyncio.create_task(langbot_client.connect())
|
|
323
|
+
|
|
324
|
+
await log_system(log_collector, "中间件启动完成")
|
|
325
|
+
logger.info("中间件启动完成")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@app.on_event("shutdown")
|
|
329
|
+
async def shutdown():
|
|
330
|
+
"""关闭时清理"""
|
|
331
|
+
logger = logging.getLogger(__name__)
|
|
332
|
+
logger.info("正在关闭中间件...")
|
|
333
|
+
|
|
334
|
+
if scheduler:
|
|
335
|
+
scheduler.stop()
|
|
336
|
+
if message_queue:
|
|
337
|
+
await message_queue.stop()
|
|
338
|
+
if state_store:
|
|
339
|
+
await state_store.stop()
|
|
340
|
+
if qianxun_client:
|
|
341
|
+
await qianxun_client.close()
|
|
342
|
+
if langbot_client:
|
|
343
|
+
await langbot_client.close()
|
|
344
|
+
|
|
345
|
+
logger.info("中间件已关闭")
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@app.post("/qianxun/callback")
|
|
349
|
+
async def qianxun_callback(request: Request):
|
|
350
|
+
"""千寻框架回调接口"""
|
|
351
|
+
logger = logging.getLogger(__name__)
|
|
352
|
+
|
|
353
|
+
try:
|
|
354
|
+
data = await request.json()
|
|
355
|
+
logger.info(f"收到回调原始数据: {data}")
|
|
356
|
+
|
|
357
|
+
callback = QianXunCallback(**data)
|
|
358
|
+
|
|
359
|
+
if callback.wxid and scheduler:
|
|
360
|
+
scheduler.set_robot_wxid(callback.wxid)
|
|
361
|
+
|
|
362
|
+
result = await message_handler.handle_callback(callback)
|
|
363
|
+
return JSONResponse(content=result)
|
|
364
|
+
|
|
365
|
+
except Exception as e:
|
|
366
|
+
logger.error(f"处理回调异常: {e}", exc_info=True)
|
|
367
|
+
return JSONResponse(
|
|
368
|
+
content={"status": "error", "message": str(e)},
|
|
369
|
+
status_code=500
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
@app.websocket("/api/ws")
|
|
374
|
+
async def websocket_route(websocket: WebSocket):
|
|
375
|
+
"""WebSocket端点"""
|
|
376
|
+
await websocket_endpoint(websocket)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@app.get("/health")
|
|
380
|
+
async def health_check():
|
|
381
|
+
"""健康检查接口"""
|
|
382
|
+
langbot_ok = langbot_client.is_connected if langbot_client else False
|
|
383
|
+
return {
|
|
384
|
+
"status": "ok",
|
|
385
|
+
"langbot_connected": langbot_ok,
|
|
386
|
+
"langbot_reconnecting": langbot_client.is_reconnecting if langbot_client else False
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@app.get("/")
|
|
391
|
+
async def root():
|
|
392
|
+
"""根路径"""
|
|
393
|
+
if static_dir:
|
|
394
|
+
return RedirectResponse(url="/app/")
|
|
395
|
+
return {
|
|
396
|
+
"name": "FORCOME 康康",
|
|
397
|
+
"version": "2.0.0",
|
|
398
|
+
"callback_url": "/qianxun/callback",
|
|
399
|
+
"api_docs": "/docs",
|
|
400
|
+
"admin": "/admin",
|
|
401
|
+
"app": "/app/ (前端未安装)"
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# 挂载React前端静态文件
|
|
406
|
+
# 优先使用当前目录的 web/dist,其次使用包内的 static 目录
|
|
407
|
+
def get_static_dir() -> Path | None:
|
|
408
|
+
"""获取静态文件目录"""
|
|
409
|
+
# 1. 当前目录的 web/dist
|
|
410
|
+
local_dist = Path.cwd() / "web" / "dist"
|
|
411
|
+
if local_dist.exists() and (local_dist / "index.html").exists():
|
|
412
|
+
return local_dist
|
|
413
|
+
|
|
414
|
+
# 2. 包内的 static 目录
|
|
415
|
+
package_static = Path(__file__).parent / "static"
|
|
416
|
+
if package_static.exists() and (package_static / "index.html").exists():
|
|
417
|
+
return package_static
|
|
418
|
+
|
|
419
|
+
return None
|
|
420
|
+
|
|
421
|
+
static_dir = get_static_dir()
|
|
422
|
+
if static_dir:
|
|
423
|
+
from fastapi.responses import FileResponse
|
|
424
|
+
|
|
425
|
+
# SPA catch-all 路由 - 处理前端路由刷新问题
|
|
426
|
+
# 必须在 StaticFiles 挂载之前定义
|
|
427
|
+
@app.get("/app/{full_path:path}")
|
|
428
|
+
async def serve_spa(full_path: str):
|
|
429
|
+
"""处理 SPA 路由,所有 /app/* 路径都返回 index.html"""
|
|
430
|
+
# 如果请求的是静态资源文件(有扩展名),尝试返回文件
|
|
431
|
+
if "." in full_path:
|
|
432
|
+
file_path = static_dir / full_path
|
|
433
|
+
if file_path.exists() and file_path.is_file():
|
|
434
|
+
return FileResponse(file_path)
|
|
435
|
+
# 否则返回 index.html,让前端路由处理
|
|
436
|
+
return FileResponse(static_dir / "index.html")
|
|
437
|
+
|
|
438
|
+
# 挂载静态资源目录(用于 /app/assets/* 等静态文件)
|
|
439
|
+
app.mount("/app", StaticFiles(directory=str(static_dir), html=True), name="react-app")
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def main():
|
|
443
|
+
"""主入口函数 - 支持 uvx ForcomeBot 运行"""
|
|
444
|
+
import yaml
|
|
445
|
+
|
|
446
|
+
config_path = get_config_path()
|
|
447
|
+
|
|
448
|
+
# 检查配置文件
|
|
449
|
+
if not config_path.exists():
|
|
450
|
+
print(f"配置文件不存在: {config_path}")
|
|
451
|
+
create_example_config(config_path)
|
|
452
|
+
return
|
|
453
|
+
|
|
454
|
+
# 加载配置获取服务器设置
|
|
455
|
+
with open(config_path, "r", encoding="utf-8") as f:
|
|
456
|
+
cfg = yaml.safe_load(f)
|
|
457
|
+
|
|
458
|
+
server_config = cfg.get("server", {})
|
|
459
|
+
host = server_config.get("host", "0.0.0.0")
|
|
460
|
+
port = server_config.get("port", 789)
|
|
461
|
+
|
|
462
|
+
print(f"""
|
|
463
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
464
|
+
║ FORCOME 康康 v2.0 ║
|
|
465
|
+
║ 千寻微信框架Pro - LangBot 中间件 ║
|
|
466
|
+
╠══════════════════════════════════════════════════════════════╣
|
|
467
|
+
║ 配置文件: {config_path}
|
|
468
|
+
║ 回调地址: http://{host}:{port}/qianxun/callback
|
|
469
|
+
║ React前端: http://{host}:{port}/app/
|
|
470
|
+
║ 管理界面: http://{host}:{port}/admin (旧版)
|
|
471
|
+
║ API文档: http://{host}:{port}/docs
|
|
472
|
+
║ 健康检查: http://{host}:{port}/health
|
|
473
|
+
║ WebSocket: ws://{host}:{port}/api/ws
|
|
474
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
475
|
+
""")
|
|
476
|
+
|
|
477
|
+
uvicorn.run(
|
|
478
|
+
"src.__main__:app",
|
|
479
|
+
host=host,
|
|
480
|
+
port=port,
|
|
481
|
+
reload=False,
|
|
482
|
+
log_level="info"
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
if __name__ == "__main__":
|
|
487
|
+
main()
|
src/api/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# API layer
|
|
2
|
+
"""RESTful API and WebSocket endpoints for admin interface."""
|
|
3
|
+
|
|
4
|
+
from .routes import router, set_dependencies
|
|
5
|
+
from .websocket import (
|
|
6
|
+
ws_manager,
|
|
7
|
+
websocket_endpoint,
|
|
8
|
+
set_websocket_dependencies,
|
|
9
|
+
broadcast_status_change,
|
|
10
|
+
broadcast_log
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"router",
|
|
15
|
+
"set_dependencies",
|
|
16
|
+
"ws_manager",
|
|
17
|
+
"websocket_endpoint",
|
|
18
|
+
"set_websocket_dependencies",
|
|
19
|
+
"broadcast_status_change",
|
|
20
|
+
"broadcast_log"
|
|
21
|
+
]
|