ErisPulse-DiscordAdapter 4.0.0__tar.gz
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.
- erispulse_discordadapter-4.0.0/DiscordAdapter/Converter.py +535 -0
- erispulse_discordadapter-4.0.0/DiscordAdapter/Core.py +878 -0
- erispulse_discordadapter-4.0.0/DiscordAdapter/__init__.py +1 -0
- erispulse_discordadapter-4.0.0/ErisPulse_DiscordAdapter.egg-info/PKG-INFO +541 -0
- erispulse_discordadapter-4.0.0/ErisPulse_DiscordAdapter.egg-info/SOURCES.txt +11 -0
- erispulse_discordadapter-4.0.0/ErisPulse_DiscordAdapter.egg-info/dependency_links.txt +1 -0
- erispulse_discordadapter-4.0.0/ErisPulse_DiscordAdapter.egg-info/entry_points.txt +2 -0
- erispulse_discordadapter-4.0.0/ErisPulse_DiscordAdapter.egg-info/top_level.txt +1 -0
- erispulse_discordadapter-4.0.0/LICENSE +21 -0
- erispulse_discordadapter-4.0.0/PKG-INFO +541 -0
- erispulse_discordadapter-4.0.0/README.md +529 -0
- erispulse_discordadapter-4.0.0/pyproject.toml +18 -0
- erispulse_discordadapter-4.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import re
|
|
3
|
+
import time
|
|
4
|
+
from typing import Dict, List, Optional
|
|
5
|
+
|
|
6
|
+
# Discord mention patterns
|
|
7
|
+
_MENTION_USER = re.compile(r"<@!?(\d+)>")
|
|
8
|
+
_MENTION_ROLE = re.compile(r"<@&(\d+)>")
|
|
9
|
+
_MENTION_CHANNEL = re.compile(r"<#(\d+)>")
|
|
10
|
+
_MENTION_ALL = re.compile(r"<@&?(\d+)>|<#(\d+)>")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DiscordConverter:
|
|
14
|
+
"""
|
|
15
|
+
Discord 事件转换器
|
|
16
|
+
|
|
17
|
+
将 Discord Gateway Dispatch 事件转换为 ErisPulse OneBot12 标准格式。
|
|
18
|
+
|
|
19
|
+
核心原则:
|
|
20
|
+
1. 严格兼容:所有标准字段遵循 OneBot12 规范
|
|
21
|
+
2. 明确扩展:平台特有功能使用 discord_ 前缀
|
|
22
|
+
3. 数据完整:原始事件数据保留在 discord_raw 字段
|
|
23
|
+
4. 时间统一:所有时间戳转换为 10 位 Unix 时间戳(秒级)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# 消息类事件
|
|
27
|
+
MESSAGE_EVENTS = {
|
|
28
|
+
"MESSAGE_CREATE",
|
|
29
|
+
"MESSAGE_UPDATE",
|
|
30
|
+
"MESSAGE_DELETE",
|
|
31
|
+
"MESSAGE_DELETE_BULK",
|
|
32
|
+
"MESSAGE_REACTION_ADD",
|
|
33
|
+
"MESSAGE_REACTION_REMOVE",
|
|
34
|
+
"MESSAGE_REACTION_REMOVE_ALL",
|
|
35
|
+
"MESSAGE_REACTION_REMOVE_EMOJI",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# 通知类事件
|
|
39
|
+
NOTICE_EVENTS = {
|
|
40
|
+
"GUILD_MEMBER_ADD",
|
|
41
|
+
"GUILD_MEMBER_REMOVE",
|
|
42
|
+
"GUILD_MEMBER_UPDATE",
|
|
43
|
+
"GUILD_ROLE_CREATE",
|
|
44
|
+
"GUILD_ROLE_DELETE",
|
|
45
|
+
"GUILD_ROLE_UPDATE",
|
|
46
|
+
"CHANNEL_CREATE",
|
|
47
|
+
"CHANNEL_DELETE",
|
|
48
|
+
"CHANNEL_UPDATE",
|
|
49
|
+
"TYPING_START",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# 通知 detail_type 映射
|
|
53
|
+
NOTICE_DETAIL_MAP = {
|
|
54
|
+
"GUILD_MEMBER_ADD": "group_member_increase",
|
|
55
|
+
"GUILD_MEMBER_REMOVE": "group_member_decrease",
|
|
56
|
+
"GUILD_MEMBER_UPDATE": "group_member_update",
|
|
57
|
+
"GUILD_ROLE_CREATE": "group_role_create",
|
|
58
|
+
"GUILD_ROLE_DELETE": "group_role_delete",
|
|
59
|
+
"GUILD_ROLE_UPDATE": "group_role_update",
|
|
60
|
+
"CHANNEL_CREATE": "channel_create",
|
|
61
|
+
"CHANNEL_DELETE": "channel_delete",
|
|
62
|
+
"CHANNEL_UPDATE": "channel_update",
|
|
63
|
+
"TYPING_START": "typing",
|
|
64
|
+
"MESSAGE_DELETE": "group_message_delete",
|
|
65
|
+
"MESSAGE_DELETE_BULK": "group_message_delete_bulk",
|
|
66
|
+
"MESSAGE_REACTION_ADD": "group_message_reaction_add",
|
|
67
|
+
"MESSAGE_REACTION_REMOVE": "group_message_reaction_remove",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def __init__(self):
|
|
71
|
+
self.bot_id = ""
|
|
72
|
+
|
|
73
|
+
def convert(self, raw_data: Dict, event_name: str) -> Optional[Dict]:
|
|
74
|
+
"""
|
|
75
|
+
将 Discord Dispatch 事件转换为 OneBot12 标准格式
|
|
76
|
+
|
|
77
|
+
:param raw_data: Discord Dispatch 的 d 字段(事件数据)
|
|
78
|
+
:param event_name: Discord Dispatch 的 t 字段(事件名)
|
|
79
|
+
:return: OneBot12 标准格式事件,不支持的事件返回 None
|
|
80
|
+
"""
|
|
81
|
+
if not isinstance(raw_data, dict):
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
event_type, detail_type = self._map_event_type(event_name, raw_data)
|
|
85
|
+
|
|
86
|
+
base = self._create_base_event(raw_data, event_name, event_type, detail_type)
|
|
87
|
+
|
|
88
|
+
handler = getattr(self, f"_handle_{event_name.lower()}", None)
|
|
89
|
+
if handler:
|
|
90
|
+
return handler(raw_data, base)
|
|
91
|
+
|
|
92
|
+
# 通用处理:尝试提取用户信息
|
|
93
|
+
self._fill_user_info(raw_data, base)
|
|
94
|
+
return base
|
|
95
|
+
|
|
96
|
+
# ==================== 基础事件构建 ====================
|
|
97
|
+
|
|
98
|
+
def _create_base_event(
|
|
99
|
+
self, raw_data: Dict, event_name: str, event_type: str, detail_type: str
|
|
100
|
+
) -> Dict:
|
|
101
|
+
return {
|
|
102
|
+
"id": self._generate_id(raw_data, event_name),
|
|
103
|
+
"time": self._extract_time(raw_data),
|
|
104
|
+
"type": event_type,
|
|
105
|
+
"detail_type": detail_type,
|
|
106
|
+
"platform": "discord",
|
|
107
|
+
"self": {
|
|
108
|
+
"platform": "discord",
|
|
109
|
+
"user_id": self.bot_id,
|
|
110
|
+
},
|
|
111
|
+
"discord_raw": raw_data,
|
|
112
|
+
"discord_raw_type": event_name,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def _map_event_type(self, event_name: str, raw_data: Dict) -> tuple:
|
|
116
|
+
if event_name in self.MESSAGE_EVENTS:
|
|
117
|
+
return "message", self._get_detail_type(raw_data)
|
|
118
|
+
elif event_name == "INTERACTION_CREATE":
|
|
119
|
+
return "request", "interaction"
|
|
120
|
+
elif event_name in self.NOTICE_EVENTS:
|
|
121
|
+
detail = self.NOTICE_DETAIL_MAP.get(event_name, event_name.lower())
|
|
122
|
+
return "notice", detail
|
|
123
|
+
elif event_name in ("READY", "RESUMED"):
|
|
124
|
+
return "meta", event_name.lower()
|
|
125
|
+
else:
|
|
126
|
+
return "notice", event_name.lower()
|
|
127
|
+
|
|
128
|
+
def _get_detail_type(self, raw_data: Dict) -> str:
|
|
129
|
+
if "guild_id" in raw_data:
|
|
130
|
+
return "channel"
|
|
131
|
+
return "private"
|
|
132
|
+
|
|
133
|
+
def _extract_time(self, raw_data: Dict) -> int:
|
|
134
|
+
timestamp = raw_data.get("timestamp")
|
|
135
|
+
if timestamp:
|
|
136
|
+
try:
|
|
137
|
+
dt = datetime.datetime.fromisoformat(
|
|
138
|
+
str(timestamp).replace("Z", "+00:00")
|
|
139
|
+
)
|
|
140
|
+
return int(dt.timestamp())
|
|
141
|
+
except Exception:
|
|
142
|
+
pass
|
|
143
|
+
return int(time.time())
|
|
144
|
+
|
|
145
|
+
def _generate_id(self, raw_data: Dict, event_name: str) -> str:
|
|
146
|
+
msg_id = raw_data.get("id") or raw_data.get("message_id")
|
|
147
|
+
if msg_id:
|
|
148
|
+
return str(msg_id)
|
|
149
|
+
return f"{event_name}_{int(time.time() * 1000)}"
|
|
150
|
+
|
|
151
|
+
def _fill_user_info(self, raw_data: Dict, base: Dict):
|
|
152
|
+
author = raw_data.get("author") or raw_data.get("user") or {}
|
|
153
|
+
if author:
|
|
154
|
+
base["user_id"] = str(author.get("id", ""))
|
|
155
|
+
base["user_nickname"] = author.get("global_name") or author.get(
|
|
156
|
+
"username", ""
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# ==================== 消息事件处理 ====================
|
|
160
|
+
|
|
161
|
+
def _handle_message_create(self, raw_data: Dict, base: Dict) -> Dict:
|
|
162
|
+
author = raw_data.get("author", {})
|
|
163
|
+
|
|
164
|
+
base["message_id"] = str(raw_data.get("id", ""))
|
|
165
|
+
base["user_id"] = str(author.get("id", ""))
|
|
166
|
+
base["user_nickname"] = author.get("global_name") or author.get("username", "")
|
|
167
|
+
|
|
168
|
+
# 解析消息段
|
|
169
|
+
segments = self._parse_message_content(raw_data)
|
|
170
|
+
base["message"] = segments
|
|
171
|
+
base["alt_message"] = self._generate_alt_message(segments)
|
|
172
|
+
|
|
173
|
+
# 频道/服务器信息
|
|
174
|
+
channel_id = str(raw_data.get("channel_id", ""))
|
|
175
|
+
guild_id = raw_data.get("guild_id")
|
|
176
|
+
|
|
177
|
+
base["channel_id"] = channel_id
|
|
178
|
+
base["discord_channel_id"] = channel_id
|
|
179
|
+
if guild_id:
|
|
180
|
+
base["discord_guild_id"] = str(guild_id)
|
|
181
|
+
base["group_id"] = channel_id
|
|
182
|
+
base["target_id"] = channel_id
|
|
183
|
+
else:
|
|
184
|
+
base["group_id"] = channel_id
|
|
185
|
+
base["target_id"] = base["user_id"]
|
|
186
|
+
|
|
187
|
+
# 话题/Thread 支持
|
|
188
|
+
if raw_data.get("thread"):
|
|
189
|
+
thread = raw_data["thread"]
|
|
190
|
+
if thread.get("id"):
|
|
191
|
+
base["thread_id"] = str(thread["id"])
|
|
192
|
+
|
|
193
|
+
# member 信息扩展
|
|
194
|
+
member = raw_data.get("member")
|
|
195
|
+
if member and isinstance(member, dict):
|
|
196
|
+
base["discord_member"] = member
|
|
197
|
+
if member.get("nick"):
|
|
198
|
+
base["discord_member_nick"] = member["nick"]
|
|
199
|
+
|
|
200
|
+
return base
|
|
201
|
+
|
|
202
|
+
def _handle_message_update(self, raw_data: Dict, base: Dict) -> Dict:
|
|
203
|
+
base["message_id"] = str(raw_data.get("id", ""))
|
|
204
|
+
|
|
205
|
+
author = raw_data.get("author")
|
|
206
|
+
if author:
|
|
207
|
+
base["user_id"] = str(author.get("id", ""))
|
|
208
|
+
base["user_nickname"] = author.get("global_name") or author.get(
|
|
209
|
+
"username", ""
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
segments = self._parse_message_content(raw_data)
|
|
213
|
+
base["message"] = segments
|
|
214
|
+
base["alt_message"] = self._generate_alt_message(segments)
|
|
215
|
+
|
|
216
|
+
channel_id = str(raw_data.get("channel_id", ""))
|
|
217
|
+
guild_id = raw_data.get("guild_id")
|
|
218
|
+
base["discord_channel_id"] = channel_id
|
|
219
|
+
if guild_id:
|
|
220
|
+
base["discord_guild_id"] = str(guild_id)
|
|
221
|
+
base["group_id"] = channel_id
|
|
222
|
+
base["discord_edit_time"] = int(time.time())
|
|
223
|
+
|
|
224
|
+
return base
|
|
225
|
+
|
|
226
|
+
def _handle_message_delete(self, raw_data: Dict, base: Dict) -> Dict:
|
|
227
|
+
base["message_id"] = str(raw_data.get("id", ""))
|
|
228
|
+
channel_id = str(raw_data.get("channel_id", ""))
|
|
229
|
+
guild_id = raw_data.get("guild_id")
|
|
230
|
+
base["discord_channel_id"] = channel_id
|
|
231
|
+
if guild_id:
|
|
232
|
+
base["discord_guild_id"] = str(guild_id)
|
|
233
|
+
base["group_id"] = channel_id
|
|
234
|
+
else:
|
|
235
|
+
base["detail_type"] = "private_message_delete"
|
|
236
|
+
return base
|
|
237
|
+
|
|
238
|
+
def _handle_message_delete_bulk(self, raw_data: Dict, base: Dict) -> Dict:
|
|
239
|
+
ids = raw_data.get("ids", [])
|
|
240
|
+
base["message_ids"] = [str(i) for i in ids]
|
|
241
|
+
channel_id = str(raw_data.get("channel_id", ""))
|
|
242
|
+
guild_id = raw_data.get("guild_id")
|
|
243
|
+
base["discord_channel_id"] = channel_id
|
|
244
|
+
if guild_id:
|
|
245
|
+
base["discord_guild_id"] = str(guild_id)
|
|
246
|
+
base["group_id"] = channel_id
|
|
247
|
+
return base
|
|
248
|
+
|
|
249
|
+
# ==================== 反应事件处理 ====================
|
|
250
|
+
|
|
251
|
+
def _handle_message_reaction_add(self, raw_data: Dict, base: Dict) -> Dict:
|
|
252
|
+
return self._fill_reaction_event(raw_data, base)
|
|
253
|
+
|
|
254
|
+
def _handle_message_reaction_remove(self, raw_data: Dict, base: Dict) -> Dict:
|
|
255
|
+
return self._fill_reaction_event(raw_data, base)
|
|
256
|
+
|
|
257
|
+
def _fill_reaction_event(self, raw_data: Dict, base: Dict) -> Dict:
|
|
258
|
+
base["message_id"] = str(raw_data.get("message_id", ""))
|
|
259
|
+
user_id = raw_data.get("user_id")
|
|
260
|
+
if user_id:
|
|
261
|
+
base["user_id"] = str(user_id)
|
|
262
|
+
channel_id = str(raw_data.get("channel_id", ""))
|
|
263
|
+
guild_id = raw_data.get("guild_id")
|
|
264
|
+
base["discord_channel_id"] = channel_id
|
|
265
|
+
if guild_id:
|
|
266
|
+
base["discord_guild_id"] = str(guild_id)
|
|
267
|
+
base["group_id"] = channel_id
|
|
268
|
+
emoji = raw_data.get("emoji", {})
|
|
269
|
+
if emoji:
|
|
270
|
+
base["discord_emoji"] = emoji
|
|
271
|
+
return base
|
|
272
|
+
|
|
273
|
+
# ==================== 通知事件处理 ====================
|
|
274
|
+
|
|
275
|
+
def _handle_guild_member_add(self, raw_data: Dict, base: Dict) -> Dict:
|
|
276
|
+
user = raw_data.get("user", {})
|
|
277
|
+
base["user_id"] = str(user.get("id", ""))
|
|
278
|
+
base["user_nickname"] = user.get("global_name") or user.get("username", "")
|
|
279
|
+
guild_id = raw_data.get("guild_id")
|
|
280
|
+
if guild_id:
|
|
281
|
+
base["discord_guild_id"] = str(guild_id)
|
|
282
|
+
base["group_id"] = str(guild_id)
|
|
283
|
+
return base
|
|
284
|
+
|
|
285
|
+
def _handle_guild_member_remove(self, raw_data: Dict, base: Dict) -> Dict:
|
|
286
|
+
user = raw_data.get("user", {})
|
|
287
|
+
base["user_id"] = str(user.get("id", ""))
|
|
288
|
+
base["user_nickname"] = user.get("global_name") or user.get("username", "")
|
|
289
|
+
guild_id = raw_data.get("guild_id")
|
|
290
|
+
if guild_id:
|
|
291
|
+
base["discord_guild_id"] = str(guild_id)
|
|
292
|
+
base["group_id"] = str(guild_id)
|
|
293
|
+
return base
|
|
294
|
+
|
|
295
|
+
def _handle_guild_member_update(self, raw_data: Dict, base: Dict) -> Dict:
|
|
296
|
+
user = raw_data.get("user", {})
|
|
297
|
+
base["user_id"] = str(user.get("id", ""))
|
|
298
|
+
base["user_nickname"] = user.get("global_name") or user.get("username", "")
|
|
299
|
+
guild_id = raw_data.get("guild_id")
|
|
300
|
+
if guild_id:
|
|
301
|
+
base["discord_guild_id"] = str(guild_id)
|
|
302
|
+
base["group_id"] = str(guild_id)
|
|
303
|
+
return base
|
|
304
|
+
|
|
305
|
+
def _handle_guild_role_create(self, raw_data: Dict, base: Dict) -> Dict:
|
|
306
|
+
guild_id = raw_data.get("guild_id")
|
|
307
|
+
if guild_id:
|
|
308
|
+
base["discord_guild_id"] = str(guild_id)
|
|
309
|
+
base["group_id"] = str(guild_id)
|
|
310
|
+
role = raw_data.get("role", {})
|
|
311
|
+
if role:
|
|
312
|
+
base["discord_role"] = role
|
|
313
|
+
return base
|
|
314
|
+
|
|
315
|
+
def _handle_guild_role_delete(self, raw_data: Dict, base: Dict) -> Dict:
|
|
316
|
+
guild_id = raw_data.get("guild_id")
|
|
317
|
+
if guild_id:
|
|
318
|
+
base["discord_guild_id"] = str(guild_id)
|
|
319
|
+
base["group_id"] = str(guild_id)
|
|
320
|
+
role_id = raw_data.get("role_id")
|
|
321
|
+
if role_id:
|
|
322
|
+
base["discord_role_id"] = str(role_id)
|
|
323
|
+
return base
|
|
324
|
+
|
|
325
|
+
def _handle_guild_role_update(self, raw_data: Dict, base: Dict) -> Dict:
|
|
326
|
+
return self._handle_guild_role_create(raw_data, base)
|
|
327
|
+
|
|
328
|
+
def _handle_channel_create(self, raw_data: Dict, base: Dict) -> Dict:
|
|
329
|
+
base["discord_channel"] = raw_data
|
|
330
|
+
guild_id = raw_data.get("guild_id")
|
|
331
|
+
if guild_id:
|
|
332
|
+
base["discord_guild_id"] = str(guild_id)
|
|
333
|
+
channel_id = raw_data.get("id")
|
|
334
|
+
if channel_id:
|
|
335
|
+
base["discord_channel_id"] = str(channel_id)
|
|
336
|
+
return base
|
|
337
|
+
|
|
338
|
+
def _handle_channel_delete(self, raw_data: Dict, base: Dict) -> Dict:
|
|
339
|
+
return self._handle_channel_create(raw_data, base)
|
|
340
|
+
|
|
341
|
+
def _handle_channel_update(self, raw_data: Dict, base: Dict) -> Dict:
|
|
342
|
+
return self._handle_channel_create(raw_data, base)
|
|
343
|
+
|
|
344
|
+
def _handle_typing_start(self, raw_data: Dict, base: Dict) -> Dict:
|
|
345
|
+
user_id = raw_data.get("user_id")
|
|
346
|
+
if user_id:
|
|
347
|
+
base["user_id"] = str(user_id)
|
|
348
|
+
channel_id = str(raw_data.get("channel_id", ""))
|
|
349
|
+
guild_id = raw_data.get("guild_id")
|
|
350
|
+
base["discord_channel_id"] = channel_id
|
|
351
|
+
if guild_id:
|
|
352
|
+
base["discord_guild_id"] = str(guild_id)
|
|
353
|
+
base["group_id"] = channel_id
|
|
354
|
+
return base
|
|
355
|
+
|
|
356
|
+
# ==================== 交互事件处理 ====================
|
|
357
|
+
|
|
358
|
+
def _handle_interaction_create(self, raw_data: Dict, base: Dict) -> Dict:
|
|
359
|
+
user = raw_data.get("user") or raw_data.get("member", {}).get("user", {})
|
|
360
|
+
if user:
|
|
361
|
+
base["user_id"] = str(user.get("id", ""))
|
|
362
|
+
base["user_nickname"] = user.get("global_name") or user.get("username", "")
|
|
363
|
+
channel_id = str(raw_data.get("channel_id", ""))
|
|
364
|
+
guild_id = raw_data.get("guild_id")
|
|
365
|
+
base["discord_channel_id"] = channel_id
|
|
366
|
+
if guild_id:
|
|
367
|
+
base["discord_guild_id"] = str(guild_id)
|
|
368
|
+
base["group_id"] = channel_id
|
|
369
|
+
base["discord_interaction"] = raw_data
|
|
370
|
+
return base
|
|
371
|
+
|
|
372
|
+
# ==================== 消息内容解析 ====================
|
|
373
|
+
|
|
374
|
+
def _parse_message_content(self, raw_data: Dict) -> List[Dict]:
|
|
375
|
+
segments = []
|
|
376
|
+
|
|
377
|
+
content = raw_data.get("content") or ""
|
|
378
|
+
if content:
|
|
379
|
+
segments.extend(self._parse_text_with_mentions(content, raw_data))
|
|
380
|
+
|
|
381
|
+
# Embeds
|
|
382
|
+
for embed in raw_data.get("embeds", []):
|
|
383
|
+
segments.append(
|
|
384
|
+
{
|
|
385
|
+
"type": "discord_embed",
|
|
386
|
+
"data": {"embed": embed},
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# Attachments
|
|
391
|
+
for attachment in raw_data.get("attachments", []):
|
|
392
|
+
url = attachment.get("url", "")
|
|
393
|
+
filename = attachment.get("filename", "")
|
|
394
|
+
content_type = attachment.get("content_type", "")
|
|
395
|
+
seg_type = "file"
|
|
396
|
+
if content_type.startswith("image"):
|
|
397
|
+
seg_type = "image"
|
|
398
|
+
elif content_type.startswith("video"):
|
|
399
|
+
seg_type = "video"
|
|
400
|
+
elif content_type.startswith("audio"):
|
|
401
|
+
seg_type = "audio"
|
|
402
|
+
segments.append(
|
|
403
|
+
{
|
|
404
|
+
"type": seg_type,
|
|
405
|
+
"data": {
|
|
406
|
+
"file": url,
|
|
407
|
+
"file_id": attachment.get("id", ""),
|
|
408
|
+
"file_name": filename,
|
|
409
|
+
"url": url,
|
|
410
|
+
"content_type": content_type,
|
|
411
|
+
},
|
|
412
|
+
}
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Stickers
|
|
416
|
+
for sticker in raw_data.get("sticker_items", []):
|
|
417
|
+
segments.append(
|
|
418
|
+
{
|
|
419
|
+
"type": "discord_sticker",
|
|
420
|
+
"data": sticker,
|
|
421
|
+
}
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Components (buttons, selects)
|
|
425
|
+
components = raw_data.get("components")
|
|
426
|
+
if components:
|
|
427
|
+
segments.append(
|
|
428
|
+
{
|
|
429
|
+
"type": "discord_components",
|
|
430
|
+
"data": {"components": components},
|
|
431
|
+
}
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
if not segments:
|
|
435
|
+
segments.append({"type": "text", "data": {"text": ""}})
|
|
436
|
+
|
|
437
|
+
return segments
|
|
438
|
+
|
|
439
|
+
def _parse_text_with_mentions(self, content: str, raw_data: Dict) -> List[Dict]:
|
|
440
|
+
"""解析 Discord 文本内容,将 mention 格式转换为 mention 消息段"""
|
|
441
|
+
segments = []
|
|
442
|
+
|
|
443
|
+
user_mentions = {}
|
|
444
|
+
for m in raw_data.get("mentions", []):
|
|
445
|
+
uid = m.get("id", "")
|
|
446
|
+
if uid:
|
|
447
|
+
user_mentions[uid] = m
|
|
448
|
+
|
|
449
|
+
# 合并所有 mention 模式
|
|
450
|
+
pattern = re.compile(r"<@!?(\d+)>|<@&(\d+)>|<#(\d+)>")
|
|
451
|
+
|
|
452
|
+
last_end = 0
|
|
453
|
+
for match in pattern.finditer(content):
|
|
454
|
+
start, end = match.span()
|
|
455
|
+
|
|
456
|
+
if start > last_end:
|
|
457
|
+
text = content[last_end:start]
|
|
458
|
+
if text:
|
|
459
|
+
segments.append({"type": "text", "data": {"text": text}})
|
|
460
|
+
|
|
461
|
+
user_id = match.group(1)
|
|
462
|
+
role_id = match.group(2)
|
|
463
|
+
channel_ref_id = match.group(3)
|
|
464
|
+
|
|
465
|
+
if user_id:
|
|
466
|
+
user_info = user_mentions.get(user_id, {})
|
|
467
|
+
segments.append(
|
|
468
|
+
{
|
|
469
|
+
"type": "mention",
|
|
470
|
+
"data": {
|
|
471
|
+
"user_id": user_id,
|
|
472
|
+
"user_nickname": (
|
|
473
|
+
user_info.get("global_name")
|
|
474
|
+
or user_info.get("username", "")
|
|
475
|
+
),
|
|
476
|
+
},
|
|
477
|
+
}
|
|
478
|
+
)
|
|
479
|
+
elif role_id:
|
|
480
|
+
segments.append(
|
|
481
|
+
{
|
|
482
|
+
"type": "discord_role_mention",
|
|
483
|
+
"data": {"role_id": role_id},
|
|
484
|
+
}
|
|
485
|
+
)
|
|
486
|
+
elif channel_ref_id:
|
|
487
|
+
segments.append(
|
|
488
|
+
{
|
|
489
|
+
"type": "discord_channel_mention",
|
|
490
|
+
"data": {"channel_id": channel_ref_id},
|
|
491
|
+
}
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
last_end = end
|
|
495
|
+
|
|
496
|
+
if last_end < len(content):
|
|
497
|
+
text = content[last_end:]
|
|
498
|
+
if text:
|
|
499
|
+
segments.append({"type": "text", "data": {"text": text}})
|
|
500
|
+
|
|
501
|
+
if not segments and content:
|
|
502
|
+
segments.append({"type": "text", "data": {"text": content}})
|
|
503
|
+
|
|
504
|
+
return segments
|
|
505
|
+
|
|
506
|
+
def _generate_alt_message(self, segments: List[Dict]) -> str:
|
|
507
|
+
parts = []
|
|
508
|
+
for seg in segments:
|
|
509
|
+
t = seg.get("type", "")
|
|
510
|
+
d = seg.get("data", {})
|
|
511
|
+
if t == "text":
|
|
512
|
+
parts.append(d.get("text", ""))
|
|
513
|
+
elif t == "mention":
|
|
514
|
+
parts.append(f"@{d.get('user_nickname', d.get('user_id', ''))}")
|
|
515
|
+
elif t == "mention_all":
|
|
516
|
+
parts.append("@everyone")
|
|
517
|
+
elif t == "discord_role_mention":
|
|
518
|
+
parts.append(f"@&{d.get('role_id', '')}")
|
|
519
|
+
elif t == "discord_channel_mention":
|
|
520
|
+
parts.append(f"#{d.get('channel_id', '')}")
|
|
521
|
+
elif t == "image":
|
|
522
|
+
parts.append("[图片]")
|
|
523
|
+
elif t == "video":
|
|
524
|
+
parts.append("[视频]")
|
|
525
|
+
elif t == "audio":
|
|
526
|
+
parts.append("[语音]")
|
|
527
|
+
elif t == "file":
|
|
528
|
+
parts.append(f"[文件:{d.get('file_name', '')}]")
|
|
529
|
+
elif t == "discord_embed":
|
|
530
|
+
parts.append("[嵌入消息]")
|
|
531
|
+
elif t == "discord_sticker":
|
|
532
|
+
parts.append("[贴纸]")
|
|
533
|
+
elif t == "discord_components":
|
|
534
|
+
parts.append("[组件]")
|
|
535
|
+
return "".join(parts)
|