mirror of
				https://github.com/nonebot/nonebot2.git
				synced 2025-10-30 14:36:41 +00:00 
			
		
		
		
	✨ Feature: 为消息类添加 has join include exclude 方法 (#1895)
				
					
				
			This commit is contained in:
		| @@ -19,6 +19,11 @@ __autodoc__ = { | |||||||
|     "Event": True, |     "Event": True, | ||||||
|     "Adapter": True, |     "Adapter": True, | ||||||
|     "Message": True, |     "Message": True, | ||||||
|  |     "Message.__getitem__": True, | ||||||
|  |     "Message.__contains__": True, | ||||||
|  |     "Message._construct": True, | ||||||
|     "MessageSegment": True, |     "MessageSegment": True, | ||||||
|  |     "MessageSegment.__str__": True, | ||||||
|  |     "MessageSegment.__add__": True, | ||||||
|     "MessageTemplate": True, |     "MessageTemplate": True, | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import abc | import abc | ||||||
| from copy import deepcopy | from copy import deepcopy | ||||||
|  | from typing_extensions import Self | ||||||
| from dataclasses import field, asdict, dataclass | from dataclasses import field, asdict, dataclass | ||||||
| from typing import ( | from typing import ( | ||||||
|     Any, |     Any, | ||||||
| @@ -12,6 +13,7 @@ from typing import ( | |||||||
|     TypeVar, |     TypeVar, | ||||||
|     Iterable, |     Iterable, | ||||||
|     Optional, |     Optional, | ||||||
|  |     SupportsIndex, | ||||||
|     overload, |     overload, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -19,7 +21,6 @@ from pydantic import parse_obj_as | |||||||
|  |  | ||||||
| from .template import MessageTemplate | from .template import MessageTemplate | ||||||
|  |  | ||||||
| T = TypeVar("T") |  | ||||||
| TMS = TypeVar("TMS", bound="MessageSegment") | TMS = TypeVar("TMS", bound="MessageSegment") | ||||||
| TM = TypeVar("TM", bound="Message") | TM = TypeVar("TM", bound="Message") | ||||||
|  |  | ||||||
| @@ -47,7 +48,7 @@ class MessageSegment(abc.ABC, Generic[TM]): | |||||||
|     def __len__(self) -> int: |     def __len__(self) -> int: | ||||||
|         return len(str(self)) |         return len(str(self)) | ||||||
|  |  | ||||||
|     def __ne__(self: T, other: T) -> bool: |     def __ne__(self, other: Self) -> bool: | ||||||
|         return not self == other |         return not self == other | ||||||
|  |  | ||||||
|     def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM: |     def __add__(self: TMS, other: Union[str, TMS, Iterable[TMS]]) -> TM: | ||||||
| @@ -61,7 +62,7 @@ class MessageSegment(abc.ABC, Generic[TM]): | |||||||
|         yield cls._validate |         yield cls._validate | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def _validate(cls, value): |     def _validate(cls, value) -> Self: | ||||||
|         if isinstance(value, cls): |         if isinstance(value, cls): | ||||||
|             return value |             return value | ||||||
|         if not isinstance(value, dict): |         if not isinstance(value, dict): | ||||||
| @@ -84,7 +85,10 @@ class MessageSegment(abc.ABC, Generic[TM]): | |||||||
|     def items(self): |     def items(self): | ||||||
|         return asdict(self).items() |         return asdict(self).items() | ||||||
|  |  | ||||||
|     def copy(self: T) -> T: |     def join(self: TMS, iterable: Iterable[Union[TMS, TM]]) -> TM: | ||||||
|  |         return self.get_message_class()(self).join(iterable) | ||||||
|  |  | ||||||
|  |     def copy(self) -> Self: | ||||||
|         return deepcopy(self) |         return deepcopy(self) | ||||||
|  |  | ||||||
|     @abc.abstractmethod |     @abc.abstractmethod | ||||||
| @@ -117,7 +121,7 @@ class Message(List[TMS], abc.ABC): | |||||||
|             self.extend(self._construct(message))  # pragma: no cover |             self.extend(self._construct(message))  # pragma: no cover | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def template(cls: Type[TM], format_string: Union[str, TM]) -> MessageTemplate[TM]: |     def template(cls, format_string: Union[str, TM]) -> MessageTemplate[Self]: | ||||||
|         """创建消息模板。 |         """创建消息模板。 | ||||||
|  |  | ||||||
|         用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板 |         用法和 `str.format` 大致相同, 但是可以输出消息对象, 并且支持以 `Message` 对象作为消息模板 | ||||||
| @@ -146,7 +150,7 @@ class Message(List[TMS], abc.ABC): | |||||||
|         yield cls._validate |         yield cls._validate | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def _validate(cls, value): |     def _validate(cls, value) -> Self: | ||||||
|         if isinstance(value, cls): |         if isinstance(value, cls): | ||||||
|             return value |             return value | ||||||
|         elif isinstance(value, Message): |         elif isinstance(value, Message): | ||||||
| @@ -169,16 +173,16 @@ class Message(List[TMS], abc.ABC): | |||||||
|         """构造消息数组""" |         """构造消息数组""" | ||||||
|         raise NotImplementedError |         raise NotImplementedError | ||||||
|  |  | ||||||
|     def __add__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM: |     def __add__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self: | ||||||
|         result = self.copy() |         result = self.copy() | ||||||
|         result += other |         result += other | ||||||
|         return result |         return result | ||||||
|  |  | ||||||
|     def __radd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM: |     def __radd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self: | ||||||
|         result = self.__class__(other) |         result = self.__class__(other) | ||||||
|         return result + self |         return result + self | ||||||
|  |  | ||||||
|     def __iadd__(self: TM, other: Union[str, TMS, Iterable[TMS]]) -> TM: |     def __iadd__(self, other: Union[str, TMS, Iterable[TMS]]) -> Self: | ||||||
|         if isinstance(other, str): |         if isinstance(other, str): | ||||||
|             self.extend(self._construct(other)) |             self.extend(self._construct(other)) | ||||||
|         elif isinstance(other, MessageSegment): |         elif isinstance(other, MessageSegment): | ||||||
| @@ -190,57 +194,62 @@ class Message(List[TMS], abc.ABC): | |||||||
|         return self |         return self | ||||||
|  |  | ||||||
|     @overload |     @overload | ||||||
|     def __getitem__(self: TM, __args: str) -> TM: |     def __getitem__(self, args: str) -> Self: | ||||||
|         """ |         """获取仅包含指定消息段类型的消息 | ||||||
|  |  | ||||||
|         参数: |         参数: | ||||||
|             __args: 消息段类型 |             args: 消息段类型 | ||||||
|  |  | ||||||
|         返回: |         返回: | ||||||
|             所有类型为 `__args` 的消息段 |             所有类型为 `args` 的消息段 | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|     @overload |     @overload | ||||||
|     def __getitem__(self, __args: Tuple[str, int]) -> TMS: |     def __getitem__(self, args: Tuple[str, int]) -> TMS: | ||||||
|         """ |         """索引指定类型的消息段 | ||||||
|  |  | ||||||
|         参数: |         参数: | ||||||
|             __args: 消息段类型和索引 |             args: 消息段类型和索引 | ||||||
|  |  | ||||||
|         返回: |         返回: | ||||||
|             类型为 `__args[0]` 的消息段第 `__args[1]` 个 |             类型为 `args[0]` 的消息段第 `args[1]` 个 | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|     @overload |     @overload | ||||||
|     def __getitem__(self: TM, __args: Tuple[str, slice]) -> TM: |     def __getitem__(self, args: Tuple[str, slice]) -> Self: | ||||||
|         """ |         """切片指定类型的消息段 | ||||||
|  |  | ||||||
|         参数: |         参数: | ||||||
|             __args: 消息段类型和切片 |             args: 消息段类型和切片 | ||||||
|  |  | ||||||
|         返回: |         返回: | ||||||
|             类型为 `__args[0]` 的消息段切片 `__args[1]` |             类型为 `args[0]` 的消息段切片 `args[1]` | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|     @overload |     @overload | ||||||
|     def __getitem__(self, __args: int) -> TMS: |     def __getitem__(self, args: int) -> TMS: | ||||||
|         """ |         """索引消息段 | ||||||
|  |  | ||||||
|         参数: |         参数: | ||||||
|             __args: 索引 |             args: 索引 | ||||||
|  |  | ||||||
|         返回: |         返回: | ||||||
|             第 `__args` 个消息段 |             第 `args` 个消息段 | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|     @overload |     @overload | ||||||
|     def __getitem__(self: TM, __args: slice) -> TM: |     def __getitem__(self, args: slice) -> Self: | ||||||
|         """ |         """切片消息段 | ||||||
|  |  | ||||||
|         参数: |         参数: | ||||||
|             __args: 切片 |             args: 切片 | ||||||
|  |  | ||||||
|         返回: |         返回: | ||||||
|             消息切片 `__args` |             消息切片 `args` | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|     def __getitem__( |     def __getitem__( | ||||||
|         self: TM, |         self, | ||||||
|         args: Union[ |         args: Union[ | ||||||
|             str, |             str, | ||||||
|             Tuple[str, int], |             Tuple[str, int], | ||||||
| @@ -248,7 +257,7 @@ class Message(List[TMS], abc.ABC): | |||||||
|             int, |             int, | ||||||
|             slice, |             slice, | ||||||
|         ], |         ], | ||||||
|     ) -> Union[TMS, TM]: |     ) -> Union[TMS, Self]: | ||||||
|         arg1, arg2 = args if isinstance(args, tuple) else (args, None) |         arg1, arg2 = args if isinstance(args, tuple) else (args, None) | ||||||
|         if isinstance(arg1, int) and arg2 is None: |         if isinstance(arg1, int) and arg2 is None: | ||||||
|             return super().__getitem__(arg1) |             return super().__getitem__(arg1) | ||||||
| @@ -263,15 +272,52 @@ class Message(List[TMS], abc.ABC): | |||||||
|         else: |         else: | ||||||
|             raise ValueError("Incorrect arguments to slice")  # pragma: no cover |             raise ValueError("Incorrect arguments to slice")  # pragma: no cover | ||||||
|  |  | ||||||
|     def index(self, value: Union[TMS, str], *args) -> int: |     def __contains__(self, value: Union[TMS, str]) -> bool: | ||||||
|  |         """检查消息段是否存在 | ||||||
|  |  | ||||||
|  |         参数: | ||||||
|  |             value: 消息段或消息段类型 | ||||||
|  |         返回: | ||||||
|  |             消息内是否存在给定消息段或给定类型的消息段 | ||||||
|  |         """ | ||||||
|  |         if isinstance(value, str): | ||||||
|  |             return bool(next((seg for seg in self if seg.type == value), None)) | ||||||
|  |         return super().__contains__(value) | ||||||
|  |  | ||||||
|  |     def has(self, value: Union[TMS, str]) -> bool: | ||||||
|  |         """与 {ref}``__contains__` <nonebot.adapters.Message.__contains__>` 相同""" | ||||||
|  |         return value in self | ||||||
|  |  | ||||||
|  |     def index(self, value: Union[TMS, str], *args: SupportsIndex) -> int: | ||||||
|  |         """索引消息段 | ||||||
|  |  | ||||||
|  |         参数: | ||||||
|  |             value: 消息段或者消息段类型 | ||||||
|  |             arg: start 与 end | ||||||
|  |  | ||||||
|  |         返回: | ||||||
|  |             索引 index | ||||||
|  |  | ||||||
|  |         异常: | ||||||
|  |             ValueError: 消息段不存在 | ||||||
|  |         """ | ||||||
|         if isinstance(value, str): |         if isinstance(value, str): | ||||||
|             first_segment = next((seg for seg in self if seg.type == value), None) |             first_segment = next((seg for seg in self if seg.type == value), None) | ||||||
|             if first_segment is None: |             if first_segment is None: | ||||||
|                 raise ValueError(f"Segment with type {value} is not in message") |                 raise ValueError(f"Segment with type {value!r} is not in message") | ||||||
|             return super().index(first_segment, *args) |             return super().index(first_segment, *args) | ||||||
|         return super().index(value, *args) |         return super().index(value, *args) | ||||||
|  |  | ||||||
|     def get(self: TM, type_: str, count: Optional[int] = None) -> TM: |     def get(self, type_: str, count: Optional[int] = None) -> Self: | ||||||
|  |         """获取指定类型的消息段 | ||||||
|  |  | ||||||
|  |         参数: | ||||||
|  |             type_: 消息段类型 | ||||||
|  |             count: 获取个数 | ||||||
|  |  | ||||||
|  |         返回: | ||||||
|  |             构建的新消息 | ||||||
|  |         """ | ||||||
|         if count is None: |         if count is None: | ||||||
|             return self[type_] |             return self[type_] | ||||||
|  |  | ||||||
| @@ -286,9 +332,30 @@ class Message(List[TMS], abc.ABC): | |||||||
|         return filtered |         return filtered | ||||||
|  |  | ||||||
|     def count(self, value: Union[TMS, str]) -> int: |     def count(self, value: Union[TMS, str]) -> int: | ||||||
|  |         """计算指定消息段的个数 | ||||||
|  |  | ||||||
|  |         参数: | ||||||
|  |             value: 消息段或消息段类型 | ||||||
|  |  | ||||||
|  |         返回: | ||||||
|  |             个数 | ||||||
|  |         """ | ||||||
|         return len(self[value]) if isinstance(value, str) else super().count(value) |         return len(self[value]) if isinstance(value, str) else super().count(value) | ||||||
|  |  | ||||||
|     def append(self: TM, obj: Union[str, TMS]) -> TM: |     def only(self, value: Union[TMS, str]) -> bool: | ||||||
|  |         """检查消息中是否仅包含指定消息段 | ||||||
|  |  | ||||||
|  |         参数: | ||||||
|  |             value: 指定消息段或消息段类型 | ||||||
|  |  | ||||||
|  |         返回: | ||||||
|  |             是否仅包含指定消息段 | ||||||
|  |         """ | ||||||
|  |         if isinstance(value, str): | ||||||
|  |             return all(seg.type == value for seg in self) | ||||||
|  |         return all(seg == value for seg in self) | ||||||
|  |  | ||||||
|  |     def append(self, obj: Union[str, TMS]) -> Self: | ||||||
|         """添加一个消息段到消息数组末尾。 |         """添加一个消息段到消息数组末尾。 | ||||||
|  |  | ||||||
|         参数: |         参数: | ||||||
| @@ -302,7 +369,7 @@ class Message(List[TMS], abc.ABC): | |||||||
|             raise ValueError(f"Unexpected type: {type(obj)} {obj}")  # pragma: no cover |             raise ValueError(f"Unexpected type: {type(obj)} {obj}")  # pragma: no cover | ||||||
|         return self |         return self | ||||||
|  |  | ||||||
|     def extend(self: TM, obj: Union[TM, Iterable[TMS]]) -> TM: |     def extend(self, obj: Union[Self, Iterable[TMS]]) -> Self: | ||||||
|         """拼接一个消息数组或多个消息段到消息数组末尾。 |         """拼接一个消息数组或多个消息段到消息数组末尾。 | ||||||
|  |  | ||||||
|         参数: |         参数: | ||||||
| @@ -312,18 +379,52 @@ class Message(List[TMS], abc.ABC): | |||||||
|             self.append(segment) |             self.append(segment) | ||||||
|         return self |         return self | ||||||
|  |  | ||||||
|     def copy(self: TM) -> TM: |     def join(self, iterable: Iterable[Union[TMS, Self]]) -> Self: | ||||||
|  |         """将多个消息连接并将自身作为分割 | ||||||
|  |  | ||||||
|  |         参数: | ||||||
|  |             iterable: 要连接的消息 | ||||||
|  |  | ||||||
|  |         返回: | ||||||
|  |             连接后的消息 | ||||||
|  |         """ | ||||||
|  |         ret = self.__class__() | ||||||
|  |         for index, msg in enumerate(iterable): | ||||||
|  |             if index != 0: | ||||||
|  |                 ret.extend(self) | ||||||
|  |             if isinstance(msg, MessageSegment): | ||||||
|  |                 ret.append(msg.copy()) | ||||||
|  |             else: | ||||||
|  |                 ret.extend(msg.copy()) | ||||||
|  |         return ret | ||||||
|  |  | ||||||
|  |     def copy(self) -> Self: | ||||||
|  |         """深拷贝消息""" | ||||||
|         return deepcopy(self) |         return deepcopy(self) | ||||||
|  |  | ||||||
|  |     def include(self, *types: str) -> Self: | ||||||
|  |         """过滤消息 | ||||||
|  |  | ||||||
|  |         参数: | ||||||
|  |             types: 包含的消息段类型 | ||||||
|  |  | ||||||
|  |         返回: | ||||||
|  |             新构造的消息 | ||||||
|  |         """ | ||||||
|  |         return self.__class__(seg for seg in self if seg.type in types) | ||||||
|  |  | ||||||
|  |     def exclude(self, *types: str) -> Self: | ||||||
|  |         """过滤消息 | ||||||
|  |  | ||||||
|  |         参数: | ||||||
|  |             types: 不包含的消息段类型 | ||||||
|  |  | ||||||
|  |         返回: | ||||||
|  |             新构造的消息 | ||||||
|  |         """ | ||||||
|  |         return self.__class__(seg for seg in self if seg.type not in types) | ||||||
|  |  | ||||||
|     def extract_plain_text(self) -> str: |     def extract_plain_text(self) -> str: | ||||||
|         """提取消息内纯文本消息""" |         """提取消息内纯文本消息""" | ||||||
|  |  | ||||||
|         return "".join(str(seg) for seg in self if seg.is_text()) |         return "".join(str(seg) for seg in self if seg.is_text()) | ||||||
|  |  | ||||||
|  |  | ||||||
| __autodoc__ = { |  | ||||||
|     "MessageSegment.__str__": True, |  | ||||||
|     "MessageSegment.__add__": True, |  | ||||||
|     "Message.__getitem__": True, |  | ||||||
|     "Message._construct": True, |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ CMD_RESULT = TypedDict( | |||||||
|     { |     { | ||||||
|         "command": Optional[Tuple[str, ...]], |         "command": Optional[Tuple[str, ...]], | ||||||
|         "raw_command": Optional[str], |         "raw_command": Optional[str], | ||||||
|         "command_arg": Optional[Message[MessageSegment]], |         "command_arg": Optional[Message], | ||||||
|         "command_start": Optional[str], |         "command_start": Optional[str], | ||||||
|         "command_whitespace": Optional[str], |         "command_whitespace": Optional[str], | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -41,6 +41,26 @@ def test_segment_validate(): | |||||||
|         parse_obj_as(MessageSegment, {"data": {}}) |         parse_obj_as(MessageSegment, {"data": {}}) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_segment_join(): | ||||||
|  |     Message = make_fake_message() | ||||||
|  |     MessageSegment = Message.get_segment_class() | ||||||
|  |  | ||||||
|  |     seg = MessageSegment.text("test") | ||||||
|  |     iterable = [ | ||||||
|  |         MessageSegment.text("first"), | ||||||
|  |         Message([MessageSegment.text("second"), MessageSegment.text("third")]), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     assert seg.join(iterable) == Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("first"), | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.text("second"), | ||||||
|  |             MessageSegment.text("third"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_segment(): | def test_segment(): | ||||||
|     Message = make_fake_message() |     Message = make_fake_message() | ||||||
|     MessageSegment = Message.get_segment_class() |     MessageSegment = Message.get_segment_class() | ||||||
| @@ -146,3 +166,124 @@ def test_message_validate(): | |||||||
|  |  | ||||||
|     with pytest.raises(ValidationError): |     with pytest.raises(ValidationError): | ||||||
|         parse_obj_as(Message, object()) |         parse_obj_as(Message, object()) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_message_contains(): | ||||||
|  |     Message = make_fake_message() | ||||||
|  |     MessageSegment = Message.get_segment_class() | ||||||
|  |  | ||||||
|  |     message = Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.image("test2"), | ||||||
|  |             MessageSegment.image("test3"), | ||||||
|  |             MessageSegment.text("test4"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     assert message.has(MessageSegment.text("test")) is True | ||||||
|  |     assert MessageSegment.text("test") in message | ||||||
|  |     assert message.has("image") is True | ||||||
|  |     assert "image" in message | ||||||
|  |  | ||||||
|  |     assert message.has(MessageSegment.text("foo")) is False | ||||||
|  |     assert MessageSegment.text("foo") not in message | ||||||
|  |     assert message.has("foo") is False | ||||||
|  |     assert "foo" not in message | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_message_only(): | ||||||
|  |     Message = make_fake_message() | ||||||
|  |     MessageSegment = Message.get_segment_class() | ||||||
|  |  | ||||||
|  |     message = Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.text("test2"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     assert message.only("text") is True | ||||||
|  |     assert message.only(MessageSegment.text("test")) is False | ||||||
|  |  | ||||||
|  |     message = Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.image("test2"), | ||||||
|  |             MessageSegment.image("test3"), | ||||||
|  |             MessageSegment.text("test4"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     assert message.only("text") is False | ||||||
|  |  | ||||||
|  |     message = Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     assert message.only(MessageSegment.text("test")) is True | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_message_join(): | ||||||
|  |     Message = make_fake_message() | ||||||
|  |     MessageSegment = Message.get_segment_class() | ||||||
|  |  | ||||||
|  |     msg = Message([MessageSegment.text("test")]) | ||||||
|  |     iterable = [ | ||||||
|  |         MessageSegment.text("first"), | ||||||
|  |         Message([MessageSegment.text("second"), MessageSegment.text("third")]), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     assert msg.join(iterable) == Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("first"), | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.text("second"), | ||||||
|  |             MessageSegment.text("third"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_message_include(): | ||||||
|  |     Message = make_fake_message() | ||||||
|  |     MessageSegment = Message.get_segment_class() | ||||||
|  |  | ||||||
|  |     message = Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.image("test2"), | ||||||
|  |             MessageSegment.image("test3"), | ||||||
|  |             MessageSegment.text("test4"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     assert message.include("text") == Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.text("test4"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_message_exclude(): | ||||||
|  |     Message = make_fake_message() | ||||||
|  |     MessageSegment = Message.get_segment_class() | ||||||
|  |  | ||||||
|  |     message = Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.image("test2"), | ||||||
|  |             MessageSegment.image("test3"), | ||||||
|  |             MessageSegment.text("test4"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |     assert message.exclude("image") == Message( | ||||||
|  |         [ | ||||||
|  |             MessageSegment.text("test"), | ||||||
|  |             MessageSegment.text("test4"), | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ def escape_text(s: str, *, escape_comma: bool = True) -> str: | |||||||
|  |  | ||||||
|  |  | ||||||
| def make_fake_message(): | def make_fake_message(): | ||||||
|     class FakeMessageSegment(MessageSegment): |     class FakeMessageSegment(MessageSegment["FakeMessage"]): | ||||||
|         @classmethod |         @classmethod | ||||||
|         def get_message_class(cls): |         def get_message_class(cls): | ||||||
|             return FakeMessage |             return FakeMessage | ||||||
| @@ -36,7 +36,7 @@ def make_fake_message(): | |||||||
|         def is_text(self) -> bool: |         def is_text(self) -> bool: | ||||||
|             return self.type == "text" |             return self.type == "text" | ||||||
|  |  | ||||||
|     class FakeMessage(Message): |     class FakeMessage(Message[FakeMessageSegment]): | ||||||
|         @classmethod |         @classmethod | ||||||
|         def get_segment_class(cls): |         def get_segment_class(cls): | ||||||
|             return FakeMessageSegment |             return FakeMessageSegment | ||||||
| @@ -50,7 +50,9 @@ def make_fake_message(): | |||||||
|                     yield FakeMessageSegment(**seg) |                     yield FakeMessageSegment(**seg) | ||||||
|             return |             return | ||||||
|  |  | ||||||
|         def __add__(self, other): |         def __add__( | ||||||
|  |             self, other: Union[str, FakeMessageSegment, Iterable[FakeMessageSegment]] | ||||||
|  |         ): | ||||||
|             other = escape_text(other) if isinstance(other, str) else other |             other = escape_text(other) if isinstance(other, str) else other | ||||||
|             return super().__add__(other) |             return super().__add__(other) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -120,16 +120,37 @@ Message( | |||||||
|  |  | ||||||
| ### 遍历 | ### 遍历 | ||||||
|  |  | ||||||
| `Message` 继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。 | 消息序列继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。 | ||||||
|  |  | ||||||
| ```python | ```python | ||||||
| for segment in message: | for segment in message: | ||||||
|     ... |     ... | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| ### 索引与切片 | ### 比较 | ||||||
|  |  | ||||||
| `Message` 对列表的索引与切片进行了增强,在原有列表 int 索引与切片的基础上,支持 `type` 过滤索引与切片。 | 消息和消息段都可以使用 `==` 或 `!=` 运算符比较是否相同。 | ||||||
|  |  | ||||||
|  | ```python | ||||||
|  | MessageSegment.text("text") != MessageSegment.text("foo") | ||||||
|  |  | ||||||
|  | some_message == Message([MessageSegment.text("text")]) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 检查消息段 | ||||||
|  |  | ||||||
|  | 我们可以通过 `in` 运算符或消息序列的 `has` 方法来: | ||||||
|  |  | ||||||
|  | ```python | ||||||
|  | # 是否存在消息段 | ||||||
|  | MessageSegment.text("text") in message | ||||||
|  | # 是否存在指定类型的消息段 | ||||||
|  | "text" in message | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 过滤、索引与切片 | ||||||
|  |  | ||||||
|  | 消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片。 | ||||||
|  |  | ||||||
| ```python | ```python | ||||||
| from nonebot.adapters.console import Message, MessageSegment | from nonebot.adapters.console import Message, MessageSegment | ||||||
| @@ -160,7 +181,14 @@ message["markdown", 0:2] == Message( | |||||||
| ) | ) | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| 同样的,`Message` 对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。 | 我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤。 | ||||||
|  |  | ||||||
|  | ```python | ||||||
|  | message.include("text", "markdown") | ||||||
|  | message.exclude("text") | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | 同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。 | ||||||
|  |  | ||||||
| ```python | ```python | ||||||
| # 指定类型首个消息段索引 | # 指定类型首个消息段索引 | ||||||
| @@ -169,7 +197,7 @@ message.index("markdown") == 1 | |||||||
| message.count("markdown") == 2 | message.count("markdown") == 2 | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| 此外,`Message` 添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。 | 此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。 | ||||||
|  |  | ||||||
| ```python | ```python | ||||||
| # 获取指定类型指定个数的消息段 | # 获取指定类型指定个数的消息段 | ||||||
| @@ -214,6 +242,31 @@ msg.append(MessageSegment.text("text")) | |||||||
| msg.extend([MessageSegment.text("text")]) | msg.extend([MessageSegment.text("text")]) | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | 我们也可以通过消息段或消息序列的 `join` 方法来拼接一串消息: | ||||||
|  |  | ||||||
|  | ```python | ||||||
|  | seg = MessageSegment.text("text") | ||||||
|  | msg = seg.join( | ||||||
|  |     [ | ||||||
|  |         MessageSegment.text("first"), | ||||||
|  |         Message( | ||||||
|  |             [ | ||||||
|  |                 MessageSegment.text("second"), | ||||||
|  |                 MessageSegment.text("third"), | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | msg == Message( | ||||||
|  |     [ | ||||||
|  |         MessageSegment.text("first"), | ||||||
|  |         MessageSegment.text("text"), | ||||||
|  |         MessageSegment.text("second"), | ||||||
|  |         MessageSegment.text("third"), | ||||||
|  |     ] | ||||||
|  | ) | ||||||
|  | ``` | ||||||
|  |  | ||||||
| ### 使用消息模板 | ### 使用消息模板 | ||||||
|  |  | ||||||
| 为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列 | 为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user