AstrBot 4.7.3__py3-none-any.whl → 4.8.0__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.
Files changed (52) hide show
  1. astrbot/cli/__init__.py +1 -1
  2. astrbot/core/agent/message.py +21 -5
  3. astrbot/core/astr_agent_run_util.py +15 -1
  4. astrbot/core/config/default.py +113 -1
  5. astrbot/core/db/__init__.py +30 -1
  6. astrbot/core/db/sqlite.py +55 -1
  7. astrbot/core/message/components.py +6 -1
  8. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +64 -5
  9. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
  10. astrbot/core/platform/manager.py +67 -9
  11. astrbot/core/platform/platform.py +99 -2
  12. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +19 -5
  13. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +5 -7
  14. astrbot/core/platform/sources/discord/discord_platform_adapter.py +1 -2
  15. astrbot/core/platform/sources/lark/lark_adapter.py +1 -3
  16. astrbot/core/platform/sources/misskey/misskey_adapter.py +1 -2
  17. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +2 -0
  18. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +1 -3
  19. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +32 -9
  20. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +13 -1
  21. astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
  22. astrbot/core/platform/sources/slack/client.py +50 -39
  23. astrbot/core/platform/sources/slack/slack_adapter.py +21 -7
  24. astrbot/core/platform/sources/slack/slack_event.py +3 -3
  25. astrbot/core/platform/sources/telegram/tg_adapter.py +4 -3
  26. astrbot/core/platform/sources/webchat/webchat_adapter.py +95 -29
  27. astrbot/core/platform/sources/webchat/webchat_event.py +33 -33
  28. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +1 -2
  29. astrbot/core/platform/sources/wecom/wecom_adapter.py +51 -9
  30. astrbot/core/platform/sources/wecom/wecom_event.py +1 -1
  31. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
  32. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
  33. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +52 -11
  34. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +1 -1
  35. astrbot/core/platform_message_history_mgr.py +3 -3
  36. astrbot/core/provider/provider.py +35 -0
  37. astrbot/core/provider/sources/whisper_api_source.py +43 -11
  38. astrbot/core/utils/file_extract.py +23 -0
  39. astrbot/core/utils/tencent_record_helper.py +1 -1
  40. astrbot/core/utils/webhook_utils.py +47 -0
  41. astrbot/dashboard/routes/__init__.py +2 -0
  42. astrbot/dashboard/routes/chat.py +300 -70
  43. astrbot/dashboard/routes/config.py +32 -165
  44. astrbot/dashboard/routes/knowledge_base.py +1 -1
  45. astrbot/dashboard/routes/platform.py +100 -0
  46. astrbot/dashboard/routes/plugin.py +65 -6
  47. astrbot/dashboard/server.py +3 -1
  48. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/METADATA +48 -37
  49. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/RECORD +52 -49
  50. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/WHEEL +0 -0
  51. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/entry_points.txt +0 -0
  52. {astrbot-4.7.3.dist-info → astrbot-4.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -2,6 +2,7 @@ import asyncio
2
2
  import inspect
3
3
  import os
4
4
  import traceback
5
+ import uuid
5
6
 
6
7
  from quart import request
7
8
 
@@ -13,16 +14,14 @@ from astrbot.core.config.default import (
13
14
  CONFIG_METADATA_3_SYSTEM,
14
15
  DEFAULT_CONFIG,
15
16
  DEFAULT_VALUE_MAP,
17
+ WEBHOOK_SUPPORTED_PLATFORMS,
16
18
  )
17
19
  from astrbot.core.config.i18n_utils import ConfigMetadataI18n
18
20
  from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
19
21
  from astrbot.core.platform.register import platform_cls_map, platform_registry
20
22
  from astrbot.core.provider import Provider
21
- from astrbot.core.provider.entities import ProviderType
22
- from astrbot.core.provider.provider import RerankProvider
23
23
  from astrbot.core.provider.register import provider_registry
24
24
  from astrbot.core.star.star import star_registry
25
- from astrbot.core.utils.astrbot_path import get_astrbot_path
26
25
 
27
26
  from .route import Response, Route, RouteContext
28
27
 
@@ -356,169 +355,20 @@ class ConfigRoute(Route):
356
355
  f"Attempting to check provider: {status_info['name']} (ID: {status_info['id']}, Type: {status_info['type']}, Model: {status_info['model']})",
357
356
  )
358
357
 
359
- if provider_capability_type == ProviderType.CHAT_COMPLETION:
360
- try:
361
- logger.debug(f"Sending 'Ping' to provider: {status_info['name']}")
362
- response = await asyncio.wait_for(
363
- provider.text_chat(prompt="REPLY `PONG` ONLY"),
364
- timeout=45.0,
365
- )
366
- logger.debug(
367
- f"Received response from {status_info['name']}: {response}",
368
- )
369
- if response is not None:
370
- status_info["status"] = "available"
371
- response_text_snippet = ""
372
- if (
373
- hasattr(response, "completion_text")
374
- and response.completion_text
375
- ):
376
- response_text_snippet = (
377
- response.completion_text[:70] + "..."
378
- if len(response.completion_text) > 70
379
- else response.completion_text
380
- )
381
- elif hasattr(response, "result_chain") and response.result_chain:
382
- try:
383
- response_text_snippet = (
384
- response.result_chain.get_plain_text()[:70] + "..."
385
- if len(response.result_chain.get_plain_text()) > 70
386
- else response.result_chain.get_plain_text()
387
- )
388
- except Exception as _:
389
- pass
390
- logger.info(
391
- f"Provider {status_info['name']} (ID: {status_info['id']}) is available. Response snippet: '{response_text_snippet}'",
392
- )
393
- else:
394
- status_info["error"] = (
395
- "Test call returned None, but expected an LLMResponse object."
396
- )
397
- logger.warning(
398
- f"Provider {status_info['name']} (ID: {status_info['id']}) test call returned None.",
399
- )
400
-
401
- except asyncio.TimeoutError:
402
- status_info["error"] = (
403
- "Connection timed out after 45 seconds during test call."
404
- )
405
- logger.warning(
406
- f"Provider {status_info['name']} (ID: {status_info['id']}) timed out.",
407
- )
408
- except Exception as e:
409
- error_message = str(e)
410
- status_info["error"] = error_message
411
- logger.warning(
412
- f"Provider {status_info['name']} (ID: {status_info['id']}) is unavailable. Error: {error_message}",
413
- )
414
- logger.debug(
415
- f"Traceback for {status_info['name']}:\n{traceback.format_exc()}",
416
- )
417
-
418
- elif provider_capability_type == ProviderType.EMBEDDING:
419
- try:
420
- # For embedding, we can call the get_embedding method with a short prompt.
421
- embedding_result = await provider.get_embedding("health_check")
422
- if isinstance(embedding_result, list) and (
423
- not embedding_result or isinstance(embedding_result[0], float)
424
- ):
425
- status_info["status"] = "available"
426
- else:
427
- status_info["status"] = "unavailable"
428
- status_info["error"] = (
429
- f"Embedding test failed: unexpected result type {type(embedding_result)}"
430
- )
431
- except Exception as e:
432
- logger.error(
433
- f"Error testing embedding provider {provider_name}: {e}",
434
- exc_info=True,
435
- )
436
- status_info["status"] = "unavailable"
437
- status_info["error"] = f"Embedding test failed: {e!s}"
438
-
439
- elif provider_capability_type == ProviderType.TEXT_TO_SPEECH:
440
- try:
441
- # For TTS, we can call the get_audio method with a short prompt.
442
- audio_result = await provider.get_audio("你好")
443
- if isinstance(audio_result, str) and audio_result:
444
- status_info["status"] = "available"
445
- else:
446
- status_info["status"] = "unavailable"
447
- status_info["error"] = (
448
- f"TTS test failed: unexpected result type {type(audio_result)}"
449
- )
450
- except Exception as e:
451
- logger.error(
452
- f"Error testing TTS provider {provider_name}: {e}",
453
- exc_info=True,
454
- )
455
- status_info["status"] = "unavailable"
456
- status_info["error"] = f"TTS test failed: {e!s}"
457
- elif provider_capability_type == ProviderType.SPEECH_TO_TEXT:
458
- try:
459
- logger.debug(
460
- f"Sending health check audio to provider: {status_info['name']}",
461
- )
462
- sample_audio_path = os.path.join(
463
- get_astrbot_path(),
464
- "samples",
465
- "stt_health_check.wav",
466
- )
467
- if not os.path.exists(sample_audio_path):
468
- status_info["status"] = "unavailable"
469
- status_info["error"] = (
470
- "STT test failed: sample audio file not found."
471
- )
472
- logger.warning(
473
- f"STT test for {status_info['name']} failed: sample audio file not found at {sample_audio_path}",
474
- )
475
- else:
476
- text_result = await provider.get_text(sample_audio_path)
477
- if isinstance(text_result, str) and text_result:
478
- status_info["status"] = "available"
479
- snippet = (
480
- text_result[:70] + "..."
481
- if len(text_result) > 70
482
- else text_result
483
- )
484
- logger.info(
485
- f"Provider {status_info['name']} (ID: {status_info['id']}) is available. Response snippet: '{snippet}'",
486
- )
487
- else:
488
- status_info["status"] = "unavailable"
489
- status_info["error"] = (
490
- f"STT test failed: unexpected result type {type(text_result)}"
491
- )
492
- logger.warning(
493
- f"STT test for {status_info['name']} failed: unexpected result type {type(text_result)}",
494
- )
495
- except Exception as e:
496
- logger.error(
497
- f"Error testing STT provider {provider_name}: {e}",
498
- exc_info=True,
499
- )
500
- status_info["status"] = "unavailable"
501
- status_info["error"] = f"STT test failed: {e!s}"
502
- elif provider_capability_type == ProviderType.RERANK:
503
- try:
504
- assert isinstance(provider, RerankProvider)
505
- await provider.rerank("Apple", documents=["apple", "banana"])
506
- status_info["status"] = "available"
507
- except Exception as e:
508
- logger.error(
509
- f"Error testing rerank provider {provider_name}: {e}",
510
- exc_info=True,
511
- )
512
- status_info["status"] = "unavailable"
513
- status_info["error"] = f"Rerank test failed: {e!s}"
514
-
515
- else:
516
- logger.debug(
517
- f"Provider {provider_name} is not a Chat Completion or Embedding provider. Marking as available without test. Meta: {meta}",
518
- )
358
+ try:
359
+ await provider.test()
519
360
  status_info["status"] = "available"
520
- status_info["error"] = (
521
- "This provider type is not tested and is assumed to be available."
361
+ logger.info(
362
+ f"Provider {status_info['name']} (ID: {status_info['id']}) is available.",
363
+ )
364
+ except Exception as e:
365
+ error_message = str(e)
366
+ status_info["error"] = error_message
367
+ logger.warning(
368
+ f"Provider {status_info['name']} (ID: {status_info['id']}) is unavailable. Error: {error_message}",
369
+ )
370
+ logger.debug(
371
+ f"Traceback for {status_info['name']}:\n{traceback.format_exc()}",
522
372
  )
523
373
 
524
374
  return status_info
@@ -707,6 +557,15 @@ class ConfigRoute(Route):
707
557
 
708
558
  async def post_new_platform(self):
709
559
  new_platform_config = await request.json
560
+
561
+ # 如果是支持统一 webhook 模式的平台,且启用了统一 webhook 模式,自动生成 webhook_uuid
562
+ platform_type = new_platform_config.get("type", "")
563
+ if platform_type in WEBHOOK_SUPPORTED_PLATFORMS:
564
+ if new_platform_config.get("unified_webhook_mode", False):
565
+ # 如果没有 webhook_uuid 或为空,自动生成
566
+ if not new_platform_config.get("webhook_uuid"):
567
+ new_platform_config["webhook_uuid"] = uuid.uuid4().hex[:16]
568
+
710
569
  self.config["platform"].append(new_platform_config)
711
570
  try:
712
571
  save_config(self.config, self.config, is_core=True)
@@ -736,6 +595,14 @@ class ConfigRoute(Route):
736
595
  if not platform_id or not new_config:
737
596
  return Response().error("参数错误").__dict__
738
597
 
598
+ # 如果是支持统一 webhook 模式的平台,且启用了统一 webhook 模式,确保有 webhook_uuid
599
+ platform_type = new_config.get("type", "")
600
+ if platform_type in WEBHOOK_SUPPORTED_PLATFORMS:
601
+ if new_config.get("unified_webhook_mode", False):
602
+ # 如果没有 webhook_uuid 或为空,自动生成
603
+ if not new_config.get("webhook_uuid"):
604
+ new_config["webhook_uuid"] = uuid.uuid4().hex
605
+
739
606
  for i, platform in enumerate(self.config["platform"]):
740
607
  if platform["id"] == platform_id:
741
608
  self.config["platform"][i] = new_config
@@ -274,7 +274,7 @@ class KnowledgeBaseRoute(Route):
274
274
  except Exception as e:
275
275
  return (
276
276
  Response()
277
- .error(f"测试重排序模型失败: {e!s},请检查控制台日志输出。")
277
+ .error(f"测试重排序模型失败: {e!s},请检查平台日志输出。")
278
278
  .__dict__
279
279
  )
280
280
 
@@ -0,0 +1,100 @@
1
+ """统一 Webhook 路由
2
+
3
+ 提供统一的 webhook 回调入口,支持多个平台使用同一端口接收回调。
4
+ """
5
+
6
+ from quart import request
7
+
8
+ from astrbot.core import logger
9
+ from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
10
+ from astrbot.core.platform import Platform
11
+
12
+ from .route import Response, Route, RouteContext
13
+
14
+
15
+ class PlatformRoute(Route):
16
+ """统一 Webhook 路由"""
17
+
18
+ def __init__(
19
+ self,
20
+ context: RouteContext,
21
+ core_lifecycle: AstrBotCoreLifecycle,
22
+ ) -> None:
23
+ super().__init__(context)
24
+ self.core_lifecycle = core_lifecycle
25
+ self.platform_manager = core_lifecycle.platform_manager
26
+
27
+ self._register_webhook_routes()
28
+
29
+ def _register_webhook_routes(self):
30
+ """注册 webhook 路由"""
31
+ # 统一 webhook 入口,支持 GET 和 POST
32
+ self.app.add_url_rule(
33
+ "/api/platform/webhook/<webhook_uuid>",
34
+ view_func=self.unified_webhook_callback,
35
+ methods=["GET", "POST"],
36
+ )
37
+
38
+ # 平台统计信息接口
39
+ self.app.add_url_rule(
40
+ "/api/platform/stats",
41
+ view_func=self.get_platform_stats,
42
+ methods=["GET"],
43
+ )
44
+
45
+ async def unified_webhook_callback(self, webhook_uuid: str):
46
+ """统一 webhook 回调入口
47
+
48
+ Args:
49
+ webhook_uuid: 平台配置中的 webhook_uuid
50
+
51
+ Returns:
52
+ 根据平台适配器返回相应的响应
53
+ """
54
+ # 根据 webhook_uuid 查找对应的平台
55
+ platform_adapter = self._find_platform_by_uuid(webhook_uuid)
56
+
57
+ if not platform_adapter:
58
+ logger.warning(f"未找到 webhook_uuid 为 {webhook_uuid} 的平台")
59
+ return Response().error("未找到对应平台").__dict__, 404
60
+
61
+ # 调用平台适配器的 webhook_callback 方法
62
+ try:
63
+ result = await platform_adapter.webhook_callback(request)
64
+ return result
65
+ except NotImplementedError:
66
+ logger.error(
67
+ f"平台 {platform_adapter.meta().name} 未实现 webhook_callback 方法"
68
+ )
69
+ return Response().error("平台未支持统一 Webhook 模式").__dict__, 500
70
+ except Exception as e:
71
+ logger.error(f"处理 webhook 回调时发生错误: {e}", exc_info=True)
72
+ return Response().error("处理回调失败").__dict__, 500
73
+
74
+ def _find_platform_by_uuid(self, webhook_uuid: str) -> Platform | None:
75
+ """根据 webhook_uuid 查找对应的平台适配器
76
+
77
+ Args:
78
+ webhook_uuid: webhook UUID
79
+
80
+ Returns:
81
+ 平台适配器实例,未找到则返回 None
82
+ """
83
+ for platform in self.platform_manager.platform_insts:
84
+ if platform.config.get("webhook_uuid") == webhook_uuid:
85
+ if platform.config.get("unified_webhook_mode", False):
86
+ return platform
87
+ return None
88
+
89
+ async def get_platform_stats(self):
90
+ """获取所有平台的统计信息
91
+
92
+ Returns:
93
+ 包含平台统计信息的响应
94
+ """
95
+ try:
96
+ stats = self.platform_manager.get_all_stats()
97
+ return Response().ok(stats).__dict__
98
+ except Exception as e:
99
+ logger.error(f"获取平台统计信息失败: {e}", exc_info=True)
100
+ return Response().error(f"获取统计信息失败: {e}").__dict__, 500
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import json
2
3
  import os
3
4
  import ssl
@@ -19,6 +20,10 @@ from astrbot.core.star.star_manager import PluginManager
19
20
 
20
21
  from .route import Response, Route, RouteContext
21
22
 
23
+ PLUGIN_UPDATE_CONCURRENCY = (
24
+ 3 # limit concurrent updates to avoid overwhelming plugin sources
25
+ )
26
+
22
27
 
23
28
  class PluginRoute(Route):
24
29
  def __init__(
@@ -33,6 +38,7 @@ class PluginRoute(Route):
33
38
  "/plugin/install": ("POST", self.install_plugin),
34
39
  "/plugin/install-upload": ("POST", self.install_plugin_upload),
35
40
  "/plugin/update": ("POST", self.update_plugin),
41
+ "/plugin/update-all": ("POST", self.update_all_plugins),
36
42
  "/plugin/uninstall": ("POST", self.uninstall_plugin),
37
43
  "/plugin/market_list": ("GET", self.get_online_plugins),
38
44
  "/plugin/off": ("POST", self.off_plugin),
@@ -63,7 +69,7 @@ class PluginRoute(Route):
63
69
  .__dict__
64
70
  )
65
71
 
66
- data = await request.json
72
+ data = await request.get_json()
67
73
  plugin_name = data.get("name", None)
68
74
  try:
69
75
  success, message = await self.plugin_manager.reload(plugin_name)
@@ -346,7 +352,7 @@ class PluginRoute(Route):
346
352
  .__dict__
347
353
  )
348
354
 
349
- post_data = await request.json
355
+ post_data = await request.get_json()
350
356
  repo_url = post_data["url"]
351
357
 
352
358
  proxy: str = post_data.get("proxy", None)
@@ -393,7 +399,7 @@ class PluginRoute(Route):
393
399
  .__dict__
394
400
  )
395
401
 
396
- post_data = await request.json
402
+ post_data = await request.get_json()
397
403
  plugin_name = post_data["name"]
398
404
  delete_config = post_data.get("delete_config", False)
399
405
  delete_data = post_data.get("delete_data", False)
@@ -418,7 +424,7 @@ class PluginRoute(Route):
418
424
  .__dict__
419
425
  )
420
426
 
421
- post_data = await request.json
427
+ post_data = await request.get_json()
422
428
  plugin_name = post_data["name"]
423
429
  proxy: str = post_data.get("proxy", None)
424
430
  try:
@@ -432,6 +438,59 @@ class PluginRoute(Route):
432
438
  logger.error(f"/api/plugin/update: {traceback.format_exc()}")
433
439
  return Response().error(str(e)).__dict__
434
440
 
441
+ async def update_all_plugins(self):
442
+ if DEMO_MODE:
443
+ return (
444
+ Response()
445
+ .error("You are not permitted to do this operation in demo mode")
446
+ .__dict__
447
+ )
448
+
449
+ post_data = await request.get_json()
450
+ plugin_names: list[str] = post_data.get("names") or []
451
+ proxy: str = post_data.get("proxy", "")
452
+
453
+ if not isinstance(plugin_names, list) or not plugin_names:
454
+ return Response().error("插件列表不能为空").__dict__
455
+
456
+ results = []
457
+ sem = asyncio.Semaphore(PLUGIN_UPDATE_CONCURRENCY)
458
+
459
+ async def _update_one(name: str):
460
+ async with sem:
461
+ try:
462
+ logger.info(f"批量更新插件 {name}")
463
+ await self.plugin_manager.update_plugin(name, proxy)
464
+ return {"name": name, "status": "ok", "message": "更新成功"}
465
+ except Exception as e:
466
+ logger.error(
467
+ f"/api/plugin/update-all: 更新插件 {name} 失败: {traceback.format_exc()}",
468
+ )
469
+ return {"name": name, "status": "error", "message": str(e)}
470
+
471
+ raw_results = await asyncio.gather(
472
+ *(_update_one(name) for name in plugin_names),
473
+ return_exceptions=True,
474
+ )
475
+ for name, result in zip(plugin_names, raw_results):
476
+ if isinstance(result, asyncio.CancelledError):
477
+ raise result
478
+ if isinstance(result, BaseException):
479
+ results.append(
480
+ {"name": name, "status": "error", "message": str(result)}
481
+ )
482
+ else:
483
+ results.append(result)
484
+
485
+ failed = [r for r in results if r["status"] == "error"]
486
+ message = (
487
+ "批量更新完成,全部成功。"
488
+ if not failed
489
+ else f"批量更新完成,其中 {len(failed)}/{len(results)} 个插件失败。"
490
+ )
491
+
492
+ return Response().ok({"results": results}, message).__dict__
493
+
435
494
  async def off_plugin(self):
436
495
  if DEMO_MODE:
437
496
  return (
@@ -440,7 +499,7 @@ class PluginRoute(Route):
440
499
  .__dict__
441
500
  )
442
501
 
443
- post_data = await request.json
502
+ post_data = await request.get_json()
444
503
  plugin_name = post_data["name"]
445
504
  try:
446
505
  await self.plugin_manager.turn_off_plugin(plugin_name)
@@ -458,7 +517,7 @@ class PluginRoute(Route):
458
517
  .__dict__
459
518
  )
460
519
 
461
- post_data = await request.json
520
+ post_data = await request.get_json()
462
521
  plugin_name = post_data["name"]
463
522
  try:
464
523
  await self.plugin_manager.turn_on_plugin(plugin_name)
@@ -16,6 +16,7 @@ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
16
16
  from astrbot.core.utils.io import get_local_ip_addresses
17
17
 
18
18
  from .routes import *
19
+ from .routes.platform import PlatformRoute
19
20
  from .routes.route import Response, RouteContext
20
21
  from .routes.session_management import SessionManagementRoute
21
22
  from .routes.t2i import T2iRoute
@@ -79,6 +80,7 @@ class AstrBotDashboard:
79
80
  self.persona_route = PersonaRoute(self.context, db, core_lifecycle)
80
81
  self.t2i_route = T2iRoute(self.context, core_lifecycle)
81
82
  self.kb_route = KnowledgeBaseRoute(self.context, core_lifecycle)
83
+ self.platform_route = PlatformRoute(self.context, core_lifecycle)
82
84
 
83
85
  self.app.add_url_rule(
84
86
  "/api/plug/<path:subpath>",
@@ -102,7 +104,7 @@ class AstrBotDashboard:
102
104
  async def auth_middleware(self):
103
105
  if not request.path.startswith("/api"):
104
106
  return None
105
- allowed_endpoints = ["/api/auth/login", "/api/file"]
107
+ allowed_endpoints = ["/api/auth/login", "/api/file", "/api/platform/webhook"]
106
108
  if any(request.path.startswith(prefix) for prefix in allowed_endpoints):
107
109
  return None
108
110
  # 声明 JWT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: AstrBot
3
- Version: 4.7.3
3
+ Version: 4.8.0
4
4
  Summary: Easy-to-use multi-platform LLM chatbot and development framework
5
5
  License-File: LICENSE
6
6
  Keywords: Astrbot,Astrbot Module,Astrbot Plugin
@@ -61,11 +61,14 @@ Description-Content-Type: text/markdown
61
61
 
62
62
  ![AstrBot-Logo-Simplified](https://github.com/user-attachments/assets/ffd99b6b-3272-4682-beaa-6fe74250f7d9)
63
63
 
64
- </p>
65
-
66
64
  <div align="center">
67
65
 
68
- <br>
66
+
67
+ <a href="https://github.com/AstrBotDevs/AstrBot/blob/master/README_en.md">English</a> |
68
+ <a href="https://github.com/AstrBotDevs/AstrBot/blob/master/README_ja.md">日本語</a> |
69
+ <a href="https://github.com/AstrBotDevs/AstrBot/blob/master/README_zh-TW.md">繁體中文</a> |
70
+ <a href="https://github.com/AstrBotDevs/AstrBot/blob/master/README_fr.md">Français</a> |
71
+ <a href="https://github.com/AstrBotDevs/AstrBot/blob/master/README_ru.md">Русский</a>
69
72
 
70
73
  <div>
71
74
  <a href="https://trendshift.io/repositories/12875" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12875" alt="Soulter%2FAstrBot | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
@@ -75,35 +78,37 @@ Description-Content-Type: text/markdown
75
78
  <br>
76
79
 
77
80
  <div>
78
- <img src="https://img.shields.io/github/v/release/AstrBotDevs/AstrBot?style=for-the-badge&color=76bad9" href="https://github.com/AstrBotDevs/AstrBot/releases/latest">
79
- <img src="https://img.shields.io/badge/python-3.10+-blue.svg?style=for-the-badge&color=76bad9" alt="python">
80
- <a href="https://hub.docker.com/r/soulter/astrbot"><img alt="Docker pull" src="https://img.shields.io/docker/pulls/soulter/astrbot.svg?style=for-the-badge&color=76bad9"/></a>
81
- <a href="https://qm.qq.com/cgi-bin/qm/qr?k=wtbaNx7EioxeaqS9z7RQWVXPIxg2zYr7&jump_from=webapi&authKey=vlqnv/AV2DbJEvGIcxdlNSpfxVy+8vVqijgreRdnVKOaydpc+YSw4MctmEbr0k5"><img alt="QQ_community" src="https://img.shields.io/badge/QQ群-775869627-purple?style=for-the-badge&color=76bad9"></a>
82
- <a href="https://t.me/+hAsD2Ebl5as3NmY1"><img alt="Telegram_community" src="https://img.shields.io/badge/Telegram-AstrBot-purple?style=for-the-badge&color=76bad9"></a>
83
- <img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fplugin-num&query=%24.result&suffix=%E4%B8%AA&style=for-the-badge&label=%E6%8F%92%E4%BB%B6%E5%B8%82%E5%9C%BA&cacheSeconds=3600">
81
+ <img src="https://img.shields.io/github/v/release/AstrBotDevs/AstrBot?color=76bad9" href="https://github.com/AstrBotDevs/AstrBot/releases/latest">
82
+ <img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="python">
83
+ <img src="https://deepwiki.com/badge.svg" href="https://deepwiki.com/AstrBotDevs/AstrBot">
84
+ <a href="https://hub.docker.com/r/soulter/astrbot"><img alt="Docker pull" src="https://img.shields.io/docker/pulls/soulter/astrbot.svg?color=76bad9"/></a>
85
+ <img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.soulter.top%2Fastrbot%2Fplugin-num&query=%24.result&suffix=%E4%B8%AA&label=%E6%8F%92%E4%BB%B6%E5%B8%82%E5%9C%BA&cacheSeconds=3600">
86
+ <img src="https://gitcode.com/Soulter/AstrBot/star/badge.svg" href="https://gitcode.com/Soulter/AstrBot">
84
87
  </div>
85
88
 
86
89
  <br>
87
90
 
88
- <a href="https://github.com/AstrBotDevs/AstrBot/blob/master/README_en.md">English</a> |
89
- <a href="https://github.com/AstrBotDevs/AstrBot/blob/master/README_ja.md">日本語</a> |
90
91
  <a href="https://astrbot.app/">文档</a> |
91
92
  <a href="https://blog.astrbot.app/">Blog</a> |
92
93
  <a href="https://astrbot.featurebase.app/roadmap">路线图</a> |
93
94
  <a href="https://github.com/AstrBotDevs/AstrBot/issues">问题提交</a>
94
95
  </div>
95
96
 
96
- AstrBot 是一个开源的一站式 Agent 聊天机器人平台,可无缝接入主流即时通讯软件,为个人、开发者和团队打造可靠、可扩展的对话式智能基础设施。无论是个人 AI 伙伴、智能客服、自动化助手,还是企业知识库,AstrBot 都能在你的即时通讯软件平台的工作流中快速构建生产可用的 AI 应用。
97
+ AstrBot 是一个开源的一站式 Agent 聊天机器人平台,可接入主流即时通讯软件,为个人、开发者和团队打造可靠、可扩展的对话式智能基础设施。无论是个人 AI 伙伴、智能客服、自动化助手,还是企业知识库,AstrBot 都能在你的即时通讯软件平台的工作流中快速构建生产可用的 AI 应用。
98
+
99
+ <img width="1776" height="1080" alt="image" src="https://github.com/user-attachments/assets/00782c4c-4437-4d97-aabc-605e3738da5c" />
97
100
 
98
101
  ## 主要功能
99
102
 
100
- 1. **大模型对话**。支持接入多种大模型服务。支持多模态、工具调用、MCP、原生知识库、人设等功能。
101
- 2. **多消息平台支持**。支持接入 QQ、企业微信、微信公众号、飞书、Telegram、钉钉、Discord、KOOK 等平台。支持速率限制、白名单、百度内容审核。
102
- 3. **Agent**。完善适配的 Agentic 能力。支持多轮工具调用、内置沙盒代码执行器、网页搜索等功能。
103
- 4. **插件扩展**。深度优化的插件机制,支持[开发插件](https://astrbot.app/dev/plugin.html)扩展功能,社区插件生态丰富。
104
- 5. **WebUI**。可视化配置和管理机器人,功能齐全。
103
+ 1. 💯 免费 & 开源。
104
+ 1. AI 大模型对话,多模态,Agent,MCP,知识库,人格设定。
105
+ 2. 🤖 支持接入 Dify、阿里云百炼、Coze 等智能体平台。
106
+ 2. 🌐 多平台,支持 QQ、企业微信、飞书、钉钉、微信公众号、Telegram、Slack 以及[更多](#支持的消息平台)
107
+ 3. 📦 插件扩展,已有近 800 个插件可一键安装。
108
+ 5. 💻 WebUI 支持。
109
+ 6. 🌐 国际化(i18n)支持。
105
110
 
106
- ## 部署方式
111
+ ## 快速开始
107
112
 
108
113
  #### Docker 部署(推荐 🥳)
109
114
 
@@ -111,6 +116,12 @@ AstrBot 是一个开源的一站式 Agent 聊天机器人平台,可无缝接
111
116
 
112
117
  请参阅官方文档 [使用 Docker 部署 AstrBot](https://astrbot.app/deploy/astrbot/docker.html#%E4%BD%BF%E7%94%A8-docker-%E9%83%A8%E7%BD%B2-astrbot) 。
113
118
 
119
+ #### uv 部署
120
+
121
+ ```bash
122
+ uvx astrbot
123
+ ```
124
+
114
125
  #### 宝塔面板部署
115
126
 
116
127
  AstrBot 与宝塔面板合作,已上架至宝塔面板。
@@ -162,24 +173,6 @@ uv run main.py
162
173
 
163
174
  或者请参阅官方文档 [通过源码部署 AstrBot](https://astrbot.app/deploy/astrbot/cli.html) 。
164
175
 
165
- ## 🌍 社区
166
-
167
- ### QQ 群组
168
-
169
- - 1 群:322154837
170
- - 3 群:630166526
171
- - 5 群:822130018
172
- - 6 群:753075035
173
- - 开发者群:975206796
174
-
175
- ### Telegram 群组
176
-
177
- <a href="https://t.me/+hAsD2Ebl5as3NmY1"><img alt="Telegram_community" src="https://img.shields.io/badge/Telegram-AstrBot-purple?style=for-the-badge&color=76bad9"></a>
178
-
179
- ### Discord 群组
180
-
181
- <a href="https://discord.gg/hAVk6tgV36"><img alt="Discord_community" src="https://img.shields.io/badge/Discord-AstrBot-purple?style=for-the-badge&color=76bad9"></a>
182
-
183
176
  ## 支持的消息平台
184
177
 
185
178
  **官方维护**
@@ -266,6 +259,24 @@ pip install pre-commit
266
259
  pre-commit install
267
260
  ```
268
261
 
262
+ ## 🌍 社区
263
+
264
+ ### QQ 群组
265
+
266
+ - 1 群:322154837
267
+ - 3 群:630166526
268
+ - 5 群:822130018
269
+ - 6 群:753075035
270
+ - 开发者群:975206796
271
+
272
+ ### Telegram 群组
273
+
274
+ <a href="https://t.me/+hAsD2Ebl5as3NmY1"><img alt="Telegram_community" src="https://img.shields.io/badge/Telegram-AstrBot-purple?style=for-the-badge&color=76bad9"></a>
275
+
276
+ ### Discord 群组
277
+
278
+ <a href="https://discord.gg/hAVk6tgV36"><img alt="Discord_community" src="https://img.shields.io/badge/Discord-AstrBot-purple?style=for-the-badge&color=76bad9"></a>
279
+
269
280
  ## ❤️ Special Thanks
270
281
 
271
282
  特别感谢所有 Contributors 和插件开发者对 AstrBot 的贡献 ❤️