AstrBot 4.7.4__py3-none-any.whl → 4.9.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.
- astrbot/cli/__init__.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
- astrbot/core/agent/tool.py +7 -2
- astrbot/core/astr_agent_run_util.py +15 -1
- astrbot/core/astr_agent_tool_exec.py +5 -1
- astrbot/core/config/astrbot_config.py +4 -0
- astrbot/core/config/default.py +116 -1
- astrbot/core/core_lifecycle.py +1 -1
- astrbot/core/db/__init__.py +32 -4
- astrbot/core/db/migration/migra_3_to_4.py +2 -0
- astrbot/core/db/migration/sqlite_v3.py +6 -4
- astrbot/core/db/po.py +16 -15
- astrbot/core/db/sqlite.py +56 -1
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
- astrbot/core/event_bus.py +6 -1
- astrbot/core/knowledge_base/retrieval/manager.py +5 -1
- astrbot/core/log.py +2 -1
- astrbot/core/message/components.py +9 -3
- astrbot/core/persona_mgr.py +2 -2
- astrbot/core/pipeline/content_safety_check/stage.py +1 -1
- astrbot/core/pipeline/context_utils.py +2 -1
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
- astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
- astrbot/core/pipeline/process_stage/stage.py +1 -1
- astrbot/core/pipeline/respond/stage.py +4 -2
- astrbot/core/pipeline/result_decorate/stage.py +68 -21
- astrbot/core/pipeline/scheduler.py +5 -1
- astrbot/core/pipeline/waking_check/stage.py +10 -0
- astrbot/core/platform/astr_message_event.py +5 -3
- astrbot/core/platform/astrbot_message.py +2 -2
- astrbot/core/platform/manager.py +71 -9
- astrbot/core/platform/platform.py +109 -4
- astrbot/core/platform/platform_metadata.py +1 -1
- astrbot/core/platform/register.py +1 -0
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +13 -8
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +28 -22
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
- astrbot/core/platform/sources/discord/client.py +16 -4
- astrbot/core/platform/sources/discord/components.py +2 -2
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +53 -26
- astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
- astrbot/core/platform/sources/lark/lark_adapter.py +178 -22
- astrbot/core/platform/sources/lark/lark_event.py +39 -4
- astrbot/core/platform/sources/lark/server.py +206 -0
- astrbot/core/platform/sources/misskey/misskey_adapter.py +3 -5
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +64 -18
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +14 -10
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -11
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +15 -2
- astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
- astrbot/core/platform/sources/slack/client.py +58 -40
- astrbot/core/platform/sources/slack/slack_adapter.py +36 -16
- astrbot/core/platform/sources/slack/slack_event.py +11 -10
- astrbot/core/platform/sources/telegram/tg_adapter.py +2 -3
- astrbot/core/platform/sources/telegram/tg_event.py +23 -27
- astrbot/core/platform/sources/webchat/webchat_adapter.py +97 -31
- astrbot/core/platform/sources/webchat/webchat_event.py +35 -35
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +27 -11
- astrbot/core/platform/sources/wecom/wecom_adapter.py +75 -36
- astrbot/core/platform/sources/wecom/wecom_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +81 -35
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +11 -8
- astrbot/core/platform_message_history_mgr.py +3 -3
- astrbot/core/provider/func_tool_manager.py +3 -3
- astrbot/core/provider/manager.py +130 -74
- astrbot/core/provider/provider.py +12 -1
- astrbot/core/provider/sources/azure_tts_source.py +31 -9
- astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
- astrbot/core/provider/sources/dashscope_tts.py +3 -2
- astrbot/core/provider/sources/edge_tts_source.py +1 -1
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
- astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
- astrbot/core/provider/sources/gemini_source.py +12 -10
- astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
- astrbot/core/provider/sources/openai_embedding_source.py +2 -2
- astrbot/core/provider/sources/openai_source.py +4 -0
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
- astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
- astrbot/core/provider/sources/whisper_api_source.py +44 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
- astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
- astrbot/core/star/context.py +2 -2
- astrbot/core/star/register/star_handler.py +22 -5
- astrbot/core/star/star_handler.py +85 -4
- astrbot/core/updator.py +3 -3
- astrbot/core/utils/io.py +1 -1
- astrbot/core/utils/session_waiter.py +17 -10
- astrbot/core/utils/shared_preferences.py +32 -0
- astrbot/core/utils/t2i/__init__.py +2 -2
- astrbot/core/utils/t2i/local_strategy.py +25 -31
- astrbot/core/utils/tencent_record_helper.py +2 -2
- astrbot/core/utils/version_comparator.py +6 -3
- astrbot/core/utils/webhook_utils.py +66 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +311 -76
- astrbot/dashboard/routes/config.py +14 -5
- astrbot/dashboard/routes/knowledge_base.py +254 -79
- astrbot/dashboard/routes/log.py +13 -8
- astrbot/dashboard/routes/platform.py +100 -0
- astrbot/dashboard/routes/plugin.py +108 -51
- astrbot/dashboard/routes/route.py +2 -0
- astrbot/dashboard/server.py +9 -4
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/METADATA +50 -37
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/RECORD +111 -108
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -20,7 +20,7 @@ class FontManager:
|
|
|
20
20
|
_font_cache = {}
|
|
21
21
|
|
|
22
22
|
@classmethod
|
|
23
|
-
def get_font(cls, size: int) -> ImageFont.FreeTypeFont:
|
|
23
|
+
def get_font(cls, size: int) -> ImageFont.FreeTypeFont|ImageFont.ImageFont:
|
|
24
24
|
"""获取指定大小的字体,优先从缓存获取"""
|
|
25
25
|
if size in cls._font_cache:
|
|
26
26
|
return cls._font_cache[size]
|
|
@@ -66,23 +66,17 @@ class TextMeasurer:
|
|
|
66
66
|
"""测量文本尺寸的工具类"""
|
|
67
67
|
|
|
68
68
|
@staticmethod
|
|
69
|
-
def get_text_size(text: str, font: ImageFont.FreeTypeFont) ->
|
|
69
|
+
def get_text_size(text: str, font: ImageFont.FreeTypeFont|ImageFont.ImageFont) -> tuple[int, int]:
|
|
70
70
|
"""获取文本的尺寸"""
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if hasattr(font, "getbbox")
|
|
76
|
-
else font.getsize(text)
|
|
77
|
-
)
|
|
78
|
-
except Exception:
|
|
79
|
-
# 兼容旧版本
|
|
80
|
-
return font.getsize(text)
|
|
71
|
+
|
|
72
|
+
# 依赖库Pillow>=11.2.1,不再需要考虑<9.0.0
|
|
73
|
+
left, top, right, bottom = font.getbbox("Hello world")
|
|
74
|
+
return int(right - left), int(bottom - top)
|
|
81
75
|
|
|
82
76
|
@staticmethod
|
|
83
77
|
def split_text_to_fit_width(
|
|
84
|
-
text: str, font: ImageFont.FreeTypeFont, max_width: int
|
|
85
|
-
) ->
|
|
78
|
+
text: str, font: ImageFont.FreeTypeFont|ImageFont.ImageFont, max_width: int
|
|
79
|
+
) -> list[str]:
|
|
86
80
|
"""将文本拆分为多行,确保每行不超过指定宽度"""
|
|
87
81
|
lines = []
|
|
88
82
|
if not text:
|
|
@@ -126,7 +120,7 @@ class MarkdownElement(ABC):
|
|
|
126
120
|
def render(
|
|
127
121
|
self,
|
|
128
122
|
image: Image.Image,
|
|
129
|
-
draw: ImageDraw.
|
|
123
|
+
draw: ImageDraw.ImageDraw,
|
|
130
124
|
x: int,
|
|
131
125
|
y: int,
|
|
132
126
|
image_width: int,
|
|
@@ -152,7 +146,7 @@ class TextElement(MarkdownElement):
|
|
|
152
146
|
def render(
|
|
153
147
|
self,
|
|
154
148
|
image: Image.Image,
|
|
155
|
-
draw: ImageDraw.
|
|
149
|
+
draw: ImageDraw.ImageDraw,
|
|
156
150
|
x: int,
|
|
157
151
|
y: int,
|
|
158
152
|
image_width: int,
|
|
@@ -186,7 +180,7 @@ class BoldTextElement(MarkdownElement):
|
|
|
186
180
|
def render(
|
|
187
181
|
self,
|
|
188
182
|
image: Image.Image,
|
|
189
|
-
draw: ImageDraw.
|
|
183
|
+
draw: ImageDraw.ImageDraw,
|
|
190
184
|
x: int,
|
|
191
185
|
y: int,
|
|
192
186
|
image_width: int,
|
|
@@ -251,7 +245,7 @@ class ItalicTextElement(MarkdownElement):
|
|
|
251
245
|
def render(
|
|
252
246
|
self,
|
|
253
247
|
image: Image.Image,
|
|
254
|
-
draw: ImageDraw.
|
|
248
|
+
draw: ImageDraw.ImageDraw,
|
|
255
249
|
x: int,
|
|
256
250
|
y: int,
|
|
257
251
|
image_width: int,
|
|
@@ -299,7 +293,7 @@ class ItalicTextElement(MarkdownElement):
|
|
|
299
293
|
# 倾斜变换,使用仿射变换实现斜体效果
|
|
300
294
|
# 变换矩阵: [1, 0.2, 0, 0, 1, 0]
|
|
301
295
|
italic_img = text_img.transform(
|
|
302
|
-
text_img.size, Image.AFFINE, (1, 0.2, 0, 0, 1, 0), Image.BICUBIC
|
|
296
|
+
text_img.size, Image.Transform.AFFINE, (1, 0.2, 0, 0, 1, 0), Image.Resampling.BICUBIC
|
|
303
297
|
)
|
|
304
298
|
|
|
305
299
|
# 粘贴到原图像
|
|
@@ -331,7 +325,7 @@ class UnderlineTextElement(MarkdownElement):
|
|
|
331
325
|
def render(
|
|
332
326
|
self,
|
|
333
327
|
image: Image.Image,
|
|
334
|
-
draw: ImageDraw.
|
|
328
|
+
draw: ImageDraw.ImageDraw,
|
|
335
329
|
x: int,
|
|
336
330
|
y: int,
|
|
337
331
|
image_width: int,
|
|
@@ -371,7 +365,7 @@ class StrikethroughTextElement(MarkdownElement):
|
|
|
371
365
|
def render(
|
|
372
366
|
self,
|
|
373
367
|
image: Image.Image,
|
|
374
|
-
draw: ImageDraw.
|
|
368
|
+
draw: ImageDraw.ImageDraw,
|
|
375
369
|
x: int,
|
|
376
370
|
y: int,
|
|
377
371
|
image_width: int,
|
|
@@ -422,7 +416,7 @@ class HeaderElement(MarkdownElement):
|
|
|
422
416
|
def render(
|
|
423
417
|
self,
|
|
424
418
|
image: Image.Image,
|
|
425
|
-
draw: ImageDraw.
|
|
419
|
+
draw: ImageDraw.ImageDraw,
|
|
426
420
|
x: int,
|
|
427
421
|
y: int,
|
|
428
422
|
image_width: int,
|
|
@@ -458,7 +452,7 @@ class QuoteElement(MarkdownElement):
|
|
|
458
452
|
def render(
|
|
459
453
|
self,
|
|
460
454
|
image: Image.Image,
|
|
461
|
-
draw: ImageDraw.
|
|
455
|
+
draw: ImageDraw.ImageDraw,
|
|
462
456
|
x: int,
|
|
463
457
|
y: int,
|
|
464
458
|
image_width: int,
|
|
@@ -502,7 +496,7 @@ class ListItemElement(MarkdownElement):
|
|
|
502
496
|
def render(
|
|
503
497
|
self,
|
|
504
498
|
image: Image.Image,
|
|
505
|
-
draw: ImageDraw.
|
|
499
|
+
draw: ImageDraw.ImageDraw,
|
|
506
500
|
x: int,
|
|
507
501
|
y: int,
|
|
508
502
|
image_width: int,
|
|
@@ -532,7 +526,7 @@ class ListItemElement(MarkdownElement):
|
|
|
532
526
|
class CodeBlockElement(MarkdownElement):
|
|
533
527
|
"""代码块元素"""
|
|
534
528
|
|
|
535
|
-
def __init__(self, content:
|
|
529
|
+
def __init__(self, content: list[str]):
|
|
536
530
|
super().__init__("\n".join(content))
|
|
537
531
|
|
|
538
532
|
def calculate_height(self, image_width: int, font_size: int) -> int:
|
|
@@ -552,7 +546,7 @@ class CodeBlockElement(MarkdownElement):
|
|
|
552
546
|
def render(
|
|
553
547
|
self,
|
|
554
548
|
image: Image.Image,
|
|
555
|
-
draw: ImageDraw.
|
|
549
|
+
draw: ImageDraw.ImageDraw,
|
|
556
550
|
x: int,
|
|
557
551
|
y: int,
|
|
558
552
|
image_width: int,
|
|
@@ -595,7 +589,7 @@ class InlineCodeElement(MarkdownElement):
|
|
|
595
589
|
def render(
|
|
596
590
|
self,
|
|
597
591
|
image: Image.Image,
|
|
598
|
-
draw: ImageDraw.
|
|
592
|
+
draw: ImageDraw.ImageDraw,
|
|
599
593
|
x: int,
|
|
600
594
|
y: int,
|
|
601
595
|
image_width: int,
|
|
@@ -667,7 +661,7 @@ class ImageElement(MarkdownElement):
|
|
|
667
661
|
def render(
|
|
668
662
|
self,
|
|
669
663
|
image: Image.Image,
|
|
670
|
-
draw: ImageDraw.
|
|
664
|
+
draw: ImageDraw.ImageDraw,
|
|
671
665
|
x: int,
|
|
672
666
|
y: int,
|
|
673
667
|
image_width: int,
|
|
@@ -686,7 +680,7 @@ class ImageElement(MarkdownElement):
|
|
|
686
680
|
if pasted_image.width > max_width:
|
|
687
681
|
ratio = max_width / pasted_image.width
|
|
688
682
|
new_size = (int(max_width), int(pasted_image.height * ratio))
|
|
689
|
-
pasted_image = pasted_image.resize(new_size, Image.LANCZOS)
|
|
683
|
+
pasted_image = pasted_image.resize(new_size, Image.Resampling.LANCZOS)
|
|
690
684
|
|
|
691
685
|
# 计算居中位置
|
|
692
686
|
paste_x = x + (image_width - pasted_image.width) // 2 - 10
|
|
@@ -705,7 +699,7 @@ class MarkdownParser:
|
|
|
705
699
|
"""Markdown解析器,将文本解析为元素"""
|
|
706
700
|
|
|
707
701
|
@staticmethod
|
|
708
|
-
async def parse(text: str) ->
|
|
702
|
+
async def parse(text: str) -> list[MarkdownElement]:
|
|
709
703
|
elements = []
|
|
710
704
|
lines = text.split("\n")
|
|
711
705
|
|
|
@@ -847,7 +841,7 @@ class MarkdownRenderer:
|
|
|
847
841
|
self,
|
|
848
842
|
font_size: int = 26,
|
|
849
843
|
width: int = 800,
|
|
850
|
-
bg_color:
|
|
844
|
+
bg_color: tuple[int, int, int] = (255, 255, 255),
|
|
851
845
|
):
|
|
852
846
|
self.font_size = font_size
|
|
853
847
|
self.width = width
|
|
@@ -36,7 +36,7 @@ async def wav_to_tencent_silk(wav_path: str, output_path: str) -> int:
|
|
|
36
36
|
import pilk
|
|
37
37
|
except (ImportError, ModuleNotFoundError) as _:
|
|
38
38
|
raise Exception(
|
|
39
|
-
"pilk
|
|
39
|
+
"pilk 模块未安装,请前往管理面板->平台日志->安装pip库 安装 pilk 这个库",
|
|
40
40
|
)
|
|
41
41
|
# with wave.open(wav_path, 'rb') as wav:
|
|
42
42
|
# wav_data = wav.readframes(wav.getnframes())
|
|
@@ -68,7 +68,7 @@ async def convert_to_pcm_wav(input_path: str, output_path: str) -> str:
|
|
|
68
68
|
from pyffmpeg import FFmpeg
|
|
69
69
|
|
|
70
70
|
ff = FFmpeg()
|
|
71
|
-
ff.convert(
|
|
71
|
+
ff.convert(input_file=input_path, output_file=output_path)
|
|
72
72
|
except Exception as e:
|
|
73
73
|
logger.debug(f"pyffmpeg 转换失败: {e}, 尝试使用 ffmpeg 命令行进行转换")
|
|
74
74
|
|
|
@@ -60,9 +60,12 @@ class VersionComparator:
|
|
|
60
60
|
return -1
|
|
61
61
|
if isinstance(p1, str) and isinstance(p2, int):
|
|
62
62
|
return 1
|
|
63
|
-
if
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
if isinstance(p1, int) and isinstance(p2, int):
|
|
64
|
+
if p1 > p2:
|
|
65
|
+
return 1
|
|
66
|
+
if p1 < p2:
|
|
67
|
+
return -1
|
|
68
|
+
if isinstance(p1, str) and isinstance(p2, str):
|
|
66
69
|
if p1 > p2:
|
|
67
70
|
return 1
|
|
68
71
|
if p1 < p2:
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
3
|
+
from astrbot.core import astrbot_config, logger
|
|
4
|
+
from astrbot.core.config.default import WEBHOOK_SUPPORTED_PLATFORMS
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _get_callback_api_base() -> str:
|
|
8
|
+
try:
|
|
9
|
+
return astrbot_config.get("callback_api_base", "").rstrip("/")
|
|
10
|
+
except Exception as e:
|
|
11
|
+
logger.error(f"获取 callback_api_base 失败: {e!s}")
|
|
12
|
+
return ""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_dashboard_port() -> int:
|
|
16
|
+
try:
|
|
17
|
+
return astrbot_config.get("dashboard", {}).get("port", 6185)
|
|
18
|
+
except Exception as e:
|
|
19
|
+
logger.error(f"获取 dashboard 端口失败: {e!s}")
|
|
20
|
+
return 6185
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def log_webhook_info(platform_name: str, webhook_uuid: str):
|
|
24
|
+
"""打印美观的 webhook 信息日志
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
platform_name: 平台名称
|
|
28
|
+
webhook_uuid: webhook 的 UUID
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
callback_base = _get_callback_api_base()
|
|
32
|
+
|
|
33
|
+
if not callback_base:
|
|
34
|
+
callback_base = "http(s)://<your-astrbot-domain>"
|
|
35
|
+
|
|
36
|
+
if not callback_base.startswith("http"):
|
|
37
|
+
callback_base = f"http(s)://{callback_base}"
|
|
38
|
+
|
|
39
|
+
callback_base = callback_base.rstrip("/")
|
|
40
|
+
webhook_url = f"{callback_base}/api/platform/webhook/{webhook_uuid}"
|
|
41
|
+
|
|
42
|
+
display_log = (
|
|
43
|
+
"\n====================\n"
|
|
44
|
+
f"🔗 机器人平台 {platform_name} 已启用统一 Webhook 模式\n"
|
|
45
|
+
f"📍 Webhook 回调地址: \n"
|
|
46
|
+
f" ➜ http://<your-ip>:{_get_dashboard_port()}/api/platform/webhook/{webhook_uuid}\n"
|
|
47
|
+
f" ➜ {webhook_url}\n"
|
|
48
|
+
"====================\n"
|
|
49
|
+
)
|
|
50
|
+
logger.info(display_log)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def ensure_platform_webhook_config(platform_cfg: dict) -> bool:
|
|
54
|
+
"""为支持统一 webhook 的平台自动生成 webhook_uuid
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
platform_cfg (dict): 平台配置字典
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
bool: 如果生成了 webhook_uuid 则返回 True,否则返回 False
|
|
61
|
+
"""
|
|
62
|
+
pt = platform_cfg.get("type", "")
|
|
63
|
+
if pt in WEBHOOK_SUPPORTED_PLATFORMS and not platform_cfg.get("webhook_uuid"):
|
|
64
|
+
platform_cfg["webhook_uuid"] = uuid.uuid4().hex[:16]
|
|
65
|
+
return True
|
|
66
|
+
return False
|
|
@@ -6,6 +6,7 @@ from .file import FileRoute
|
|
|
6
6
|
from .knowledge_base import KnowledgeBaseRoute
|
|
7
7
|
from .log import LogRoute
|
|
8
8
|
from .persona import PersonaRoute
|
|
9
|
+
from .platform import PlatformRoute
|
|
9
10
|
from .plugin import PluginRoute
|
|
10
11
|
from .session_management import SessionManagementRoute
|
|
11
12
|
from .stat import StatRoute
|
|
@@ -22,6 +23,7 @@ __all__ = [
|
|
|
22
23
|
"KnowledgeBaseRoute",
|
|
23
24
|
"LogRoute",
|
|
24
25
|
"PersonaRoute",
|
|
26
|
+
"PlatformRoute",
|
|
25
27
|
"PluginRoute",
|
|
26
28
|
"SessionManagementRoute",
|
|
27
29
|
"StatRoute",
|