AstrBot 4.0.0b4__py3-none-any.whl → 4.1.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/api/event/filter/__init__.py +2 -0
- astrbot/cli/utils/basic.py +12 -3
- astrbot/core/astrbot_config_mgr.py +16 -9
- astrbot/core/config/default.py +82 -4
- astrbot/core/initial_loader.py +4 -1
- astrbot/core/message/components.py +59 -50
- astrbot/core/pipeline/process_stage/method/llm_request.py +6 -2
- astrbot/core/pipeline/result_decorate/stage.py +5 -1
- astrbot/core/platform/manager.py +25 -3
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +26 -14
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +11 -4
- astrbot/core/platform/sources/satori/satori_adapter.py +482 -0
- astrbot/core/platform/sources/satori/satori_event.py +221 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +0 -1
- astrbot/core/provider/entities.py +17 -15
- astrbot/core/provider/sources/gemini_source.py +57 -18
- astrbot/core/provider/sources/openai_source.py +12 -5
- astrbot/core/provider/sources/vllm_rerank_source.py +6 -0
- astrbot/core/star/__init__.py +7 -5
- astrbot/core/star/filter/command.py +9 -3
- astrbot/core/star/filter/platform_adapter_type.py +3 -0
- astrbot/core/star/register/__init__.py +2 -0
- astrbot/core/star/register/star_handler.py +18 -4
- astrbot/core/star/star_handler.py +9 -1
- astrbot/core/star/star_tools.py +116 -21
- astrbot/core/updator.py +7 -5
- astrbot/core/utils/io.py +1 -1
- astrbot/core/utils/t2i/network_strategy.py +11 -18
- astrbot/core/utils/t2i/renderer.py +8 -2
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +112 -0
- astrbot/core/zip_updator.py +26 -4
- astrbot/dashboard/routes/chat.py +6 -1
- astrbot/dashboard/routes/config.py +24 -49
- astrbot/dashboard/routes/route.py +19 -2
- astrbot/dashboard/routes/t2i.py +230 -0
- astrbot/dashboard/routes/update.py +3 -5
- astrbot/dashboard/server.py +13 -4
- {astrbot-4.0.0b4.dist-info → astrbot-4.1.0.dist-info}/METADATA +40 -53
- {astrbot-4.0.0b4.dist-info → astrbot-4.1.0.dist-info}/RECORD +43 -38
- {astrbot-4.0.0b4.dist-info → astrbot-4.1.0.dist-info}/WHEEL +0 -0
- {astrbot-4.0.0b4.dist-info → astrbot-4.1.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.0.0b4.dist-info → astrbot-4.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -37,7 +37,7 @@ from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
|
37
37
|
from astrbot.core.utils.io import download_file, download_image_by_url, file_to_base64
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
class ComponentType(Enum):
|
|
40
|
+
class ComponentType(str, Enum):
|
|
41
41
|
Plain = "Plain" # 纯文本消息
|
|
42
42
|
Face = "Face" # QQ表情
|
|
43
43
|
Record = "Record" # 语音
|
|
@@ -108,7 +108,7 @@ class BaseMessageComponent(BaseModel):
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
class Plain(BaseMessageComponent):
|
|
111
|
-
type
|
|
111
|
+
type = ComponentType.Plain
|
|
112
112
|
text: str
|
|
113
113
|
convert: T.Optional[bool] = True # 若为 False 则直接发送未转换 CQ 码的消息
|
|
114
114
|
|
|
@@ -128,8 +128,9 @@ class Plain(BaseMessageComponent):
|
|
|
128
128
|
async def to_dict(self):
|
|
129
129
|
return {"type": "text", "data": {"text": self.text}}
|
|
130
130
|
|
|
131
|
+
|
|
131
132
|
class Face(BaseMessageComponent):
|
|
132
|
-
type
|
|
133
|
+
type = ComponentType.Face
|
|
133
134
|
id: int
|
|
134
135
|
|
|
135
136
|
def __init__(self, **_):
|
|
@@ -137,7 +138,7 @@ class Face(BaseMessageComponent):
|
|
|
137
138
|
|
|
138
139
|
|
|
139
140
|
class Record(BaseMessageComponent):
|
|
140
|
-
type
|
|
141
|
+
type = ComponentType.Record
|
|
141
142
|
file: T.Optional[str] = ""
|
|
142
143
|
magic: T.Optional[bool] = False
|
|
143
144
|
url: T.Optional[str] = ""
|
|
@@ -164,19 +165,24 @@ class Record(BaseMessageComponent):
|
|
|
164
165
|
return Record(file=url, **_)
|
|
165
166
|
raise Exception("not a valid url")
|
|
166
167
|
|
|
168
|
+
@staticmethod
|
|
169
|
+
def fromBase64(bs64_data: str, **_):
|
|
170
|
+
return Record(file=f"base64://{bs64_data}", **_)
|
|
171
|
+
|
|
167
172
|
async def convert_to_file_path(self) -> str:
|
|
168
173
|
"""将这个语音统一转换为本地文件路径。这个方法避免了手动判断语音数据类型,直接返回语音数据的本地路径(如果是网络 URL, 则会自动进行下载)。
|
|
169
174
|
|
|
170
175
|
Returns:
|
|
171
176
|
str: 语音的本地路径,以绝对路径表示。
|
|
172
177
|
"""
|
|
173
|
-
if
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
if not self.file:
|
|
179
|
+
raise Exception(f"not a valid file: {self.file}")
|
|
180
|
+
if self.file.startswith("file:///"):
|
|
181
|
+
return self.file[8:]
|
|
182
|
+
elif self.file.startswith("http"):
|
|
177
183
|
file_path = await download_image_by_url(self.file)
|
|
178
184
|
return os.path.abspath(file_path)
|
|
179
|
-
elif self.file
|
|
185
|
+
elif self.file.startswith("base64://"):
|
|
180
186
|
bs64_data = self.file.removeprefix("base64://")
|
|
181
187
|
image_bytes = base64.b64decode(bs64_data)
|
|
182
188
|
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
@@ -185,8 +191,7 @@ class Record(BaseMessageComponent):
|
|
|
185
191
|
f.write(image_bytes)
|
|
186
192
|
return os.path.abspath(file_path)
|
|
187
193
|
elif os.path.exists(self.file):
|
|
188
|
-
|
|
189
|
-
return os.path.abspath(file_path)
|
|
194
|
+
return os.path.abspath(self.file)
|
|
190
195
|
else:
|
|
191
196
|
raise Exception(f"not a valid file: {self.file}")
|
|
192
197
|
|
|
@@ -197,12 +202,14 @@ class Record(BaseMessageComponent):
|
|
|
197
202
|
str: 语音的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
|
|
198
203
|
"""
|
|
199
204
|
# convert to base64
|
|
200
|
-
if
|
|
205
|
+
if not self.file:
|
|
206
|
+
raise Exception(f"not a valid file: {self.file}")
|
|
207
|
+
if self.file.startswith("file:///"):
|
|
201
208
|
bs64_data = file_to_base64(self.file[8:])
|
|
202
|
-
elif self.file
|
|
209
|
+
elif self.file.startswith("http"):
|
|
203
210
|
file_path = await download_image_by_url(self.file)
|
|
204
211
|
bs64_data = file_to_base64(file_path)
|
|
205
|
-
elif self.file
|
|
212
|
+
elif self.file.startswith("base64://"):
|
|
206
213
|
bs64_data = self.file
|
|
207
214
|
elif os.path.exists(self.file):
|
|
208
215
|
bs64_data = file_to_base64(self.file)
|
|
@@ -236,7 +243,7 @@ class Record(BaseMessageComponent):
|
|
|
236
243
|
|
|
237
244
|
|
|
238
245
|
class Video(BaseMessageComponent):
|
|
239
|
-
type
|
|
246
|
+
type = ComponentType.Video
|
|
240
247
|
file: str
|
|
241
248
|
cover: T.Optional[str] = ""
|
|
242
249
|
c: T.Optional[int] = 2
|
|
@@ -322,7 +329,7 @@ class Video(BaseMessageComponent):
|
|
|
322
329
|
|
|
323
330
|
|
|
324
331
|
class At(BaseMessageComponent):
|
|
325
|
-
type
|
|
332
|
+
type = ComponentType.At
|
|
326
333
|
qq: T.Union[int, str] # 此处str为all时代表所有人
|
|
327
334
|
name: T.Optional[str] = ""
|
|
328
335
|
|
|
@@ -344,28 +351,28 @@ class AtAll(At):
|
|
|
344
351
|
|
|
345
352
|
|
|
346
353
|
class RPS(BaseMessageComponent): # TODO
|
|
347
|
-
type
|
|
354
|
+
type = ComponentType.RPS
|
|
348
355
|
|
|
349
356
|
def __init__(self, **_):
|
|
350
357
|
super().__init__(**_)
|
|
351
358
|
|
|
352
359
|
|
|
353
360
|
class Dice(BaseMessageComponent): # TODO
|
|
354
|
-
type
|
|
361
|
+
type = ComponentType.Dice
|
|
355
362
|
|
|
356
363
|
def __init__(self, **_):
|
|
357
364
|
super().__init__(**_)
|
|
358
365
|
|
|
359
366
|
|
|
360
367
|
class Shake(BaseMessageComponent): # TODO
|
|
361
|
-
type
|
|
368
|
+
type = ComponentType.Shake
|
|
362
369
|
|
|
363
370
|
def __init__(self, **_):
|
|
364
371
|
super().__init__(**_)
|
|
365
372
|
|
|
366
373
|
|
|
367
374
|
class Anonymous(BaseMessageComponent): # TODO
|
|
368
|
-
type
|
|
375
|
+
type = ComponentType.Anonymous
|
|
369
376
|
ignore: T.Optional[bool] = False
|
|
370
377
|
|
|
371
378
|
def __init__(self, **_):
|
|
@@ -373,7 +380,7 @@ class Anonymous(BaseMessageComponent): # TODO
|
|
|
373
380
|
|
|
374
381
|
|
|
375
382
|
class Share(BaseMessageComponent):
|
|
376
|
-
type
|
|
383
|
+
type = ComponentType.Share
|
|
377
384
|
url: str
|
|
378
385
|
title: str
|
|
379
386
|
content: T.Optional[str] = ""
|
|
@@ -384,7 +391,7 @@ class Share(BaseMessageComponent):
|
|
|
384
391
|
|
|
385
392
|
|
|
386
393
|
class Contact(BaseMessageComponent): # TODO
|
|
387
|
-
type
|
|
394
|
+
type = ComponentType.Contact
|
|
388
395
|
_type: str # type 字段冲突
|
|
389
396
|
id: T.Optional[int] = 0
|
|
390
397
|
|
|
@@ -393,7 +400,7 @@ class Contact(BaseMessageComponent): # TODO
|
|
|
393
400
|
|
|
394
401
|
|
|
395
402
|
class Location(BaseMessageComponent): # TODO
|
|
396
|
-
type
|
|
403
|
+
type = ComponentType.Location
|
|
397
404
|
lat: float
|
|
398
405
|
lon: float
|
|
399
406
|
title: T.Optional[str] = ""
|
|
@@ -404,7 +411,7 @@ class Location(BaseMessageComponent): # TODO
|
|
|
404
411
|
|
|
405
412
|
|
|
406
413
|
class Music(BaseMessageComponent):
|
|
407
|
-
type
|
|
414
|
+
type = ComponentType.Music
|
|
408
415
|
_type: str
|
|
409
416
|
id: T.Optional[int] = 0
|
|
410
417
|
url: T.Optional[str] = ""
|
|
@@ -421,7 +428,7 @@ class Music(BaseMessageComponent):
|
|
|
421
428
|
|
|
422
429
|
|
|
423
430
|
class Image(BaseMessageComponent):
|
|
424
|
-
type
|
|
431
|
+
type = ComponentType.Image
|
|
425
432
|
file: T.Optional[str] = ""
|
|
426
433
|
_type: T.Optional[str] = ""
|
|
427
434
|
subType: T.Optional[int] = 0
|
|
@@ -464,14 +471,15 @@ class Image(BaseMessageComponent):
|
|
|
464
471
|
Returns:
|
|
465
472
|
str: 图片的本地路径,以绝对路径表示。
|
|
466
473
|
"""
|
|
467
|
-
url = self.url
|
|
468
|
-
if
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
474
|
+
url = self.url or self.file
|
|
475
|
+
if not url:
|
|
476
|
+
raise ValueError("No valid file or URL provided")
|
|
477
|
+
if url.startswith("file:///"):
|
|
478
|
+
return url[8:]
|
|
479
|
+
elif url.startswith("http"):
|
|
472
480
|
image_file_path = await download_image_by_url(url)
|
|
473
481
|
return os.path.abspath(image_file_path)
|
|
474
|
-
elif url
|
|
482
|
+
elif url.startswith("base64://"):
|
|
475
483
|
bs64_data = url.removeprefix("base64://")
|
|
476
484
|
image_bytes = base64.b64decode(bs64_data)
|
|
477
485
|
temp_dir = os.path.join(get_astrbot_data_path(), "temp")
|
|
@@ -480,8 +488,7 @@ class Image(BaseMessageComponent):
|
|
|
480
488
|
f.write(image_bytes)
|
|
481
489
|
return os.path.abspath(image_file_path)
|
|
482
490
|
elif os.path.exists(url):
|
|
483
|
-
|
|
484
|
-
return os.path.abspath(image_file_path)
|
|
491
|
+
return os.path.abspath(url)
|
|
485
492
|
else:
|
|
486
493
|
raise Exception(f"not a valid file: {url}")
|
|
487
494
|
|
|
@@ -492,13 +499,15 @@ class Image(BaseMessageComponent):
|
|
|
492
499
|
str: 图片的 base64 编码,不以 base64:// 或者 data:image/jpeg;base64, 开头。
|
|
493
500
|
"""
|
|
494
501
|
# convert to base64
|
|
495
|
-
url = self.url
|
|
496
|
-
if
|
|
502
|
+
url = self.url or self.file
|
|
503
|
+
if not url:
|
|
504
|
+
raise ValueError("No valid file or URL provided")
|
|
505
|
+
if url.startswith("file:///"):
|
|
497
506
|
bs64_data = file_to_base64(url[8:])
|
|
498
|
-
elif url
|
|
507
|
+
elif url.startswith("http"):
|
|
499
508
|
image_file_path = await download_image_by_url(url)
|
|
500
509
|
bs64_data = file_to_base64(image_file_path)
|
|
501
|
-
elif url
|
|
510
|
+
elif url.startswith("base64://"):
|
|
502
511
|
bs64_data = url
|
|
503
512
|
elif os.path.exists(url):
|
|
504
513
|
bs64_data = file_to_base64(url)
|
|
@@ -532,7 +541,7 @@ class Image(BaseMessageComponent):
|
|
|
532
541
|
|
|
533
542
|
|
|
534
543
|
class Reply(BaseMessageComponent):
|
|
535
|
-
type
|
|
544
|
+
type = ComponentType.Reply
|
|
536
545
|
id: T.Union[str, int]
|
|
537
546
|
"""所引用的消息 ID"""
|
|
538
547
|
chain: T.Optional[T.List["BaseMessageComponent"]] = []
|
|
@@ -558,7 +567,7 @@ class Reply(BaseMessageComponent):
|
|
|
558
567
|
|
|
559
568
|
|
|
560
569
|
class RedBag(BaseMessageComponent):
|
|
561
|
-
type
|
|
570
|
+
type = ComponentType.RedBag
|
|
562
571
|
title: str
|
|
563
572
|
|
|
564
573
|
def __init__(self, **_):
|
|
@@ -566,7 +575,7 @@ class RedBag(BaseMessageComponent):
|
|
|
566
575
|
|
|
567
576
|
|
|
568
577
|
class Poke(BaseMessageComponent):
|
|
569
|
-
type: str =
|
|
578
|
+
type: str = ComponentType.Poke
|
|
570
579
|
id: T.Optional[int] = 0
|
|
571
580
|
qq: T.Optional[int] = 0
|
|
572
581
|
|
|
@@ -576,7 +585,7 @@ class Poke(BaseMessageComponent):
|
|
|
576
585
|
|
|
577
586
|
|
|
578
587
|
class Forward(BaseMessageComponent):
|
|
579
|
-
type
|
|
588
|
+
type = ComponentType.Forward
|
|
580
589
|
id: str
|
|
581
590
|
|
|
582
591
|
def __init__(self, **_):
|
|
@@ -586,7 +595,7 @@ class Forward(BaseMessageComponent):
|
|
|
586
595
|
class Node(BaseMessageComponent):
|
|
587
596
|
"""群合并转发消息"""
|
|
588
597
|
|
|
589
|
-
type
|
|
598
|
+
type = ComponentType.Node
|
|
590
599
|
id: T.Optional[int] = 0 # 忽略
|
|
591
600
|
name: T.Optional[str] = "" # qq昵称
|
|
592
601
|
uin: T.Optional[str] = "0" # qq号
|
|
@@ -638,7 +647,7 @@ class Node(BaseMessageComponent):
|
|
|
638
647
|
|
|
639
648
|
|
|
640
649
|
class Nodes(BaseMessageComponent):
|
|
641
|
-
type
|
|
650
|
+
type = ComponentType.Nodes
|
|
642
651
|
nodes: T.List[Node]
|
|
643
652
|
|
|
644
653
|
def __init__(self, nodes: T.List[Node], **_):
|
|
@@ -664,7 +673,7 @@ class Nodes(BaseMessageComponent):
|
|
|
664
673
|
|
|
665
674
|
|
|
666
675
|
class Xml(BaseMessageComponent):
|
|
667
|
-
type
|
|
676
|
+
type = ComponentType.Xml
|
|
668
677
|
data: str
|
|
669
678
|
resid: T.Optional[int] = 0
|
|
670
679
|
|
|
@@ -673,7 +682,7 @@ class Xml(BaseMessageComponent):
|
|
|
673
682
|
|
|
674
683
|
|
|
675
684
|
class Json(BaseMessageComponent):
|
|
676
|
-
type
|
|
685
|
+
type = ComponentType.Json
|
|
677
686
|
data: T.Union[str, dict]
|
|
678
687
|
resid: T.Optional[int] = 0
|
|
679
688
|
|
|
@@ -684,7 +693,7 @@ class Json(BaseMessageComponent):
|
|
|
684
693
|
|
|
685
694
|
|
|
686
695
|
class CardImage(BaseMessageComponent):
|
|
687
|
-
type
|
|
696
|
+
type = ComponentType.CardImage
|
|
688
697
|
file: str
|
|
689
698
|
cache: T.Optional[bool] = True
|
|
690
699
|
minwidth: T.Optional[int] = 400
|
|
@@ -703,7 +712,7 @@ class CardImage(BaseMessageComponent):
|
|
|
703
712
|
|
|
704
713
|
|
|
705
714
|
class TTS(BaseMessageComponent):
|
|
706
|
-
type
|
|
715
|
+
type = ComponentType.TTS
|
|
707
716
|
text: str
|
|
708
717
|
|
|
709
718
|
def __init__(self, **_):
|
|
@@ -711,7 +720,7 @@ class TTS(BaseMessageComponent):
|
|
|
711
720
|
|
|
712
721
|
|
|
713
722
|
class Unknown(BaseMessageComponent):
|
|
714
|
-
type
|
|
723
|
+
type = ComponentType.Unknown
|
|
715
724
|
text: str
|
|
716
725
|
|
|
717
726
|
def toString(self):
|
|
@@ -723,7 +732,7 @@ class File(BaseMessageComponent):
|
|
|
723
732
|
文件消息段
|
|
724
733
|
"""
|
|
725
734
|
|
|
726
|
-
type
|
|
735
|
+
type = ComponentType.File
|
|
727
736
|
name: T.Optional[str] = "" # 名字
|
|
728
737
|
file_: T.Optional[str] = "" # 本地路径
|
|
729
738
|
url: T.Optional[str] = "" # url
|
|
@@ -853,7 +862,7 @@ class File(BaseMessageComponent):
|
|
|
853
862
|
|
|
854
863
|
|
|
855
864
|
class WechatEmoji(BaseMessageComponent):
|
|
856
|
-
type
|
|
865
|
+
type = ComponentType.WechatEmoji
|
|
857
866
|
md5: T.Optional[str] = ""
|
|
858
867
|
md5_len: T.Optional[int] = 0
|
|
859
868
|
cdnurl: T.Optional[str] = ""
|
|
@@ -299,7 +299,9 @@ class LLMRequestSubStage(Stage):
|
|
|
299
299
|
self.max_context_length - 1,
|
|
300
300
|
)
|
|
301
301
|
self.streaming_response: bool = settings["streaming_response"]
|
|
302
|
-
self.max_step: int = settings.get("max_agent_step",
|
|
302
|
+
self.max_step: int = settings.get("max_agent_step", 30)
|
|
303
|
+
if isinstance(self.max_step, bool): # workaround: #2622
|
|
304
|
+
self.max_step = 30
|
|
303
305
|
self.show_tool_use: bool = settings.get("show_tool_use_status", True)
|
|
304
306
|
|
|
305
307
|
for bwp in self.bot_wake_prefixs:
|
|
@@ -434,7 +436,9 @@ class LLMRequestSubStage(Stage):
|
|
|
434
436
|
provider_cfg = provider.provider_config.get("modalities", ["tool_use"])
|
|
435
437
|
# 如果模型不支持工具使用,但请求中包含工具列表,则清空。
|
|
436
438
|
if "tool_use" not in provider_cfg:
|
|
437
|
-
logger.debug(
|
|
439
|
+
logger.debug(
|
|
440
|
+
f"用户设置提供商 {provider} 不支持工具使用,清空工具列表。"
|
|
441
|
+
)
|
|
438
442
|
req.func_tool = None
|
|
439
443
|
# 插件可用性设置
|
|
440
444
|
if event.plugins_name is not None and req.func_tool:
|
|
@@ -36,6 +36,7 @@ class ResultDecorateStage(Stage):
|
|
|
36
36
|
self.t2i_word_threshold = 150
|
|
37
37
|
self.t2i_strategy = ctx.astrbot_config["t2i_strategy"]
|
|
38
38
|
self.t2i_use_network = self.t2i_strategy == "remote"
|
|
39
|
+
self.t2i_active_template = ctx.astrbot_config["t2i_active_template"]
|
|
39
40
|
|
|
40
41
|
self.forward_threshold = ctx.astrbot_config["platform_settings"][
|
|
41
42
|
"forward_threshold"
|
|
@@ -247,7 +248,10 @@ class ResultDecorateStage(Stage):
|
|
|
247
248
|
render_start = time.time()
|
|
248
249
|
try:
|
|
249
250
|
url = await html_renderer.render_t2i(
|
|
250
|
-
plain_str,
|
|
251
|
+
plain_str,
|
|
252
|
+
return_url=True,
|
|
253
|
+
use_network=self.t2i_use_network,
|
|
254
|
+
template_name=self.t2i_active_template,
|
|
251
255
|
)
|
|
252
256
|
except BaseException:
|
|
253
257
|
logger.error("文本转图片失败,使用文本发送。")
|
astrbot/core/platform/manager.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import List
|
|
|
6
6
|
from asyncio import Queue
|
|
7
7
|
from .register import platform_cls_map
|
|
8
8
|
from astrbot.core import logger
|
|
9
|
+
from astrbot.core.star.star_handler import star_handlers_registry, star_map, EventType
|
|
9
10
|
from .sources.webchat.webchat_adapter import WebChatAdapter
|
|
10
11
|
|
|
11
12
|
|
|
@@ -66,15 +67,21 @@ class PlatformManager:
|
|
|
66
67
|
WeChatPadProAdapter, # noqa: F401
|
|
67
68
|
)
|
|
68
69
|
case "lark":
|
|
69
|
-
from .sources.lark.lark_adapter import
|
|
70
|
+
from .sources.lark.lark_adapter import (
|
|
71
|
+
LarkPlatformAdapter,
|
|
72
|
+
) # noqa: F401
|
|
70
73
|
case "dingtalk":
|
|
71
74
|
from .sources.dingtalk.dingtalk_adapter import (
|
|
72
75
|
DingtalkPlatformAdapter, # noqa: F401
|
|
73
76
|
)
|
|
74
77
|
case "telegram":
|
|
75
|
-
from .sources.telegram.tg_adapter import
|
|
78
|
+
from .sources.telegram.tg_adapter import (
|
|
79
|
+
TelegramPlatformAdapter,
|
|
80
|
+
) # noqa: F401
|
|
76
81
|
case "wecom":
|
|
77
|
-
from .sources.wecom.wecom_adapter import
|
|
82
|
+
from .sources.wecom.wecom_adapter import (
|
|
83
|
+
WecomPlatformAdapter,
|
|
84
|
+
) # noqa: F401
|
|
78
85
|
case "weixin_official_account":
|
|
79
86
|
from .sources.weixin_official_account.weixin_offacc_adapter import (
|
|
80
87
|
WeixinOfficialAccountPlatformAdapter, # noqa
|
|
@@ -85,6 +92,10 @@ class PlatformManager:
|
|
|
85
92
|
)
|
|
86
93
|
case "slack":
|
|
87
94
|
from .sources.slack.slack_adapter import SlackAdapter # noqa: F401
|
|
95
|
+
case "satori":
|
|
96
|
+
from .sources.satori.satori_adapter import (
|
|
97
|
+
SatoriPlatformAdapter,
|
|
98
|
+
) # noqa: F401
|
|
88
99
|
except (ImportError, ModuleNotFoundError) as e:
|
|
89
100
|
logger.error(
|
|
90
101
|
f"加载平台适配器 {platform_config['type']} 失败,原因:{e}。请检查依赖库是否安装。提示:可以在 管理面板->控制台->安装Pip库 中安装依赖库。"
|
|
@@ -113,6 +124,17 @@ class PlatformManager:
|
|
|
113
124
|
)
|
|
114
125
|
)
|
|
115
126
|
)
|
|
127
|
+
handlers = star_handlers_registry.get_handlers_by_event_type(
|
|
128
|
+
EventType.OnPlatformLoadedEvent
|
|
129
|
+
)
|
|
130
|
+
for handler in handlers:
|
|
131
|
+
try:
|
|
132
|
+
logger.info(
|
|
133
|
+
f"hook(on_platform_loaded) -> {star_map[handler.handler_module_path].name} - {handler.handler_name}"
|
|
134
|
+
)
|
|
135
|
+
await handler.handler()
|
|
136
|
+
except Exception:
|
|
137
|
+
logger.error(traceback.format_exc())
|
|
116
138
|
|
|
117
139
|
async def _task_wrapper(self, task: asyncio.Task):
|
|
118
140
|
try:
|
|
@@ -67,12 +67,19 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|
|
67
67
|
session_id: str,
|
|
68
68
|
messages: list[dict],
|
|
69
69
|
):
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
# session_id 必须是纯数字字符串
|
|
71
|
+
session_id = int(session_id) if session_id.isdigit() else None
|
|
72
|
+
|
|
73
|
+
if is_group and isinstance(session_id, int):
|
|
73
74
|
await bot.send_group_msg(group_id=session_id, message=messages)
|
|
74
|
-
|
|
75
|
+
elif not is_group and isinstance(session_id, int):
|
|
75
76
|
await bot.send_private_msg(user_id=session_id, message=messages)
|
|
77
|
+
elif isinstance(event, Event): # 最后兜底
|
|
78
|
+
await bot.send(event=event, message=messages)
|
|
79
|
+
else:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"无法发送消息:缺少有效的数字 session_id({session_id}) 或 event({event})"
|
|
82
|
+
)
|
|
76
83
|
|
|
77
84
|
@classmethod
|
|
78
85
|
async def send_message(
|
|
@@ -83,7 +90,15 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|
|
83
90
|
is_group: bool = False,
|
|
84
91
|
session_id: str = None,
|
|
85
92
|
):
|
|
86
|
-
"""
|
|
93
|
+
"""发送消息至 QQ 协议端(aiocqhttp)。
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
bot (CQHttp): aiocqhttp 机器人实例
|
|
97
|
+
message_chain (MessageChain): 要发送的消息链
|
|
98
|
+
event (Event | None, optional): aiocqhttp 事件对象.
|
|
99
|
+
is_group (bool, optional): 是否为群消息.
|
|
100
|
+
session_id (str | None, optional): 会话 ID(群号或 QQ 号
|
|
101
|
+
"""
|
|
87
102
|
|
|
88
103
|
# 转发消息、文件消息不能和普通消息混在一起发送
|
|
89
104
|
send_one_by_one = any(
|
|
@@ -122,18 +137,15 @@ class AiocqhttpMessageEvent(AstrMessageEvent):
|
|
|
122
137
|
|
|
123
138
|
async def send(self, message: MessageChain):
|
|
124
139
|
"""发送消息"""
|
|
125
|
-
event = self.message_obj
|
|
126
|
-
|
|
127
|
-
is_group =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
session_id = self.get_group_id()
|
|
131
|
-
else:
|
|
132
|
-
session_id = self.get_sender_id()
|
|
140
|
+
event = getattr(self.message_obj, "raw_message", None)
|
|
141
|
+
|
|
142
|
+
is_group = bool(self.get_group_id())
|
|
143
|
+
session_id = self.get_group_id() if is_group else self.get_sender_id()
|
|
144
|
+
|
|
133
145
|
await self.send_message(
|
|
134
146
|
bot=self.bot,
|
|
135
147
|
message_chain=message,
|
|
136
|
-
event=event,
|
|
148
|
+
event=event, # 不强制要求一定是 Event
|
|
137
149
|
is_group=is_group,
|
|
138
150
|
session_id=session_id,
|
|
139
151
|
)
|
|
@@ -308,13 +308,20 @@ class AiocqhttpAdapter(Platform):
|
|
|
308
308
|
continue
|
|
309
309
|
|
|
310
310
|
at_info = await self.bot.call_action(
|
|
311
|
-
action="
|
|
311
|
+
action="get_group_member_info",
|
|
312
|
+
group_id=event.group_id,
|
|
312
313
|
user_id=int(m["data"]["qq"]),
|
|
314
|
+
no_cache=False,
|
|
313
315
|
)
|
|
314
316
|
if at_info:
|
|
315
|
-
nickname = at_info.get("
|
|
316
|
-
|
|
317
|
-
|
|
317
|
+
nickname = at_info.get("card", "")
|
|
318
|
+
if nickname == "":
|
|
319
|
+
at_info = await self.bot.call_action(
|
|
320
|
+
action="get_stranger_info",
|
|
321
|
+
user_id=int(m["data"]["qq"]),
|
|
322
|
+
no_cache=False,
|
|
323
|
+
)
|
|
324
|
+
nickname = at_info.get("nick", "") or at_info.get("nickname", "")
|
|
318
325
|
is_at_self = str(m["data"]["qq"]) in {abm.self_id, "all"}
|
|
319
326
|
|
|
320
327
|
abm.message.append(
|