diff --git a/client/base.go b/client/base.go index 7595a8dc..ee2bbb58 100644 --- a/client/base.go +++ b/client/base.go @@ -95,6 +95,8 @@ type QQClient struct { NewFriendRequestEvent EventHandle[*event.NewFriendRequest] // 好友申请 FriendRecallEvent EventHandle[*event.FriendRecall] RenameEvent EventHandle[*event.Rename] + FriendPokeEvent EventHandle[*event.FriendPokeEvent] + GroupPokeEvent EventHandle[*event.GroupPokeEvent] // client event handles eventHandlers eventHandlers diff --git a/client/event/friend.go b/client/event/friend.go index e72fa334..bcb88c05 100644 --- a/client/event/friend.go +++ b/client/event/friend.go @@ -1,6 +1,8 @@ package event import ( + "strconv" + "github.com/LagrangeDev/LagrangeGo/client/packets/pb/message" ) @@ -26,6 +28,12 @@ type ( Uid string Nickname string } + + // FriendPokeEvent 好友戳一戳事件 from miraigo + FriendPokeEvent struct { + Sender uint32 + Receiver uint32 + } ) func ParseFriendRequestNotice(event *message.FriendRequest, msg *message.PushMsg) *NewFriendRequest { @@ -63,3 +71,18 @@ func ParseFriendRenameEvent(event *message.FriendRenameMsg) *Rename { Nickname: event.Body.Data.RenameData.NickName, } } + +func ParsePokeEvent(event *message.PokeEventData) *FriendPokeEvent { + e := FriendPokeEvent{} + for _, data := range event.Extra { + switch data.Key { + case "uin_str1": + sender, _ := strconv.Atoi(data.Value) + e.Sender = uint32(sender) + case "uin_str2": + receiver, _ := strconv.Atoi(data.Value) + e.Receiver = uint32(receiver) + } + } + return &e +} diff --git a/client/event/group.go b/client/event/group.go index b9b39a9c..abe735ac 100644 --- a/client/event/group.go +++ b/client/event/group.go @@ -69,6 +69,13 @@ type ( SenderNick string OperatorNick string } + + // GroupPokeEvent 群戳一戳事件 from miraigo + GroupPokeEvent struct { + GroupUin uint32 + Sender uint32 + Receiver uint32 + } ) type GroupInvite struct { @@ -218,3 +225,12 @@ func ParseGroupDigestEvent(event *message.EssenceNotify) *GroupDigestEvent { OperatorNick: event.EssenceMessage.OperatorName, } } + +func PaeseGroupPokeEvent(event *message.PokeEvent, groupUin uint32) *GroupPokeEvent { + e := ParsePokeEvent(event.Data) + return &GroupPokeEvent{ + GroupUin: groupUin, + Sender: e.Sender, + Receiver: e.Receiver, + } +} diff --git a/client/listener.go b/client/listener.go index f5bda462..52e93a8a 100644 --- a/client/listener.go +++ b/client/listener.go @@ -2,9 +2,10 @@ package client import ( "errors" + "reflect" "runtime/debug" - "github.com/RomiChan/protobuf/proto" + "github.com/LagrangeDev/LagrangeGo/internal/proto" eventConverter "github.com/LagrangeDev/LagrangeGo/client/event" "github.com/LagrangeDev/LagrangeGo/client/internal/network" @@ -37,7 +38,6 @@ func decodeOlPushServicePacket(c *QQClient, pkt *network.Packet) (any, error) { if pkg.Body == nil { return nil, errors.New("message body is empty") } - switch typ { case 166, 208: // 166 for private msg, 208 for private record prvMsg := msgConverter.ParsePrivateMessage(&msg) @@ -165,6 +165,13 @@ func decodeOlPushServicePacket(c *QQClient, pkt *network.Packet) (any, error) { } c.RenameEvent.dispatch(c, eventConverter.ParseSelfRenameEvent(&pb, &c.transport.Sig)) return nil, nil + case 290: // friend poke event + pb := message.PokeEventData{} + err = proto.Unmarshal(pkg.Body.MsgContent, &pb) + if err != nil { + return nil, err + } + c.FriendPokeEvent.dispatch(c, eventConverter.ParsePokeEvent(&pb)) default: c.warning("unknown subtype %d of type 0x210, proto data: %x", subType, pkg.Body.MsgContent) } @@ -179,7 +186,20 @@ func decodeOlPushServicePacket(c *QQClient, pkt *network.Packet) (any, error) { } c.GroupDigestEvent.dispatch(c, eventConverter.ParseGroupDigestEvent(&pb)) return nil, nil - case 20: // nudget(grp_id only) + case 20: // group poke event + pb := message.PokeEvent{} + err = proto.Unmarshal(pkg.Body.MsgContent, &pb) + if err != nil { + return nil, err + } + result, _ := proto.ReadField(4, pkg.Body.MsgContent) + var groupUin uint32 + for _, r := range result { + if r.Type == reflect.TypeOf(int64(0)) { + groupUin = uint32(r.Vaule.(int64)) + } + } + c.GroupPokeEvent.dispatch(c, eventConverter.PaeseGroupPokeEvent(&pb, groupUin)) return nil, nil case 17: // recall reader := binary.NewReader(pkg.Body.MsgContent) diff --git a/client/packets/pb/message/component.pb.go b/client/packets/pb/message/component.pb.go index 1e15fae8..9a37835e 100644 --- a/client/packets/pb/message/component.pb.go +++ b/client/packets/pb/message/component.pb.go @@ -177,3 +177,28 @@ type PokeExtra struct { Field8 uint32 `protobuf:"varint,8,opt"` _ [0]func() } + +type PokeEvent struct { + // sfixed64 GroupUin = 4; + Field13 uint32 `protobuf:"varint,13,opt"` + Data *PokeEventData `protobuf:"bytes,26,opt"` + Field27 uint32 `protobuf:"varint,27,opt"` + Field39 uint32 `protobuf:"varint,39,opt"` + _ [0]func() +} + +type PokeEventData struct { + Field1 uint32 `protobuf:"varint,1,opt"` + Field2 uint32 `protobuf:"varint,2,opt"` + Field3 uint32 `protobuf:"varint,3,opt"` + Seq uint32 `protobuf:"varint,6,opt"` + Extra []*PokeDataExtra `protobuf:"bytes,7,rep"` + XMLDescription string `protobuf:"bytes,8,opt"` + Random uint32 `protobuf:"varint,10,opt"` +} + +type PokeDataExtra struct { + Key string `protobuf:"bytes,1,opt"` + Value string `protobuf:"bytes,2,opt"` + _ [0]func() +} diff --git a/client/packets/pb/message/component.proto b/client/packets/pb/message/component.proto index e2da53e7..ad633ed9 100644 --- a/client/packets/pb/message/component.proto +++ b/client/packets/pb/message/component.proto @@ -175,3 +175,25 @@ message PokeExtra { uint32 Field8 = 8; } +message PokeEvent { + // sfixed64 GroupUin = 4; + uint32 Field13 = 13; + PokeEventData Data = 26; + uint32 Field27 = 27; + uint32 Field39 = 39; +} + +message PokeEventData { + uint32 Field1 = 1; + uint32 Field2 = 2; + uint32 Field3 = 3; + uint32 Seq = 6; + repeated PokeDataExtra Extra = 7; + string XMLDescription = 8; + uint32 Random = 10; +} + +message PokeDataExtra { + string key = 1; + string value = 2; +} diff --git a/internal/proto/dynamic_read.go b/internal/proto/dynamic_read.go new file mode 100644 index 00000000..023ee09f --- /dev/null +++ b/internal/proto/dynamic_read.go @@ -0,0 +1,113 @@ +package proto + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "reflect" +) + +const ( + WireVarint = 0 + WireFixed64 = 1 + WireBytes = 2 + WireStartGroup = 3 + WireEndGroup = 4 + WireFixed32 = 5 + WireSFixed32 = 5 | (1 << 3) + WireSFixed64 = 1 | (1 << 3) +) + +type TypeValue struct { + Type reflect.Type + Vaule any +} + +func readVarint(r io.Reader) (int64, error) { + var result int64 + var shift uint + for { + var b byte + err := binary.Read(r, binary.LittleEndian, &b) + if err != nil { + return 0, err + } + result |= int64(b&0x7F) << shift + if b&0x80 == 0 { + break + } + shift += 7 + } + return result, nil +} + +func readFixed64(r io.Reader) (uint64, error) { + var result uint64 + err := binary.Read(r, binary.LittleEndian, &result) + return result, err +} + +// 从字节数组读取一个定长32位整数 +func readFixed32(r io.Reader) (uint32, error) { + var result uint32 + err := binary.Read(r, binary.LittleEndian, &result) + return result, err +} + +func readSFixed32(r io.Reader) (int32, error) { + var result int32 + err := binary.Read(r, binary.LittleEndian, &result) + return result, err +} + +// 从字节数组读取有符号定长64位整数 +func readSFixed64(r io.Reader) (int64, error) { + var result int64 + err := binary.Read(r, binary.LittleEndian, &result) + return result, err +} + +// 从字节数组读取字节切片 +func readBytes(r io.Reader) ([]byte, error) { + length, err := readVarint(r) + if err != nil { + return nil, err + } + result := make([]byte, length) + _, err = io.ReadFull(r, result) + return result, err +} + +func ReadField(targetField int64, data []byte) ([]*TypeValue, error) { + r := bytes.NewReader(data) + result := make([]*TypeValue, 0, 2) + for r.Len() > 0 { + key, err := readVarint(r) + if err != nil { + return nil, err + } + field := key >> 3 + wireType := key & 0x7 + var value any + switch wireType { + case WireVarint: + value, err = readVarint(r) + case WireFixed64: + value, err = readFixed64(r) + case WireBytes: + value, err = readBytes(r) + case WireFixed32: + value, err = readFixed32(r) + default: + return nil, fmt.Errorf("unsupported wire type: %d", wireType) + } + if field == targetField { + result = append(result, &TypeValue{ + Type: reflect.TypeOf(value), + Vaule: value, + }) + } + } + return result, nil +}