diff --git a/Makefile b/Makefile index 0b7f6d41..9f98d5bb 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ PROTO_FILES := \ $(PROTO_DIR)/service/*.proto \ $(PROTO_DIR)/service/highway/*.proto \ $(PROTO_DIR)/service/oidb/*.proto \ + $(PROTO_DIR)/status/*.proto \ $(PROTO_DIR)/*.proto diff --git a/client/base.go b/client/base.go index 3025cb05..7a3a8af1 100644 --- a/client/base.go +++ b/client/base.go @@ -6,6 +6,7 @@ import ( "time" "github.com/LagrangeDev/LagrangeGo/entity" + "github.com/LagrangeDev/LagrangeGo/event" "github.com/LagrangeDev/LagrangeGo/info" "github.com/LagrangeDev/LagrangeGo/message" @@ -56,9 +57,15 @@ type QQClient struct { friendCache map[uint32]*entity.Friend groupCache map[uint32]map[uint32]*entity.GroupMember - GroupMessageEvent EventHandle[*message.GroupMessage] - PrivateMessageEvent EventHandle[*message.PrivateMessage] - TempMessageEvent EventHandle[*message.TempMessage] + GroupMessageEvent EventHandle[*message.GroupMessage] + PrivateMessageEvent EventHandle[*message.PrivateMessage] + TempMessageEvent EventHandle[*message.TempMessage] + GroupInvitedEvent EventHandle[*event.GroupMemberJoinRequest] + GroupJoinEvent EventHandle[*event.GroupMemberJoined] + GroupLeaveEvent EventHandle[*event.GroupMemberQuit] + GroupMemberJoinEvent EventHandle[*event.GroupMemberJoined] + GroupMemberLeaveEvent EventHandle[*event.GroupMemberQuit] + // GroupMuteEvent EventHandle[*event.GroupMuteMember] TODO: empty implementation now } func (c *QQClient) SendOidbPacket(pkt *oidb.OidbPacket) error { diff --git a/client/event.go b/client/event.go index ff10b1fe..d437738a 100644 --- a/client/event.go +++ b/client/event.go @@ -5,6 +5,7 @@ import ( "runtime/debug" "sync" + "github.com/LagrangeDev/LagrangeGo/event" "github.com/LagrangeDev/LagrangeGo/message" "github.com/LagrangeDev/LagrangeGo/utils" @@ -46,13 +47,27 @@ func (handle *EventHandle[T]) dispatch(client *QQClient, event T) { // OnEvent 事件响应,耗时操作,需提交协程处理 func OnEvent(client *QQClient, msg any) { - switch msg.(type) { + switch msg := msg.(type) { case *message.PrivateMessage: - client.PrivateMessageEvent.dispatch(client, msg.(*message.PrivateMessage)) + client.PrivateMessageEvent.dispatch(client, msg) case *message.GroupMessage: - client.GroupMessageEvent.dispatch(client, msg.(*message.GroupMessage)) + client.GroupMessageEvent.dispatch(client, msg) case *message.TempMessage: - client.TempMessageEvent.dispatch(client, msg.(*message.TempMessage)) + client.TempMessageEvent.dispatch(client, msg) + case *event.GroupMemberJoinRequest: + client.GroupInvitedEvent.dispatch(client, msg) + case *event.GroupMemberJoined: + if client.uin == msg.Uin { + client.GroupJoinEvent.dispatch(client, msg) + } else { + client.GroupMemberJoinEvent.dispatch(client, msg) + } + case *event.GroupMemberQuit: + if client.uin == msg.Uin { + client.GroupLeaveEvent.dispatch(client, msg) + } else { + client.GroupMemberLeaveEvent.dispatch(client, msg) + } case nil: networkLogger.Errorf("nil event msg, ignore") default: diff --git a/client/listener.go b/client/listener.go index 5b125445..f7f84819 100644 --- a/client/listener.go +++ b/client/listener.go @@ -4,11 +4,13 @@ import ( "errors" "runtime/debug" - "github.com/LagrangeDev/LagrangeGo/packets/wtlogin" + "github.com/RomiChan/protobuf/proto" + eventConverter "github.com/LagrangeDev/LagrangeGo/event" msgConverter "github.com/LagrangeDev/LagrangeGo/message" "github.com/LagrangeDev/LagrangeGo/packets/pb/message" - "github.com/RomiChan/protobuf/proto" + "github.com/LagrangeDev/LagrangeGo/packets/pb/status" + "github.com/LagrangeDev/LagrangeGo/packets/wtlogin" ) var listeners = map[string]func(*QQClient, *wtlogin.SSOPacket) (any, error){ @@ -40,6 +42,47 @@ func decodeOlPushServicePacket(c *QQClient, pkt *wtlogin.SSOPacket) (any, error) return msgConverter.ParseGroupMessage(&msg), nil case 141: // temp msg return msgConverter.ParseTempMessage(&msg), nil + case 33: // member joined + pb := status.MemberChanged{} + err := proto.Unmarshal(msg.Message.Body.MsgContent, &pb) + if err != nil { + return nil, err + } + return eventConverter.ParseMemberJoined(&msg, &pb), nil + case 34: // member exit + pb := status.MemberChanged{} + err := proto.Unmarshal(msg.Message.Body.MsgContent, &pb) + if err != nil { + return nil, err + } + return eventConverter.ParseMemberQuit(&msg, &pb), nil + case 84: + pb := status.MemberJoinRequest{} + err := proto.Unmarshal(msg.Message.Body.MsgContent, &pb) + if err != nil { + return nil, err + } + return eventConverter.ParseMemberJoinRequest(&pb), nil + case 525: + pb := status.MemberInviteRequest{} + err := proto.Unmarshal(msg.Message.Body.MsgContent, &pb) + if err != nil { + return nil, err + } + return eventConverter.ParseMemberJoinRequestFromInvite(&pb), nil + case 0x2DC: // grp event, 732 + subType := msg.Message.ContentHead.SubType.Unwrap() + switch subType { + case 12: // mute + pb := map[int]any{} + err := proto.Unmarshal(msg.Message.Body.MsgContent, &pb) + if err != nil { + return nil, err + } + return eventConverter.ParseGroupMuteEvent(&pb), nil + default: + networkLogger.Warningf("Unsupported group event, subType: %v", subType) + } default: networkLogger.Warningf("Unsupported message type: %v", msg.Message.ContentHead.Type) } diff --git a/event/group.go b/event/group.go new file mode 100644 index 00000000..7a23cf6f --- /dev/null +++ b/event/group.go @@ -0,0 +1,96 @@ +package event + +import ( + "github.com/LagrangeDev/LagrangeGo/packets/pb/message" + "github.com/LagrangeDev/LagrangeGo/packets/pb/status" +) + +type ( + GroupEvent struct { + GroupID uint32 + } + + GroupMuteMember struct { + GroupEvent + OperatorUid string + TargetUid string // when TargetUid is empty, mute all members + Duration uint32 + } + + GroupMemberJoinRequest struct { + GroupEvent + Uid string + InvitorUid string + Answer string // 问题:(.*)答案:(.*) + } + + GroupMemberJoined struct { + GroupEvent + Uin uint32 + Uid string + JoinType int32 + } + + GroupMemberQuit struct { + GroupEvent + Uin uint32 + Uid string + ExitType int32 + OperatorUid string + } +) + +func (gmq *GroupMemberQuit) IsKicked() bool { + return gmq.ExitType == 131 +} + +func ParseMemberJoinRequest(event *status.MemberJoinRequest) *GroupMemberJoinRequest { + return &GroupMemberJoinRequest{ + GroupEvent: GroupEvent{ + GroupID: event.GroupID, + }, + Uid: event.Uid, + Answer: event.RequestField.Unwrap(), + } +} +func ParseMemberJoinRequestFromInvite(event *status.MemberInviteRequest) *GroupMemberJoinRequest { + if event.Cmd == 87 { + inn := event.Info.Inner + return &GroupMemberJoinRequest{ + GroupEvent: GroupEvent{ + GroupID: inn.GroupID, + }, + Uid: inn.Uid, + InvitorUid: inn.InvitorUid, + } + } + return nil +} + +func ParseMemberJoined(msg *message.PushMsg, event *status.MemberChanged) *GroupMemberJoined { + return &GroupMemberJoined{ + GroupEvent: GroupEvent{ + GroupID: msg.Message.ResponseHead.FromUin, + }, + Uin: event.Uin, + Uid: event.Uid, + JoinType: event.JoinType.Unwrap(), + } +} + +func ParseMemberQuit(msg *message.PushMsg, event *status.MemberChanged) *GroupMemberQuit { + return &GroupMemberQuit{ + GroupEvent: GroupEvent{ + GroupID: msg.Message.ResponseHead.FromUin, + }, + Uin: event.Uin, + Uid: event.Uid, + OperatorUid: event.OperatorUid.Unwrap(), + ExitType: event.ExitType.Unwrap(), + } +} + +func ParseGroupMuteEvent(event *map[int]any) *GroupMuteMember { + // TODO Parse GroupMuteEvent + return nil +} diff --git a/packets/pb/status/group.pb.go b/packets/pb/status/group.pb.go new file mode 100644 index 00000000..9905b847 --- /dev/null +++ b/packets/pb/status/group.pb.go @@ -0,0 +1,43 @@ +// Code generated by protoc-gen-golite. DO NOT EDIT. +// source: pb/status/group.proto + +package status + +import ( + proto "github.com/RomiChan/protobuf/proto" +) + +type MemberChanged struct { + Uin uint32 `protobuf:"varint,1,opt"` + Uid string `protobuf:"bytes,3,opt"` + ExitType proto.Option[int32] `protobuf:"varint,4,opt"` // 131 kick 130 exit + OperatorUid proto.Option[string] `protobuf:"bytes,5,opt"` + JoinType proto.Option[int32] `protobuf:"varint,6,opt"` // 6 scan qr + _ [0]func() +} + +type MemberJoinRequest struct { + GroupID uint32 `protobuf:"varint,1,opt"` + Uid string `protobuf:"bytes,3,opt"` + Src int32 `protobuf:"varint,4,opt"` + RequestField proto.Option[string] `protobuf:"bytes,5,opt"` + Field9 []byte `protobuf:"bytes,9,opt"` +} + +type InviteInner struct { + GroupID uint32 `protobuf:"varint,1,opt"` + Uid string `protobuf:"bytes,5,opt"` + InvitorUid string `protobuf:"bytes,6,opt"` + _ [0]func() +} + +type InviteInfo struct { + Inner *InviteInner `protobuf:"bytes,1,opt"` + _ [0]func() +} + +type MemberInviteRequest struct { + Cmd int32 `protobuf:"varint,1,opt"` + Info *InviteInfo `protobuf:"bytes,2,opt"` + _ [0]func() +} diff --git a/packets/pb/status/group.proto b/packets/pb/status/group.proto new file mode 100644 index 00000000..fd50ea9b --- /dev/null +++ b/packets/pb/status/group.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; + +option go_package = "github.com/LagrangeDev/LagrangeGo/packets/pb/status"; + +message MemberChanged { + uint32 Uin = 1; + string Uid = 3; + optional int32 ExitType = 4; // 131 kick 130 exit + optional string OperatorUid = 5; + optional int32 JoinType = 6; // 6 scan qr +} + +message MemberJoinRequest { + // JoinType: Direct(scan qrcode or search GroupID) + + uint32 GroupID = 1; + string Uid = 3; + int32 Src = 4; + optional string RequestField = 5; + optional bytes Field9 = 9; +} + +message InviteInner { + uint32 GroupID = 1; + string Uid = 5; + string InvitorUid = 6; +} + +message InviteInfo { + InviteInner Inner = 1; +} + +message MemberInviteRequest { + // JoinType: Direct(scan qrcode or search GroupID) + + int32 Cmd = 1; + InviteInfo Info = 2; +} \ No newline at end of file