package milky import ( "cellbot/internal/protocol" "fmt" "strconv" "github.com/bytedance/sonic" "go.uber.org/zap" ) // EventConverter 事件转换器 // 将 Milky 事件转换为通用 protocol.Event type EventConverter struct { logger *zap.Logger } // NewEventConverter 创建事件转换器 func NewEventConverter(logger *zap.Logger) *EventConverter { return &EventConverter{ logger: logger.Named("event-converter"), } } // Convert 转换事件 func (c *EventConverter) Convert(raw []byte) (protocol.Event, error) { // 解析原始事件 var milkyEvent Event if err := sonic.Unmarshal(raw, &milkyEvent); err != nil { return nil, fmt.Errorf("failed to unmarshal event: %w", err) } c.logger.Debug("Converting event", zap.String("event_type", milkyEvent.EventType), zap.Int64("self_id", milkyEvent.SelfID)) // 根据事件类型转换 switch milkyEvent.EventType { case EventTypeMessageReceive: return c.convertMessageEvent(&milkyEvent) case EventTypeFriendRequest: return c.convertFriendRequestEvent(&milkyEvent) case EventTypeGroupJoinRequest: return c.convertGroupJoinRequestEvent(&milkyEvent) case EventTypeGroupInvitedJoinRequest: return c.convertGroupInvitedJoinRequestEvent(&milkyEvent) case EventTypeGroupInvitation: return c.convertGroupInvitationEvent(&milkyEvent) case EventTypeMessageRecall: return c.convertMessageRecallEvent(&milkyEvent) case EventTypeBotOffline: return c.convertBotOfflineEvent(&milkyEvent) case EventTypeFriendNudge: return c.convertFriendNudgeEvent(&milkyEvent) case EventTypeFriendFileUpload: return c.convertFriendFileUploadEvent(&milkyEvent) case EventTypeGroupAdminChange: return c.convertGroupAdminChangeEvent(&milkyEvent) case EventTypeGroupEssenceMessageChange: return c.convertGroupEssenceMessageChangeEvent(&milkyEvent) case EventTypeGroupMemberIncrease: return c.convertGroupMemberIncreaseEvent(&milkyEvent) case EventTypeGroupMemberDecrease: return c.convertGroupMemberDecreaseEvent(&milkyEvent) case EventTypeGroupNameChange: return c.convertGroupNameChangeEvent(&milkyEvent) case EventTypeGroupMessageReaction: return c.convertGroupMessageReactionEvent(&milkyEvent) case EventTypeGroupMute: return c.convertGroupMuteEvent(&milkyEvent) case EventTypeGroupWholeMute: return c.convertGroupWholeMuteEvent(&milkyEvent) case EventTypeGroupNudge: return c.convertGroupNudgeEvent(&milkyEvent) case EventTypeGroupFileUpload: return c.convertGroupFileUploadEvent(&milkyEvent) default: c.logger.Warn("Unknown event type", zap.String("event_type", milkyEvent.EventType)) return nil, fmt.Errorf("unknown event type: %s", milkyEvent.EventType) } } // convertMessageEvent 转换消息事件 func (c *EventConverter) convertMessageEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) // 解析消息数据 var msgData IncomingMessage dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &msgData); err != nil { return nil, fmt.Errorf("failed to parse message data: %w", err) } // 构建消息文本 messageText := c.buildMessageText(msgData.Segments) event := &protocol.MessageEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeMessage, DetailType: msgData.MessageScene, // friend, group, temp SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}{ "peer_id": msgData.PeerID, "message_seq": msgData.MessageSeq, "sender_id": msgData.SenderID, "time": msgData.Time, "segments": msgData.Segments, }, }, MessageID: strconv.FormatInt(msgData.MessageSeq, 10), Message: messageText, AltText: messageText, } // 添加场景特定数据 if msgData.Friend != nil { event.Data["friend"] = msgData.Friend } if msgData.Group != nil { event.Data["group"] = msgData.Group } if msgData.GroupMember != nil { event.Data["group_member"] = msgData.GroupMember } return event, nil } // convertFriendRequestEvent 转换好友请求事件 func (c *EventConverter) convertFriendRequestEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data FriendRequestEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse friend request data: %w", err) } event := &protocol.RequestEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeRequest, DetailType: "friend", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}{ "initiator_id": data.InitiatorID, "initiator_uid": data.InitiatorUID, "comment": data.Comment, "via": data.Via, }, }, RequestID: strconv.FormatInt(data.InitiatorID, 10), UserID: strconv.FormatInt(data.InitiatorID, 10), Comment: data.Comment, Flag: data.InitiatorUID, } return event, nil } // convertGroupJoinRequestEvent 转换入群申请事件 func (c *EventConverter) convertGroupJoinRequestEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupJoinRequestEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group join request data: %w", err) } event := &protocol.RequestEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeRequest, DetailType: "group", SubType: "add", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}{ "group_id": data.GroupID, "notification_seq": data.NotificationSeq, "is_filtered": data.IsFiltered, "initiator_id": data.InitiatorID, "comment": data.Comment, }, }, RequestID: strconv.FormatInt(data.NotificationSeq, 10), UserID: strconv.FormatInt(data.InitiatorID, 10), Comment: data.Comment, Flag: strconv.FormatInt(data.NotificationSeq, 10), } return event, nil } // convertGroupInvitedJoinRequestEvent 转换群成员邀请他人入群事件 func (c *EventConverter) convertGroupInvitedJoinRequestEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupInvitedJoinRequestEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group invited join request data: %w", err) } event := &protocol.RequestEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeRequest, DetailType: "group", SubType: "invite", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}{ "group_id": data.GroupID, "notification_seq": data.NotificationSeq, "initiator_id": data.InitiatorID, "target_user_id": data.TargetUserID, }, }, RequestID: strconv.FormatInt(data.NotificationSeq, 10), UserID: strconv.FormatInt(data.InitiatorID, 10), Comment: "", Flag: strconv.FormatInt(data.NotificationSeq, 10), } return event, nil } // convertGroupInvitationEvent 转换他人邀请自身入群事件 func (c *EventConverter) convertGroupInvitationEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupInvitationEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group invitation data: %w", err) } event := &protocol.RequestEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeRequest, DetailType: "group", SubType: "invite_self", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}{ "group_id": data.GroupID, "invitation_seq": data.InvitationSeq, "initiator_id": data.InitiatorID, }, }, RequestID: strconv.FormatInt(data.InvitationSeq, 10), UserID: strconv.FormatInt(data.InitiatorID, 10), Comment: "", Flag: strconv.FormatInt(data.InvitationSeq, 10), } return event, nil } // convertMessageRecallEvent 转换消息撤回事件 func (c *EventConverter) convertMessageRecallEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data MessageRecallEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse message recall data: %w", err) } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "message_recall", SubType: data.MessageScene, Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}{ "message_scene": data.MessageScene, "peer_id": data.PeerID, "message_seq": data.MessageSeq, "sender_id": data.SenderID, "operator_id": data.OperatorID, "display_suffix": data.DisplaySuffix, }, }, UserID: strconv.FormatInt(data.SenderID, 10), Operator: strconv.FormatInt(data.OperatorID, 10), } return event, nil } // convertBotOfflineEvent 转换机器人离线事件 func (c *EventConverter) convertBotOfflineEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data BotOfflineEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse bot offline data: %w", err) } event := &protocol.MetaEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeMeta, DetailType: "bot_offline", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}{ "reason": data.Reason, }, }, Status: "offline", } return event, nil } // convertFriendNudgeEvent 转换好友戳一戳事件 func (c *EventConverter) convertFriendNudgeEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data FriendNudgeEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse friend nudge data: %w", err) } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "friend_nudge", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, UserID: strconv.FormatInt(data.UserID, 10), } return event, nil } // convertFriendFileUploadEvent 转换好友文件上传事件 func (c *EventConverter) convertFriendFileUploadEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data FriendFileUploadEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse friend file upload data: %w", err) } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "friend_file_upload", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, UserID: strconv.FormatInt(data.UserID, 10), } return event, nil } // convertGroupAdminChangeEvent 转换群管理员变更事件 func (c *EventConverter) convertGroupAdminChangeEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupAdminChangeEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group admin change data: %w", err) } subType := "unset" if data.IsSet { subType = "set" } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_admin", SubType: subType, Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), UserID: strconv.FormatInt(data.UserID, 10), } return event, nil } // convertGroupEssenceMessageChangeEvent 转换群精华消息变更事件 func (c *EventConverter) convertGroupEssenceMessageChangeEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupEssenceMessageChangeEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group essence message change data: %w", err) } subType := "delete" if data.IsSet { subType = "add" } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_essence", SubType: subType, Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), } return event, nil } // convertGroupMemberIncreaseEvent 转换群成员增加事件 func (c *EventConverter) convertGroupMemberIncreaseEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupMemberIncreaseEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group member increase data: %w", err) } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_increase", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), UserID: strconv.FormatInt(data.UserID, 10), } if data.OperatorID != nil { event.Operator = strconv.FormatInt(*data.OperatorID, 10) } return event, nil } // convertGroupMemberDecreaseEvent 转换群成员减少事件 func (c *EventConverter) convertGroupMemberDecreaseEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupMemberDecreaseEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group member decrease data: %w", err) } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_decrease", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), UserID: strconv.FormatInt(data.UserID, 10), } if data.OperatorID != nil { event.Operator = strconv.FormatInt(*data.OperatorID, 10) } return event, nil } // convertGroupNameChangeEvent 转换群名称变更事件 func (c *EventConverter) convertGroupNameChangeEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupNameChangeEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group name change data: %w", err) } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_name_change", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), Operator: strconv.FormatInt(data.OperatorID, 10), } return event, nil } // convertGroupMessageReactionEvent 转换群消息回应事件 func (c *EventConverter) convertGroupMessageReactionEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupMessageReactionEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group message reaction data: %w", err) } subType := "remove" if data.IsAdd { subType = "add" } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_message_reaction", SubType: subType, Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), UserID: strconv.FormatInt(data.UserID, 10), } return event, nil } // convertGroupMuteEvent 转换群禁言事件 func (c *EventConverter) convertGroupMuteEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupMuteEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group mute data: %w", err) } subType := "ban" if data.Duration == 0 { subType = "lift_ban" } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_ban", SubType: subType, Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), UserID: strconv.FormatInt(data.UserID, 10), Operator: strconv.FormatInt(data.OperatorID, 10), } return event, nil } // convertGroupWholeMuteEvent 转换群全体禁言事件 func (c *EventConverter) convertGroupWholeMuteEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupWholeMuteEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group whole mute data: %w", err) } subType := "ban" if !data.IsMute { subType = "lift_ban" } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_whole_ban", SubType: subType, Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), Operator: strconv.FormatInt(data.OperatorID, 10), } return event, nil } // convertGroupNudgeEvent 转换群戳一戳事件 func (c *EventConverter) convertGroupNudgeEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupNudgeEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group nudge data: %w", err) } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_nudge", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), UserID: strconv.FormatInt(data.SenderID, 10), } return event, nil } // convertGroupFileUploadEvent 转换群文件上传事件 func (c *EventConverter) convertGroupFileUploadEvent(milkyEvent *Event) (protocol.Event, error) { selfID := strconv.FormatInt(milkyEvent.SelfID, 10) var data GroupFileUploadEventData dataBytes, _ := sonic.Marshal(milkyEvent.Data) if err := sonic.Unmarshal(dataBytes, &data); err != nil { return nil, fmt.Errorf("failed to parse group file upload data: %w", err) } event := &protocol.NoticeEvent{ BaseEvent: protocol.BaseEvent{ Type: protocol.EventTypeNotice, DetailType: "group_file_upload", SubType: "", Timestamp: milkyEvent.Time, SelfID: selfID, Data: map[string]interface{}(milkyEvent.Data), }, GroupID: strconv.FormatInt(data.GroupID, 10), UserID: strconv.FormatInt(data.UserID, 10), } return event, nil } // buildMessageText 构建消息文本 func (c *EventConverter) buildMessageText(segments []IncomingSegment) string { var text string for _, seg := range segments { if seg.Type == "text" { if textData, ok := seg.Data["text"].(string); ok { text += textData } } else if seg.Type == "mention" { if userID, ok := seg.Data["user_id"].(float64); ok { text += fmt.Sprintf("@%d", int64(userID)) } } else if seg.Type == "image" { text += "[图片]" } else if seg.Type == "voice" { text += "[语音]" } else if seg.Type == "video" { text += "[视频]" } else if seg.Type == "file" { text += "[文件]" } else if seg.Type == "face" { text += "[表情]" } else if seg.Type == "forward" { text += "[转发消息]" } } return text }