butterbot-python 3.1.0.dev1__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.
- butterbot/__init__.py +15 -0
- butterbot/app/__init__.py +41 -0
- butterbot/app/bot_app.py +374 -0
- butterbot/app/config.py +156 -0
- butterbot/app/source_manager.py +367 -0
- butterbot/core/__init__.py +46 -0
- butterbot/core/api/__init__.py +9 -0
- butterbot/core/api/base_api.py +32 -0
- butterbot/core/context/README.md +1 -0
- butterbot/core/context/__init__.py +9 -0
- butterbot/core/context/api_registry.py +109 -0
- butterbot/core/context/app_context.py +51 -0
- butterbot/core/context/config_provider.py +14 -0
- butterbot/core/data/__init__.py +15 -0
- butterbot/core/data/base_data.py +29 -0
- butterbot/core/data/base_model.py +197 -0
- butterbot/core/event/README.md +1 -0
- butterbot/core/event/__init__.py +10 -0
- butterbot/core/event/event.py +27 -0
- butterbot/core/event/event_bus.py +276 -0
- butterbot/core/event/subscriber.py +130 -0
- butterbot/core/exceptions.py +90 -0
- butterbot/core/filter/__init__.py +11 -0
- butterbot/core/filter/base_filter.py +74 -0
- butterbot/core/source/README.md +1 -0
- butterbot/core/source/__init__.py +9 -0
- butterbot/core/source/base_source.py +134 -0
- butterbot/core/types/__init__.py +9 -0
- butterbot/core/types/base_type.py +74 -0
- butterbot/sources/__init__.py +1 -0
- butterbot/sources/bilibili/README.md +10 -0
- butterbot/sources/bilibili/__init__.py +20 -0
- butterbot/sources/bilibili/api/__init__.py +5 -0
- butterbot/sources/bilibili/api/bili_api.py +123 -0
- butterbot/sources/bilibili/data/__init__.py +52 -0
- butterbot/sources/bilibili/data/danmaku_gift_data.py +135 -0
- butterbot/sources/bilibili/data/danmaku_guard_data.py +54 -0
- butterbot/sources/bilibili/data/danmaku_msg_data.py +71 -0
- butterbot/sources/bilibili/data/dto/__init__.py +59 -0
- butterbot/sources/bilibili/data/dto/danmaku_gift_dto.py +193 -0
- butterbot/sources/bilibili/data/dto/danmaku_guard_buy_dto.py +54 -0
- butterbot/sources/bilibili/data/dto/danmaku_msg_dto.py +123 -0
- butterbot/sources/bilibili/data/dto/dynamic_dto.py +276 -0
- butterbot/sources/bilibili/data/dto/live_room_dto.py +169 -0
- butterbot/sources/bilibili/data/dto/video_part_dto.py +18 -0
- butterbot/sources/bilibili/data/dynamic_data.py +362 -0
- butterbot/sources/bilibili/data/live_room_data.py +162 -0
- butterbot/sources/bilibili/data/video_part.py +46 -0
- butterbot/sources/bilibili/source/__init__.py +15 -0
- butterbot/sources/bilibili/source/base_polling_source.py +130 -0
- butterbot/sources/bilibili/source/bili_danmaku_source.py +230 -0
- butterbot/sources/bilibili/source/bili_dynamic_source.py +135 -0
- butterbot/sources/bilibili/source/bili_live_source.py +137 -0
- butterbot/sources/bilibili/types/__init__.py +11 -0
- butterbot/sources/bilibili/types/bili_type.py +32 -0
- butterbot/sources/napcat/README.md +10 -0
- butterbot/sources/napcat/__init__.py +20 -0
- butterbot/sources/napcat/api/__init__.py +3 -0
- butterbot/sources/napcat/api/napcat_api.py +316 -0
- butterbot/sources/napcat/data/__init__.py +179 -0
- butterbot/sources/napcat/data/event_data.py +441 -0
- butterbot/sources/napcat/data/segment_data.py +432 -0
- butterbot/sources/napcat/events.py +91 -0
- butterbot/sources/napcat/filters/__init__.py +19 -0
- butterbot/sources/napcat/filters/filters.py +202 -0
- butterbot/sources/napcat/source/__init__.py +5 -0
- butterbot/sources/napcat/source/napcat_source.py +58 -0
- butterbot/sources/napcat/types/__init__.py +5 -0
- butterbot/sources/napcat/types/napcat_type.py +61 -0
- butterbot/utils/README.md +15 -0
- butterbot/utils/__init__.py +11 -0
- butterbot/utils/data_pair.py +27 -0
- butterbot/utils/logging_config.py +521 -0
- butterbot/utils/terminal.py +308 -0
- butterbot/utils/websocket.py +1270 -0
- butterbot_python-3.1.0.dev1.dist-info/METADATA +769 -0
- butterbot_python-3.1.0.dev1.dist-info/RECORD +79 -0
- butterbot_python-3.1.0.dev1.dist-info/WHEEL +4 -0
- butterbot_python-3.1.0.dev1.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Napcat 预置过滤器.
|
|
2
|
+
|
|
3
|
+
提供一组可直接组合使用的 ``BaseFilter`` 子类:
|
|
4
|
+
|
|
5
|
+
- ``GroupFilter`` — 按群号过滤
|
|
6
|
+
- ``UserFilter`` — 按用户 ID 过滤
|
|
7
|
+
- ``SenderRoleFilter`` — 按发送者角色过滤(owner / admin / member)
|
|
8
|
+
- ``TextFilter`` — 按消息文本关键词过滤
|
|
9
|
+
- ``CommandFilter`` — 按完整命令精确匹配
|
|
10
|
+
- ``PrefixFilter`` — 按消息前缀匹配
|
|
11
|
+
|
|
12
|
+
所有过滤器遵循"安全"原则:事件不包含目标字段时默认拦截,
|
|
13
|
+
确保只处理明确匹配的事件类型。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from logging import getLogger
|
|
17
|
+
from typing import TYPE_CHECKING, Literal
|
|
18
|
+
|
|
19
|
+
from butterbot.core.filter import BaseFilter
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from butterbot.core.event import Event
|
|
23
|
+
|
|
24
|
+
_log = getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GroupFilter(BaseFilter):
|
|
28
|
+
"""仅允许指定群组的事件通过。
|
|
29
|
+
|
|
30
|
+
对不包含 ``group_id`` 字段的事件类型(如私聊、元事件),默认拦截。
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
``GroupFilter(123456)`` — 仅 group_id=123456 的事件
|
|
34
|
+
``GroupFilter(123456, 789012)`` — group_id 为 123456 **或** 789012 的事件
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, *group_ids: int) -> None:
|
|
38
|
+
self.filters = list(group_ids)
|
|
39
|
+
|
|
40
|
+
def check(self, event: "Event") -> bool:
|
|
41
|
+
group_id = getattr(event.data, "group_id", None)
|
|
42
|
+
if group_id is None:
|
|
43
|
+
return False # 非群聊事件,安全拦截
|
|
44
|
+
result = group_id in self.filters
|
|
45
|
+
if not result:
|
|
46
|
+
_log.debug("事件 %s 被 GroupFilter 拦截: group_id=%s", event.id, group_id)
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UserFilter(BaseFilter):
|
|
51
|
+
"""仅允许指定用户的事件通过。
|
|
52
|
+
|
|
53
|
+
对不包含 ``user_id`` 字段的事件类型,默认拦截。
|
|
54
|
+
|
|
55
|
+
Usage:
|
|
56
|
+
``UserFilter(10001)`` — 仅 user_id=10001 的事件
|
|
57
|
+
``UserFilter(10001, 10002)`` — user_id 为 10001 **或** 10002 的事件
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, *user_ids: int) -> None:
|
|
61
|
+
self.filters = list(user_ids)
|
|
62
|
+
|
|
63
|
+
def check(self, event: "Event") -> bool:
|
|
64
|
+
user_id = getattr(event.data, "user_id", None)
|
|
65
|
+
if user_id is None:
|
|
66
|
+
return False
|
|
67
|
+
result = user_id in self.filters
|
|
68
|
+
if not result:
|
|
69
|
+
_log.debug("事件 %s 被 UserFilter 拦截: user_id=%s", event.id, user_id)
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SenderRoleFilter(BaseFilter):
|
|
74
|
+
"""仅允许指定发送者角色的群消息通过。
|
|
75
|
+
|
|
76
|
+
通过 ``group_id`` 字段的存在判断是否为群聊事件,
|
|
77
|
+
确认后检查 ``sender.role`` 的值。
|
|
78
|
+
|
|
79
|
+
非群聊事件或缺少必要字段时默认拦截。
|
|
80
|
+
|
|
81
|
+
Usage:
|
|
82
|
+
``SenderRoleFilter("owner", "admin")`` — 仅群主或管理员消息
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(self, *roles: Literal["owner", "admin", "member"]) -> None:
|
|
86
|
+
self.filters = list(roles)
|
|
87
|
+
|
|
88
|
+
def check(self, event: "Event") -> bool:
|
|
89
|
+
data = event.data
|
|
90
|
+
# 通过 group_id 判断是否为群聊事件
|
|
91
|
+
if getattr(data, "group_id", None) is None:
|
|
92
|
+
return False
|
|
93
|
+
sender = getattr(data, "sender", None)
|
|
94
|
+
if sender is None:
|
|
95
|
+
return False
|
|
96
|
+
role = getattr(sender, "role", None)
|
|
97
|
+
if role is None:
|
|
98
|
+
return False
|
|
99
|
+
result = role in self.filters
|
|
100
|
+
if not result:
|
|
101
|
+
_log.debug("事件 %s 被 SenderRoleFilter 拦截: role=%s", event.id, role)
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TextFilter(BaseFilter):
|
|
106
|
+
"""仅允许消息文本包含指定关键词的事件通过。
|
|
107
|
+
|
|
108
|
+
非消息事件(通知、请求等)默认拦截。
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
*keywords: 关键词列表,任一匹配即通过
|
|
112
|
+
case_sensitive: 是否大小写敏感,默认 ``False``
|
|
113
|
+
|
|
114
|
+
Usage:
|
|
115
|
+
``TextFilter("help")`` — 消息包含 "help"(大小写不敏感)
|
|
116
|
+
``TextFilter("hello", case_sensitive=True)`` — 消息包含 "hello"(精确大小写)
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(self, *keywords: str, case_sensitive: bool = False) -> None:
|
|
120
|
+
self.filters = list(keywords)
|
|
121
|
+
self._case_sensitive = case_sensitive
|
|
122
|
+
|
|
123
|
+
def check(self, event: "Event") -> bool:
|
|
124
|
+
data = event.data
|
|
125
|
+
message = getattr(data, "message", None)
|
|
126
|
+
if message is None:
|
|
127
|
+
return False # 非消息事件,安全拦截
|
|
128
|
+
|
|
129
|
+
text = getattr(message, "plain_text", "")
|
|
130
|
+
if not self._case_sensitive:
|
|
131
|
+
text_lower = text.lower()
|
|
132
|
+
return any(k.lower() in text_lower for k in self.filters)
|
|
133
|
+
|
|
134
|
+
return any(k in text for k in self.filters)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class CommandFilter(BaseFilter):
|
|
138
|
+
"""仅允许消息以指定**完整命令**开头的事件通过。
|
|
139
|
+
|
|
140
|
+
使用 ``text.split()[0]`` 提取命令部分进行精确匹配。
|
|
141
|
+
适用于路由如 ``/help``、``/ban`` 等 bot 命令。
|
|
142
|
+
|
|
143
|
+
非消息事件默认拦截。
|
|
144
|
+
|
|
145
|
+
Usage:
|
|
146
|
+
``CommandFilter("/help")`` — 匹配 ``/help`` 和 ``/help args``,**不**匹配 ``/helpme``
|
|
147
|
+
``CommandFilter("/help", "/status")`` — 匹配 ``/help`` **或** ``/status``
|
|
148
|
+
|
|
149
|
+
See Also:
|
|
150
|
+
``PrefixFilter`` — 如需按前缀(如所有 ``/`` 开头的消息)匹配
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(self, *commands: str) -> None:
|
|
154
|
+
self.filters = list(commands)
|
|
155
|
+
|
|
156
|
+
def check(self, event: "Event") -> bool:
|
|
157
|
+
data = event.data
|
|
158
|
+
message = getattr(data, "message", None)
|
|
159
|
+
if message is None:
|
|
160
|
+
return False # 非消息事件,安全拦截
|
|
161
|
+
|
|
162
|
+
text = getattr(message, "plain_text", "")
|
|
163
|
+
# 先取列表再判空:`" ".split(maxsplit=1)` 返回 [],
|
|
164
|
+
# 直接 [0] 会在纯空白消息上抛 IndexError,导致回调 task 异常、事件被吞。
|
|
165
|
+
parts = text.split(maxsplit=1)
|
|
166
|
+
command = parts[0] if parts else ""
|
|
167
|
+
result = command in self.filters
|
|
168
|
+
if not result:
|
|
169
|
+
_log.debug("事件 %s 被 CommandFilter 拦截: command=%s", event.id, command)
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class PrefixFilter(BaseFilter):
|
|
174
|
+
"""仅允许消息以指定前缀开头的事件通过。
|
|
175
|
+
|
|
176
|
+
适合需要捕获所有以某字符开头的消息的场景,
|
|
177
|
+
如所有 ``/`` 开头的 bot 命令。
|
|
178
|
+
|
|
179
|
+
非消息事件默认拦截。
|
|
180
|
+
|
|
181
|
+
Usage:
|
|
182
|
+
``PrefixFilter("/")`` — 匹配任何 ``/`` 开头的消息
|
|
183
|
+
``PrefixFilter("!", ".")`` — 匹配 ``!`` **或** ``.`` 开头的消息
|
|
184
|
+
|
|
185
|
+
See Also:
|
|
186
|
+
``CommandFilter`` — 如需精确匹配完整命令
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def __init__(self, *prefixes: str) -> None:
|
|
190
|
+
self.filters = list(prefixes)
|
|
191
|
+
|
|
192
|
+
def check(self, event: "Event") -> bool:
|
|
193
|
+
data = event.data
|
|
194
|
+
message = getattr(data, "message", None)
|
|
195
|
+
if message is None:
|
|
196
|
+
return False # 非消息事件,安全拦截
|
|
197
|
+
|
|
198
|
+
text = getattr(message, "plain_text", "")
|
|
199
|
+
result = any(text.startswith(prefix) for prefix in self.filters)
|
|
200
|
+
if not result:
|
|
201
|
+
_log.debug("事件 %s 被 PrefixFilter 拦截: text=%s", event.id, text)
|
|
202
|
+
return result
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from logging import getLogger
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from butterbot.core.event import Event
|
|
5
|
+
from butterbot.core.source import BaseSource
|
|
6
|
+
|
|
7
|
+
from ..api import (
|
|
8
|
+
NapcatApi,
|
|
9
|
+
)
|
|
10
|
+
from ..data import NapcatData
|
|
11
|
+
from ..types import (
|
|
12
|
+
NapcatType,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_log = getLogger("NapcatSource")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class NapcatSource(BaseSource):
|
|
19
|
+
"""Napcat 事件源.
|
|
20
|
+
|
|
21
|
+
使用ws协议连接napcat服务器,接收并发布事件。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
supported_types = NapcatType
|
|
25
|
+
config_key = "napcat"
|
|
26
|
+
|
|
27
|
+
async def on_start(self) -> None:
|
|
28
|
+
self.api.set_handler(self._process_messages)
|
|
29
|
+
await self.api.start()
|
|
30
|
+
|
|
31
|
+
async def on_stop(self) -> None:
|
|
32
|
+
await self.api.stop()
|
|
33
|
+
|
|
34
|
+
async def _process_messages(self, message: dict[str, Any]) -> None:
|
|
35
|
+
"""处理接收到的消息."""
|
|
36
|
+
event: Event[NapcatData] | None = None
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
# 使用 BaseDataModel 的自动分发构造
|
|
40
|
+
napcat_event = NapcatData.from_dict(message)
|
|
41
|
+
# 状态由已完成 discriminator 分发的 Data 类型提供。
|
|
42
|
+
# 这样生产路径不再同时维护一套 raw dict 分支路由。
|
|
43
|
+
napcat_type = napcat_event.event_type
|
|
44
|
+
|
|
45
|
+
if napcat_type.matches(NapcatType.ALL):
|
|
46
|
+
event = Event(data=napcat_event, status=napcat_type)
|
|
47
|
+
else:
|
|
48
|
+
_log.warning("未处理的消息类型: %s", napcat_type)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
_log.error("解析消息失败: %s, 原始消息: %s", e, message)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
if event is not None:
|
|
54
|
+
await self.ctx.bus.publish(self.uuid, event)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def api(self) -> NapcatApi:
|
|
58
|
+
return self.ctx.api_ctx.get(NapcatApi, self.config_key)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from butterbot.core.types import BaseType
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NapcatType(BaseType):
|
|
5
|
+
"""NapCat 事件类型枚举.
|
|
6
|
+
|
|
7
|
+
按 OneBot 协议 `post_type` → 二级字段组织层级结构:
|
|
8
|
+
|
|
9
|
+
- ``napcat.meta``: 元事件(lifecycle / heartbeat)
|
|
10
|
+
- ``napcat.message``: 消息事件(group / private)
|
|
11
|
+
- ``napcat.sent``: 自身消息事件(group / private)
|
|
12
|
+
- ``napcat.request``: 请求事件(friend / group)
|
|
13
|
+
- ``napcat.notice``: 通知事件(group_upload / poke / …)
|
|
14
|
+
|
|
15
|
+
大类(如 ``MESSAGE``)可匹配下属所有子类,
|
|
16
|
+
子类(如 ``GROUP_MESSAGE``)只匹配自身。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# ── 通配 ──
|
|
20
|
+
ALL = "napcat.all"
|
|
21
|
+
UNKNOWN = "napcat.unknown"
|
|
22
|
+
|
|
23
|
+
# ── 元事件 ──
|
|
24
|
+
META = "napcat.meta"
|
|
25
|
+
LIFECYCLE_META = "napcat.meta.lifecycle"
|
|
26
|
+
HEARTBEAT_META = "napcat.meta.heartbeat"
|
|
27
|
+
|
|
28
|
+
# ── 消息事件 ──
|
|
29
|
+
MESSAGE = "napcat.message"
|
|
30
|
+
GROUP_MESSAGE = "napcat.message.group"
|
|
31
|
+
PRIVATE_MESSAGE = "napcat.message.private"
|
|
32
|
+
|
|
33
|
+
# ── 自身消息 ──
|
|
34
|
+
SENT = "napcat.sent"
|
|
35
|
+
GROUP_SENT = "napcat.sent.group"
|
|
36
|
+
PRIVATE_SENT = "napcat.sent.private"
|
|
37
|
+
|
|
38
|
+
# ── 请求事件 ──
|
|
39
|
+
REQUEST = "napcat.request"
|
|
40
|
+
FRIEND_REQUEST = "napcat.request.friend"
|
|
41
|
+
GROUP_REQUEST = "napcat.request.group"
|
|
42
|
+
|
|
43
|
+
# ── 通知事件 ──
|
|
44
|
+
NOTICE = "napcat.notice"
|
|
45
|
+
GROUP_UPLOAD_NOTICE = "napcat.notice.group_upload"
|
|
46
|
+
GROUP_ADMIN_NOTICE = "napcat.notice.group_admin"
|
|
47
|
+
GROUP_DECREASE_NOTICE = "napcat.notice.group_decrease"
|
|
48
|
+
GROUP_INCREASE_NOTICE = "napcat.notice.group_increase"
|
|
49
|
+
GROUP_BAN_NOTICE = "napcat.notice.group_ban"
|
|
50
|
+
FRIEND_ADD_NOTICE = "napcat.notice.friend_add"
|
|
51
|
+
GROUP_RECALL_NOTICE = "napcat.notice.group_recall"
|
|
52
|
+
FRIEND_RECALL_NOTICE = "napcat.notice.friend_recall"
|
|
53
|
+
GROUP_EMOJI_LIKE_NOTICE = "napcat.notice.group_msg_emoji_like"
|
|
54
|
+
REACTION_NOTICE = "napcat.notice.reaction"
|
|
55
|
+
GROUP_ESSENCE_NOTICE = "napcat.notice.essence"
|
|
56
|
+
GROUP_CARD_NOTICE = "napcat.notice.group_card"
|
|
57
|
+
|
|
58
|
+
# 三级分发:notice_type=notify → sub_type
|
|
59
|
+
POKE_NOTIFY = "napcat.notice.poke"
|
|
60
|
+
LUCKY_KING_NOTIFY = "napcat.notice.lucky_king"
|
|
61
|
+
HONOR_NOTIFY = "napcat.notice.honor"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 工具模块
|
|
2
|
+
|
|
3
|
+
## 日志初始化
|
|
4
|
+
|
|
5
|
+
导入 `butterbot.utils` 不会修改根记录器或创建日志文件。应用入口需要显式调用:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
from butterbot.utils import setup_logging
|
|
9
|
+
|
|
10
|
+
setup_logging()
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
重复调用 `setup_logging()` 会关闭并替换上一次由该函数创建的 handler,不会累积文件句柄。
|
|
14
|
+
日志文件名和 `LOG_REDIRECT_RULES` 中的重定向文件名必须位于 `LOG_FILE_PATH`
|
|
15
|
+
目录内;绝对路径或包含 `..` 的目录逃逸路径会直接报错。
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .data_pair import DataPair
|
|
2
|
+
from .logging_config import setup_logging
|
|
3
|
+
from .websocket import AsyncWebSocketClient, ListenerId, MessageType
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"AsyncWebSocketClient",
|
|
7
|
+
"DataPair",
|
|
8
|
+
"ListenerId",
|
|
9
|
+
"MessageType",
|
|
10
|
+
"setup_logging",
|
|
11
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from copy import copy
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Generic, Literal
|
|
4
|
+
|
|
5
|
+
from butterbot.core.data import BaseDataT
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class DataPair(Generic[BaseDataT]):
|
|
10
|
+
"""存储新旧数据对"""
|
|
11
|
+
|
|
12
|
+
old: BaseDataT | None = None
|
|
13
|
+
new: BaseDataT | None = None
|
|
14
|
+
|
|
15
|
+
def update(self, new_data: BaseDataT):
|
|
16
|
+
"""更新数据"""
|
|
17
|
+
if self.old is None:
|
|
18
|
+
self.old = new_data
|
|
19
|
+
self.new = new_data
|
|
20
|
+
else:
|
|
21
|
+
self.old = self.new
|
|
22
|
+
self.new = new_data
|
|
23
|
+
|
|
24
|
+
def get_data(self, value: Literal["old", "new"]) -> BaseDataT:
|
|
25
|
+
"""获取数据浅拷贝"""
|
|
26
|
+
data = getattr(self, value)
|
|
27
|
+
return copy(data)
|