nagisa_types/event.rs
1//! 统一事件 [`Event`] 及其各类载荷:[`MessageEvent`](消息)、[`Notice`](通知)、
2//! [`Request`](请求,配 [`RequestToken`] 回传同意/拒绝)、[`Meta`](连接/就绪/心跳等元事件),
3//! 外加逃生口 [`Event::Raw`]。事件按细粒度变体建模(OneBot 的重载哨兵在适配器侧拆成具体变体),
4//! 各变体字段的跨协议来源/缺口写在对应 `///` 上。[`Event`] 上的 [`peer`](Event::peer) /
5//! [`group`](Event::group) / [`sender`](Event::sender) 是跨变体抽取寻址信息的便捷方法。
6use crate::capability::Protocol;
7use crate::entity::{FileMeta, FriendInfo, GroupInfo, ImplStatus, MemberInfo};
8use crate::id::{MessageId, Peer, Scene, Uin};
9use crate::message::Message;
10use serde_json::Value;
11
12#[derive(Clone, Debug)]
13#[non_exhaustive]
14pub enum Event {
15 /// `MessageEvent` 较大且是热路径;装箱以避免 `Event` 整体膨胀
16 /// (`Event` 通常以 `Arc<Event>` 传递,模式匹配会自动穿透 `Box`)。
17 Message(Box<MessageEvent>),
18 Notice(Notice),
19 Request(Request),
20 Meta(Meta),
21 /// 逃生口:未知/协议私有事件,绝不丢弃。
22 Raw(RawEvent),
23}
24
25/// 群匿名消息发送者标识(OneBot 群消息 `sub_type=anonymous` 的 `anonymous` 子对象)。
26/// `flag` 用于 `set_group_anonymous_ban`。OneBot-only;Milky 为 None。
27/// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/event/message.md (§群消息 anonymous)
28#[derive(Clone, Debug)]
29pub struct Anonymous {
30 pub id: i64,
31 pub name: String,
32 pub flag: String,
33}
34
35/// 消息气泡样式(Lagrange.OneBot 群/私聊消息事件的 `message_style` 块)。
36/// 仅在 Lagrange 端出现;其余协议为 None。`bubble_id`/`pendant_id` 为气泡/挂件
37/// 资源 id,`pal_type` 为伙伴类型;`raw` 保留完整原始块以防协议追加字段(绝不丢弃)。
38/// ENDPOINT: Lagrange.OneBot Lagrange.OneBot/Message/MessageStyle(message_style 块)
39/// (https://github.com/LagrangeDev/Lagrange.Core)。
40#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct MessageStyle {
42 pub bubble_id: Option<i64>,
43 pub pendant_id: Option<i64>,
44 pub pal_type: Option<i64>,
45 pub raw: Value,
46}
47
48#[derive(Clone, Debug)]
49pub struct MessageEvent {
50 pub id: MessageId,
51 pub peer: Peer,
52 pub sender: Uin,
53 pub self_id: Uin,
54 pub time: i64,
55 pub content: Message,
56 pub is_self: bool,
57 pub group: Option<GroupInfo>,
58 pub member: Option<MemberInfo>,
59 /// 好友场景消息附带的好友实体(Milky friend-scene 给实体;OneBot 为 None)。
60 pub friend: Option<FriendInfo>,
61 /// 群匿名消息的匿名发送者标识(OneBot-only;非匿名/Milky 为 None)。
62 pub anonymous: Option<Anonymous>,
63 /// 消息字体(OneBot `font`,遗留字段;Milky 为 None)。
64 pub font: Option<i32>,
65 /// 私聊临时会话的来源对端(OneBot/LLOneBot 私聊消息的 `target_id`:本号给
66 /// 对端发私聊时对端 uin);群消息/Milky 为 None。
67 /// ENDPOINT: LLOneBot/NapCat 私聊消息事件 `target_id` 字段。
68 pub target_id: Option<Uin>,
69 /// 消息气泡样式(Lagrange `message_style` 块);其余协议为 None。
70 pub message_style: Option<MessageStyle>,
71 pub raw: Value,
72}
73
74#[derive(Clone, Debug)]
75pub struct NudgeDisplay {
76 pub action: String,
77 pub suffix: String,
78 pub action_img_url: Option<String>,
79}
80
81#[derive(Clone, Copy, PartialEq, Eq, Debug)]
82pub enum ReactionKind {
83 Face,
84 Emoji,
85}
86
87/// 群荣誉类型。
88/// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md (§群成员荣誉变更)
89#[derive(Clone, Copy, PartialEq, Eq, Debug)]
90pub enum HonorKind {
91 /// 龙王。
92 Talkative,
93 /// 群聊之火。
94 Performer,
95 /// 快乐源泉。
96 Emotion,
97 /// 协议未知值——降级于此,绝不 panic。
98 Unknown,
99}
100
101/// 群成员减少的原因(OneBot `group_decrease` 的 sub_type)。
102/// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md (§群成员减少)
103///
104/// **跨协议非对称(固有限制)**:[`KickMe`](Self::KickMe) 仅 **OneBot** wire 能产出
105/// (`group_decrease` sub_type=`kick_me`)。Milky 的 `GroupMemberDecreaseNotification`
106/// 不区分「被踢的是不是 bot 自己」,只能解码为 [`Leave`](Self::Leave)/[`Kick`](Self::Kick)
107/// ——故下游「bot 自身被踢」的 gating 逻辑**不得依赖 Milky 后端下的 `KickMe`**
108/// (Milky 永不产出它)。这是 Milky wire 的天然缺口,非解码缺陷。
109#[derive(Clone, Copy, PartialEq, Eq, Debug)]
110pub enum MemberDecreaseReason {
111 /// 主动退群。
112 Leave,
113 /// 被管理员踢出。
114 Kick,
115 /// 机器人自身被踢出。**仅 OneBot 可产出**(Milky wire 无此区分,详见枚举级文档)。
116 KickMe,
117 /// 群解散(NapCat `group_decrease` sub_type=`disband`,群主解散群时对每个成员产出)。
118 /// ENDPOINT: NapCat packages/napcat-onebot/event/notice/OB11GroupDecreaseEvent.ts
119 /// (https://github.com/NapNeko/NapCatQQ)。
120 Disband,
121 /// 协议未提供/未知。
122 Unknown,
123}
124
125/// 单条表情回应(`group_msg_emoji_like` 的 `likes[]` 元素 / Milky/Lagrange 单 reaction)。
126/// `count` 为该 emoji 的累计回应人数(缺省 None)。
127/// ENDPOINT: NapCat packages/napcat-onebot/event/notice/OB11MsgEmojiLikeEvent.ts
128/// (https://github.com/NapNeko/NapCatQQ);cross-checked LLOneBot
129/// src/onebot11/event/notice/OB11MsgEmojiLikeEvent.ts。
130#[derive(Clone, Debug, PartialEq, Eq)]
131pub struct EmojiLike {
132 pub face_id: String,
133 pub count: Option<i64>,
134}
135
136/// 在线(临时)文件 notice 的方向(NapCat `online_file_send`/`online_file_receive`)。
137/// ENDPOINT: NapCat(在线文件收发 notice)。
138#[derive(Clone, Copy, PartialEq, Eq, Debug)]
139pub enum OnlineFileDirection {
140 /// 我方/对端发出在线文件(`online_file_send`)。
141 Send,
142 /// 收到在线文件(`online_file_receive`)。
143 Receive,
144}
145
146/// 闪传(flash transfer)notice 的进度阶段(LLOneBot `flash_file` 的 sub_type)。
147/// ENDPOINT: LLOneBot src/onebot11/event/notice/OB11FlashTransferNoticeEvent.ts
148/// (https://github.com/LLOneBot/LLOneBot)。
149#[derive(Clone, Copy, PartialEq, Eq, Debug)]
150pub enum FlashFilePhase {
151 Downloading,
152 Downloaded,
153 Uploading,
154 Uploaded,
155 /// 协议未知阶段——降级于此,绝不 panic。
156 Unknown,
157}
158
159#[derive(Clone, Debug)]
160#[non_exhaustive]
161pub enum Notice {
162 Recall {
163 peer: Peer,
164 id: MessageId,
165 sender: Uin,
166 operator: Uin,
167 suffix: Option<String>,
168 },
169 MemberIncrease {
170 group: Uin,
171 user: Uin,
172 operator: Option<Uin>,
173 invitor: Option<Uin>,
174 },
175 MemberDecrease {
176 group: Uin,
177 user: Uin,
178 operator: Option<Uin>,
179 reason: MemberDecreaseReason,
180 },
181 AdminChange {
182 group: Uin,
183 user: Uin,
184 operator: Option<Uin>,
185 is_set: bool,
186 },
187 Mute {
188 group: Uin,
189 user: Uin,
190 operator: Uin,
191 duration: i32,
192 },
193 WholeMute {
194 group: Uin,
195 operator: Uin,
196 is_mute: bool,
197 },
198 GroupNameChange {
199 group: Uin,
200 new_name: String,
201 operator: Uin,
202 },
203 /// 群红包运气王(OneBot `notify/lucky_king`)。`user` = 发红包者,`target` = 运气王。
204 /// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md (§群红包运气王)
205 LuckyKing {
206 group: Uin,
207 user: Uin,
208 target: Uin,
209 },
210 /// 群成员荣誉变更(龙王/群聊之火/快乐源泉)。
211 /// OFFICIAL: https://github.com/botuniverse/onebot-11/blob/master/event/notice.md (§群成员荣誉变更)
212 Honor {
213 group: Uin,
214 user: Uin,
215 honor: HonorKind,
216 },
217 /// 群名片变更。
218 /// ENDPOINT: NapCat https://github.com/NapNeko/NapCatQQ/blob/main/src/onebot/event/notice/OB11GroupCardEvent.ts
219 GroupCardChange {
220 group: Uin,
221 user: Uin,
222 old_card: String,
223 new_card: String,
224 },
225 FriendNudge {
226 user: Uin,
227 is_self_send: bool,
228 is_self_receive: bool,
229 display: NudgeDisplay,
230 },
231 GroupNudge {
232 group: Uin,
233 sender: Uin,
234 receiver: Uin,
235 display: NudgeDisplay,
236 },
237 /// 表情回应。`face_id`/`count` 反映首个 emoji(向后兼容单事件语义);
238 /// `likes` 携带本次 notice 的**全部** emoji(NapCat/LLOneBot `group_msg_emoji_like`
239 /// 可一次回应多个 emoji),其余协议为单元素。绝不丢弃非首 like。
240 /// `sub_type` 为原始 wire 子类型(Lagrange `reaction` 的 `sub_type`:`add`/`remove`),
241 /// `is_add` 由其派生;非 Lagrange 来源(Milky/emoji_like 等无独立 sub_type)为 None。
242 Reaction {
243 group: Uin,
244 user: Uin,
245 seq: i64,
246 face_id: String,
247 kind: ReactionKind,
248 is_add: bool,
249 sub_type: Option<String>,
250 count: Option<i64>,
251 likes: Vec<EmojiLike>,
252 },
253 /// 群精华消息变更。`sender` 为被设/取消精华消息的原作者(Lagrange `essence`
254 /// 的 `sender_id`);OneBot v11/Milky 无该字段时为 None。
255 EssenceChange {
256 group: Uin,
257 seq: i64,
258 sender: Option<Uin>,
259 operator: Option<Uin>,
260 is_set: bool,
261 },
262 GroupFileUpload {
263 group: Uin,
264 user: Uin,
265 file: FileMeta,
266 },
267 FriendFileUpload {
268 user: Uin,
269 file: FileMeta,
270 is_self: bool,
271 },
272 /// 新增好友成功(OneBot `friend_add`)。常用于自动打招呼。
273 FriendAdd {
274 user: Uin,
275 },
276 PeerPinChange {
277 peer: Peer,
278 is_pinned: bool,
279 },
280 /// 机器人离线(Lagrange `bot_offline`)。`reason` 为离线原因文案,`tag` 为
281 /// Lagrange 区分离线种类的标签(与 `reason` 不同维度,如踢下线/网络等);
282 /// 无 tag 的来源为 None。
283 BotOffline {
284 reason: String,
285 tag: Option<String>,
286 },
287 /// 群解散(LLOneBot `group_dismiss`:群主解散整个群时下发一次,区别于逐成员的
288 /// `group_decrease`/`disband`)。`operator` 为解散者(通常为群主)。
289 /// ENDPOINT: LLOneBot src/onebot11/event/notice/(群解散 notice)
290 /// (https://github.com/LLOneBot/LLOneBot)。
291 GroupDismiss {
292 group: Uin,
293 operator: Uin,
294 },
295 /// 群头衔变更(NapCat/LLOneBot `notify` sub_type=`title`)。`title` 为新头衔。
296 /// ENDPOINT: NapCat packages/napcat-onebot/event/notice/OB11GroupTitleEvent.ts
297 /// (https://github.com/NapNeko/NapCatQQ);cross-checked LLOneBot。
298 GroupTitleChange {
299 group: Uin,
300 user: Uin,
301 title: String,
302 },
303 /// 输入状态(对端正在输入,NapCat/LLOneBot `notify` sub_type=`input_status`)。
304 /// 群场景 `group` 为 Some;私聊为 None。`status_text` 为展示文案,`event_type` 为
305 /// 原始状态码(0=输入中等,端相关)。
306 /// ENDPOINT: NapCat packages/napcat-onebot/event/notice/OB11InputStatusEvent.ts
307 /// (https://github.com/NapNeko/NapCatQQ);cross-checked LLOneBot。
308 InputStatus {
309 user: Uin,
310 group: Option<Uin>,
311 status_text: String,
312 event_type: i64,
313 },
314 /// 资料卡点赞通知(NapCat/LLOneBot `notify` sub_type=`profile_like`)。
315 /// `operator` 为点赞者,`times` 为本次点赞次数,`operator_nick` 为点赞者昵称(可空)。
316 /// ENDPOINT: NapCat packages/napcat-onebot/event/notice/OB11ProfileLikeEvent.ts
317 /// (https://github.com/NapNeko/NapCatQQ);cross-checked LLOneBot。
318 ProfileLike {
319 operator: Uin,
320 operator_nick: String,
321 times: i64,
322 },
323 /// 灰字提示(NapCat/LLOneBot `notify` sub_type=`gray_tip`)。承载群/好友灰条系统提示
324 /// 文本;具体子类繁多,统一以 `content` 透出文本,结构细节保留在事件 `raw`。
325 /// ENDPOINT: NapCat packages/napcat-onebot/event/notice/(gray tip notice)
326 /// (https://github.com/NapNeko/NapCatQQ);cross-checked LLOneBot。
327 GrayTip {
328 group: Option<Uin>,
329 user: Option<Uin>,
330 content: String,
331 },
332 /// 戳一戳撤回(LLOneBot `notify` sub_type=`poke_recall`)。对端撤回了一次戳一戳。
333 /// `group` 群场景为 Some,私聊为 None。
334 /// ENDPOINT: LLOneBot src/onebot11/event/notice/(poke_recall notice)
335 /// (https://github.com/LLOneBot/LLOneBot)。
336 PokeRecall {
337 group: Option<Uin>,
338 user: Uin,
339 },
340 /// 在线(临时)文件收发 notice(NapCat `online_file_send`/`online_file_receive`)。
341 /// `direction` 区分发出/接收;群场景 `group` 为 Some,私聊为 None。
342 /// ENDPOINT: NapCat(在线文件 notice)。
343 OnlineFile {
344 direction: OnlineFileDirection,
345 user: Uin,
346 group: Option<Uin>,
347 },
348 /// 闪传进度 notice(LLOneBot `flash_file`:downloading/downloaded/uploading/uploaded)。
349 /// `phase` 为进度阶段;`group` 群场景为 Some。完整闪传载荷保留在事件 `raw`。
350 /// ENDPOINT: LLOneBot src/onebot11/event/notice/OB11FlashTransferNoticeEvent.ts
351 /// (https://github.com/LLOneBot/LLOneBot)。
352 FlashFile {
353 phase: FlashFilePhase,
354 user: Uin,
355 group: Option<Uin>,
356 },
357 /// 单侧/未知 notice:OneBot 的 group_card/honor 等也归此。
358 Other {
359 protocol: Protocol,
360 kind: String,
361 raw: Value,
362 },
363}
364
365/// 不透明请求令牌——同意/拒绝时 round-trip;内部按协议打包不同字段。
366/// 业务侧视为不透明(只在事件里收到、回传给 `Bot::handle_request`);
367/// 内部 `RequestTokenInner` 标 `#[doc(hidden)]`,仅供各 adapter crate 构造/解构。
368#[derive(Clone, Debug)]
369pub struct RequestToken(#[doc(hidden)] pub RequestTokenInner);
370
371#[doc(hidden)]
372#[derive(Clone, Debug)]
373pub enum RequestTokenInner {
374 /// OneBot:不透明 flag 字符串。
375 OneBotFlag(String),
376 /// Milky 好友请求:按 initiator_uid + is_filtered。
377 MilkyFriend { initiator_uid: String, is_filtered: bool },
378 /// Milky 入群/邀请他人入群:notification_seq + 类型 + group_id + is_filtered。
379 MilkyGroupNotification { notification_seq: i64, notification_type: String, group_id: Uin, is_filtered: bool },
380 /// Milky 邀请自身入群:group_id + invitation_seq。
381 MilkyInvitation { group_id: Uin, invitation_seq: i64 },
382}
383
384/// 请求处理状态(Milky `FriendRequest.state` 枚举)。
385/// OFFICIAL: https://github.com/SaltifyDev/milky/blob/main/protocol/src/ir/api/friend.ts
386/// (get_friend_requests → FriendRequest.state)。OneBot 无此字段 → `Unknown`。
387#[derive(Clone, Copy, PartialEq, Eq, Debug)]
388pub enum RequestState {
389 /// 待处理。
390 Pending,
391 /// 已同意。
392 Accepted,
393 /// 已拒绝。
394 Rejected,
395 /// 已忽略。
396 Ignored,
397 /// 协议未提供或为未知枚举值(绝不 panic)。
398 Unknown,
399}
400
401#[derive(Clone, Debug)]
402#[non_exhaustive]
403pub enum Request {
404 /// 好友请求。`target_user_id`/`state`/`time`/`is_filtered` 为 Milky
405 /// `FriendRequest` 富字段(OneBot 无:`target_user_id`/`time` → None,
406 /// `state` → `RequestState::Unknown`,`is_filtered` → false)。
407 Friend {
408 initiator: Uin,
409 initiator_uid: Option<String>,
410 comment: String,
411 via: String,
412 source_group: Option<Uin>,
413 target_user_id: Option<Uin>,
414 state: RequestState,
415 time: Option<i64>,
416 is_filtered: bool,
417 token: RequestToken,
418 },
419 /// 加群请求。`invitor` 为邀请人(Lagrange `request/group` 的 `invitor_id`:
420 /// 经邀请链接申请入群时携带邀请人 uin);无邀请人/其余协议为 None。
421 GroupJoin {
422 group: Uin,
423 initiator: Uin,
424 comment: String,
425 invitor: Option<Uin>,
426 is_filtered: bool,
427 token: RequestToken,
428 },
429 GroupInvitedJoin {
430 group: Uin,
431 initiator: Uin,
432 target: Uin,
433 token: RequestToken,
434 },
435 GroupInvite {
436 group: Uin,
437 initiator: Uin,
438 comment: String,
439 source_group: Option<Uin>,
440 token: RequestToken,
441 },
442}
443
444#[derive(Clone, Debug)]
445#[non_exhaustive]
446pub enum Meta {
447 /// 协议端(传输层)连上:由**框架的事件源**在底层 socket 连接成功时发出(正向/反向 WS、
448 /// Milky WS 等),跨协议口径一致;每次(重)连都发一次。是「协议端连接」的权威信号。
449 Connect,
450 /// 协议端(传输层)断开:由**框架的事件源**在 socket 掉线、即将重连前发出。`reason`
451 /// 为底层错误文案(如有)。正常停机不发。注意:传输断开 ≠ 账号掉线(见 [`Meta::BotOffline`])。
452 Disconnect {
453 reason: Option<String>,
454 },
455 /// 框架就绪:登录已解析出**可用账号**(`self_id != 0`)、`Bot` 句柄可正常动作。每次
456 /// `run_*` 仅发**一次**(重连不重放)。需要「机器人完全就绪且有可用账号」才跑的逻辑
457 /// (定时任务 / 加载后初始化)监听此事件即可:无可用账号则它根本不发、相关逻辑自然不跑。
458 /// `nickname` 为登录账号的昵称(来自 `get_login_info`;未知时为空串)。
459 Ready {
460 self_id: Uin,
461 nickname: String,
462 },
463 Heartbeat {
464 interval: i64,
465 status: ImplStatus,
466 },
467 /// 机器人上线(Lagrange `bot_online` / OneBot lifecycle enable)。`reason` 为
468 /// Lagrange 上线原因文案(如重连/扫码);无该信息的来源为 None。
469 BotOnline {
470 reason: Option<String>,
471 },
472 BotOffline,
473}
474
475#[derive(Clone, Debug)]
476pub struct RawEvent {
477 pub protocol: Protocol,
478 pub kind: String,
479 pub raw: Value,
480}
481
482impl RequestToken {
483 /// 由 OneBot flag 构造。
484 pub fn onebot_flag(flag: impl Into<String>) -> Self {
485 RequestToken(RequestTokenInner::OneBotFlag(flag.into()))
486 }
487}
488
489impl Event {
490 /// 本事件所属的可寻址会话(消息取其 peer;通知/请求取群或好友 peer),无则 `None`。
491 /// 供开关门控与 waiter 限定作用域使用。
492 pub fn peer(&self) -> Option<Peer> {
493 use Notice as N;
494 use Request as R;
495 match self {
496 Event::Message(m) => Some(m.peer),
497 Event::Notice(n) => match n {
498 N::Recall { peer, .. } => Some(*peer),
499 N::PeerPinChange { peer, .. } => Some(*peer),
500 N::MemberIncrease { group, .. }
501 | N::MemberDecrease { group, .. }
502 | N::AdminChange { group, .. }
503 | N::Mute { group, .. }
504 | N::WholeMute { group, .. }
505 | N::GroupNameChange { group, .. }
506 | N::Honor { group, .. }
507 | N::GroupCardChange { group, .. }
508 | N::GroupNudge { group, .. }
509 | N::Reaction { group, .. }
510 | N::EssenceChange { group, .. }
511 | N::GroupFileUpload { group, .. }
512 | N::GroupDismiss { group, .. }
513 | N::GroupTitleChange { group, .. }
514 | N::LuckyKing { group, .. } => Some(Peer::group(*group)),
515 N::FriendNudge { user, .. } | N::FriendFileUpload { user, .. } | N::FriendAdd { user } => {
516 Some(Peer::friend(*user))
517 }
518 // 群/私聊均可的 notice:有 group 即群 peer,否则好友 peer。
519 N::InputStatus { user, group, .. }
520 | N::PokeRecall { user, group, .. }
521 | N::OnlineFile { user, group, .. }
522 | N::FlashFile { user, group, .. }
523 | N::GrayTip { user: Some(user), group, .. } => Some(match group {
524 Some(g) => Peer::group(*g),
525 None => Peer::friend(*user),
526 }),
527 // gray_tip 无 user(纯群灰条)时退化为群 peer(若有 group)。
528 N::GrayTip { user: None, group: Some(g), .. } => Some(Peer::group(*g)),
529 N::GrayTip { user: None, group: None, .. }
530 | N::ProfileLike { .. }
531 | N::BotOffline { .. }
532 | N::Other { .. } => None,
533 },
534 Event::Request(r) => match r {
535 R::Friend { initiator, .. } => Some(Peer::friend(*initiator)),
536 R::GroupJoin { group, .. } | R::GroupInvitedJoin { group, .. } | R::GroupInvite { group, .. } => {
537 Some(Peer::group(*group))
538 }
539 },
540 Event::Meta(_) | Event::Raw(_) => None,
541 }
542 }
543
544 /// 若本事件发生在群里,返回群号,否则 `None`。
545 pub fn group(&self) -> Option<Uin> {
546 self.peer().and_then(|p| match p.scene {
547 Scene::Group => Some(p.id),
548 _ => None,
549 })
550 }
551
552 /// 行为主体(消息发送者 / 通知操作者 / 请求发起者),无则 `None`。
553 pub fn sender(&self) -> Option<Uin> {
554 use Notice as N;
555 use Request as R;
556 match self {
557 Event::Message(m) => Some(m.sender),
558 Event::Notice(n) => match n {
559 N::Recall { sender, .. } => Some(*sender),
560 N::MemberIncrease { user, .. }
561 | N::MemberDecrease { user, .. }
562 | N::AdminChange { user, .. }
563 | N::Honor { user, .. }
564 | N::GroupCardChange { user, .. }
565 | N::Reaction { user, .. }
566 | N::GroupFileUpload { user, .. }
567 | N::GroupTitleChange { user, .. }
568 | N::InputStatus { user, .. }
569 | N::PokeRecall { user, .. }
570 | N::OnlineFile { user, .. }
571 | N::FlashFile { user, .. }
572 | N::LuckyKing { user, .. } => Some(*user),
573 N::Mute { operator, .. }
574 | N::WholeMute { operator, .. }
575 | N::GroupNameChange { operator, .. }
576 | N::GroupDismiss { operator, .. }
577 | N::ProfileLike { operator, .. } => Some(*operator),
578 N::GroupNudge { sender, .. } => Some(*sender),
579 N::FriendNudge { user, .. } | N::FriendFileUpload { user, .. } | N::FriendAdd { user } => Some(*user),
580 N::GrayTip { user, .. } => *user,
581 N::EssenceChange { .. } | N::PeerPinChange { .. } | N::BotOffline { .. } | N::Other { .. } => None,
582 },
583 Event::Request(r) => match r {
584 R::Friend { initiator, .. }
585 | R::GroupJoin { initiator, .. }
586 | R::GroupInvitedJoin { initiator, .. }
587 | R::GroupInvite { initiator, .. } => Some(*initiator),
588 },
589 Event::Meta(_) | Event::Raw(_) => None,
590 }
591 }
592}