Skip to content

Commit b3d6eba

Browse files
committed
✨ support adapter HeyBox
1 parent c65a142 commit b3d6eba

File tree

6 files changed

+149
-13
lines changed

6 files changed

+149
-13
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from nonebot_plugin_alconna.uniseg.loader import BaseLoader
2+
from nonebot_plugin_alconna.uniseg.constraint import SupportAdapter
3+
4+
5+
class Loader(BaseLoader):
6+
def get_adapter(self) -> SupportAdapter:
7+
return SupportAdapter.heybox
8+
9+
def get_builder(self):
10+
from .builder import HeyboxMessageBuilder
11+
12+
return HeyboxMessageBuilder()
13+
14+
def get_exporter(self):
15+
from .exporter import HeyboxMessageExporter
16+
17+
return HeyboxMessageExporter()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from nonebot.adapters.heybox.message import MessageSegment
2+
3+
from nonebot_plugin_alconna.uniseg.segment import Text
4+
from nonebot_plugin_alconna.uniseg.constraint import SupportAdapter
5+
from nonebot_plugin_alconna.uniseg.builder import MessageBuilder, build
6+
7+
8+
class HeyboxMessageBuilder(MessageBuilder):
9+
@classmethod
10+
def get_adapter(cls) -> SupportAdapter:
11+
return SupportAdapter.heybox
12+
13+
@build("text")
14+
def text(self, seg: MessageSegment):
15+
return Text(seg.data["text"])
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from typing import TYPE_CHECKING, Union
2+
3+
from tarina import lang
4+
from nonebot.adapters import Bot, Event
5+
from nonebot.adapters.heybox.bot import Bot as HeyboxBot
6+
from nonebot.adapters.heybox.event import UserIMMessageEvent
7+
from nonebot.adapters.heybox.message import Message, MessageSegment
8+
9+
from nonebot_plugin_alconna.uniseg.constraint import SupportScope
10+
from nonebot_plugin_alconna.uniseg.segment import At, Text, Image, Reply
11+
from nonebot_plugin_alconna.uniseg.exporter import Target, SupportAdapter, MessageExporter, SerializeFailed, export
12+
13+
14+
class HeyboxMessageExporter(MessageExporter[Message]):
15+
@classmethod
16+
def get_adapter(cls) -> SupportAdapter:
17+
return SupportAdapter.heybox
18+
19+
def get_message_type(self):
20+
return Message
21+
22+
def get_target(self, event: Event, bot: Union[Bot, None] = None) -> Target:
23+
if isinstance(event, UserIMMessageEvent):
24+
return Target(
25+
event.channel_id,
26+
parent_id=event.room_id,
27+
channel=True,
28+
adapter=self.get_adapter(),
29+
self_id=bot.self_id if bot else None,
30+
scope=SupportScope.heybox,
31+
)
32+
raise NotImplementedError
33+
34+
def get_message_id(self, event: Event) -> str:
35+
if isinstance(event, UserIMMessageEvent):
36+
return str(event.im_seq)
37+
raise NotImplementedError
38+
39+
@export
40+
async def text(self, seg: Text, bot: Union[Bot, None]) -> "MessageSegment":
41+
return MessageSegment.text(seg.text)
42+
43+
@export
44+
async def at(self, seg: At, bot: Union[Bot, None]) -> "MessageSegment":
45+
if seg.flag == "user":
46+
return MessageSegment.mention(seg.target)
47+
raise SerializeFailed(
48+
lang.require("nbp-uniseg", "failed_segment").format(adapter="qq", seg=seg, target="mention")
49+
)
50+
51+
@export
52+
async def image(self, seg: Image, bot: Union[Bot, None]) -> "MessageSegment":
53+
if seg.url:
54+
return MessageSegment.image(url=seg.url, width=seg.width or 0, height=seg.height or 0)
55+
if seg.raw:
56+
return MessageSegment.local_image(
57+
seg.raw_bytes, width=seg.width or 0, height=seg.height or 0, filename=seg.name
58+
)
59+
raise SerializeFailed(lang.require("nbp-uniseg", "invalid_segment").format(type="image", seg=seg))
60+
61+
@export
62+
async def reply(self, seg: Reply, bot: Union[Bot, None]) -> "MessageSegment":
63+
return MessageSegment("$heybox:reply", {"message_id": seg.id}) # type: ignore
64+
65+
async def send_to(self, target: Union[Target, Event], bot: Bot, message: Message, **kwargs):
66+
assert isinstance(bot, HeyboxBot)
67+
68+
reply_id = None
69+
if message.has("$heybox:reply"):
70+
reply_id = message["$heybox:reply", 0].data["message_id"]
71+
message = message.exclude("$heybox:reply")
72+
73+
if isinstance(target, Event):
74+
if TYPE_CHECKING:
75+
assert isinstance(target, UserIMMessageEvent)
76+
return await bot.send(event=target, message=message, **kwargs, is_reply=reply_id is not None)
77+
return await bot.send_to_channel(
78+
target.parent_id,
79+
target.id,
80+
message,
81+
reply_id=reply_id,
82+
)

src/nonebot_plugin_alconna/uniseg/adapters/satori/builder.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ def image(self, seg: ImageSegment):
110110
if src.startswith("data:"):
111111
mime, b64 = src[5:].split(";", 1)
112112
return Image(raw=b64decode(b64[7:]), mimetype=mime)(*self.generate(seg.children))
113-
return Image(seg.data["src"])(*self.generate(seg.children))
113+
return Image(seg.data["src"], width=seg.data.get("width"), height=seg.data.get("height"))(
114+
*self.generate(seg.children)
115+
)
114116

115117
@build("audio")
116118
def audio(self, seg: AudioSegment):
@@ -122,7 +124,7 @@ def audio(self, seg: AudioSegment):
122124
if src.startswith("data:"):
123125
mime, b64 = src[5:].split(";", 1)
124126
return Audio(raw=b64decode(b64[7:]), mimetype=mime)(*self.generate(seg.children))
125-
return Audio(seg.data["src"])(*self.generate(seg.children))
127+
return Audio(seg.data["src"], duration=seg.data.get("duration"))(*self.generate(seg.children))
126128

127129
@build("video")
128130
def video(self, seg: VideoSegment):
@@ -134,7 +136,10 @@ def video(self, seg: VideoSegment):
134136
if src.startswith("data:"):
135137
mime, b64 = src[5:].split(";", 1)
136138
return Video(raw=b64decode(b64[7:]), mimetype=mime)(*self.generate(seg.children))
137-
return Video(seg.data["src"])(*self.generate(seg.children))
139+
thumb = None
140+
if poster := seg.data.get("poster"):
141+
thumb = Image(url=poster, width=seg.data.get("width"), height=seg.data.get("height"))
142+
return Video(seg.data["src"], thumbnail=thumb)(*self.generate(seg.children))
138143

139144
@build("file")
140145
def file(self, seg: FileSegment):

src/nonebot_plugin_alconna/uniseg/message.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ def image(
203203
path: str | Path | None = None,
204204
raw: bytes | BytesIO | None = None,
205205
mimetype: str | None = None,
206+
width: int | None = None,
207+
height: int | None = None,
206208
name: str = "image.png",
207209
) -> UniMessage[TS1 | Image]:
208210
"""创建图片消息
@@ -214,6 +216,8 @@ def image(
214216
raw: 图片原始数据
215217
mimetype: 图片 MIME 类型
216218
name: 图片名称
219+
width: 图片宽度
220+
height: 图片高度
217221
返回:
218222
构建的消息
219223
"""
@@ -227,6 +231,7 @@ def video(
227231
path: str | Path | None = None,
228232
raw: bytes | BytesIO | None = None,
229233
mimetype: str | None = None,
234+
thumbnail: Image | None = None,
230235
name: str = "video.mp4",
231236
) -> UniMessage[TS1 | Video]:
232237
"""创建视频消息
@@ -237,6 +242,7 @@ def video(
237242
path: 视频路径
238243
raw: 视频原始数据
239244
mimetype: 视频 MIME 类型
245+
thumbnail: 视频缩略图
240246
name: 视频名称
241247
返回:
242248
构建的消息
@@ -251,6 +257,7 @@ def voice(
251257
path: str | Path | None = None,
252258
raw: bytes | BytesIO | None = None,
253259
mimetype: str | None = None,
260+
duration: float | None = None,
254261
name: str = "voice.wav",
255262
) -> UniMessage[TS1 | Voice]:
256263
"""创建语音消息
@@ -261,6 +268,7 @@ def voice(
261268
path: 语音路径
262269
raw: 语音原始数据
263270
mimetype: 语音 MIME 类型
271+
duration: 语音时长
264272
name: 语音名称
265273
返回:
266274
构建的消息
@@ -275,6 +283,7 @@ def audio(
275283
path: str | Path | None = None,
276284
raw: bytes | BytesIO | None = None,
277285
mimetype: str | None = None,
286+
duration: float | None = None,
278287
name: str = "audio.mp3",
279288
) -> UniMessage[TS1 | Audio]:
280289
"""创建音频消息
@@ -285,6 +294,7 @@ def audio(
285294
path: 音频路径
286295
raw: 音频原始数据
287296
mimetype: 音频 MIME 类型
297+
duration: 音频时长
288298
name: 音频名称
289299
返回:
290300
构建的消息
@@ -454,12 +464,14 @@ def image(
454464
path: str | Path | None = None,
455465
raw: bytes | BytesIO | None = None,
456466
mimetype: str | None = None,
467+
width: int | None = None,
468+
height: int | None = None,
457469
name: str = "image.png",
458470
) -> UniMessage[TS1 | Image]:
459471
if isinstance(cls_or_self, UniMessage):
460-
cls_or_self.append(Image(id, url, path, raw, mimetype, name))
472+
cls_or_self.append(Image(id, url, path, raw, mimetype, name, width, height))
461473
return cls_or_self
462-
return UniMessage(Image(id, url, path, raw, mimetype, name))
474+
return UniMessage(Image(id, url, path, raw, mimetype, name, width, height))
463475

464476
@_method
465477
def video(
@@ -469,12 +481,13 @@ def video(
469481
path: str | Path | None = None,
470482
raw: bytes | BytesIO | None = None,
471483
mimetype: str | None = None,
484+
thumbnail: Image | None = None,
472485
name: str = "video.mp4",
473486
) -> UniMessage[TS1 | Video]:
474487
if isinstance(cls_or_self, UniMessage):
475-
cls_or_self.append(Video(id, url, path, raw, mimetype, name))
488+
cls_or_self.append(Video(id, url, path, raw, mimetype, name, thumbnail))
476489
return cls_or_self
477-
return UniMessage(Video(id, url, path, raw, mimetype, name))
490+
return UniMessage(Video(id, url, path, raw, mimetype, name, thumbnail))
478491

479492
@_method
480493
def voice(
@@ -484,12 +497,13 @@ def voice(
484497
path: str | Path | None = None,
485498
raw: bytes | BytesIO | None = None,
486499
mimetype: str | None = None,
500+
duration: float | None = None,
487501
name: str = "voice.wav",
488502
) -> UniMessage[TS1 | Voice]:
489503
if isinstance(cls_or_self, UniMessage):
490-
cls_or_self.append(Voice(id, url, path, raw, mimetype, name))
504+
cls_or_self.append(Voice(id, url, path, raw, mimetype, name, duration))
491505
return cls_or_self
492-
return UniMessage(Voice(id, url, path, raw, mimetype, name))
506+
return UniMessage(Voice(id, url, path, raw, mimetype, name, duration))
493507

494508
@_method
495509
def audio(
@@ -499,12 +513,13 @@ def audio(
499513
path: str | Path | None = None,
500514
raw: bytes | BytesIO | None = None,
501515
mimetype: str | None = None,
516+
duration: float | None = None,
502517
name: str = "audio.mp3",
503518
) -> UniMessage[TS1 | Audio]:
504519
if isinstance(cls_or_self, UniMessage):
505-
cls_or_self.append(Audio(id, url, path, raw, mimetype, name))
520+
cls_or_self.append(Audio(id, url, path, raw, mimetype, name, duration))
506521
return cls_or_self
507-
return UniMessage(Audio(id, url, path, raw, mimetype, name))
522+
return UniMessage(Audio(id, url, path, raw, mimetype, name, duration))
508523

509524
@_method
510525
def file(

src/nonebot_plugin_alconna/uniseg/segment.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,8 @@ def save(self, media_save_dir: Optional[Union[str, Path]] = None) -> Path:
652652
class Image(Media):
653653
"""Image对象, 表示一类图片元素"""
654654

655+
width: Optional[int] = field(default=None)
656+
height: Optional[int] = field(default=None)
655657
name: str = field(default="image.png")
656658

657659
__default_name__ = "image.png"
@@ -661,7 +663,7 @@ class Image(Media):
661663
class Audio(Media):
662664
"""Audio对象, 表示一类音频元素"""
663665

664-
duration: Optional[int] = field(default=None)
666+
duration: Optional[float] = field(default=None)
665667
name: str = field(default="audio.mp3")
666668

667669
__default_name__ = "audio.mp3"
@@ -671,7 +673,7 @@ class Audio(Media):
671673
class Voice(Media):
672674
"""Voice对象, 表示一类语音元素"""
673675

674-
duration: Optional[int] = field(default=None)
676+
duration: Optional[float] = field(default=None)
675677
name: str = field(default="voice.wav")
676678

677679
__default_name__ = "voice.wav"

0 commit comments

Comments
 (0)