diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index cc3fc35d..720f0273 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,9 +2,9 @@ name: Go on: push: - branches: [ master ] + branches: [ master, typeparam ] pull_request: - branches: [ master ] + branches: [ master, typeparam ] jobs: @@ -16,7 +16,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v2 with: - go-version: ^1.13 + go-version: 1.18 - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/.github/workflows/goimports.yml b/.github/workflows/goimports.yml index 8d8ae0a6..adc39586 100644 --- a/.github/workflows/goimports.yml +++ b/.github/workflows/goimports.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Go 1.x uses: actions/setup-go@v2 with: - go-version: ^1.13 + go-version: 1.18 - name: Check out code into the Go module directory uses: actions/checkout@v2 diff --git a/binary/jce/reader.go b/binary/jce/reader.go index 9fd750d1..2c7cd45a 100644 --- a/binary/jce/reader.go +++ b/binary/jce/reader.go @@ -49,7 +49,7 @@ func (r *JceReader) skipHead() { } func (r *JceReader) skip(l int) { - r.skipBytes(l) + r.off += l } func (r *JceReader) skipField(t byte) { @@ -105,17 +105,6 @@ func (r *JceReader) readBytes(n int) []byte { return b } -func (r *JceReader) skipBytes(n int) { - if r.off+n > len(r.buf) { - panic("skipBytes: EOF") - } - lremain := len(r.buf[r.off:]) - if lremain < n { - n = lremain - } - r.off += n -} - func (r *JceReader) readByte() byte { if r.off >= len(r.buf) { panic("readByte: EOF") @@ -126,15 +115,21 @@ func (r *JceReader) readByte() byte { } func (r *JceReader) readUInt16() uint16 { - return goBinary.BigEndian.Uint16(r.readBytes(2)) + b := make([]byte, 2) + r.off += copy(b, r.buf[r.off:]) + return goBinary.BigEndian.Uint16(b) } func (r *JceReader) readUInt32() uint32 { - return goBinary.BigEndian.Uint32(r.readBytes(4)) + b := make([]byte, 4) + r.off += copy(b, r.buf[r.off:]) + return goBinary.BigEndian.Uint32(b) } func (r *JceReader) readUInt64() uint64 { - return goBinary.BigEndian.Uint64(r.readBytes(8)) + b := make([]byte, 8) + r.off += copy(b, r.buf[r.off:]) + return goBinary.BigEndian.Uint64(b) } func (r *JceReader) readFloat32() float32 { diff --git a/binary/jce/reader_test.go b/binary/jce/reader_test.go index 4fad5585..3a430eae 100644 --- a/binary/jce/reader_test.go +++ b/binary/jce/reader_test.go @@ -99,7 +99,7 @@ func TestJceReader_ReadBytes(t *testing.T) { assert.Equal(t, b, rb) } -func (w *JceWriter) WriteObject(i interface{}, tag byte) { +func (w *JceWriter) WriteObject(i any, tag byte) { t := reflect.TypeOf(i) if t.Kind() == reflect.Map { w.WriteMap(i, tag) @@ -192,7 +192,7 @@ type decoder struct { var decoderCache = sync.Map{} // WriteJceStructRaw 写入 Jce 结构体 -func (w *JceWriter) WriteJceStructRaw(s interface{}) { +func (w *JceWriter) WriteJceStructRaw(s any) { t := reflect.TypeOf(s) if t.Kind() != reflect.Ptr { return @@ -234,7 +234,7 @@ func (w *JceWriter) WriteJceStruct(s IJceStruct, tag byte) { w.writeHead(11, 0) } -func (w *JceWriter) WriteSlice(i interface{}, tag byte) { +func (w *JceWriter) WriteSlice(i any, tag byte) { va := reflect.ValueOf(i) if va.Kind() != reflect.Slice { panic("JceWriter.WriteSlice: not a slice") @@ -270,7 +270,7 @@ func (w *JceWriter) WriteJceStructSlice(l []IJceStruct, tag byte) { } } -func (w *JceWriter) WriteMap(m interface{}, tag byte) { +func (w *JceWriter) WriteMap(m any, tag byte) { va := reflect.ValueOf(m) if va.Kind() != reflect.Map { panic("JceWriter.WriteMap: not a map") diff --git a/binary/pool.go b/binary/pool.go index 3673b414..287b0c9f 100644 --- a/binary/pool.go +++ b/binary/pool.go @@ -8,7 +8,7 @@ import ( ) var bufferPool = sync.Pool{ - New: func() interface{} { + New: func() any { return new(Writer) }, } @@ -24,7 +24,7 @@ func SelectWriter() *Writer { // PutWriter 将 Writer 放回池中 func PutWriter(w *Writer) { // See https://golang.org/issue/23199 - const maxSize = 1 << 16 + const maxSize = 32 * 1024 if (*bytes.Buffer)(w).Cap() < maxSize { // 对于大Buffer直接丢弃 w.Reset() bufferPool.Put(w) @@ -32,7 +32,7 @@ func PutWriter(w *Writer) { } var gzipPool = sync.Pool{ - New: func() interface{} { + New: func() any { buf := new(bytes.Buffer) w := gzip.NewWriter(buf) return &GzipWriter{ @@ -64,7 +64,7 @@ type zlibWriter struct { } var zlibPool = sync.Pool{ - New: func() interface{} { + New: func() any { buf := new(bytes.Buffer) w := zlib.NewWriter(buf) return &zlibWriter{ diff --git a/binary/reader.go b/binary/reader.go index f11c608f..3fcaa701 100644 --- a/binary/reader.go +++ b/binary/reader.go @@ -52,17 +52,20 @@ func (r *Reader) ReadBytesShort() []byte { } func (r *Reader) ReadUInt16() uint16 { - b := r.ReadBytes(2) + b := make([]byte, 2) + _, _ = r.buf.Read(b) return binary.BigEndian.Uint16(b) } func (r *Reader) ReadInt32() int32 { - b := r.ReadBytes(4) + b := make([]byte, 4) + _, _ = r.buf.Read(b) return int32(binary.BigEndian.Uint32(b)) } func (r *Reader) ReadInt64() int64 { - b := r.ReadBytes(8) + b := make([]byte, 8) + _, _ = r.buf.Read(b) return int64(binary.BigEndian.Uint64(b)) } @@ -154,7 +157,8 @@ func (r *NetworkReader) ReadBytes(len int) ([]byte, error) { } func (r *NetworkReader) ReadInt32() (int32, error) { - b, err := r.ReadBytes(4) + b := make([]byte, 4) + _, err := r.conn.Read(b) if err != nil { return 0, err } diff --git a/binary/utils.go b/binary/utils.go index 999aa19b..58fa592c 100644 --- a/binary/utils.go +++ b/binary/utils.go @@ -6,7 +6,6 @@ import ( "compress/zlib" binary2 "encoding/binary" "encoding/hex" - "io" "net" "github.com/Mrs4s/MiraiGo/utils" @@ -34,7 +33,7 @@ func ZlibUncompress(src []byte) []byte { var out bytes.Buffer r, _ := zlib.NewReader(b) defer r.Close() - io.Copy(&out, r) + _, _ = out.ReadFrom(r) return out.Bytes() } @@ -42,7 +41,8 @@ func ZlibCompress(data []byte) []byte { zw := acquireZlibWriter() _, _ = zw.w.Write(data) _ = zw.w.Close() - ret := append([]byte(nil), zw.buf.Bytes()...) + ret := make([]byte, len(zw.buf.Bytes())) + copy(ret, zw.buf.Bytes()) releaseZlibWriter(zw) return ret } @@ -51,7 +51,8 @@ func GZipCompress(data []byte) []byte { gw := AcquireGzipWriter() _, _ = gw.Write(data) _ = gw.Close() - ret := append([]byte(nil), gw.buf.Bytes()...) + ret := make([]byte, len(gw.buf.Bytes())) + copy(ret, gw.buf.Bytes()) ReleaseGzipWriter(gw) return ret } @@ -61,7 +62,7 @@ func GZipUncompress(src []byte) []byte { var out bytes.Buffer r, _ := gzip.NewReader(b) defer r.Close() - _, _ = io.Copy(&out, r) + _, _ = out.ReadFrom(r) return out.Bytes() } @@ -118,7 +119,7 @@ func ToChunkedBytesF(b []byte, size int, f func([]byte)) { } } -func ToBytes(i interface{}) []byte { +func ToBytes(i any) []byte { return NewWriterF(func(w *Writer) { // TODO: more types switch t := i.(type) { diff --git a/binary/writer.go b/binary/writer.go index d8a8703a..837cd862 100644 --- a/binary/writer.go +++ b/binary/writer.go @@ -12,7 +12,8 @@ type Writer bytes.Buffer func NewWriterF(f func(writer *Writer)) []byte { w := SelectWriter() f(w) - b := append([]byte(nil), w.Bytes()...) + b := make([]byte, len(w.Bytes())) + copy(b, w.Bytes()) w.put() return b } diff --git a/client/builders.go b/client/builders.go index 0154ae6a..ca9e85c4 100644 --- a/client/builders.go +++ b/client/builders.go @@ -2,7 +2,7 @@ package client import ( "crypto/md5" - "encoding/hex" + "fmt" "math/rand" "time" @@ -903,7 +903,7 @@ func (c *QQClient) buildOffPicUpPacket(target int64, md5 []byte, size int32) (ui DstUin: proto.Uint64(uint64(target)), FileMd5: md5, FileSize: proto.Uint64(uint64(size)), - FileName: []byte(hex.EncodeToString(md5) + ".jpg"), + FileName: []byte(fmt.Sprintf("%x.jpg", md5)), SrcTerm: proto.Uint32(5), PlatformType: proto.Uint32(9), BuType: proto.Uint32(1), diff --git a/client/c2c_processor.go b/client/c2c_processor.go index f71d2b71..064bdfd9 100644 --- a/client/c2c_processor.go +++ b/client/c2c_processor.go @@ -71,7 +71,7 @@ func (c *QQClient) c2cMessageSyncProcessor(rsp *msg.GetMessageResponse, info *ne _, _ = c.sendAndWait(c.buildDeleteMessageRequestPacket(delItems)) } if rsp.GetSyncFlag() != msg.SyncFlag_STOP { - c.Debug("continue sync with flag: %v", rsp.SyncFlag) + c.debug("continue sync with flag: %v", rsp.SyncFlag) seq, pkt := c.buildGetMessageRequestPacket(rsp.GetSyncFlag(), time.Now().Unix()) _, _ = c.sendAndWait(seq, pkt, info.Params) } @@ -80,12 +80,12 @@ func (c *QQClient) c2cMessageSyncProcessor(rsp *msg.GetMessageResponse, info *ne func (c *QQClient) commMsgProcessor(pMsg *msg.Message, info *network.IncomingPacketInfo) { strKey := fmt.Sprintf("%d%d%d%d", pMsg.Head.GetFromUin(), pMsg.Head.GetToUin(), pMsg.Head.GetMsgSeq(), pMsg.Head.GetMsgUid()) if _, ok := c.msgSvcCache.GetAndUpdate(strKey, time.Hour); ok { - c.Debug("c2c msg %v already exists in cache. skip.", pMsg.Head.GetMsgUid()) + c.debug("c2c msg %v already exists in cache. skip.", pMsg.Head.GetMsgUid()) return } - c.msgSvcCache.Add(strKey, "", time.Hour) + c.msgSvcCache.Add(strKey, unit{}, time.Hour) if c.lastC2CMsgTime > int64(pMsg.Head.GetMsgTime()) && (c.lastC2CMsgTime-int64(pMsg.Head.GetMsgTime())) > 60*10 { - c.Debug("c2c msg filtered by time. lastMsgTime: %v msgTime: %v", c.lastC2CMsgTime, pMsg.Head.GetMsgTime()) + c.debug("c2c msg filtered by time. lastMsgTime: %v msgTime: %v", c.lastC2CMsgTime, pMsg.Head.GetMsgTime()) return } c.lastC2CMsgTime = int64(pMsg.Head.GetMsgTime()) @@ -95,7 +95,7 @@ func (c *QQClient) commMsgProcessor(pMsg *msg.Message, info *network.IncomingPac if decoder, _ := peekC2CDecoder(pMsg.Head.GetMsgType()); decoder != nil { decoder(c, pMsg, info) } else { - c.Debug("unknown msg type on c2c processor: %v - %v", pMsg.Head.GetMsgType(), pMsg.Head.GetC2CCmd()) + c.debug("unknown msg type on c2c processor: %v - %v", pMsg.Head.GetMsgType(), pMsg.Head.GetC2CCmd()) } } @@ -132,12 +132,12 @@ func privateMessageDecoder(c *QQClient, pMsg *msg.Message, _ *network.IncomingPa } if pMsg.Head.GetFromUin() == c.Uin { - c.dispatchPrivateMessageSelf(c.parsePrivateMessage(pMsg)) + c.SelfPrivateMessageEvent.dispatch(c, c.parsePrivateMessage(pMsg)) return } - c.dispatchPrivateMessage(c.parsePrivateMessage(pMsg)) + c.PrivateMessageEvent.dispatch(c, c.parsePrivateMessage(pMsg)) default: - c.Debug("unknown c2c cmd on private msg decoder: %v", pMsg.Head.GetC2CCmd()) + c.debug("unknown c2c cmd on private msg decoder: %v", pMsg.Head.GetC2CCmd()) } } @@ -149,7 +149,7 @@ func privatePttDecoder(c *QQClient, pMsg *msg.Message, _ *network.IncomingPacket // m := binary.NewReader(pMsg.Body.RichText.Ptt.Reserve[1:]).ReadTlvMap(1) // T3 -> timestamp T8 -> voiceType T9 -> voiceLength T10 -> PbReserveStruct } - c.dispatchPrivateMessage(c.parsePrivateMessage(pMsg)) + c.PrivateMessageEvent.dispatch(c, c.parsePrivateMessage(pMsg)) } func tempSessionDecoder(c *QQClient, pMsg *msg.Message, _ *network.IncomingPacketInfo) { @@ -206,7 +206,7 @@ func tempSessionDecoder(c *QQClient, pMsg *msg.Message, _ *network.IncomingPacke if pMsg.Head.GetFromUin() == c.Uin { return } - c.dispatchTempMessage(&TempMessageEvent{ + c.TempMessageEvent.dispatch(c, &TempMessageEvent{ Message: c.parseTempMessage(pMsg), Session: session, }) @@ -219,20 +219,20 @@ func troopAddMemberBroadcastDecoder(c *QQClient, pMsg *msg.Message, _ *network.I group := c.FindGroupByUin(pMsg.Head.GetFromUin()) if pMsg.Head.GetAuthUin() == c.Uin { if group == nil && c.ReloadGroupList() == nil { - c.dispatchJoinGroupEvent(c.FindGroupByUin(pMsg.Head.GetFromUin())) + c.GroupJoinEvent.dispatch(c, c.FindGroupByUin(pMsg.Head.GetFromUin())) } } else { if group != nil && group.FindMember(pMsg.Head.GetAuthUin()) == nil { mem, err := c.GetMemberInfo(group.Code, pMsg.Head.GetAuthUin()) if err != nil { - c.Debug("error to fetch new member info: %v", err) + c.debug("error to fetch new member info: %v", err) return } group.Update(func(info *GroupInfo) { info.Members = append(info.Members, mem) info.sort() }) - c.dispatchNewMemberEvent(&MemberJoinGroupEvent{ + c.GroupMemberJoinEvent.dispatch(c, &MemberJoinGroupEvent{ Group: group, Member: mem, }) @@ -255,7 +255,7 @@ func troopSystemMessageDecoder(c *QQClient, pMsg *msg.Message, info *network.Inc reader := binary.NewReader(pMsg.Body.MsgContent) groupCode := uint32(reader.ReadInt32()) if info := c.FindGroup(int64(groupCode)); info != nil && pMsg.Head.GetGroupName() != "" && info.Name != pMsg.Head.GetGroupName() { - c.Debug("group %v name updated. %v -> %v", groupCode, info.Name, pMsg.Head.GetGroupName()) + c.debug("group %v name updated. %v -> %v", groupCode, info.Name, pMsg.Head.GetGroupName()) info.Name = pMsg.Head.GetGroupName() } } @@ -267,7 +267,7 @@ func msgType0x211Decoder(c *QQClient, pMsg *msg.Message, info *network.IncomingP sub4 := msg.SubMsgType0X4Body{} if err := proto.Unmarshal(pMsg.Body.MsgContent, &sub4); err != nil { err = errors.Wrap(err, "unmarshal sub msg 0x4 error") - c.Error("unmarshal sub msg 0x4 error: %v", err) + c.error("unmarshal sub msg 0x4 error: %v", err) return } if sub4.NotOnlineFile != nil && sub4.NotOnlineFile.GetSubcmd() == 1 { // subcmd: 1 -> sendPacket, 2-> recv @@ -275,7 +275,7 @@ func msgType0x211Decoder(c *QQClient, pMsg *msg.Message, info *network.IncomingP if err != nil { return } - c.dispatchOfflineFileEvent(&OfflineFileEvent{ + c.OfflineFileEvent.dispatch(c, &OfflineFileEvent{ FileName: string(sub4.NotOnlineFile.FileName), FileSize: sub4.NotOnlineFile.GetFileSize(), Sender: pMsg.Head.GetFromUin(), diff --git a/client/client.go b/client/client.go index a5a3563d..de7ebeae 100644 --- a/client/client.go +++ b/client/client.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" "net" + "net/netip" "sort" "strconv" "sync" @@ -13,17 +14,18 @@ import ( "github.com/pkg/errors" "go.uber.org/atomic" + "github.com/RomiChan/syncx" + "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client/internal/auth" "github.com/Mrs4s/MiraiGo/client/internal/highway" "github.com/Mrs4s/MiraiGo/client/internal/network" "github.com/Mrs4s/MiraiGo/client/internal/oicq" "github.com/Mrs4s/MiraiGo/client/pb/msg" + "github.com/Mrs4s/MiraiGo/message" "github.com/Mrs4s/MiraiGo/utils" ) -//go:generate go run github.com/a8m/syncmap -o "handler_map_gen.go" -pkg client -name HandlerMap "map[uint16]*handlerInfo" - type QQClient struct { Uin int64 PasswordMd5 [16]byte @@ -48,17 +50,17 @@ type QQClient struct { // protocol public field SequenceId atomic.Int32 SessionId []byte - RandomKey []byte TCP *network.TCPListener // todo: combine other protocol state into one struct ConnectTime time.Time transport *network.Transport oicq *oicq.Codec + logger Logger // internal state - handlers HandlerMap - waiters sync.Map - servers []*net.TCPAddr + handlers syncx.Map[uint16, *handlerInfo] + waiters syncx.Map[string, func(any, error)] + servers []netip.AddrPort currServerIndex int retryTimes int version *auth.AppVersion @@ -76,19 +78,47 @@ type QQClient struct { // otherSrvAddrs []string // fileStorageInfo *jce.FileStoragePushFSSvcList + // event handles + eventHandlers eventHandlers + PrivateMessageEvent EventHandle[*message.PrivateMessage] + TempMessageEvent EventHandle[*TempMessageEvent] + GroupMessageEvent EventHandle[*message.GroupMessage] + SelfPrivateMessageEvent EventHandle[*message.PrivateMessage] + SelfGroupMessageEvent EventHandle[*message.GroupMessage] + GroupMuteEvent EventHandle[*GroupMuteEvent] + GroupMessageRecalledEvent EventHandle[*GroupMessageRecalledEvent] + FriendMessageRecalledEvent EventHandle[*FriendMessageRecalledEvent] + GroupJoinEvent EventHandle[*GroupInfo] + GroupLeaveEvent EventHandle[*GroupLeaveEvent] + GroupMemberJoinEvent EventHandle[*MemberJoinGroupEvent] + GroupMemberLeaveEvent EventHandle[*MemberLeaveGroupEvent] + MemberCardUpdatedEvent EventHandle[*MemberCardUpdatedEvent] + GroupNameUpdatedEvent EventHandle[*GroupNameUpdatedEvent] + GroupMemberPermissionChangedEvent EventHandle[*MemberPermissionChangedEvent] + GroupInvitedEvent EventHandle[*GroupInvitedRequest] + UserWantJoinGroupEvent EventHandle[*UserJoinGroupRequest] + NewFriendEvent EventHandle[*NewFriendEvent] + NewFriendRequestEvent EventHandle[*NewFriendRequest] + DisconnectedEvent EventHandle[*ClientDisconnectedEvent] + GroupNotifyEvent EventHandle[INotifyEvent] + FriendNotifyEvent EventHandle[INotifyEvent] + MemberSpecialTitleUpdatedEvent EventHandle[*MemberSpecialTitleUpdatedEvent] + GroupDigestEvent EventHandle[*GroupDigestEvent] + OtherClientStatusChangedEvent EventHandle[*OtherClientStatusChangedEvent] + OfflineFileEvent EventHandle[*OfflineFileEvent] + // message state - msgSvcCache *utils.Cache + msgSvcCache *utils.Cache[unit] lastC2CMsgTime int64 - transCache *utils.Cache + transCache *utils.Cache[unit] groupSysMsgCache *GroupSystemMessages - msgBuilders sync.Map - onlinePushCache *utils.Cache + msgBuilders syncx.Map[int32, *messageBuilder] + onlinePushCache *utils.Cache[unit] heartbeatEnabled bool requestPacketRequestID atomic.Int32 groupSeq atomic.Int32 friendSeq atomic.Int32 highwayApplyUpSeq atomic.Int32 - eventHandlers eventHandlers groupListLock sync.Mutex } @@ -103,7 +133,7 @@ type QiDianAccountInfo struct { } type handlerInfo struct { - fun func(i interface{}, err error) + fun func(i any, err error) dynamic bool params network.RequestParams } @@ -115,7 +145,7 @@ func (h *handlerInfo) getParams() network.RequestParams { return h.params } -var decoders = map[string]func(*QQClient, *network.IncomingPacketInfo, []byte) (interface{}, error){ +var decoders = map[string]func(*QQClient, *network.IncomingPacketInfo, []byte) (any, error){ "wtlogin.login": decodeLoginResponse, "wtlogin.exchange_emp": decodeExchangeEmpResponse, "wtlogin.trans_emp": decodeTransEmpResponse, @@ -164,10 +194,9 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { sig: &auth.SigInfo{ OutPacketSessionID: []byte{0x02, 0xB0, 0x5B, 0x8B}, }, - msgSvcCache: utils.NewCache(time.Second * 15), - transCache: utils.NewCache(time.Second * 15), - onlinePushCache: utils.NewCache(time.Second * 15), - servers: []*net.TCPAddr{}, + msgSvcCache: utils.NewCache[unit](time.Second * 15), + transCache: utils.NewCache[unit](time.Second * 15), + onlinePushCache: utils.NewCache[unit](time.Second * 15), alive: true, highwaySession: new(highway.Session), @@ -197,28 +226,29 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { } adds, err := net.LookupIP("msfwifi.3g.qq.com") // host servers if err == nil && len(adds) > 0 { - var hostAddrs []*net.TCPAddr + var hostAddrs []netip.AddrPort for _, addr := range adds { - hostAddrs = append(hostAddrs, &net.TCPAddr{ - IP: addr, - Port: 8080, - }) + ip, ok := netip.AddrFromSlice(addr.To4()) + if ok { + hostAddrs = append(hostAddrs, netip.AddrPortFrom(ip, 8080)) + } } cli.servers = append(hostAddrs, cli.servers...) } if len(cli.servers) == 0 { - cli.servers = []*net.TCPAddr{ // default servers - {IP: net.IP{42, 81, 172, 81}, Port: 80}, - {IP: net.IP{114, 221, 148, 59}, Port: 14000}, - {IP: net.IP{42, 81, 172, 147}, Port: 443}, - {IP: net.IP{125, 94, 60, 146}, Port: 80}, - {IP: net.IP{114, 221, 144, 215}, Port: 80}, - {IP: net.IP{42, 81, 172, 22}, Port: 80}, + cli.servers = []netip.AddrPort{ // default servers + netip.AddrPortFrom(netip.AddrFrom4([4]byte{42, 81, 172, 81}), 80), + netip.AddrPortFrom(netip.AddrFrom4([4]byte{114, 221, 148, 59}), 14000), + netip.AddrPortFrom(netip.AddrFrom4([4]byte{42, 81, 172, 147}), 443), + netip.AddrPortFrom(netip.AddrFrom4([4]byte{125, 94, 60, 146}), 80), + netip.AddrPortFrom(netip.AddrFrom4([4]byte{114, 221, 144, 215}), 80), + netip.AddrPortFrom(netip.AddrFrom4([4]byte{42, 81, 172, 22}), 80), } } pings := make([]int64, len(cli.servers)) wg := sync.WaitGroup{} wg.Add(len(cli.servers)) + // println(len(cli.servers)) for i := range cli.servers { go func(index int) { defer wg.Done() @@ -239,7 +269,6 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient { } cli.TCP.PlannedDisconnect(cli.plannedDisconnect) cli.TCP.UnexpectedDisconnect(cli.unexpectedDisconnect) - rand.Read(cli.RandomKey) return cli } @@ -391,7 +420,7 @@ func (c *QQClient) SubmitSMS(code string) (*LoginResponse, error) { func (c *QQClient) RequestSMS() bool { rsp, err := c.sendAndWait(c.buildSMSRequestPacket()) if err != nil { - c.Error("request sms error: %v", err) + c.error("request sms error: %v", err) return false } return rsp.(LoginResponse).Error == SMSNeededError @@ -399,18 +428,18 @@ func (c *QQClient) RequestSMS() bool { func (c *QQClient) init(tokenLogin bool) error { if len(c.sig.G) == 0 { - c.Warning("device lock is disable. http api may fail.") + c.warning("device lock is disable. http api may fail.") } c.highwaySession.Uin = strconv.FormatInt(c.Uin, 10) if err := c.registerClient(); err != nil { return errors.Wrap(err, "register error") } if tokenLogin { - notify := make(chan struct{}) - d := c.waitPacket("StatSvc.ReqMSFOffline", func(i interface{}, err error) { + notify := make(chan struct{}, 2) + d := c.waitPacket("StatSvc.ReqMSFOffline", func(i any, err error) { notify <- struct{}{} }) - d2 := c.waitPacket("MessageSvc.PushForceOffline", func(i interface{}, err error) { + d2 := c.waitPacket("MessageSvc.PushForceOffline", func(i any, err error) { notify <- struct{}{} }) select { @@ -651,7 +680,7 @@ func (c *QQClient) FindGroup(code int64) *GroupInfo { return nil } -func (c *QQClient) SolveGroupJoinRequest(i interface{}, accept, block bool, reason string) { +func (c *QQClient) SolveGroupJoinRequest(i any, accept, block bool, reason string) { if accept { block = false reason = "" @@ -680,7 +709,7 @@ func (c *QQClient) SolveFriendRequest(req *NewFriendRequest, accept bool) { func (c *QQClient) getSKey() string { if c.sig.SKeyExpiredTime < time.Now().Unix() && len(c.sig.G) > 0 { - c.Debug("skey expired. refresh...") + c.debug("skey expired. refresh...") _, _ = c.sendAndWait(c.buildRequestTgtgtNopicsigPacket()) } return string(c.sig.SKey) @@ -761,7 +790,7 @@ func (c *QQClient) UpdateProfile(profile ProfileDetailUpdate) { _, _ = c.sendAndWait(c.buildUpdateProfileDetailPacket(profile)) } -func (c *QQClient) SetCustomServer(servers []*net.TCPAddr) { +func (c *QQClient) SetCustomServer(servers []netip.AddrPort) { c.servers = append(servers, c.servers...) } diff --git a/client/decoders.go b/client/decoders.go index 736014f5..d5e5427d 100644 --- a/client/decoders.go +++ b/client/decoders.go @@ -2,8 +2,7 @@ package client import ( "crypto/md5" - "encoding/hex" - "net" + "net/netip" "strconv" "strings" "sync" @@ -31,7 +30,7 @@ var ( ) // wtlogin.login -func decodeLoginResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeLoginResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { reader := binary.NewReader(payload) reader.ReadUInt16() // sub command t := reader.ReadByte() @@ -45,11 +44,11 @@ func decodeLoginResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []b } if t == 0 { // login success // if t150, ok := m[0x150]; ok { - // c.t150 = t150 + // c.t150 = t150 + // } + // if t161, ok := m[0x161]; ok { + // c.decodeT161(t161) // } - if t161, ok := m[0x161]; ok { - c.decodeT161(t161) - } if m.Exists(0x403) { c.sig.RandSeed = m[0x403] } @@ -171,15 +170,15 @@ func decodeLoginResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []b ErrorMessage: t146r.ReadStringShort(), }, nil } - c.Debug("unknown login response: %v", t) + c.debug("unknown login response: %v", t) for k, v := range m { - c.Debug("Type: %v Value: %v", strconv.FormatInt(int64(k), 16), hex.EncodeToString(v)) + c.debug("Type: %d Value: %x", k, v) } return nil, errors.Errorf("unknown login response: %v", t) // ? } // StatSvc.register -func decodeClientRegisterResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeClientRegisterResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -188,7 +187,7 @@ func decodeClientRegisterResponse(c *QQClient, _ *network.IncomingPacketInfo, pa svcRsp.ReadFrom(jce.NewJceReader(data.Map["SvcRespRegister"]["QQService.SvcRespRegister"][1:])) if svcRsp.Result != "" || svcRsp.ReplyCode != 0 { if svcRsp.Result != "" { - c.Error("reg error: %v", svcRsp.Result) + c.error("reg error: %v", svcRsp.Result) } return nil, errors.New("reg failed") } @@ -196,7 +195,7 @@ func decodeClientRegisterResponse(c *QQClient, _ *network.IncomingPacketInfo, pa } // wtlogin.exchange_emp -func decodeExchangeEmpResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeExchangeEmpResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { reader := binary.NewReader(payload) cmd := reader.ReadUInt16() t := reader.ReadByte() @@ -216,7 +215,7 @@ func decodeExchangeEmpResponse(c *QQClient, _ *network.IncomingPacketInfo, paylo } // wtlogin.trans_emp -func decodeTransEmpResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeTransEmpResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { if len(payload) < 48 { return nil, errors.New("missing payload length") } @@ -299,7 +298,7 @@ func decodeTransEmpResponse(c *QQClient, _ *network.IncomingPacketInfo, payload } // ConfigPushSvc.PushReq -func decodePushReqPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodePushReqPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -313,16 +312,16 @@ func decodePushReqPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []b ssoPkt := jce.NewJceReader(jceBuf) servers := ssoPkt.ReadSsoServerInfos(1) if len(servers) > 0 { - var adds []*net.TCPAddr + var adds []netip.AddrPort for _, s := range servers { if strings.Contains(s.Server, "com") { continue } - c.Debug("got new server addr: %v location: %v", s.Server, s.Location) - adds = append(adds, &net.TCPAddr{ - IP: net.ParseIP(s.Server), - Port: int(s.Port), - }) + c.debug("got new server addr: %v location: %v", s.Server, s.Location) + addr, err := netip.ParseAddr(s.Server) + if err == nil { + adds = append(adds, netip.AddrPortFrom(addr, uint16(s.Port))) + } } f := true for _, e := range c.eventHandlers.serverUpdatedHandlers { @@ -341,7 +340,7 @@ func decodePushReqPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []b fmtPkt := jce.NewJceReader(jceBuf) list := &jce.FileStoragePushFSSvcList{} list.ReadFrom(fmtPkt) - c.Debug("got file storage svc push.") + c.debug("got file storage svc push.") // c.fileStorageInfo = list rsp := cmd0x6ff.C501RspBody{} if err := proto.Unmarshal(list.BigDataChannel.PbBuf, &rsp); err == nil && rsp.RspBody != nil { @@ -372,7 +371,7 @@ func decodePushReqPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []b } // MessageSvc.PbGetMsg -func decodeMessageSvcPacket(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeMessageSvcPacket(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := msg.GetMessageResponse{} err := proto.Unmarshal(payload, &rsp) if err != nil { @@ -383,7 +382,7 @@ func decodeMessageSvcPacket(c *QQClient, info *network.IncomingPacketInfo, paylo } // MessageSvc.PushNotify -func decodeSvcNotify(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeSvcNotify(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload[4:])) data := &jce.RequestDataVersion2{} @@ -410,7 +409,7 @@ func decodeSvcNotify(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) } // SummaryCard.ReqSummaryCard -func decodeSummaryCardResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeSummaryCardResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -458,7 +457,7 @@ func decodeSummaryCardResponse(_ *QQClient, _ *network.IncomingPacketInfo, paylo } // friendlist.getFriendGroupList -func decodeFriendGroupListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeFriendGroupListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion3{} @@ -483,7 +482,7 @@ func decodeFriendGroupListResponse(_ *QQClient, _ *network.IncomingPacketInfo, p } // friendlist.delFriend -func decodeFriendDeleteResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeFriendDeleteResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion3{} @@ -496,7 +495,7 @@ func decodeFriendDeleteResponse(_ *QQClient, _ *network.IncomingPacketInfo, payl } // friendlist.GetTroopListReqV2 -func decodeGroupListResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGroupListResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion3{} @@ -528,7 +527,7 @@ func decodeGroupListResponse(c *QQClient, _ *network.IncomingPacketInfo, payload } // friendlist.GetTroopMemberListReq -func decodeGroupMemberListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGroupMemberListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion3{} @@ -564,7 +563,7 @@ func decodeGroupMemberListResponse(_ *QQClient, _ *network.IncomingPacketInfo, p } // group_member_card.get_group_member_card_info -func decodeGroupMemberInfoResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGroupMemberInfoResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := pb.GroupMemberRspBody{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -597,7 +596,7 @@ func decodeGroupMemberInfoResponse(c *QQClient, _ *network.IncomingPacketInfo, p } // LongConn.OffPicUp -func decodeOffPicUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeOffPicUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := cmd0x352.RspBody{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -635,7 +634,7 @@ func decodeOffPicUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload } // OnlinePush.PbPushTransMsg -func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { info := msg.TransMsgInfo{} err := proto.Unmarshal(payload, &info) if err != nil { @@ -646,7 +645,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay if _, ok := c.transCache.Get(idStr); ok { return nil, nil } - c.transCache.Add(idStr, "", time.Second*15) + c.transCache.Add(idStr, unit{}, time.Second*15) if info.GetMsgType() == 34 { data.ReadInt32() data.ReadByte() @@ -659,10 +658,10 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay switch typ { case 0x02: if target == c.Uin { - c.dispatchLeaveGroupEvent(&GroupLeaveEvent{Group: g}) + c.GroupLeaveEvent.dispatch(c, &GroupLeaveEvent{Group: g}) } else if m := g.FindMember(target); m != nil { g.removeMember(target) - c.dispatchMemberLeaveEvent(&MemberLeaveGroupEvent{ + c.GroupMemberLeaveEvent.dispatch(c, &MemberLeaveGroupEvent{ Group: g, Member: m, }) @@ -672,13 +671,13 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay return nil, err } if target == c.Uin { - c.dispatchLeaveGroupEvent(&GroupLeaveEvent{ + c.GroupLeaveEvent.dispatch(c, &GroupLeaveEvent{ Group: g, Operator: g.FindMember(operator), }) } else if m := g.FindMember(target); m != nil { g.removeMember(target) - c.dispatchMemberLeaveEvent(&MemberLeaveGroupEvent{ + c.GroupMemberLeaveEvent.dispatch(c, &MemberLeaveGroupEvent{ Group: g, Member: m, Operator: g.FindMember(operator), @@ -687,7 +686,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay case 0x82: if m := g.FindMember(target); m != nil { g.removeMember(target) - c.dispatchMemberLeaveEvent(&MemberLeaveGroupEvent{ + c.GroupMemberLeaveEvent.dispatch(c, &MemberLeaveGroupEvent{ Group: g, Member: m, }) @@ -695,7 +694,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay case 0x83: if m := g.FindMember(target); m != nil { g.removeMember(target) - c.dispatchMemberLeaveEvent(&MemberLeaveGroupEvent{ + c.GroupMemberLeaveEvent.dispatch(c, &MemberLeaveGroupEvent{ Group: g, Member: m, Operator: g.FindMember(operator), @@ -724,7 +723,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay if mem.Permission != newPermission { old := mem.Permission mem.Permission = newPermission - c.dispatchPermissionChanged(&MemberPermissionChangedEvent{ + c.GroupMemberPermissionChangedEvent.dispatch(c, &MemberPermissionChangedEvent{ Group: g, Member: mem, OldPermission: old, @@ -738,7 +737,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay } // ProfileService.Pb.ReqSystemMsgNew.Friend -func decodeSystemMsgFriendPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeSystemMsgFriendPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := structmsg.RspSystemMsgNew{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -748,7 +747,7 @@ func decodeSystemMsgFriendPacket(c *QQClient, _ *network.IncomingPacketInfo, pay } st := rsp.Friendmsgs[0] if st.Msg != nil { - c.dispatchNewFriendRequest(&NewFriendRequest{ + c.NewFriendRequestEvent.dispatch(c, &NewFriendRequest{ RequestId: st.MsgSeq, Message: st.Msg.MsgAdditional, RequesterUin: st.ReqUin, @@ -760,7 +759,7 @@ func decodeSystemMsgFriendPacket(c *QQClient, _ *network.IncomingPacketInfo, pay } // MessageSvc.PushForceOffline -func decodeForceOfflinePacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeForceOfflinePacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -768,28 +767,25 @@ func decodeForceOfflinePacket(c *QQClient, _ *network.IncomingPacketInfo, payloa r := jce.NewJceReader(data.Map["req_PushForceOffline"]["PushNotifyPack.RequestPushForceOffline"][1:]) tips := r.ReadString(2) c.Disconnect() - go c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: tips}) + go c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: tips}) return nil, nil } // StatSvc.ReqMSFOffline -func decodeMSFOfflinePacket(c *QQClient, _ *network.IncomingPacketInfo, _ []byte) (interface{}, error) { +func decodeMSFOfflinePacket(c *QQClient, _ *network.IncomingPacketInfo, _ []byte) (any, error) { // c.lastLostMsg = "服务器端强制下线." c.Disconnect() // 这个decoder不能消耗太多时间, event另起线程处理 - go c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "服务端强制下线."}) + go c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "服务端强制下线."}) return nil, nil } // OidbSvc.0xd79 -func decodeWordSegmentation(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeWordSegmentation(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := &oidb.D79RspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } if rsp.Content != nil { return rsp.Content.SliceContent, nil @@ -797,7 +793,7 @@ func decodeWordSegmentation(_ *QQClient, _ *network.IncomingPacketInfo, payload return nil, errors.New("no word received") } -func decodeSidExpiredPacket(c *QQClient, i *network.IncomingPacketInfo, _ []byte) (interface{}, error) { +func decodeSidExpiredPacket(c *QQClient, i *network.IncomingPacketInfo, _ []byte) (any, error) { _, err := c.sendAndWait(c.buildRequestChangeSigPacket(3554528)) if err != nil { return nil, errors.Wrap(err, "resign client error") @@ -827,6 +823,6 @@ func decodeAppInfoResponse(_ *QQClient, _ *incomingPacketInfo, payload []byte) ( } */ -func ignoreDecoder(_ *QQClient, _ *network.IncomingPacketInfo, _ []byte) (interface{}, error) { +func ignoreDecoder(_ *QQClient, _ *network.IncomingPacketInfo, _ []byte) (any, error) { return nil, nil } diff --git a/client/entities.go b/client/entities.go index adf22a15..6f12069b 100644 --- a/client/entities.go +++ b/client/entities.go @@ -175,12 +175,6 @@ type ( client *QQClient } - LogEvent struct { - Type string - Message string - Dump []byte - } - ServerUpdatedEvent struct { Servers []jce.SsoServerInfo } @@ -296,6 +290,9 @@ type ( SigSession []byte SessionKey []byte } + + // unit is an alias for struct{}, like `()` in rust + unit = struct{} ) const ( diff --git a/client/events.go b/client/events.go index 0a3634f7..f71c34a7 100644 --- a/client/events.go +++ b/client/events.go @@ -8,12 +8,40 @@ import ( "github.com/Mrs4s/MiraiGo/message" ) +// protected all EventHandle, since write is very rare, use +// only one lock to save memory +var eventMu sync.RWMutex + +type EventHandle[T any] struct { + // QQClient? + handlers []func(client *QQClient, event T) +} + +func (handle *EventHandle[T]) Subscribe(handler func(client *QQClient, event T)) { + eventMu.Lock() + defer eventMu.Unlock() + // shrink the slice + newHandlers := make([]func(client *QQClient, event T), len(handle.handlers)+1) + copy(newHandlers, handle.handlers) + newHandlers[len(handle.handlers)] = handler + handle.handlers = newHandlers +} + +func (handle *EventHandle[T]) dispatch(client *QQClient, event T) { + eventMu.RLock() + defer func() { + eventMu.RUnlock() + if pan := recover(); pan != nil { + fmt.Printf("event error: %v\n%s", pan, debug.Stack()) + } + }() + for _, handler := range handle.handlers { + handler(client, event) + } +} + type eventHandlers struct { - privateMessageHandlers []func(*QQClient, *message.PrivateMessage) - tempMessageHandlers []func(*QQClient, *TempMessageEvent) - groupMessageHandlers []func(*QQClient, *message.GroupMessage) - selfPrivateMessageHandlers []func(*QQClient, *message.PrivateMessage) - selfGroupMessageHandlers []func(*QQClient, *message.GroupMessage) + // todo: move to event handle guildChannelMessageHandlers []func(*QQClient, *message.GuildChannelMessage) guildMessageReactionsUpdatedHandlers []func(*QQClient, *GuildMessageReactionsUpdatedEvent) guildMessageRecalledHandlers []func(*QQClient, *GuildMessageRecalledEvent) @@ -21,58 +49,9 @@ type eventHandlers struct { guildChannelCreatedHandlers []func(*QQClient, *GuildChannelOperationEvent) guildChannelDestroyedHandlers []func(*QQClient, *GuildChannelOperationEvent) memberJoinedGuildHandlers []func(*QQClient, *MemberJoinGuildEvent) - groupMuteEventHandlers []func(*QQClient, *GroupMuteEvent) - groupRecalledHandlers []func(*QQClient, *GroupMessageRecalledEvent) - friendRecalledHandlers []func(*QQClient, *FriendMessageRecalledEvent) - joinGroupHandlers []func(*QQClient, *GroupInfo) - leaveGroupHandlers []func(*QQClient, *GroupLeaveEvent) - memberJoinedHandlers []func(*QQClient, *MemberJoinGroupEvent) - memberLeavedHandlers []func(*QQClient, *MemberLeaveGroupEvent) - memberCardUpdatedHandlers []func(*QQClient, *MemberCardUpdatedEvent) - groupNameUpdatedHandlers []func(*QQClient, *GroupNameUpdatedEvent) - permissionChangedHandlers []func(*QQClient, *MemberPermissionChangedEvent) - groupInvitedHandlers []func(*QQClient, *GroupInvitedRequest) - joinRequestHandlers []func(*QQClient, *UserJoinGroupRequest) - friendRequestHandlers []func(*QQClient, *NewFriendRequest) - newFriendHandlers []func(*QQClient, *NewFriendEvent) - disconnectHandlers []func(*QQClient, *ClientDisconnectedEvent) - logHandlers []func(*QQClient, *LogEvent) - serverUpdatedHandlers []func(*QQClient, *ServerUpdatedEvent) bool - groupNotifyHandlers []func(*QQClient, INotifyEvent) - friendNotifyHandlers []func(*QQClient, INotifyEvent) - memberTitleUpdatedHandlers []func(*QQClient, *MemberSpecialTitleUpdatedEvent) - offlineFileHandlers []func(*QQClient, *OfflineFileEvent) - otherClientStatusChangedHandlers []func(*QQClient, *OtherClientStatusChangedEvent) - groupDigestHandlers []func(*QQClient, *GroupDigestEvent) - groupMessageReceiptHandlers sync.Map -} -func (c *QQClient) OnPrivateMessage(f func(*QQClient, *message.PrivateMessage)) { - c.eventHandlers.privateMessageHandlers = append(c.eventHandlers.privateMessageHandlers, f) -} - -func (c *QQClient) OnPrivateMessageF(filter func(*message.PrivateMessage) bool, f func(*QQClient, *message.PrivateMessage)) { - c.OnPrivateMessage(func(client *QQClient, msg *message.PrivateMessage) { - if filter(msg) { - f(client, msg) - } - }) -} - -func (c *QQClient) OnTempMessage(f func(*QQClient, *TempMessageEvent)) { - c.eventHandlers.tempMessageHandlers = append(c.eventHandlers.tempMessageHandlers, f) -} - -func (c *QQClient) OnGroupMessage(f func(*QQClient, *message.GroupMessage)) { - c.eventHandlers.groupMessageHandlers = append(c.eventHandlers.groupMessageHandlers, f) -} - -func (c *QQClient) OnSelfPrivateMessage(f func(*QQClient, *message.PrivateMessage)) { - c.eventHandlers.selfPrivateMessageHandlers = append(c.eventHandlers.selfPrivateMessageHandlers, f) -} - -func (c *QQClient) OnSelfGroupMessage(f func(*QQClient, *message.GroupMessage)) { - c.eventHandlers.selfGroupMessageHandlers = append(c.eventHandlers.selfGroupMessageHandlers, f) + serverUpdatedHandlers []func(*QQClient, *ServerUpdatedEvent) bool + groupMessageReceiptHandlers sync.Map } func (s *GuildService) OnGuildChannelMessage(f func(*QQClient, *message.GuildChannelMessage)) { @@ -103,99 +82,10 @@ func (s *GuildService) OnMemberJoinedGuild(f func(*QQClient, *MemberJoinGuildEve s.c.eventHandlers.memberJoinedGuildHandlers = append(s.c.eventHandlers.memberJoinedGuildHandlers, f) } -func (c *QQClient) OnGroupMuted(f func(*QQClient, *GroupMuteEvent)) { - c.eventHandlers.groupMuteEventHandlers = append(c.eventHandlers.groupMuteEventHandlers, f) -} - -func (c *QQClient) OnJoinGroup(f func(*QQClient, *GroupInfo)) { - c.eventHandlers.joinGroupHandlers = append(c.eventHandlers.joinGroupHandlers, f) -} - -func (c *QQClient) OnLeaveGroup(f func(*QQClient, *GroupLeaveEvent)) { - c.eventHandlers.leaveGroupHandlers = append(c.eventHandlers.leaveGroupHandlers, f) -} - -func (c *QQClient) OnGroupMemberJoined(f func(*QQClient, *MemberJoinGroupEvent)) { - c.eventHandlers.memberJoinedHandlers = append(c.eventHandlers.memberJoinedHandlers, f) -} - -func (c *QQClient) OnGroupMemberLeaved(f func(*QQClient, *MemberLeaveGroupEvent)) { - c.eventHandlers.memberLeavedHandlers = append(c.eventHandlers.memberLeavedHandlers, f) -} - -func (c *QQClient) OnGroupMemberCardUpdated(f func(*QQClient, *MemberCardUpdatedEvent)) { - c.eventHandlers.memberCardUpdatedHandlers = append(c.eventHandlers.memberCardUpdatedHandlers, f) -} - -func (c *QQClient) OnGroupNameUpdated(f func(*QQClient, *GroupNameUpdatedEvent)) { - c.eventHandlers.groupNameUpdatedHandlers = append(c.eventHandlers.groupNameUpdatedHandlers, f) -} - -func (c *QQClient) OnGroupMemberPermissionChanged(f func(*QQClient, *MemberPermissionChangedEvent)) { - c.eventHandlers.permissionChangedHandlers = append(c.eventHandlers.permissionChangedHandlers, f) -} - -func (c *QQClient) OnGroupMessageRecalled(f func(*QQClient, *GroupMessageRecalledEvent)) { - c.eventHandlers.groupRecalledHandlers = append(c.eventHandlers.groupRecalledHandlers, f) -} - -func (c *QQClient) OnFriendMessageRecalled(f func(*QQClient, *FriendMessageRecalledEvent)) { - c.eventHandlers.friendRecalledHandlers = append(c.eventHandlers.friendRecalledHandlers, f) -} - -func (c *QQClient) OnGroupInvited(f func(*QQClient, *GroupInvitedRequest)) { - c.eventHandlers.groupInvitedHandlers = append(c.eventHandlers.groupInvitedHandlers, f) -} - -func (c *QQClient) OnUserWantJoinGroup(f func(*QQClient, *UserJoinGroupRequest)) { - c.eventHandlers.joinRequestHandlers = append(c.eventHandlers.joinRequestHandlers, f) -} - -func (c *QQClient) OnNewFriendRequest(f func(*QQClient, *NewFriendRequest)) { - c.eventHandlers.friendRequestHandlers = append(c.eventHandlers.friendRequestHandlers, f) -} - -func (c *QQClient) OnNewFriendAdded(f func(*QQClient, *NewFriendEvent)) { - c.eventHandlers.newFriendHandlers = append(c.eventHandlers.newFriendHandlers, f) -} - -func (c *QQClient) OnDisconnected(f func(*QQClient, *ClientDisconnectedEvent)) { - c.eventHandlers.disconnectHandlers = append(c.eventHandlers.disconnectHandlers, f) -} - func (c *QQClient) OnServerUpdated(f func(*QQClient, *ServerUpdatedEvent) bool) { c.eventHandlers.serverUpdatedHandlers = append(c.eventHandlers.serverUpdatedHandlers, f) } -func (c *QQClient) OnReceivedOfflineFile(f func(*QQClient, *OfflineFileEvent)) { - c.eventHandlers.offlineFileHandlers = append(c.eventHandlers.offlineFileHandlers, f) -} - -func (c *QQClient) OnOtherClientStatusChanged(f func(*QQClient, *OtherClientStatusChangedEvent)) { - c.eventHandlers.otherClientStatusChangedHandlers = append(c.eventHandlers.otherClientStatusChangedHandlers, f) -} - -func (c *QQClient) OnLog(f func(*QQClient, *LogEvent)) { - c.eventHandlers.logHandlers = append(c.eventHandlers.logHandlers, f) -} - -func (c *QQClient) OnGroupNotify(f func(*QQClient, INotifyEvent)) { - c.eventHandlers.groupNotifyHandlers = append(c.eventHandlers.groupNotifyHandlers, f) -} - -func (c *QQClient) OnFriendNotify(f func(*QQClient, INotifyEvent)) { - c.eventHandlers.friendNotifyHandlers = append(c.eventHandlers.friendNotifyHandlers, f) -} - -func (c *QQClient) OnMemberSpecialTitleUpdated(f func(*QQClient, *MemberSpecialTitleUpdatedEvent)) { - c.eventHandlers.memberTitleUpdatedHandlers = append(c.eventHandlers.memberTitleUpdatedHandlers, f) -} - -// OnGroupDigest 群精华消息事件注册 -func (c *QQClient) OnGroupDigest(f func(*QQClient, *GroupDigestEvent)) { - c.eventHandlers.groupDigestHandlers = append(c.eventHandlers.groupDigestHandlers, f) -} - func NewUinFilterPrivate(uin int64) func(*message.PrivateMessage) bool { return func(msg *message.PrivateMessage) bool { return msg.Sender.Uin == uin @@ -210,61 +100,6 @@ func (c *QQClient) onGroupMessageReceipt(id string, f ...func(*QQClient, *groupM c.eventHandlers.groupMessageReceiptHandlers.LoadOrStore(id, f[0]) } -func (c *QQClient) dispatchPrivateMessage(msg *message.PrivateMessage) { - if msg == nil { - return - } - for _, f := range c.eventHandlers.privateMessageHandlers { - cover(func() { - f(c, msg) - }) - } -} - -func (c *QQClient) dispatchTempMessage(e *TempMessageEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.tempMessageHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchGroupMessage(msg *message.GroupMessage) { - if msg == nil { - return - } - for _, f := range c.eventHandlers.groupMessageHandlers { - cover(func() { - f(c, msg) - }) - } -} - -func (c *QQClient) dispatchPrivateMessageSelf(msg *message.PrivateMessage) { - if msg == nil { - return - } - for _, f := range c.eventHandlers.selfPrivateMessageHandlers { - cover(func() { - f(c, msg) - }) - } -} - -func (c *QQClient) dispatchGroupMessageSelf(msg *message.GroupMessage) { - if msg == nil { - return - } - for _, f := range c.eventHandlers.selfGroupMessageHandlers { - cover(func() { - f(c, msg) - }) - } -} - func (c *QQClient) dispatchGuildChannelMessage(msg *message.GuildChannelMessage) { if msg == nil { return @@ -342,255 +177,13 @@ func (c *QQClient) dispatchMemberJoinedGuildEvent(e *MemberJoinGuildEvent) { } } -func (c *QQClient) dispatchGroupMuteEvent(e *GroupMuteEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.groupMuteEventHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchGroupMessageRecalledEvent(e *GroupMessageRecalledEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.groupRecalledHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchFriendMessageRecalledEvent(e *FriendMessageRecalledEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.friendRecalledHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchJoinGroupEvent(group *GroupInfo) { - if group == nil { - return - } - for _, f := range c.eventHandlers.joinGroupHandlers { - cover(func() { - f(c, group) - }) - } -} - -func (c *QQClient) dispatchLeaveGroupEvent(e *GroupLeaveEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.leaveGroupHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchNewMemberEvent(e *MemberJoinGroupEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.memberJoinedHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchMemberLeaveEvent(e *MemberLeaveGroupEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.memberLeavedHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchMemberCardUpdatedEvent(e *MemberCardUpdatedEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.memberCardUpdatedHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchGroupNameUpdatedEvent(e *GroupNameUpdatedEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.groupNameUpdatedHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchPermissionChanged(e *MemberPermissionChangedEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.permissionChangedHandlers { - cover(func() { - f(c, e) - }) - } -} - func (c *QQClient) dispatchGroupMessageReceiptEvent(e *groupMessageReceiptEvent) { - c.eventHandlers.groupMessageReceiptHandlers.Range(func(_, f interface{}) bool { + c.eventHandlers.groupMessageReceiptHandlers.Range(func(_, f any) bool { go f.(func(*QQClient, *groupMessageReceiptEvent))(c, e) return true }) } -func (c *QQClient) dispatchGroupInvitedEvent(e *GroupInvitedRequest) { - if e == nil { - return - } - for _, f := range c.eventHandlers.groupInvitedHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchJoinGroupRequest(r *UserJoinGroupRequest) { - if r == nil { - return - } - for _, f := range c.eventHandlers.joinRequestHandlers { - cover(func() { - f(c, r) - }) - } -} - -func (c *QQClient) dispatchNewFriendRequest(r *NewFriendRequest) { - if r == nil { - return - } - for _, f := range c.eventHandlers.friendRequestHandlers { - cover(func() { - f(c, r) - }) - } -} - -func (c *QQClient) dispatchNewFriendEvent(e *NewFriendEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.newFriendHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchGroupNotifyEvent(e INotifyEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.groupNotifyHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchFriendNotifyEvent(e INotifyEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.friendNotifyHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchMemberSpecialTitleUpdateEvent(e *MemberSpecialTitleUpdatedEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.memberTitleUpdatedHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchDisconnectEvent(e *ClientDisconnectedEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.disconnectHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchOfflineFileEvent(e *OfflineFileEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.offlineFileHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchOtherClientStatusChangedEvent(e *OtherClientStatusChangedEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.otherClientStatusChangedHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchGroupDigestEvent(e *GroupDigestEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.groupDigestHandlers { - cover(func() { - f(c, e) - }) - } -} - -func (c *QQClient) dispatchLogEvent(e *LogEvent) { - if e == nil { - return - } - for _, f := range c.eventHandlers.logHandlers { - cover(func() { - f(c, e) - }) - } -} - func cover(f func()) { defer func() { if pan := recover(); pan != nil { diff --git a/client/face.go b/client/face.go index dca3a22f..dec2ab4d 100644 --- a/client/face.go +++ b/client/face.go @@ -41,7 +41,7 @@ func (c *QQClient) buildFaceroamRequestPacket() (uint16, []byte) { return c.uniPacket("Faceroam.OpReq", payload) } -func decodeFaceroamResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeFaceroamResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := faceroam.FaceroamRspBody{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") diff --git a/client/global.go b/client/global.go index 909adde3..ca623397 100644 --- a/client/global.go +++ b/client/global.go @@ -6,6 +6,7 @@ import ( "fmt" "math/rand" "net" + "net/netip" "sort" "strconv" "strings" @@ -114,7 +115,7 @@ func GenIMEI() string { return final.String() } -func getSSOAddress() ([]*net.TCPAddr, error) { +func getSSOAddress() ([]netip.AddrPort, error) { protocol := SystemDeviceInfo.Protocol.Version() key, _ := hex.DecodeString("F0441F5FF42DA58FDCF7949ABA62D411") payload := jce.NewJceWriter(). // see ServerConfig.d @@ -122,7 +123,7 @@ func getSSOAddress() ([]*net.TCPAddr, error) { WriteString("00000", 4).WriteInt32(100, 5). WriteInt32(int32(protocol.AppId), 6).WriteString(SystemDeviceInfo.IMEI, 7). WriteInt64(0, 8).WriteInt64(0, 9).WriteInt64(0, 10). - WriteInt64(0, 11).WriteByte(0, 12).WriteInt64(0, 13).WriteByte(1, 14).Bytes() + WriteInt64(0, 11).WriteByte(0, 12).WriteInt64(0, 13).Bytes() buf := &jce.RequestDataVersion3{ Map: map[string][]byte{"HttpServerListReq": packUniRequestData(payload)}, } @@ -140,7 +141,7 @@ func getSSOAddress() ([]*net.TCPAddr, error) { tea := binary.NewTeaCipher(key) encpkt := tea.Encrypt(b) cl() - rsp, err := utils.HttpPostBytes("https://configsvr.msf.3g.qq.com/configsvr/serverlist.jsp", encpkt) + rsp, err := utils.HttpPostBytes("https://configsvr.msf.3g.qq.com/configsvr/serverlist.jsp?mType=getssolist", encpkt) if err != nil { return nil, errors.Wrap(err, "unable to fetch server list") } @@ -150,15 +151,15 @@ func getSSOAddress() ([]*net.TCPAddr, error) { data.ReadFrom(jce.NewJceReader(rspPkt.SBuffer)) reader := jce.NewJceReader(data.Map["HttpServerListRes"][1:]) servers := reader.ReadSsoServerInfos(2) - adds := make([]*net.TCPAddr, 0, len(servers)) + adds := make([]netip.AddrPort, 0, len(servers)) for _, s := range servers { if strings.Contains(s.Server, "com") { continue } - adds = append(adds, &net.TCPAddr{ - IP: net.ParseIP(s.Server), - Port: int(s.Port), - }) + ip, ok := netip.AddrFromSlice(net.ParseIP(s.Server)) + if ok { + adds = append(adds, netip.AddrPortFrom(ip, uint16(s.Port))) + } } return adds, nil } @@ -244,14 +245,15 @@ func (c *QQClient) parseTempMessage(msg *msg.Message) *message.TempMessage { } func (c *QQClient) messageBuilder(seq int32) *messageBuilder { - builder := &messageBuilder{} - actual, ok := c.msgBuilders.LoadOrStore(seq, builder) + actual, ok := c.msgBuilders.Load(seq) if !ok { + builder := &messageBuilder{} + actual, _ = c.msgBuilders.LoadOrStore(seq, builder) time.AfterFunc(time.Minute, func() { c.msgBuilders.Delete(seq) // delete avoid memory leak }) } - return actual.(*messageBuilder) + return actual } type messageBuilder struct { @@ -295,11 +297,6 @@ func packUniRequestData(data []byte) []byte { func genForwardTemplate(resID, preview, summary string, ts int64, items []*msg.PbMultiMsgItem) *message.ForwardElement { template := forwardDisplay(resID, strconv.FormatInt(ts, 10), preview, summary) - for _, item := range items { - if item.GetFileName() == "MultiMsg" { - *item.FileName = strconv.FormatInt(ts, 10) - } - } return &message.ForwardElement{ FileName: strconv.FormatInt(ts, 10), Content: template, @@ -356,59 +353,16 @@ func (c *QQClient) packOIDBPackageProto(cmd, serviceType int32, msg proto.Messag return c.packOIDBPackage(cmd, serviceType, b) } -func unpackOIDBPackage(buff []byte, payload proto.Message) error { +func unpackOIDBPackage(payload []byte, rsp proto.Message) error { pkg := new(oidb.OIDBSSOPkg) - if err := proto.Unmarshal(buff, pkg); err != nil { + if err := proto.Unmarshal(payload, pkg); err != nil { return errors.Wrap(err, "failed to unmarshal protobuf message") } if pkg.Result != 0 { return errors.Errorf("oidb result unsuccessful: %v msg: %v", pkg.Result, pkg.ErrorMsg) } - if err := proto.Unmarshal(pkg.Bodybuffer, payload); err != nil { + if err := proto.Unmarshal(pkg.Bodybuffer, rsp); err != nil { return errors.Wrap(err, "failed to unmarshal protobuf message") } return nil } - -func (c *QQClient) Error(msg string, args ...interface{}) { - c.dispatchLogEvent(&LogEvent{ - Type: "ERROR", - Message: fmt.Sprintf(msg, args...), - }) -} - -func (c *QQClient) Warning(msg string, args ...interface{}) { - c.dispatchLogEvent(&LogEvent{ - Type: "WARNING", - Message: fmt.Sprintf(msg, args...), - }) -} - -func (c *QQClient) Info(msg string, args ...interface{}) { - c.dispatchLogEvent(&LogEvent{ - Type: "INFO", - Message: fmt.Sprintf(msg, args...), - }) -} - -func (c *QQClient) Debug(msg string, args ...interface{}) { - c.dispatchLogEvent(&LogEvent{ - Type: "DEBUG", - Message: fmt.Sprintf(msg, args...), - }) -} - -func (c *QQClient) Trace(msg string, args ...interface{}) { - c.dispatchLogEvent(&LogEvent{ - Type: "TRACE", - Message: fmt.Sprintf(msg, args...), - }) -} - -func (c *QQClient) Dump(msg string, data []byte, args ...interface{}) { - c.dispatchLogEvent(&LogEvent{ - Type: "DUMP", - Message: fmt.Sprintf(msg, args...), - Dump: data, - }) -} diff --git a/client/group_file.go b/client/group_file.go index c2799a15..bb3c428a 100644 --- a/client/group_file.go +++ b/client/group_file.go @@ -71,7 +71,7 @@ func init() { func (c *QQClient) GetGroupFileSystem(groupCode int64) (fs *GroupFileSystem, err error) { defer func() { if pan := recover(); pan != nil { - c.Error("get group fs error: %v\n%s", pan, debug.Stack()) + c.error("get group fs error: %v\n%s", pan, debug.Stack()) err = errors.New("fs error") } }() @@ -104,7 +104,7 @@ func (c *QQClient) GetGroupFileUrl(groupCode int64, fileId string, busId int32) return "" } url := i.(string) - url += "?fname=" + hex.EncodeToString([]byte(fileId)) + url += fmt.Sprintf("?fname=%x", fileId) return url } @@ -276,7 +276,7 @@ func (fs *GroupFileSystem) DeleteFile(parentFolderID, fileId string, busId int32 } func (c *QQClient) buildGroupFileUploadReqPacket(parentFolderID, fileName string, groupCode, fileSize int64, md5, sha1 []byte) (uint16, []byte) { - b, _ := proto.Marshal(&oidb.D6D6ReqBody{UploadFileReq: &oidb.UploadFileReqBody{ + body := &oidb.D6D6ReqBody{UploadFileReq: &oidb.UploadFileReqBody{ GroupCode: &groupCode, AppId: proto.Int32(3), BusId: proto.Int32(102), @@ -288,14 +288,8 @@ func (c *QQClient) buildGroupFileUploadReqPacket(parentFolderID, fileName string Sha: sha1, Md5: md5, SupportMultiUpload: proto.Bool(true), - }}) - req := &oidb.OIDBSSOPkg{ - Command: 1750, - ServiceType: 0, - Bodybuffer: b, - ClientVersion: "android 8.4.8", - } - payload, _ := proto.Marshal(req) + }} + payload := c.packOIDBPackageProto(1750, 0, body) return c.uniPacket("OidbSvc.0x6d6_0", payload) } @@ -328,31 +322,19 @@ func (c *QQClient) buildGroupFileListRequestPacket(groupCode int64, folderID str StartIndex: &startIndex, Context: EmptyBytes, }} - b, _ := proto.Marshal(body) - req := &oidb.OIDBSSOPkg{ - Command: 1752, - ServiceType: 1, - Bodybuffer: b, - ClientVersion: "android 8.4.8", - } - payload, _ := proto.Marshal(req) + payload := c.packOIDBPackageProto(1752, 1, body) return c.uniPacket("OidbSvc.0x6d8_1", payload) } func (c *QQClient) buildGroupFileCountRequestPacket(groupCode int64) (uint16, []byte) { - body := &oidb.D6D8ReqBody{GroupFileCountReq: &oidb.GetFileCountReqBody{ - GroupCode: proto.Uint64(uint64(groupCode)), - AppId: proto.Uint32(3), - BusId: proto.Uint32(0), - }} - b, _ := proto.Marshal(body) - req := &oidb.OIDBSSOPkg{ - Command: 1752, - ServiceType: 2, - Bodybuffer: b, - ClientVersion: "android 8.4.8", + body := &oidb.D6D8ReqBody{ + GroupFileCountReq: &oidb.GetFileCountReqBody{ + GroupCode: proto.Uint64(uint64(groupCode)), + AppId: proto.Uint32(3), + BusId: proto.Uint32(0), + }, } - payload, _ := proto.Marshal(req) + payload := c.packOIDBPackageProto(1752, 2, body) return c.uniPacket("OidbSvc.0x6d8_1", payload) } @@ -361,14 +343,7 @@ func (c *QQClient) buildGroupFileSpaceRequestPacket(groupCode int64) (uint16, [] GroupCode: proto.Uint64(uint64(groupCode)), AppId: proto.Uint32(3), }} - b, _ := proto.Marshal(body) - req := &oidb.OIDBSSOPkg{ - Command: 1752, - ServiceType: 3, - Bodybuffer: b, - ClientVersion: "android 8.4.8", - } - payload, _ := proto.Marshal(req) + payload := c.packOIDBPackageProto(1752, 3, body) return c.uniPacket("OidbSvc.0x6d8_1", payload) } @@ -411,13 +386,7 @@ func (c *QQClient) buildGroupFileDownloadReqPacket(groupCode int64, fileId strin FileId: &fileId, }, } - b, _ := proto.Marshal(body) - req := &oidb.OIDBSSOPkg{ - Command: 1750, - ServiceType: 2, - Bodybuffer: b, - } - payload, _ := proto.Marshal(req) + payload := c.packOIDBPackageProto(1750, 2, body) return c.uniPacket("OidbSvc.0x6d6_2", payload) } @@ -429,38 +398,25 @@ func (c *QQClient) buildGroupFileDeleteReqPacket(groupCode int64, parentFolderId ParentFolderId: &parentFolderId, FileId: &fileId, }} - b, _ := proto.Marshal(body) - req := &oidb.OIDBSSOPkg{ - Command: 1750, - ServiceType: 3, - Bodybuffer: b, - ClientVersion: "android 8.4.8", - } - payload, _ := proto.Marshal(req) + payload := c.packOIDBPackageProto(1750, 3, body) return c.uniPacket("OidbSvc.0x6d6_3", payload) } -func decodeOIDB6d81Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeOIDB6d81Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.D6D8RspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } return &rsp, nil } // OidbSvc.0x6d6_2 -func decodeOIDB6d62Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeOIDB6d62Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.D6D6RspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } if rsp.DownloadFileRsp.DownloadUrl == nil { return nil, errors.New(rsp.DownloadFileRsp.GetClientWording()) @@ -470,50 +426,38 @@ func decodeOIDB6d62Response(_ *QQClient, _ *network.IncomingPacketInfo, payload return fmt.Sprintf("http://%s/ftn_handler/%s/", ip, url), nil } -func decodeOIDB6d63Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeOIDB6d63Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.D6D6RspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if rsp.DeleteFileRsp == nil { - return "", nil + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } return rsp.DeleteFileRsp.GetClientWording(), nil } -func decodeOIDB6d60Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeOIDB6d60Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.D6D6RspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } return rsp.UploadFileRsp, nil } -func decodeOIDB6d7Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeOIDB6d7Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.D6D7RspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + if retCode := rsp.CreateFolderRsp.GetRetCode(); retCode != 0 { + return nil, errors.Errorf("create folder error: %v", retCode) } - if rsp.CreateFolderRsp != nil && rsp.CreateFolderRsp.GetRetCode() != 0 { - return nil, errors.Errorf("create folder error: %v", rsp.CreateFolderRsp.GetRetCode()) + if retCode := rsp.RenameFolderRsp.GetRetCode(); retCode != 0 { + return nil, errors.Errorf("rename folder error: %v", retCode) } - if rsp.RenameFolderRsp != nil && rsp.RenameFolderRsp.GetRetCode() != 0 { - return nil, errors.Errorf("rename folder error: %v", rsp.CreateFolderRsp.GetRetCode()) - } - if rsp.DeleteFolderRsp != nil && rsp.DeleteFolderRsp.GetRetCode() != 0 { - return nil, errors.Errorf("delete folder error: %v", rsp.CreateFolderRsp.GetRetCode()) + if retCode := rsp.DeleteFolderRsp.GetRetCode(); retCode != 0 { + return nil, errors.Errorf("delete folder error: %v", retCode) } return nil, nil } diff --git a/client/group_info.go b/client/group_info.go index 712076cf..f40e82d2 100644 --- a/client/group_info.go +++ b/client/group_info.go @@ -116,12 +116,7 @@ func (c *QQClient) buildGroupInfoRequestPacket(groupCode int64) (uint16, []byte) }, PcClientVersion: proto.Uint32(0), } - b, _ := proto.Marshal(body) - req := &oidb.OIDBSSOPkg{ - Command: 2189, - Bodybuffer: b, - } - payload, _ := proto.Marshal(req) + payload := c.packOIDBPackageProto(2189, 0, body) return c.uniPacket("OidbSvc.0x88d_0", payload) } @@ -188,7 +183,7 @@ func (c *QQClient) buildGroupSearchPacket(keyword string) (uint16, []byte) { } // SummaryCard.ReqSearch -func decodeGroupSearchResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGroupSearchResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -226,14 +221,11 @@ func decodeGroupSearchResponse(_ *QQClient, _ *network.IncomingPacketInfo, paylo } // OidbSvc.0x88d_0 -func decodeGroupInfoResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeGroupInfoResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.D88DRspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } if len(rsp.RspGroupInfo) == 0 { return nil, errors.New(string(rsp.StrErrorInfo)) @@ -336,7 +328,7 @@ func (g *GroupInfo) AdministratorOrOwner() bool { } func (g *GroupInfo) FindMember(uin int64) *GroupMemberInfo { - r := g.Read(func(info *GroupInfo) interface{} { + r := g.Read(func(info *GroupInfo) any { return info.FindMemberWithoutLock(uin) }) if r == nil { @@ -368,7 +360,7 @@ func (g *GroupInfo) Update(f func(*GroupInfo)) { f(g) } -func (g *GroupInfo) Read(f func(*GroupInfo) interface{}) interface{} { +func (g *GroupInfo) Read(f func(*GroupInfo) any) any { g.lock.RLock() defer g.lock.RUnlock() return f(g) diff --git a/client/group_msg.go b/client/group_msg.go index 56fd3f72..1183ad39 100644 --- a/client/group_msg.go +++ b/client/group_msg.go @@ -2,9 +2,9 @@ package client import ( "bytes" + "crypto/md5" "encoding/base64" "encoding/json" - "fmt" "math" "math/rand" "strconv" @@ -61,7 +61,7 @@ func (c *QQClient) SendGroupMessage(groupCode int64, m *message.SendingMessage, Message: m.Elements, })) if err != nil { - c.Error("%v", err) + c.error("%v", err) return nil } ret := c.sendGroupMessage(groupCode, false, &message.SendingMessage{Elements: []message.IMessageElement{lmsg}}) @@ -171,14 +171,17 @@ func (c *QQClient) uploadGroupLongMessage(groupCode int64, m *message.ForwardMes } for i, ip := range rsp.Uint32UpIp { addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])} - input := highway.Input{ + hash := md5.Sum(body) + input := highway.Transaction{ CommandID: 27, - Key: rsp.MsgSig, + Ticket: rsp.MsgSig, Body: bytes.NewReader(body), + Size: int64(len(body)), + Sum: hash[:], } err := c.highwaySession.Upload(addr, input) if err != nil { - c.Error("highway upload long message error: %v", err) + c.error("highway upload long message error: %v", err) continue } return genLongTemplate(rsp.MsgResid, m.Brief(), ts), nil @@ -186,33 +189,6 @@ func (c *QQClient) uploadGroupLongMessage(groupCode int64, m *message.ForwardMes return nil, errors.New("upload long message error: highway server list is empty or not available server.") } -func (c *QQClient) UploadGroupForwardMessage(groupCode int64, m *message.ForwardMessage) *message.ForwardElement { - if m.Length() > 200 { - return nil - } - ts := time.Now().UnixNano() - seq := c.nextGroupSeq() - data, hash, items := m.CalculateValidationDataForward(seq, rand.Int31(), groupCode) - rsp, body, err := c.multiMsgApplyUp(groupCode, data, hash, 2) - if err != nil { - return nil - } - for i, ip := range rsp.Uint32UpIp { - addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])} - input := highway.Input{ - CommandID: 27, - Key: rsp.MsgSig, - Body: bytes.NewReader(body), - } - err := c.highwaySession.Upload(addr, input) - if err != nil { - continue - } - return genForwardTemplate(rsp.MsgResid, m.Preview(), fmt.Sprintf("查看 %d 条转发消息", m.Length()), ts, items) - } - return nil -} - func (c *QQClient) multiMsgApplyUp(groupCode int64, data []byte, hash []byte, buType int32) (*multimsg.MultiMsgApplyUpRsp, []byte, error) { i, err := c.sendAndWait(c.buildMultiApplyUpPacket(data, hash, buType, utils.ToGroupUin(groupCode))) if err != nil { @@ -297,7 +273,7 @@ func (c *QQClient) buildAtAllRemainRequestPacket(groupCode int64) (uint16, []byt } // OnlinePush.PbPushGroupMsg -func decodeGroupMessagePacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGroupMessagePacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { pkt := msg.PushMessagePacket{} err := proto.Unmarshal(payload, &pkt) if err != nil { @@ -317,22 +293,22 @@ func decodeGroupMessagePacket(c *QQClient, _ *network.IncomingPacketInfo, payloa if builder.len() >= pkt.Message.Content.GetPkgNum() { c.msgBuilders.Delete(seq) if pkt.Message.Head.GetFromUin() == c.Uin { - c.dispatchGroupMessageSelf(c.parseGroupMessage(builder.build())) + c.SelfGroupMessageEvent.dispatch(c, c.parseGroupMessage(builder.build())) } else { - c.dispatchGroupMessage(c.parseGroupMessage(builder.build())) + c.GroupMessageEvent.dispatch(c, c.parseGroupMessage(builder.build())) } } return nil, nil } if pkt.Message.Head.GetFromUin() == c.Uin { - c.dispatchGroupMessageSelf(c.parseGroupMessage(pkt.Message)) + c.SelfGroupMessageEvent.dispatch(c, c.parseGroupMessage(pkt.Message)) } else { - c.dispatchGroupMessage(c.parseGroupMessage(pkt.Message)) + c.GroupMessageEvent.dispatch(c, c.parseGroupMessage(pkt.Message)) } return nil, nil } -func decodeMsgSendResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeMsgSendResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := msg.SendMessageResponse{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -340,20 +316,20 @@ func decodeMsgSendResponse(c *QQClient, _ *network.IncomingPacketInfo, payload [ switch rsp.GetResult() { case 0: // OK. case 55: - c.Error("sendPacket msg error: %v Bot has blocked target's content", rsp.GetResult()) + c.error("sendPacket msg error: %v Bot has blocked target's content", rsp.GetResult()) default: - c.Error("sendPacket msg error: %v %v", rsp.GetResult(), rsp.GetErrMsg()) + c.error("sendPacket msg error: %v %v", rsp.GetResult(), rsp.GetErrMsg()) } return nil, nil } -func decodeGetGroupMsgResponse(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGetGroupMsgResponse(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := msg.GetGroupMsgResp{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") } if rsp.GetResult() != 0 { - c.Error("get msg error: %v %v", rsp.GetResult(), rsp.GetErrmsg()) + c.error("get msg error: %v %v", rsp.GetResult(), rsp.GetErrmsg()) return nil, errors.Errorf("get msg error: %v msg: %v", rsp.GetResult(), rsp.GetErrmsg()) } var ret []*message.GroupMessage @@ -363,7 +339,7 @@ func decodeGetGroupMsgResponse(c *QQClient, info *network.IncomingPacketInfo, pa } if m.Content != nil && m.Content.GetPkgNum() > 1 && !info.Params.Bool("raw") { if m.Content.GetPkgIndex() == 0 { - c.Debug("build fragmented message from history") + c.debug("build fragmented message from history") i := m.Head.GetMsgSeq() - m.Content.GetPkgNum() builder := &messageBuilder{} for { @@ -396,14 +372,11 @@ func decodeGetGroupMsgResponse(c *QQClient, info *network.IncomingPacketInfo, pa return ret, nil } -func decodeAtAllRemainResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeAtAllRemainResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.D8A7RspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } return &AtAllRemainInfo{ CanAtAll: rsp.GetCanAtAll(), @@ -415,10 +388,10 @@ func decodeAtAllRemainResponse(_ *QQClient, _ *network.IncomingPacketInfo, paylo func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage { group := c.FindGroup(m.Head.GroupInfo.GetGroupCode()) if group == nil { - c.Debug("sync group %v.", m.Head.GroupInfo.GetGroupCode()) + c.debug("sync group %v.", m.Head.GroupInfo.GetGroupCode()) info, err := c.GetGroupInfo(m.Head.GroupInfo.GetGroupCode()) if err != nil { - c.Error("error to sync group %v : %+v", m.Head.GroupInfo.GetGroupCode(), err) + c.error("error to sync group %v : %+v", m.Head.GroupInfo.GetGroupCode(), err) return nil } group = info @@ -427,7 +400,7 @@ func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage { if len(group.Members) == 0 { mem, err := c.GetGroupMembers(group) if err != nil { - c.Error("error to sync group %v member : %+v", m.Head.GroupInfo.GroupCode, err) + c.error("error to sync group %v member : %+v", m.Head.GroupInfo.GroupCode, err) return nil } group.Members = mem @@ -463,7 +436,7 @@ func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage { mem = info group.Members = append(group.Members, mem) group.sort() - go c.dispatchNewMemberEvent(&MemberJoinGroupEvent{ + go c.GroupMemberJoinEvent.dispatch(c, &MemberJoinGroupEvent{ Group: group, Member: info, }) @@ -531,7 +504,7 @@ func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage { mem.CardName = groupCard } if old != mem.CardName { - go c.dispatchMemberCardUpdatedEvent(&MemberCardUpdatedEvent{ + c.MemberCardUpdatedEvent.dispatch(c, &MemberCardUpdatedEvent{ Group: group, OldCard: old, Member: mem, @@ -592,14 +565,11 @@ func (c *QQClient) buildEssenceMsgOperatePacket(groupCode int64, msgSeq, msgRand } // OidbSvc.0xeac_1/2 -func decodeEssenceMsgResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeEssenceMsgResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := &oidb.EACRspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } return rsp, nil } diff --git a/client/guild.go b/client/guild.go index 56d9791e..e9735b42 100644 --- a/client/guild.go +++ b/client/guild.go @@ -710,7 +710,7 @@ func convertChannelInfo(info *channel.GuildChannelInfo) *ChannelInfo { func (c *QQClient) syncChannelFirstView() { rsp, err := c.sendAndWaitDynamic(c.buildSyncChannelFirstViewPacket()) if err != nil { - c.Error("sync channel error: %v", err) + c.error("sync channel error: %v", err) return } firstViewRsp := new(channel.FirstViewRsp) @@ -723,7 +723,7 @@ func (c *QQClient) syncChannelFirstView() { c.GuildService.Nickname = self.Nickname c.GuildService.AvatarUrl = self.AvatarUrl } else { - c.Error("get self guild profile error: %v", err) + c.error("get self guild profile error: %v", err) } } @@ -737,7 +737,7 @@ func (c *QQClient) buildSyncChannelFirstViewPacket() (uint16, []byte) { return c.uniPacket("trpc.group_pro.synclogic.SyncLogic.SyncFirstView", payload) } -func decodeGuildPushFirstView(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGuildPushFirstView(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { firstViewMsg := new(channel.FirstViewMsg) if err := proto.Unmarshal(payload, firstViewMsg); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -754,7 +754,7 @@ func decodeGuildPushFirstView(c *QQClient, _ *network.IncomingPacketInfo, payloa } channels, err := c.GuildService.FetchChannelList(info.GuildId) if err != nil { - c.Warning("waring: fetch guild %v channel error %v. will use sync node to fill channel list field", guild.GuildId, err) + c.warning("waring: fetch guild %v channel error %v. will use sync node to fill channel list field", guild.GuildId, err) for _, node := range guild.ChannelNodes { meta := new(channel.ChannelMsgMeta) _ = proto.Unmarshal(node.Meta, meta) diff --git a/client/guild_eventflow.go b/client/guild_eventflow.go index d40f559d..ff1465b2 100644 --- a/client/guild_eventflow.go +++ b/client/guild_eventflow.go @@ -27,7 +27,7 @@ type tipsPushInfo struct { ChannelId uint64 } -func decodeGuildEventFlowPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGuildEventFlowPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { push := new(channel.MsgOnlinePush) if err := proto.Unmarshal(payload, push); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -80,7 +80,7 @@ func decodeGuildEventFlowPacket(c *QQClient, _ *network.IncomingPacketInfo, payl } eventBody := new(channel.EventBody) if err := proto.Unmarshal(common.PbElem, eventBody); err != nil { - c.Error("failed to unmarshal guild channel event body: %v", err) + c.error("failed to unmarshal guild channel event body: %v", err) continue } c.processGuildEventBody(m, eventBody) @@ -106,7 +106,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody var guild *GuildInfo if m.Head.RoutingHead.GetGuildId() != 0 { if guild = c.GuildService.FindGuild(m.Head.RoutingHead.GetGuildId()); guild == nil { - c.Warning("process channel event error: guild not found.") + c.warning("process channel event error: guild not found.") return } } @@ -118,7 +118,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody } channelInfo, err := c.GuildService.FetchChannelInfo(guild.GuildId, chanId.GetChanId()) if err != nil { - c.Warning("process create channel event error: fetch channel info error: %v", err) + c.warning("process create channel event error: fetch channel info error: %v", err) continue } guild.Channels = append(guild.Channels, channelInfo) @@ -148,7 +148,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody if oldInfo == nil { info, err := c.GuildService.FetchChannelInfo(m.Head.RoutingHead.GetGuildId(), eventBody.ChangeChanInfo.GetChanId()) if err != nil { - c.Error("error to decode channel info updated event: fetch channel info failed: %v", err) + c.error("error to decode channel info updated event: fetch channel info failed: %v", err) return } guild.Channels = append(guild.Channels, info) @@ -159,7 +159,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody } newInfo, err := c.GuildService.FetchChannelInfo(m.Head.RoutingHead.GetGuildId(), eventBody.ChangeChanInfo.GetChanId()) if err != nil { - c.Error("error to decode channel info updated event: fetch channel info failed: %v", err) + c.error("error to decode channel info updated event: fetch channel info failed: %v", err) return } for i := range guild.Channels { @@ -178,13 +178,13 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody case eventBody.JoinGuild != nil: /* 应该不会重复推送把, 不会吧不会吧 if mem := guild.FindMember(eventBody.JoinGuild.GetMemberTinyid()); mem != nil { - c.Info("ignore join guild event: member %v already exists", mem.TinyId) + c.info("ignore join guild event: member %v already exists", mem.TinyId) return } */ profile, err := c.GuildService.FetchGuildMemberProfileInfo(guild.GuildId, eventBody.JoinGuild.GetMemberTinyid()) if err != nil { - c.Error("error to decode member join guild event: get member profile error: %v", err) + c.error("error to decode member join guild event: get member profile error: %v", err) return } info := &GuildMemberInfo{ @@ -210,7 +210,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody if eventBody.UpdateMsg.GetEventType() == 4 { // 消息贴表情更新 (包含添加或删除) t, err := c.GuildService.pullChannelMessages(m.Head.RoutingHead.GetGuildId(), m.Head.RoutingHead.GetChannelId(), eventBody.UpdateMsg.GetMsgSeq(), eventBody.UpdateMsg.GetMsgSeq(), eventBody.UpdateMsg.GetEventVersion()-1, false) if err != nil || len(t) == 0 { - c.Error("process guild event flow error: pull eventMsg message error: %v", err) + c.error("process guild event flow error: pull eventMsg message error: %v", err) return } // 自己的消息被贴表情会单独推送一个tips, 这里不需要解析 @@ -223,7 +223,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody MessageId: t[0].Head.ContentHead.GetSeq(), CurrentReactions: decodeGuildMessageEmojiReactions(t[0]), } - tipsInfo, err := c.waitPacketTimeoutSyncF("MsgPush.PushGroupProMsg", time.Second, func(i interface{}) bool { + tipsInfo, err := c.waitPacketTimeoutSyncF("MsgPush.PushGroupProMsg", time.Second, func(i any) bool { if i == nil { return false } diff --git a/client/guild_msg.go b/client/guild_msg.go index fe0bd6a8..378a8125 100644 --- a/client/guild_msg.go +++ b/client/guild_msg.go @@ -1,7 +1,7 @@ package client import ( - "encoding/hex" + "fmt" "io" "math/rand" "strconv" @@ -88,7 +88,7 @@ func (s *GuildService) QueryImage(guildId, channelId uint64, hash []byte, size u if body.IsExists { return &message.GuildImageElement{ FileId: body.FileId, - FilePath: hex.EncodeToString(hash) + ".jpg", + FilePath: fmt.Sprintf("%x.jpg", hash), Size: int32(size), DownloadIndex: body.DownloadIndex, Width: body.Width, @@ -177,7 +177,7 @@ func (c *QQClient) buildGuildImageStorePacket(guildId, channelId uint64, hash [] FileId: proto.Uint64(0), FileMd5: hash, FileSize: &size, - FileName: []byte(hex.EncodeToString(hash) + ".jpg"), + FileName: []byte(fmt.Sprintf("%x.jpg", hash)), SrcTerm: proto.Uint32(5), PlatformType: proto.Uint32(9), BuType: proto.Uint32(211), @@ -230,7 +230,7 @@ func decodeGuildMessageEmojiReactions(content *channel.ChannelMsgContent) (r []* return } -func decodeGuildImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGuildImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { body := new(cmd0x388.D388RspBody) if err := proto.Unmarshal(payload, body); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") diff --git a/client/handler_map_gen.go b/client/handler_map_gen.go deleted file mode 100644 index d88a52fb..00000000 --- a/client/handler_map_gen.go +++ /dev/null @@ -1,387 +0,0 @@ -// Code generated by syncmap; DO NOT EDIT. - -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package client - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -// Map is like a Go map[interface{}]interface{} but is safe for concurrent use -// by multiple goroutines without additional locking or coordination. -// Loads, stores, and deletes run in amortized constant time. -// -// The Map type is specialized. Most code should use a plain Go map instead, -// with separate locking or coordination, for better type safety and to make it -// easier to maintain other invariants along with the map content. -// -// The Map type is optimized for two common use cases: (1) when the entry for a given -// key is only ever written once but read many times, as in caches that only grow, -// or (2) when multiple goroutines read, write, and overwrite entries for disjoint -// sets of keys. In these two cases, use of a Map may significantly reduce lock -// contention compared to a Go map paired with a separate Mutex or RWMutex. -// -// The zero Map is empty and ready for use. A Map must not be copied after first use. -type HandlerMap struct { - mu sync.Mutex - - // read contains the portion of the map's contents that are safe for - // concurrent access (with or without mu held). - // - // The read field itself is always safe to load, but must only be stored with - // mu held. - // - // Entries stored in read may be updated concurrently without mu, but updating - // a previously-expunged entry requires that the entry be copied to the dirty - // map and unexpunged with mu held. - read atomic.Value // readOnly - - // dirty contains the portion of the map's contents that require mu to be - // held. To ensure that the dirty map can be promoted to the read map quickly, - // it also includes all of the non-expunged entries in the read map. - // - // Expunged entries are not stored in the dirty map. An expunged entry in the - // clean map must be unexpunged and added to the dirty map before a new value - // can be stored to it. - // - // If the dirty map is nil, the next write to the map will initialize it by - // making a shallow copy of the clean map, omitting stale entries. - dirty map[uint16]*entryHandlerMap - - // misses counts the number of loads since the read map was last updated that - // needed to lock mu to determine whether the key was present. - // - // Once enough misses have occurred to cover the cost of copying the dirty - // map, the dirty map will be promoted to the read map (in the unamended - // state) and the next store to the map will make a new dirty copy. - misses int -} - -// readOnly is an immutable struct stored atomically in the Map.read field. -type readOnlyHandlerMap struct { - m map[uint16]*entryHandlerMap - amended bool // true if the dirty map contains some key not in m. -} - -// expunged is an arbitrary pointer that marks entries which have been deleted -// from the dirty map. -var expungedHandlerMap = unsafe.Pointer(new(*handlerInfo)) - -// An entry is a slot in the map corresponding to a particular key. -type entryHandlerMap struct { - // p points to the interface{} value stored for the entry. - // - // If p == nil, the entry has been deleted and m.dirty == nil. - // - // If p == expunged, the entry has been deleted, m.dirty != nil, and the entry - // is missing from m.dirty. - // - // Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty - // != nil, in m.dirty[key]. - // - // An entry can be deleted by atomic replacement with nil: when m.dirty is - // next created, it will atomically replace nil with expunged and leave - // m.dirty[key] unset. - // - // An entry's associated value can be updated by atomic replacement, provided - // p != expunged. If p == expunged, an entry's associated value can be updated - // only after first setting m.dirty[key] = e so that lookups using the dirty - // map find the entry. - p unsafe.Pointer // *interface{} -} - -func newEntryHandlerMap(i *handlerInfo) *entryHandlerMap { - return &entryHandlerMap{p: unsafe.Pointer(&i)} -} - -// Load returns the value stored in the map for a key, or nil if no -// value is present. -// The ok result indicates whether value was found in the map. -func (m *HandlerMap) Load(key uint16) (value *handlerInfo, ok bool) { - read, _ := m.read.Load().(readOnlyHandlerMap) - e, ok := read.m[key] - if !ok && read.amended { - m.mu.Lock() - // Avoid reporting a spurious miss if m.dirty got promoted while we were - // blocked on m.mu. (If further loads of the same key will not miss, it's - // not worth copying the dirty map for this key.) - read, _ = m.read.Load().(readOnlyHandlerMap) - e, ok = read.m[key] - if !ok && read.amended { - e, ok = m.dirty[key] - // Regardless of whether the entry was present, record a miss: this key - // will take the slow path until the dirty map is promoted to the read - // map. - m.missLocked() - } - m.mu.Unlock() - } - if !ok { - return value, false - } - return e.load() -} - -func (e *entryHandlerMap) load() (value *handlerInfo, ok bool) { - p := atomic.LoadPointer(&e.p) - if p == nil || p == expungedHandlerMap { - return value, false - } - return *(**handlerInfo)(p), true -} - -// Store sets the value for a key. -func (m *HandlerMap) Store(key uint16, value *handlerInfo) { - read, _ := m.read.Load().(readOnlyHandlerMap) - if e, ok := read.m[key]; ok && e.tryStore(&value) { - return - } - - m.mu.Lock() - read, _ = m.read.Load().(readOnlyHandlerMap) - if e, ok := read.m[key]; ok { - if e.unexpungeLocked() { - // The entry was previously expunged, which implies that there is a - // non-nil dirty map and this entry is not in it. - m.dirty[key] = e - } - e.storeLocked(&value) - } else if e, ok := m.dirty[key]; ok { - e.storeLocked(&value) - } else { - if !read.amended { - // We're adding the first new key to the dirty map. - // Make sure it is allocated and mark the read-only map as incomplete. - m.dirtyLocked() - m.read.Store(readOnlyHandlerMap{m: read.m, amended: true}) - } - m.dirty[key] = newEntryHandlerMap(value) - } - m.mu.Unlock() -} - -// tryStore stores a value if the entry has not been expunged. -// -// If the entry is expunged, tryStore returns false and leaves the entry -// unchanged. -func (e *entryHandlerMap) tryStore(i **handlerInfo) bool { - for { - p := atomic.LoadPointer(&e.p) - if p == expungedHandlerMap { - return false - } - if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) { - return true - } - } -} - -// unexpungeLocked ensures that the entry is not marked as expunged. -// -// If the entry was previously expunged, it must be added to the dirty map -// before m.mu is unlocked. -func (e *entryHandlerMap) unexpungeLocked() (wasExpunged bool) { - return atomic.CompareAndSwapPointer(&e.p, expungedHandlerMap, nil) -} - -// storeLocked unconditionally stores a value to the entry. -// -// The entry must be known not to be expunged. -func (e *entryHandlerMap) storeLocked(i **handlerInfo) { - atomic.StorePointer(&e.p, unsafe.Pointer(i)) -} - -// LoadOrStore returns the existing value for the key if present. -// Otherwise, it stores and returns the given value. -// The loaded result is true if the value was loaded, false if stored. -func (m *HandlerMap) LoadOrStore(key uint16, value *handlerInfo) (actual *handlerInfo, loaded bool) { - // Avoid locking if it's a clean hit. - read, _ := m.read.Load().(readOnlyHandlerMap) - if e, ok := read.m[key]; ok { - actual, loaded, ok := e.tryLoadOrStore(value) - if ok { - return actual, loaded - } - } - - m.mu.Lock() - read, _ = m.read.Load().(readOnlyHandlerMap) - if e, ok := read.m[key]; ok { - if e.unexpungeLocked() { - m.dirty[key] = e - } - actual, loaded, _ = e.tryLoadOrStore(value) - } else if e, ok := m.dirty[key]; ok { - actual, loaded, _ = e.tryLoadOrStore(value) - m.missLocked() - } else { - if !read.amended { - // We're adding the first new key to the dirty map. - // Make sure it is allocated and mark the read-only map as incomplete. - m.dirtyLocked() - m.read.Store(readOnlyHandlerMap{m: read.m, amended: true}) - } - m.dirty[key] = newEntryHandlerMap(value) - actual, loaded = value, false - } - m.mu.Unlock() - - return actual, loaded -} - -// tryLoadOrStore atomically loads or stores a value if the entry is not -// expunged. -// -// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and -// returns with ok==false. -func (e *entryHandlerMap) tryLoadOrStore(i *handlerInfo) (actual *handlerInfo, loaded, ok bool) { - p := atomic.LoadPointer(&e.p) - if p == expungedHandlerMap { - return actual, false, false - } - if p != nil { - return *(**handlerInfo)(p), true, true - } - - // Copy the interface after the first load to make this method more amenable - // to escape analysis: if we hit the "load" path or the entry is expunged, we - // shouldn't bother heap-allocating. - ic := i - for { - if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) { - return i, false, true - } - p = atomic.LoadPointer(&e.p) - if p == expungedHandlerMap { - return actual, false, false - } - if p != nil { - return *(**handlerInfo)(p), true, true - } - } -} - -// LoadAndDelete deletes the value for a key, returning the previous value if any. -// The loaded result reports whether the key was present. -func (m *HandlerMap) LoadAndDelete(key uint16) (value *handlerInfo, loaded bool) { - read, _ := m.read.Load().(readOnlyHandlerMap) - e, ok := read.m[key] - if !ok && read.amended { - m.mu.Lock() - read, _ = m.read.Load().(readOnlyHandlerMap) - e, ok = read.m[key] - if !ok && read.amended { - e, ok = m.dirty[key] - delete(m.dirty, key) - // Regardless of whether the entry was present, record a miss: this key - // will take the slow path until the dirty map is promoted to the read - // map. - m.missLocked() - } - m.mu.Unlock() - } - if ok { - return e.delete() - } - return value, false -} - -// Delete deletes the value for a key. -func (m *HandlerMap) Delete(key uint16) { - m.LoadAndDelete(key) -} - -func (e *entryHandlerMap) delete() (value *handlerInfo, ok bool) { - for { - p := atomic.LoadPointer(&e.p) - if p == nil || p == expungedHandlerMap { - return value, false - } - if atomic.CompareAndSwapPointer(&e.p, p, nil) { - return *(**handlerInfo)(p), true - } - } -} - -// Range calls f sequentially for each key and value present in the map. -// If f returns false, range stops the iteration. -// -// Range does not necessarily correspond to any consistent snapshot of the Map's -// contents: no key will be visited more than once, but if the value for any key -// is stored or deleted concurrently, Range may reflect any mapping for that key -// from any point during the Range call. -// -// Range may be O(N) with the number of elements in the map even if f returns -// false after a constant number of calls. -func (m *HandlerMap) Range(f func(key uint16, value *handlerInfo) bool) { - // We need to be able to iterate over all of the keys that were already - // present at the start of the call to Range. - // If read.amended is false, then read.m satisfies that property without - // requiring us to hold m.mu for a long time. - read, _ := m.read.Load().(readOnlyHandlerMap) - if read.amended { - // m.dirty contains keys not in read.m. Fortunately, Range is already O(N) - // (assuming the caller does not break out early), so a call to Range - // amortizes an entire copy of the map: we can promote the dirty copy - // immediately! - m.mu.Lock() - read, _ = m.read.Load().(readOnlyHandlerMap) - if read.amended { - read = readOnlyHandlerMap{m: m.dirty} - m.read.Store(read) - m.dirty = nil - m.misses = 0 - } - m.mu.Unlock() - } - - for k, e := range read.m { - v, ok := e.load() - if !ok { - continue - } - if !f(k, v) { - break - } - } -} - -func (m *HandlerMap) missLocked() { - m.misses++ - if m.misses < len(m.dirty) { - return - } - m.read.Store(readOnlyHandlerMap{m: m.dirty}) - m.dirty = nil - m.misses = 0 -} - -func (m *HandlerMap) dirtyLocked() { - if m.dirty != nil { - return - } - - read, _ := m.read.Load().(readOnlyHandlerMap) - m.dirty = make(map[uint16]*entryHandlerMap, len(read.m)) - for k, e := range read.m { - if !e.tryExpungeLocked() { - m.dirty[k] = e - } - } -} - -func (e *entryHandlerMap) tryExpungeLocked() (isExpunged bool) { - p := atomic.LoadPointer(&e.p) - for p == nil { - if atomic.CompareAndSwapPointer(&e.p, nil, expungedHandlerMap) { - return true - } - p = atomic.LoadPointer(&e.p) - } - return p == expungedHandlerMap -} diff --git a/client/http_api.go b/client/http_api.go index 118daf0d..98a18afb 100644 --- a/client/http_api.go +++ b/client/http_api.go @@ -5,13 +5,11 @@ import ( "encoding/json" "fmt" "html" - "io" "mime/multipart" "net/http" "net/textproto" "net/url" "strconv" - "strings" "github.com/pkg/errors" @@ -21,51 +19,6 @@ import ( "github.com/Mrs4s/MiraiGo/utils" ) -/* -------- VipInfo -------- */ - -type VipInfo struct { - Uin int64 - Name string - Level int - LevelSpeed float64 - VipLevel string - VipGrowthSpeed int - VipGrowthTotal int -} - -func (c *QQClient) GetVipInfo(target int64) (*VipInfo, error) { - b, err := utils.HttpGetBytes(fmt.Sprintf("https://h5.vip.qq.com/p/mc/cardv2/other?platform=1&qq=%d&adtag=geren&aid=mvip.pingtai.mobileqq.androidziliaoka.fromqita", target), c.getCookiesWithDomain("h5.vip.qq.com")) - if err != nil { - return nil, err - } - ret := VipInfo{Uin: target} - b = b[bytes.Index(b, []byte(``))+24:] - t := b[:bytes.Index(b, []byte(``))] - ret.Name = string(t) - b = b[bytes.Index(b, []byte(`LV`))+17:] - t = b[:bytes.Index(b, []byte(`

`))] - ret.Level, _ = strconv.Atoi(string(t)) - b = b[bytes.Index(b, []byte(`
`))+35:] - b = b[bytes.Index(b, []byte(`

`))+3:] - t = b[:bytes.Index(b, []byte(`倍`))] - ret.LevelSpeed, _ = strconv.ParseFloat(string(t), 64) - b = b[bytes.Index(b, []byte(`

`))+35:] - b = b[bytes.Index(b, []byte(`

`))+3:] - st := string(b[:bytes.Index(b, []byte(`

`))]) - st = strings.Replace(st, "", "", 1) - st = strings.Replace(st, "", "", 1) - ret.VipLevel = st - b = b[bytes.Index(b, []byte(`
`))+35:] - b = b[bytes.Index(b, []byte(`

`))+3:] - t = b[:bytes.Index(b, []byte(`

`))] - ret.VipGrowthSpeed, _ = strconv.Atoi(string(t)) - b = b[bytes.Index(b, []byte(`
`))+35:] - b = b[bytes.Index(b, []byte(`

`))+3:] - t = b[:bytes.Index(b, []byte(`

`))] - ret.VipGrowthTotal, _ = strconv.Atoi(string(t)) - return &ret, nil -} - /* -------- GroupHonorInfo -------- */ type ( @@ -165,6 +118,32 @@ func (c *QQClient) GetTts(text string) ([]byte, error) { /* -------- GroupNotice -------- */ +type groupNoticeRsp struct { + Feeds []*struct { + SenderId uint32 `json:"u"` + PublishTime uint64 `json:"pubt"` + Message struct { + Text string `json:"text"` + Images []noticeImage `json:"pics"` + } `json:"msg"` + } `json:"feeds"` +} + +type GroupNoticeMessage struct { + SenderId uint32 `json:"sender_id"` + PublishTime uint64 `json:"publish_time"` + Message struct { + Text string `json:"text"` + Images []GroupNoticeImage `json:"images"` + } `json:"message"` +} + +type GroupNoticeImage struct { + Height string `json:"height"` + Width string `json:"width"` + ID string `json:"id"` +} + type noticePicUpResponse struct { ErrorCode int `json:"ec"` ErrorMessage string `json:"em"` @@ -177,36 +156,77 @@ type noticeImage struct { ID string `json:"id"` } +func (c *QQClient) GetGroupNotice(groupCode int64) (l []*GroupNoticeMessage, err error) { + v := url.Values{} + v.Set("bkn", strconv.Itoa(c.getCSRFToken())) + v.Set("qid", strconv.FormatInt(groupCode, 10)) + v.Set("ft", "23") + v.Set("ni", "1") + v.Set("n", "1") + v.Set("i", "1") + v.Set("log_read", "1") + v.Set("platform", "1") + v.Set("s", "-1") + v.Set("n", "20") + + req, _ := http.NewRequest(http.MethodGet, "https://web.qun.qq.com/cgi-bin/announce/get_t_list?"+v.Encode(), nil) + req.Header.Set("Cookie", c.getCookies()) + rsp, err := utils.Client.Do(req) + if err != nil { + return + } + defer rsp.Body.Close() + + r := groupNoticeRsp{} + err = json.NewDecoder(rsp.Body).Decode(&r) + if err != nil { + return + } + + return c.parseGroupNoticeJson(&r), nil +} + +func (c *QQClient) parseGroupNoticeJson(s *groupNoticeRsp) []*GroupNoticeMessage { + o := make([]*GroupNoticeMessage, 0, len(s.Feeds)) + for _, v := range s.Feeds { + + ims := make([]GroupNoticeImage, 0, len(v.Message.Images)) + for i := 0; i < len(v.Message.Images); i++ { + ims = append(ims, GroupNoticeImage{ + Height: v.Message.Images[i].Height, + Width: v.Message.Images[i].Width, + ID: v.Message.Images[i].ID, + }) + } + + o = append(o, &GroupNoticeMessage{ + SenderId: v.SenderId, + PublishTime: v.PublishTime, + Message: struct { + Text string `json:"text"` + Images []GroupNoticeImage `json:"images"` + }{ + Text: v.Message.Text, + Images: ims, + }, + }) + } + + return o +} + func (c *QQClient) uploadGroupNoticePic(img []byte) (*noticeImage, error) { buf := new(bytes.Buffer) w := multipart.NewWriter(buf) - err := w.WriteField("bkn", strconv.Itoa(c.getCSRFToken())) - if err != nil { - return nil, errors.Wrap(err, "write multipart failed") - } - err = w.WriteField("source", "troopNotice") - if err != nil { - return nil, errors.Wrap(err, "write multipart failed") - } - err = w.WriteField("m", "0") - if err != nil { - return nil, errors.Wrap(err, "write multipart failed") - } + _ = w.WriteField("bkn", strconv.Itoa(c.getCSRFToken())) + _ = w.WriteField("source", "troopNotice") + _ = w.WriteField("m", "0") h := make(textproto.MIMEHeader) h.Set("Content-Disposition", `form-data; name="pic_up"; filename="temp_uploadFile.png"`) h.Set("Content-Type", "image/png") - fw, err := w.CreatePart(h) - if err != nil { - return nil, errors.Wrap(err, "create multipart field failed") - } - _, err = fw.Write(img) - if err != nil { - return nil, errors.Wrap(err, "write multipart failed") - } - err = w.Close() - if err != nil { - return nil, errors.Wrap(err, "close multipart failed") - } + fw, _ := w.CreatePart(h) + _, _ = fw.Write(img) + _ = w.Close() req, err := http.NewRequest("POST", "https://web.qun.qq.com/cgi-bin/announce/upload_img", buf) if err != nil { return nil, errors.Wrap(err, "new request error") @@ -218,12 +238,8 @@ func (c *QQClient) uploadGroupNoticePic(img []byte) (*noticeImage, error) { return nil, errors.Wrap(err, "post error") } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, errors.Wrap(err, "read body error") - } - res := noticePicUpResponse{} - err = json.Unmarshal(body, &res) + var res noticePicUpResponse + err = json.NewDecoder(resp.Body).Decode(&res) if err != nil { return nil, errors.Wrap(err, "failed to unmarshal json") } diff --git a/client/image.go b/client/image.go index 3806b493..0764f203 100644 --- a/client/image.go +++ b/client/image.go @@ -1,8 +1,7 @@ package client import ( - "bytes" - "encoding/hex" + "fmt" "io" "math/rand" "strings" @@ -91,8 +90,9 @@ func (c *QQClient) uploadGroupOrGuildImage(target message.Source, img io.ReadSee }.Encode() } - var r interface{} + var r any var err error + var input highway.Transaction switch target.SourceType { case message.SourceGroup: r, err = c.sendAndWait(c.buildGroupImageStorePacket(target.PrimaryID, fh, int32(length))) @@ -115,22 +115,18 @@ func (c *QQClient) uploadGroupOrGuildImage(target message.Source, img io.ReadSee } } + input = highway.Transaction{ + CommandID: cmd, + Body: img, + Size: length, + Sum: fh, + Ticket: rsp.UploadKey, + Ext: ext, + } if tc > 1 && length > 3*1024*1024 { - _, err = c.highwaySession.UploadBDHMultiThread(highway.BdhMultiThreadInput{ - CommandID: cmd, - Body: utils.ReaderAtFrom2ReadSeeker(img, nil), - Size: length, - Sum: fh, - Ticket: rsp.UploadKey, - Ext: ext, - }, 4) + _, err = c.highwaySession.UploadBDHMultiThread(input, tc) } else { - _, err = c.highwaySession.UploadBDH(highway.BdhInput{ - CommandID: cmd, - Body: img, - Ticket: rsp.UploadKey, - Ext: ext, - }) + _, err = c.highwaySession.UploadBDH(input) } if err != nil { return nil, errors.Wrap(err, "upload failed") @@ -145,7 +141,7 @@ ok: width := int32(i.Width) height := int32(i.Height) if err != nil && target.SourceType != message.SourceGroup { - c.Warning("waring: decode image error: %v. this image will be displayed by wrong size in pc guild client", err) + c.warning("waring: decode image error: %v. this image will be displayed by wrong size in pc guild client", err) width = 200 height = 200 } @@ -158,7 +154,7 @@ ok: } return &message.GuildImageElement{ FileId: rsp.FileId, - FilePath: hex.EncodeToString(fh) + ".jpg", + FilePath: fmt.Sprintf("%x.jpg", fh), Size: int32(length), DownloadIndex: rsp.DownloadIndex, Width: width, @@ -207,18 +203,18 @@ func (c *QQClient) uploadPrivateImage(target int64, img io.ReadSeeker, count int return e, nil } -func (c *QQClient) ImageOcr(img interface{}) (*OcrResponse, error) { +func (c *QQClient) ImageOcr(img any) (*OcrResponse, error) { url := "" switch e := img.(type) { case *message.GroupImageElement: url = e.Url if b, err := utils.HTTPGetReadCloser(e.Url, ""); err == nil { - if url, err = c.uploadOcrImage(b); err != nil { + if url, err = c.uploadOcrImage(b, e.Size, e.Md5); err != nil { url = e.Url } _ = b.Close() } - rsp, err := c.sendAndWait(c.buildImageOcrRequestPacket(url, strings.ToUpper(hex.EncodeToString(e.Md5)), e.Size, e.Width, e.Height)) + rsp, err := c.sendAndWait(c.buildImageOcrRequestPacket(url, fmt.Sprintf("%X", e.Md5), e.Size, e.Width, e.Height)) if err != nil { return nil, err } @@ -318,7 +314,7 @@ func (c *QQClient) buildGroupImageDownloadPacket(fileId, groupCode int64, fileMd return c.uniPacket("ImgStore.GroupPicDown", payload) } -func (c *QQClient) uploadOcrImage(img io.Reader) (string, error) { +func (c *QQClient) uploadOcrImage(img io.Reader, size int32, sum []byte) (string, error) { r := make([]byte, 16) rand.Read(r) ext, _ := proto.Marshal(&highway2.CommFileExtReq{ @@ -326,13 +322,13 @@ func (c *QQClient) uploadOcrImage(img io.Reader) (string, error) { Uuid: binary.GenUUID(r), }) - buf, _ := io.ReadAll(img) - rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{ + rsp, err := c.highwaySession.UploadBDH(highway.Transaction{ CommandID: 76, - Body: bytes.NewReader(buf), + Body: img, + Size: int64(size), + Sum: sum, Ticket: c.highwaySession.SigSession, Ext: ext, - Encrypt: false, }) if err != nil { return "", errors.Wrap(err, "upload ocr image error") @@ -365,7 +361,7 @@ func (c *QQClient) buildImageOcrRequestPacket(url, md5 string, size, weight, hei } // ImgStore.GroupPicUp -func decodeGroupImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGroupImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { pkt := cmd0x388.D388RspBody{} err := proto.Unmarshal(payload, &pkt) if err != nil { @@ -392,7 +388,7 @@ func decodeGroupImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, p }, nil } -func decodeGroupImageDownloadResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGroupImageDownloadResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { pkt := cmd0x388.D388RspBody{} if err := proto.Unmarshal(payload, &pkt); err != nil { return nil, errors.Wrap(err, "unmarshal protobuf message error") @@ -403,18 +399,15 @@ func decodeGroupImageDownloadResponse(_ *QQClient, _ *network.IncomingPacketInfo if len(pkt.GetimgUrlRsp[0].FailMsg) != 0 { return nil, errors.New(utils.B2S(pkt.GetimgUrlRsp[0].FailMsg)) } - return "https://" + utils.B2S(pkt.GetimgUrlRsp[0].DownDomain) + utils.B2S(pkt.GetimgUrlRsp[0].BigDownPara), nil + return fmt.Sprintf("https://%s%s", pkt.GetimgUrlRsp[0].DownDomain, pkt.GetimgUrlRsp[0].BigDownPara), nil } // OidbSvc.0xe07_0 -func decodeImageOcrResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeImageOcrResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.DE07RspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } if rsp.Wording != "" { if strings.Contains(rsp.Wording, "服务忙") { diff --git a/client/internal/highway/bdh.go b/client/internal/highway/bdh.go index 4d76a182..352cd49f 100644 --- a/client/internal/highway/bdh.go +++ b/client/internal/highway/bdh.go @@ -12,31 +12,21 @@ import ( "golang.org/x/sync/errgroup" "github.com/Mrs4s/MiraiGo/binary" - "github.com/Mrs4s/MiraiGo/client/internal/network" "github.com/Mrs4s/MiraiGo/client/pb" "github.com/Mrs4s/MiraiGo/internal/proto" - "github.com/Mrs4s/MiraiGo/utils" ) -type BdhInput struct { +type Transaction struct { CommandID int32 - Body io.ReadSeeker + Body io.Reader + Sum []byte // md5 sum of body + Size int64 // body size Ticket []byte Ext []byte Encrypt bool } -type BdhMultiThreadInput struct { - CommandID int32 - Body io.ReaderAt - Sum []byte - Size int64 - Ticket []byte - Ext []byte - Encrypt bool -} - -func (bdh *BdhInput) encrypt(key []byte) error { +func (bdh *Transaction) encrypt(key []byte) error { if bdh.Encrypt { if len(key) == 0 { return errors.New("session key not found. maybe miss some packet?") @@ -46,25 +36,13 @@ func (bdh *BdhInput) encrypt(key []byte) error { return nil } -func (bdh *BdhMultiThreadInput) encrypt(key []byte) error { - if bdh.Encrypt { - if len(key) == 0 { - return errors.New("session key not found. maybe miss some packet?") - } - bdh.Ext = binary.NewTeaCipher(key).Encrypt(bdh.Ext) - } - return nil -} - -func (s *Session) UploadBDH(input BdhInput) ([]byte, error) { +func (s *Session) UploadBDH(trans Transaction) ([]byte, error) { if len(s.SsoAddr) == 0 { return nil, errors.New("srv addrs not found. maybe miss some packet?") } addr := s.SsoAddr[0].String() - sum, length := utils.ComputeMd5AndLength(input.Body) - _, _ = input.Body.Seek(0, io.SeekStart) - if err := input.encrypt(s.SessionKey); err != nil { + if err := trans.encrypt(s.SessionKey); err != nil { return nil, err } conn, err := net.DialTimeout("tcp", addr, time.Second*20) @@ -79,13 +57,17 @@ func (s *Session) UploadBDH(input BdhInput) ([]byte, error) { } const chunkSize = 256 * 1024 - var rspExt []byte + var rspExt, chunk []byte offset := 0 - chunk := make([]byte, chunkSize) + if trans.Size > chunkSize { + chunk = make([]byte, chunkSize) + } else { + chunk = make([]byte, trans.Size) + } for { - chunk = chunk[:chunkSize] - rl, err := io.ReadFull(input.Body, chunk) - if errors.Is(err, io.EOF) { + chunk = chunk[:cap(chunk)] + rl, err := io.ReadFull(trans.Body, chunk) + if rl == 0 { break } if errors.Is(err, io.ErrUnexpectedEOF) { @@ -93,19 +75,20 @@ func (s *Session) UploadBDH(input BdhInput) ([]byte, error) { } ch := md5.Sum(chunk) head, _ := proto.Marshal(&pb.ReqDataHighwayHead{ - MsgBasehead: s.dataHighwayHead(4096, input.CommandID, 2052), + MsgBasehead: s.dataHighwayHead(_REQ_CMD_DATA, 4096, trans.CommandID, 2052), MsgSeghead: &pb.SegHead{ - Filesize: length, + Filesize: trans.Size, Dataoffset: int64(offset), Datalength: int32(rl), - Serviceticket: input.Ticket, + Serviceticket: trans.Ticket, Md5: ch[:], - FileMd5: sum, + FileMd5: trans.Sum, }, - ReqExtendinfo: input.Ext, + ReqExtendinfo: trans.Ext, }) offset += rl - _, err = io.Copy(conn, network.HeadBodyFrame(head, chunk)) + frame := newFrame(head, chunk) + _, err = frame.WriteTo(conn) if err != nil { return nil, errors.Wrap(err, "write conn error") } @@ -120,23 +103,17 @@ func (s *Session) UploadBDH(input BdhInput) ([]byte, error) { rspExt = rspHead.RspExtendinfo } if rspHead.MsgSeghead != nil && rspHead.MsgSeghead.Serviceticket != nil { - input.Ticket = rspHead.MsgSeghead.Serviceticket + trans.Ticket = rspHead.MsgSeghead.Serviceticket } } return rspExt, nil } -func (s *Session) UploadBDHMultiThread(input BdhMultiThreadInput, threadCount int) ([]byte, error) { +func (s *Session) UploadBDHMultiThread(trans Transaction, threadCount int) ([]byte, error) { // for small file and small thread count, // use UploadBDH instead of UploadBDHMultiThread - if input.Size < 1024*1024*3 || threadCount < 2 { - return s.UploadBDH(BdhInput{ - CommandID: input.CommandID, - Body: io.NewSectionReader(input.Body, 0, input.Size), - Ticket: input.Ticket, - Ext: input.Ext, - Encrypt: input.Encrypt, - }) + if trans.Size < 1024*1024*3 || threadCount < 2 { + return s.UploadBDH(trans) } if len(s.SsoAddr) == 0 { @@ -144,40 +121,25 @@ func (s *Session) UploadBDHMultiThread(input BdhMultiThreadInput, threadCount in } addr := s.SsoAddr[0].String() - if err := input.encrypt(s.SessionKey); err != nil { + if err := trans.encrypt(s.SessionKey); err != nil { return nil, err } - type BlockMetaData struct { - Id int - Offset int64 - } - const blockSize int64 = 1024 * 512 + const blockSize int64 = 256 * 1024 var ( - blocks []BlockMetaData - rspExt []byte - BlockId = ^uint32(0) // -1 - uploadedCount uint32 - cond = sync.NewCond(&sync.Mutex{}) + rspExt []byte + completedThread uint32 + cond = sync.NewCond(&sync.Mutex{}) + offset = int64(0) + count = (trans.Size + blockSize - 1) / blockSize + id = 0 ) - // Init Blocks - { - var temp int64 = 0 - for temp+blockSize < input.Size { - blocks = append(blocks, BlockMetaData{ - Id: len(blocks), - Offset: temp, - }) - temp += blockSize - } - blocks = append(blocks, BlockMetaData{ - Id: len(blocks), - Offset: temp, - }) - } doUpload := func() error { // send signal complete uploading - defer cond.Signal() + defer func() { + atomic.AddUint32(&completedThread, 1) + cond.Signal() + }() conn, err := net.DialTimeout("tcp", addr, time.Second*20) if err != nil { @@ -189,50 +151,45 @@ func (s *Session) UploadBDHMultiThread(input BdhMultiThreadInput, threadCount in return err } - buffer := make([]byte, blockSize) + chunk := make([]byte, blockSize) for { - nextId := atomic.AddUint32(&BlockId, 1) - if nextId >= uint32(len(blocks)) { - break - } - block := blocks[nextId] - if block.Id == len(blocks)-1 { - cond.L.Lock() - for atomic.LoadUint32(&uploadedCount) != uint32(len(blocks))-1 { + cond.L.Lock() // lock protect reading + off := offset + offset += blockSize + id++ + if int64(id) == count { // last + for atomic.LoadUint32(&completedThread) != uint32(threadCount-1) { cond.Wait() } + } else if int64(id) > count { cond.L.Unlock() + break } - buffer = buffer[:blockSize] - - cond.L.Lock() // lock protect reading - n, err := input.Body.ReadAt(buffer, block.Offset) + chunk = chunk[:blockSize] + n, err := io.ReadFull(trans.Body, chunk) cond.L.Unlock() - if err != nil { - if err == io.EOF { - break - } - if err == io.ErrUnexpectedEOF { - buffer = buffer[:n] - } else { - return err - } + if n == 0 { + break } - ch := md5.Sum(buffer) + if errors.Is(err, io.ErrUnexpectedEOF) { + chunk = chunk[:n] + } + ch := md5.Sum(chunk) head, _ := proto.Marshal(&pb.ReqDataHighwayHead{ - MsgBasehead: s.dataHighwayHead(4096, input.CommandID, 2052), + MsgBasehead: s.dataHighwayHead(_REQ_CMD_DATA, 4096, trans.CommandID, 2052), MsgSeghead: &pb.SegHead{ - Filesize: input.Size, - Dataoffset: block.Offset, + Filesize: trans.Size, + Dataoffset: off, Datalength: int32(n), - Serviceticket: input.Ticket, + Serviceticket: trans.Ticket, Md5: ch[:], - FileMd5: input.Sum, + FileMd5: trans.Sum, }, - ReqExtendinfo: input.Ext, + ReqExtendinfo: trans.Ext, }) - _, err = io.Copy(conn, network.HeadBodyFrame(head, buffer)) + frame := newFrame(head, chunk) + _, err = frame.WriteTo(conn) if err != nil { return errors.Wrap(err, "write conn error") } @@ -246,7 +203,6 @@ func (s *Session) UploadBDHMultiThread(input BdhMultiThreadInput, threadCount in if rspHead.RspExtendinfo != nil { rspExt = rspHead.RspExtendinfo } - atomic.AddUint32(&uploadedCount, 1) } return nil } diff --git a/client/internal/highway/frame.go b/client/internal/highway/frame.go new file mode 100644 index 00000000..ba1b6b38 --- /dev/null +++ b/client/internal/highway/frame.go @@ -0,0 +1,33 @@ +package highway + +import ( + "encoding/binary" + "net" +) + +var etx = []byte{0x29} + +// newFrame 包格式 +// * STX: 0x28(40) +// * head length +// * body length +// * head data +// * body data +// * ETX: 0x29(41) +// 节省内存, 可被go runtime优化为writev操作 +func newFrame(head []byte, body []byte) net.Buffers { + buffers := make(net.Buffers, 4) + // buffer0 format: + // * STX + // * head length + // * body length + buffer0 := make([]byte, 9) + buffer0[0] = 0x28 + binary.BigEndian.PutUint32(buffer0[1:], uint32(len(head))) + binary.BigEndian.PutUint32(buffer0[5:], uint32(len(body))) + buffers[0] = buffer0 + buffers[1] = head + buffers[2] = body + buffers[3] = etx + return buffers +} diff --git a/client/internal/highway/highway.go b/client/internal/highway/highway.go index 81cd0ffd..05b53018 100644 --- a/client/internal/highway/highway.go +++ b/client/internal/highway/highway.go @@ -12,12 +12,17 @@ import ( "github.com/pkg/errors" "github.com/Mrs4s/MiraiGo/binary" - "github.com/Mrs4s/MiraiGo/client/internal/network" "github.com/Mrs4s/MiraiGo/client/pb" "github.com/Mrs4s/MiraiGo/internal/proto" "github.com/Mrs4s/MiraiGo/utils" ) +// see com/tencent/mobileqq/highway/utils/BaseConstants.java#L120-L121 +const ( + _REQ_CMD_DATA = "PicUp.DataUp" + _REQ_CMD_HEART_BREAK = "PicUp.Echo" +) + type Session struct { Uin string AppID int32 @@ -42,15 +47,7 @@ func (s *Session) AppendAddr(ip, port uint32) { s.SsoAddr = append(s.SsoAddr, addr) } -type Input struct { - CommandID int32 - Key []byte - Body io.ReadSeeker -} - -func (s *Session) Upload(addr Addr, input Input) error { - fh, length := utils.ComputeMd5AndLength(input.Body) - _, _ = input.Body.Seek(0, io.SeekStart) +func (s *Session) Upload(addr Addr, trans Transaction) error { conn, err := net.DialTimeout("tcp", addr.String(), time.Second*3) if err != nil { return errors.Wrap(err, "connect error") @@ -61,11 +58,9 @@ func (s *Session) Upload(addr Addr, input Input) error { chunk := make([]byte, chunkSize) offset := 0 reader := binary.NewNetworkReader(conn) - w := binary.SelectWriter() - defer binary.PutWriter(w) for { chunk = chunk[:chunkSize] - rl, err := io.ReadFull(input.Body, chunk) + rl, err := io.ReadFull(trans.Body, chunk) if errors.Is(err, io.EOF) { break } @@ -74,19 +69,20 @@ func (s *Session) Upload(addr Addr, input Input) error { } ch := md5.Sum(chunk) head, _ := proto.Marshal(&pb.ReqDataHighwayHead{ - MsgBasehead: s.dataHighwayHead(4096, input.CommandID, 2052), + MsgBasehead: s.dataHighwayHead(_REQ_CMD_DATA, 4096, trans.CommandID, 2052), MsgSeghead: &pb.SegHead{ - Filesize: length, + Filesize: trans.Size, Dataoffset: int64(offset), Datalength: int32(rl), - Serviceticket: input.Key, + Serviceticket: trans.Ticket, Md5: ch[:], - FileMd5: fh, + FileMd5: trans.Sum, }, ReqExtendinfo: []byte{}, }) offset += rl - _, err = io.Copy(conn, network.HeadBodyFrame(head, chunk)) + frame := newFrame(head, chunk) + _, err = frame.WriteTo(conn) if err != nil { return errors.Wrap(err, "write conn error") } @@ -133,7 +129,7 @@ func (s *Session) UploadExciting(input ExcitingInput) ([]byte, error) { } ch := md5.Sum(chunk) head, _ := proto.Marshal(&pb.ReqDataHighwayHead{ - MsgBasehead: s.dataHighwayHead(0, input.CommandID, 0), + MsgBasehead: s.dataHighwayHead(_REQ_CMD_DATA, 0, input.CommandID, 0), MsgSeghead: &pb.SegHead{ Filesize: fileLength, Dataoffset: offset, @@ -145,7 +141,8 @@ func (s *Session) UploadExciting(input ExcitingInput) ([]byte, error) { ReqExtendinfo: input.Ext, }) offset += int64(rl) - req, _ := http.NewRequest("POST", url, network.HeadBodyFrame(head, chunk)) + frame := newFrame(head, chunk) + req, _ := http.NewRequest("POST", url, &frame) req.Header.Set("Accept", "*/*") req.Header.Set("Connection", "Keep-Alive") req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)") @@ -180,33 +177,25 @@ func (s *Session) nextSeq() int32 { return atomic.AddInt32(&s.seq, 2) } -func (s *Session) dataHighwayHead(flag, cmd, locale int32) *pb.DataHighwayHead { +func (s *Session) dataHighwayHead(cmd string, flag, cmdID, locale int32) *pb.DataHighwayHead { return &pb.DataHighwayHead{ Version: 1, Uin: s.Uin, - Command: "PicUp.DataUp", + Command: cmd, Seq: s.nextSeq(), Appid: s.AppID, Dataflag: flag, - CommandId: cmd, + CommandId: cmdID, LocaleId: locale, } } func (s *Session) sendHeartbreak(conn net.Conn) error { head, _ := proto.Marshal(&pb.ReqDataHighwayHead{ - MsgBasehead: &pb.DataHighwayHead{ - Version: 1, - Uin: s.Uin, - Command: "PicUp.Echo", - Seq: s.nextSeq(), - Appid: s.AppID, - Dataflag: 4096, - CommandId: 0, - LocaleId: 2052, - }, + MsgBasehead: s.dataHighwayHead(_REQ_CMD_HEART_BREAK, 4096, 0, 2052), }) - _, err := io.Copy(conn, network.HeadBodyFrame(head, nil)) + frame := newFrame(head, nil) + _, err := frame.WriteTo(conn) return err } diff --git a/client/internal/network/conn.go b/client/internal/network/conn.go index a43614c1..682b3034 100644 --- a/client/internal/network/conn.go +++ b/client/internal/network/conn.go @@ -34,9 +34,9 @@ func (t *TCPListener) UnexpectedDisconnect(f func(*TCPListener, error)) { t.unexpectedDisconnect = f } -func (t *TCPListener) Connect(addr *net.TCPAddr) error { +func (t *TCPListener) Connect(addr string) error { t.Close() - conn, err := net.DialTCP("tcp", nil, addr) + conn, err := net.Dial("tcp", addr) if err != nil { return errors.Wrap(err, "dial tcp error") } diff --git a/client/internal/network/frame.go b/client/internal/network/frame.go deleted file mode 100644 index c272a64a..00000000 --- a/client/internal/network/frame.go +++ /dev/null @@ -1,50 +0,0 @@ -package network - -import ( - "encoding/binary" - "io" - "net" - "sync" -) - -var etx = []byte{0x29} - -type Buffers struct { - net.Buffers -} - -var pool = sync.Pool{ - New: func() interface{} { - lenHead := make([]byte, 9) - lenHead[0] = 0x28 - return &Buffers{net.Buffers{lenHead, nil, nil, etx}} - }, -} - -func (b *Buffers) WriteTo(w io.Writer) (n int64, err error) { - defer pool.Put(b) // implement auto put to pool - return b.Buffers.WriteTo(w) -} - -// HeadBodyFrame 包格式 -// * STX -// * head length -// * body length -// * head data -// * body data -// * ETX -// 节省内存, 可被go runtime优化为writev操作 -func HeadBodyFrame(head []byte, body []byte) *Buffers { - b := pool.Get().(*Buffers) - if len(b.Buffers) == 0 { - lenHead := make([]byte, 9) - lenHead[0] = 0x28 - b.Buffers = net.Buffers{lenHead, nil, nil, etx} - } - b.Buffers[2] = body - b.Buffers[1] = head - _ = b.Buffers[0][8] - binary.BigEndian.PutUint32(b.Buffers[0][1:], uint32(len(head))) - binary.BigEndian.PutUint32(b.Buffers[0][5:], uint32(len(body))) - return b -} diff --git a/client/internal/network/packet.go b/client/internal/network/packet.go index bccfe152..492791ef 100644 --- a/client/internal/network/packet.go +++ b/client/internal/network/packet.go @@ -6,7 +6,7 @@ type IncomingPacketInfo struct { Params RequestParams } -type RequestParams map[string]interface{} +type RequestParams map[string]any func (p RequestParams) Bool(k string) bool { if p == nil { diff --git a/client/internal/network/request.go b/client/internal/network/request.go index b34930f5..6e61686b 100644 --- a/client/internal/network/request.go +++ b/client/internal/network/request.go @@ -7,8 +7,6 @@ const ( RequestTypeSimple = 0x0B ) -var emptyKey = make([]byte, 16) - type EncryptType uint32 const ( diff --git a/client/internal/network/response.go b/client/internal/network/response.go index c2d97f3c..bdaa4c40 100644 --- a/client/internal/network/response.go +++ b/client/internal/network/response.go @@ -46,6 +46,7 @@ func (t *Transport) ReadResponse(head []byte) (*Response, error) { case EncryptTypeD2Key: body = binary.NewTeaCipher(t.Sig.D2Key).Decrypt(body) case EncryptTypeEmptyKey: + emptyKey := make([]byte, 16) body = binary.NewTeaCipher(emptyKey).Decrypt(body) } err := t.readSSOFrame(resp, body) diff --git a/client/internal/network/transport.go b/client/internal/network/transport.go index dd79a8d9..e2286e33 100644 --- a/client/internal/network/transport.go +++ b/client/internal/network/transport.go @@ -89,6 +89,7 @@ func (t *Transport) PackPacket(req *Request) []byte { case EncryptTypeD2Key: body = binary.NewTeaCipher(t.Sig.D2Key).Encrypt(body) case EncryptTypeEmptyKey: + emptyKey := make([]byte, 16) body = binary.NewTeaCipher(emptyKey).Encrypt(body) } w.Write(body) diff --git a/client/internal/oicq/oicq.go b/client/internal/oicq/oicq.go index c4469a40..9abe167f 100644 --- a/client/internal/oicq/oicq.go +++ b/client/internal/oicq/oicq.go @@ -84,7 +84,8 @@ func (c *Codec) Marshal(m *Message) []byte { } w.WriteByte(0x03) - buf := append([]byte(nil), w.Bytes()...) + buf := make([]byte, len(w.Bytes())) + copy(buf, w.Bytes()) goBinary.BigEndian.PutUint16(buf[1:3], uint16(len(buf))) return buf } diff --git a/client/log.go b/client/log.go new file mode 100644 index 00000000..3aa6a1e4 --- /dev/null +++ b/client/log.go @@ -0,0 +1,43 @@ +package client + +type Logger interface { + Info(format string, args ...any) + Warning(format string, args ...any) + Error(format string, args ...any) + Debug(format string, args ...any) + Dump(dumped []byte, format string, args ...any) +} + +func (c *QQClient) SetLogger(logger Logger) { + c.logger = logger +} + +func (c *QQClient) info(msg string, args ...any) { + if c.logger != nil { + c.logger.Info(msg, args...) + } +} + +func (c *QQClient) warning(msg string, args ...any) { + if c.logger != nil { + c.logger.Warning(msg, args...) + } +} + +func (c *QQClient) error(msg string, args ...any) { + if c.logger != nil { + c.logger.Error(msg, args...) + } +} + +func (c *QQClient) debug(msg string, args ...any) { + if c.logger != nil { + c.logger.Debug(msg, args...) + } +} + +func (c *QQClient) dump(msg string, data []byte, args ...any) { + if c.logger != nil { + c.logger.Dump(data, msg, args...) + } +} diff --git a/client/model_show.go b/client/model_show.go index 6b77a43b..c4160dd3 100644 --- a/client/model_show.go +++ b/client/model_show.go @@ -48,9 +48,8 @@ func (c *QQClient) getGtk(domain string) int { accu = accu + (accu << 5) + int(b) } return 2147483647 & accu - } else { - return 0 } + return 0 } func (c *QQClient) GetModelShow(modelName string) ([]*ModelVariant, error) { diff --git a/client/multimsg.go b/client/multimsg.go index 0348f903..8ca22486 100644 --- a/client/multimsg.go +++ b/client/multimsg.go @@ -2,14 +2,18 @@ package client import ( "bytes" + "crypto/md5" "fmt" "math" + "math/rand" + "strconv" "strings" "time" "github.com/pkg/errors" "github.com/Mrs4s/MiraiGo/binary" + "github.com/Mrs4s/MiraiGo/client/internal/highway" "github.com/Mrs4s/MiraiGo/client/internal/network" "github.com/Mrs4s/MiraiGo/client/pb/longmsg" "github.com/Mrs4s/MiraiGo/client/pb/msg" @@ -47,7 +51,7 @@ func (c *QQClient) buildMultiApplyUpPacket(data, hash []byte, buType int32, grou } // MultiMsg.ApplyUp -func decodeMultiApplyUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeMultiApplyUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { body := multimsg.MultiRspBody{} if err := proto.Unmarshal(payload, &body); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -87,7 +91,7 @@ func (c *QQClient) buildMultiApplyDownPacket(resID string) (uint16, []byte) { } // MultiMsg.ApplyDown -func decodeMultiApplyDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeMultiApplyDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { body := multimsg.MultiRspBody{} if err := proto.Unmarshal(payload, &body); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -225,6 +229,88 @@ func forwardDisplay(resID, fileName, preview, summary string) string { sb.WriteString(`
`) sb.WriteString(summary) // todo: 私聊的聊天记录? - sb.WriteString(``) + sb.WriteString(``) return sb.String() } + +func (c *QQClient) NewForwardMessageBuilder(groupCode int64) *ForwardMessageBuilder { + return &ForwardMessageBuilder{ + c: c, + groupCode: groupCode, + } +} + +type ForwardMessageBuilder struct { + c *QQClient + groupCode int64 + objs []*msg.PbMultiMsgItem +} + +// NestedNode 返回一个嵌套转发节点,其内容将会被 Builder 重定位 +func (builder *ForwardMessageBuilder) NestedNode() *message.ForwardElement { + filename := strconv.FormatInt(time.Now().UnixNano(), 10) // 大概率不会重复 + return &message.ForwardElement{FileName: filename} +} + +// Link 将真实的消息内容填充 reloc +func (builder *ForwardMessageBuilder) Link(reloc *message.ForwardElement, fmsg *message.ForwardMessage) { + seq := builder.c.nextGroupSeq() + m := fmsg.PackForwardMessage(seq, rand.Int31(), builder.groupCode) + builder.objs = append(builder.objs, &msg.PbMultiMsgItem{ + FileName: proto.String(reloc.FileName), + Buffer: &msg.PbMultiMsgNew{ + Msg: m, + }, + }) + reloc.Content = forwardDisplay("", reloc.FileName, fmsg.Preview(), fmt.Sprintf("查看 %d 条转发消息", fmsg.Length())) +} + +// Main 最外层的转发消息, 调用该方法后即上传消息 +func (builder *ForwardMessageBuilder) Main(m *message.ForwardMessage) *message.ForwardElement { + if m.Length() > 200 { + return nil + } + c := builder.c + seq := c.nextGroupSeq() + fm := m.PackForwardMessage(seq, rand.Int31(), builder.groupCode) + const filename = "MultiMsg" + builder.objs = append(builder.objs, &msg.PbMultiMsgItem{ + FileName: proto.String(filename), + Buffer: &msg.PbMultiMsgNew{ + Msg: fm, + }, + }) + trans := &msg.PbMultiMsgTransmit{ + Msg: fm, + PbItemList: builder.objs, + } + b, _ := proto.Marshal(trans) + data := binary.GZipCompress(b) + hash := md5.Sum(data) + rsp, body, err := c.multiMsgApplyUp(builder.groupCode, data, hash[:], 2) + if err != nil { + return nil + } + content := forwardDisplay(rsp.MsgResid, filename, m.Preview(), fmt.Sprintf("查看 %d 条转发消息", m.Length())) + for i, ip := range rsp.Uint32UpIp { + addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])} + hash := md5.Sum(body) + input := highway.Transaction{ + CommandID: 27, + Ticket: rsp.MsgSig, + Body: bytes.NewReader(body), + Sum: hash[:], + Size: int64(len(body)), + } + err := c.highwaySession.Upload(addr, input) + if err != nil { + continue + } + return &message.ForwardElement{ + FileName: filename, + Content: content, + ResId: rsp.MsgResid, + } + } + return nil +} diff --git a/client/network.go b/client/network.go index d5cbf229..868ebd03 100644 --- a/client/network.go +++ b/client/network.go @@ -40,34 +40,36 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo { r := &ConnectionQualityInfo{} wg := sync.WaitGroup{} wg.Add(2) + + currentServerAddr := c.servers[c.currServerIndex].String() go func() { defer wg.Done() var err error - if r.ChatServerLatency, err = qualityTest(c.servers[c.currServerIndex].String()); err != nil { - c.Error("test chat server latency error: %v", err) + if r.ChatServerLatency, err = qualityTest(currentServerAddr); err != nil { + c.error("test chat server latency error: %v", err) r.ChatServerLatency = 9999 } if addr, err := net.ResolveIPAddr("ip", "ssl.htdata.qq.com"); err == nil { if r.LongMessageServerLatency, err = qualityTest((&net.TCPAddr{IP: addr.IP, Port: 443}).String()); err != nil { - c.Error("test long message server latency error: %v", err) + c.error("test long message server latency error: %v", err) r.LongMessageServerLatency = 9999 } } else { - c.Error("resolve long message server error: %v", err) + c.error("resolve long message server error: %v", err) r.LongMessageServerLatency = 9999 } if c.highwaySession.AddrLength() > 0 { if r.SrvServerLatency, err = qualityTest(c.highwaySession.SsoAddr[0].String()); err != nil { - c.Error("test srv server latency error: %v", err) + c.error("test srv server latency error: %v", err) r.SrvServerLatency = 9999 } } }() go func() { defer wg.Done() - res := utils.RunTCPPingLoop(c.servers[c.currServerIndex].String(), 10) + res := utils.RunTCPPingLoop(currentServerAddr, 10) r.ChatServerPacketLoss = res.PacketsLoss if c.highwaySession.AddrLength() > 0 { res = utils.RunTCPPingLoop(c.highwaySession.SsoAddr[0].String(), 10) @@ -78,7 +80,7 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo { if _, err := utils.HttpGetBytes("https://ssl.htdata.qq.com", ""); err == nil { r.LongMessageServerResponseLatency = time.Since(start).Milliseconds() } else { - c.Error("test long message server response latency error: %v", err) + c.error("test long message server response latency error: %v", err) r.LongMessageServerResponseLatency = 9999 } wg.Wait() @@ -87,8 +89,9 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo { // connect 连接到 QQClient.servers 中的服务器 func (c *QQClient) connect() error { - c.Info("connect to server: %v", c.servers[c.currServerIndex].String()) - err := c.TCP.Connect(c.servers[c.currServerIndex]) + addr := c.servers[c.currServerIndex].String() + c.info("connect to server: %v", addr) + err := c.TCP.Connect(addr) c.currServerIndex++ if c.currServerIndex == len(c.servers) { c.currServerIndex = 0 @@ -98,19 +101,19 @@ func (c *QQClient) connect() error { if c.retryTimes > len(c.servers) { return errors.New("All servers are unreachable") } - c.Error("connect server error: %v", err) + c.error("connect server error: %v", err) return err } c.once.Do(func() { - c.OnGroupMessage(func(_ *QQClient, _ *message.GroupMessage) { + c.GroupMessageEvent.Subscribe(func(_ *QQClient, _ *message.GroupMessage) { c.stat.MessageReceived.Add(1) c.stat.LastMessageTime.Store(time.Now().Unix()) }) - c.OnPrivateMessage(func(_ *QQClient, _ *message.PrivateMessage) { + c.PrivateMessageEvent.Subscribe(func(_ *QQClient, _ *message.PrivateMessage) { c.stat.MessageReceived.Add(1) c.stat.LastMessageTime.Store(time.Now().Unix()) }) - c.OnTempMessage(func(_ *QQClient, _ *TempMessageEvent) { + c.TempMessageEvent.Subscribe(func(_ *QQClient, _ *TempMessageEvent) { c.stat.MessageReceived.Add(1) c.stat.LastMessageTime.Store(time.Now().Unix()) }) @@ -129,14 +132,14 @@ func (c *QQClient) quickReconnect() { c.Disconnect() time.Sleep(time.Millisecond * 200) if err := c.connect(); err != nil { - c.Error("connect server error: %v", err) - c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "quick reconnect failed"}) + c.error("connect server error: %v", err) + c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "quick reconnect failed"}) return } if err := c.registerClient(); err != nil { - c.Error("register client failed: %v", err) + c.error("register client failed: %v", err) c.Disconnect() - c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "register error"}) + c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "register error"}) return } } @@ -148,9 +151,9 @@ func (c *QQClient) Disconnect() { } // sendAndWait 向服务器发送一个数据包, 并等待返回 -func (c *QQClient) sendAndWait(seq uint16, pkt []byte, params ...network.RequestParams) (interface{}, error) { +func (c *QQClient) sendAndWait(seq uint16, pkt []byte, params ...network.RequestParams) (any, error) { type T struct { - Response interface{} + Response any Error error } ch := make(chan T, 1) @@ -160,7 +163,7 @@ func (c *QQClient) sendAndWait(seq uint16, pkt []byte, params ...network.Request p = params[0] } - c.handlers.Store(seq, &handlerInfo{fun: func(i interface{}, err error) { + c.handlers.Store(seq, &handlerInfo{fun: func(i any, err error) { ch <- T{ Response: i, Error: err, @@ -204,7 +207,7 @@ func (c *QQClient) sendPacket(pkt []byte) error { // waitPacket // 等待一个或多个数据包解析, 优先级低于 sendAndWait // 返回终止解析函数 -func (c *QQClient) waitPacket(cmd string, f func(interface{}, error)) func() { +func (c *QQClient) waitPacket(cmd string, f func(any, error)) func() { c.waiters.Store(cmd, f) return func() { c.waiters.Delete(cmd) @@ -213,9 +216,9 @@ func (c *QQClient) waitPacket(cmd string, f func(interface{}, error)) func() { // waitPacketTimeoutSyncF // 等待一个数据包解析, 优先级低于 sendAndWait -func (c *QQClient) waitPacketTimeoutSyncF(cmd string, timeout time.Duration, filter func(interface{}) bool) (r interface{}, e error) { - notifyChan := make(chan bool) - defer c.waitPacket(cmd, func(i interface{}, err error) { +func (c *QQClient) waitPacketTimeoutSyncF(cmd string, timeout time.Duration, filter func(any) bool) (r any, e error) { + notifyChan := make(chan bool, 4) + defer c.waitPacket(cmd, func(i any, err error) { if filter(i) { r = i e = err @@ -234,7 +237,7 @@ func (c *QQClient) waitPacketTimeoutSyncF(cmd string, timeout time.Duration, fil // 发送数据包并返回需要解析的 response func (c *QQClient) sendAndWaitDynamic(seq uint16, pkt []byte) ([]byte, error) { ch := make(chan []byte, 1) - c.handlers.Store(seq, &handlerInfo{fun: func(i interface{}, err error) { ch <- i.([]byte) }, dynamic: true}) + c.handlers.Store(seq, &handlerInfo{fun: func(i any, err error) { ch <- i.([]byte) }, dynamic: true}) err := c.sendPacket(pkt) if err != nil { c.handlers.Delete(seq) @@ -251,25 +254,25 @@ func (c *QQClient) sendAndWaitDynamic(seq uint16, pkt []byte) ([]byte, error) { // plannedDisconnect 计划中断线事件 func (c *QQClient) plannedDisconnect(_ *network.TCPListener) { - c.Debug("planned disconnect.") + c.debug("planned disconnect.") c.stat.DisconnectTimes.Add(1) c.Online.Store(false) } // unexpectedDisconnect 非预期断线事件 func (c *QQClient) unexpectedDisconnect(_ *network.TCPListener, e error) { - c.Error("unexpected disconnect: %v", e) + c.error("unexpected disconnect: %v", e) c.stat.DisconnectTimes.Add(1) c.Online.Store(false) if err := c.connect(); err != nil { - c.Error("connect server error: %v", err) - c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "connection dropped by server."}) + c.error("connect server error: %v", err) + c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "connection dropped by server."}) return } if err := c.registerClient(); err != nil { - c.Error("register client failed: %v", err) + c.error("register client failed: %v", err) c.Disconnect() - c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "register error"}) + c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "register error"}) return } } @@ -284,7 +287,7 @@ func (c *QQClient) netLoop() { continue } if l < 4 || l > 1024*1024*10 { // max 10MB - c.Error("parse incoming packet error: invalid packet length %v", l) + c.error("parse incoming packet error: invalid packet length %v", l) errCount++ if errCount > 2 { go c.quickReconnect() @@ -295,10 +298,10 @@ func (c *QQClient) netLoop() { resp, err := c.transport.ReadResponse(data) // pkt, err := packets.ParseIncomingPacket(data, c.sig.D2Key) if err != nil { - c.Error("parse incoming packet error: %v", err) + c.error("parse incoming packet error: %v", err) if errors.Is(err, network.ErrSessionExpired) || errors.Is(err, network.ErrPacketDropped) { c.Disconnect() - go c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "session expired"}) + go c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "session expired"}) continue } errCount++ @@ -310,7 +313,7 @@ func (c *QQClient) netLoop() { if resp.EncryptType == network.EncryptTypeEmptyKey { m, err := c.oicq.Unmarshal(resp.Body) if err != nil { - c.Error("decrypt payload error: %v", err) + c.error("decrypt payload error: %v", err) if errors.Is(err, oicq.ErrUnknownFlag) { go c.quickReconnect() } @@ -319,7 +322,7 @@ func (c *QQClient) netLoop() { resp.Body = m.Body } errCount = 0 - c.Debug("rev pkt: %v seq: %v", resp.CommandName, resp.SequenceID) + c.debug("rev pkt: %v seq: %v", resp.CommandName, resp.SequenceID) c.stat.PacketReceived.Add(1) pkt := &packets.IncomingPacket{ SequenceId: uint16(resp.SequenceID), @@ -329,15 +332,15 @@ func (c *QQClient) netLoop() { go func(pkt *packets.IncomingPacket) { defer func() { if pan := recover(); pan != nil { - c.Error("panic on decoder %v : %v\n%s", pkt.CommandName, pan, debug.Stack()) - c.Dump("packet decode error: %v - %v", pkt.Payload, pkt.CommandName, pan) + c.error("panic on decoder %v : %v\n%s", pkt.CommandName, pan, debug.Stack()) + c.dump("packet decode error: %v - %v", pkt.Payload, pkt.CommandName, pan) } }() if decoder, ok := decoders[pkt.CommandName]; ok { // found predefined decoder info, ok := c.handlers.LoadAndDelete(pkt.SequenceId) - var decoded interface{} + var decoded any decoded = pkt.Payload if info == nil || !info.dynamic { decoded, err = decoder(c, &network.IncomingPacketInfo{ @@ -346,19 +349,19 @@ func (c *QQClient) netLoop() { Params: info.getParams(), }, pkt.Payload) if err != nil { - c.Debug("decode pkt %v error: %+v", pkt.CommandName, err) + c.debug("decode pkt %v error: %+v", pkt.CommandName, err) } } if ok { info.fun(decoded, err) } else if f, ok := c.waiters.Load(pkt.CommandName); ok { // 在不存在handler的情况下触发wait - f.(func(interface{}, error))(decoded, err) + f(decoded, err) } } else if f, ok := c.handlers.LoadAndDelete(pkt.SequenceId); ok { // does not need decoder f.fun(pkt.Payload, nil) } else { - c.Debug("Unhandled Command: %s\nSeq: %d\nThis message can be ignored.", pkt.CommandName, pkt.SequenceId) + c.debug("Unhandled Command: %s\nSeq: %d\nThis message can be ignored.", pkt.CommandName, pkt.SequenceId) } }(pkt) } diff --git a/client/notify.go b/client/notify.go index 634f88cb..df2e8aa0 100644 --- a/client/notify.go +++ b/client/notify.go @@ -62,7 +62,7 @@ func (c *QQClient) grayTipProcessor(groupCode int64, tipInfo *notify.GeneralGray } } if sender != 0 { - c.dispatchGroupNotifyEvent(&GroupPokeNotifyEvent{ + c.GroupNotifyEvent.dispatch(c, &GroupPokeNotifyEvent{ GroupCode: groupCode, Sender: sender, Receiver: receiver, @@ -81,7 +81,7 @@ func (c *QQClient) grayTipProcessor(groupCode int64, tipInfo *notify.GeneralGray uin, _ = strconv.ParseInt(templ.Value, 10, 64) } } - c.dispatchGroupNotifyEvent(&MemberHonorChangedNotifyEvent{ + c.GroupNotifyEvent.dispatch(c, &MemberHonorChangedNotifyEvent{ GroupCode: groupCode, Honor: func() HonorType { switch tipInfo.TemplId { @@ -139,13 +139,13 @@ func (c *QQClient) msgGrayTipProcessor(groupCode int64, tipInfo *notify.AIOGrayT } } if event.Uin == 0 { - c.Error("process special title updated tips error: missing cmd") + c.error("process special title updated tips error: missing cmd") return } if mem := c.FindGroup(groupCode).FindMember(event.Uin); mem != nil { mem.SpecialTitle = event.NewTitle } - c.dispatchMemberSpecialTitleUpdateEvent(event) + c.MemberSpecialTitleUpdatedEvent.dispatch(c, event) } } diff --git a/client/offline_file.go b/client/offline_file.go index 0fd8099e..a4b7e531 100644 --- a/client/offline_file.go +++ b/client/offline_file.go @@ -33,18 +33,18 @@ func (c *QQClient) buildOfflineFileDownloadRequestPacket(uuid []byte) (uint16, [ return seq, packet } -func decodeOfflineFileDownloadResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeOfflineFileDownloadResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := cmd0x346.C346RspBody{} if err := proto.Unmarshal(payload, &rsp); err != nil { - c.Error("unmarshal cmd0x346 rsp body error: %v", err) + c.error("unmarshal cmd0x346 rsp body error: %v", err) return nil, errors.Wrap(err, "unmarshal cmd0x346 rsp body error") } if rsp.ApplyDownloadRsp == nil { - c.Error("decode apply download 1200 error: apply rsp is nil.") + c.error("decode apply download 1200 error: apply rsp is nil.") return nil, errors.New("apply rsp is nil") } if rsp.ApplyDownloadRsp.RetCode != 0 { - c.Error("decode apply download 1200 error: %v", rsp.ApplyDownloadRsp.RetCode) + c.error("decode apply download 1200 error: %v", rsp.ApplyDownloadRsp.RetCode) return nil, errors.Errorf("apply download rsp error: %d", rsp.ApplyDownloadRsp.RetCode) } return "http://" + rsp.ApplyDownloadRsp.DownloadInfo.DownloadDomain + rsp.ApplyDownloadRsp.DownloadInfo.DownloadUrl, nil diff --git a/client/online_push.go b/client/online_push.go index 5a913b92..e237e0d1 100644 --- a/client/online_push.go +++ b/client/online_push.go @@ -23,7 +23,7 @@ var msg0x210Decoders = map[int64]func(*QQClient, []byte) error{ } // OnlinePush.ReqPush -func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -37,7 +37,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa if _, ok := c.onlinePushCache.Get(k); ok { continue } - c.onlinePushCache.Add(k, "", time.Second*30) + c.onlinePushCache.Add(k, unit{}, time.Second*30) // 0x2dc if m.MsgType == 732 { r := binary.NewReader(m.VMsg) @@ -53,7 +53,17 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa r.ReadBytes(6) target := int64(uint32(r.ReadInt32())) t := r.ReadInt32() - c.dispatchGroupMuteEvent(&GroupMuteEvent{ + + if target != 0 { + member := c.FindGroup(groupCode).FindMember(target) + if t > 0 { + member.ShutUpTimestamp = time.Now().Add(time.Second * time.Duration(t)).Unix() + } else { + member.ShutUpTimestamp = 0 + } + } + + c.GroupMuteEvent.dispatch(c, &GroupMuteEvent{ GroupCode: groupCode, OperatorUin: operator, TargetUin: target, @@ -68,7 +78,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa if rm.MsgType == 2 { continue } - c.dispatchGroupMessageRecalledEvent(&GroupMessageRecalledEvent{ + c.GroupMessageRecalledEvent.dispatch(c, &GroupMessageRecalledEvent{ GroupCode: groupCode, OperatorUin: b.OptMsgRecall.Uin, AuthorUin: rm.AuthorUin, @@ -82,7 +92,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa } if b.OptMsgRedTips != nil { if b.OptMsgRedTips.LuckyFlag == 1 { // 运气王提示 - c.dispatchGroupNotifyEvent(&GroupRedBagLuckyKingNotifyEvent{ + c.GroupNotifyEvent.dispatch(c, &GroupRedBagLuckyKingNotifyEvent{ GroupCode: groupCode, Sender: int64(b.OptMsgRedTips.SenderUin), LuckyKing: int64(b.OptMsgRedTips.LuckyUin), @@ -91,7 +101,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa } if b.QqGroupDigestMsg != nil { digest := b.QqGroupDigestMsg - c.dispatchGroupDigestEvent(&GroupDigestEvent{ + c.GroupDigestEvent.dispatch(c, &GroupDigestEvent{ GroupCode: int64(digest.GroupCode), MessageID: int32(digest.Seq), InternalMessageID: int32(digest.Random), @@ -118,7 +128,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa return nil, errors.Wrap(err, "decode online push 0x210 error") } } else { - c.Debug("unknown online push 0x210 sub type 0x%v", strconv.FormatInt(subType, 16)) + c.debug("unknown online push 0x210 sub type 0x%v", strconv.FormatInt(subType, 16)) } } } @@ -132,7 +142,7 @@ func msgType0x210Sub8ADecoder(c *QQClient, protobuf []byte) error { } for _, m := range s8a.MsgInfo { if m.ToUin == c.Uin { - c.dispatchFriendMessageRecalledEvent(&FriendMessageRecalledEvent{ + c.FriendMessageRecalledEvent.dispatch(c, &FriendMessageRecalledEvent{ FriendUin: m.FromUin, MessageId: m.MsgSeq, Time: m.MsgTime, @@ -152,7 +162,7 @@ func msgType0x210SubB3Decoder(c *QQClient, protobuf []byte) error { Nickname: b3.MsgAddFrdNotify.Nick, } c.FriendList = append(c.FriendList, frd) - c.dispatchNewFriendEvent(&NewFriendEvent{Friend: frd}) + c.NewFriendEvent.dispatch(c, &NewFriendEvent{Friend: frd}) return nil } @@ -167,7 +177,7 @@ func msgType0x210SubD4Decoder(c *QQClient, protobuf []byte) error { groupLeaveLock.Unlock() return err } - c.dispatchLeaveGroupEvent(&GroupLeaveEvent{Group: g}) + c.GroupLeaveEvent.dispatch(c, &GroupLeaveEvent{Group: g}) } groupLeaveLock.Unlock() return nil @@ -185,7 +195,7 @@ func msgType0x210Sub27Decoder(c *QQClient, protobuf []byte) error { if g := c.FindGroup(int64(m.ModGroupProfile.GetGroupCode())); g != nil { old := g.Name g.Name = string(info.Value) - c.dispatchGroupNameUpdatedEvent(&GroupNameUpdatedEvent{ + c.GroupNameUpdatedEvent.dispatch(c, &GroupNameUpdatedEvent{ Group: g, OldName: old, NewName: g.Name, @@ -221,7 +231,7 @@ func msgType0x210Sub122Decoder(c *QQClient, protobuf []byte) error { if sender == 0 { return nil } - c.dispatchFriendNotifyEvent(&FriendPokeNotifyEvent{ + c.FriendNotifyEvent.dispatch(c, &FriendPokeNotifyEvent{ Sender: sender, Receiver: receiver, }) @@ -241,7 +251,7 @@ func msgType0x210Sub44Decoder(c *QQClient, protobuf []byte) error { if s44.GroupSyncMsg.GrpCode == 0 { // member sync return errors.New("invalid group code") } - c.Debug("syncing members.") + c.debug("syncing members.") if group := c.FindGroup(s44.GroupSyncMsg.GrpCode); group != nil { group.lock.Lock() defer group.lock.Unlock() @@ -257,7 +267,7 @@ func msgType0x210Sub44Decoder(c *QQClient, protobuf []byte) error { group.Members = newMem for _, m := range newMem { if lastJoinTime < m.JoinTime { - go c.dispatchNewMemberEvent(&MemberJoinGroupEvent{ + c.GroupMemberJoinEvent.dispatch(c, &MemberJoinGroupEvent{ Group: group, Member: m, }) diff --git a/client/private_msg.go b/client/private_msg.go index e467c67d..fb89ec58 100644 --- a/client/private_msg.go +++ b/client/private_msg.go @@ -61,7 +61,7 @@ func (c *QQClient) SendPrivateMessage(target int64, m *message.SendingMessage) * }, Elements: m.Elements, } - go c.dispatchPrivateMessageSelf(ret) + go c.SelfPrivateMessageEvent.dispatch(c, ret) return ret } @@ -194,7 +194,6 @@ func (c *QQClient) buildGroupTempSendingPacket(groupUin, target int64, msgSeq, r } func (c *QQClient) buildWPATempSendingPacket(uin int64, sig []byte, msgSeq, r int32, time int64, m *message.SendingMessage) (uint16, []byte) { - req := &msg.SendMessageRequest{ RoutingHead: &msg.RoutingHead{WpaTmp: &msg.WPATmp{ ToUin: proto.Uint64(uint64(uin)), diff --git a/client/ptt.go b/client/ptt.go index 0d3e7fdf..4fb33873 100644 --- a/client/ptt.go +++ b/client/ptt.go @@ -1,7 +1,9 @@ package client import ( + "crypto/md5" "encoding/hex" + "fmt" "io" "github.com/pkg/errors" @@ -71,12 +73,13 @@ func (c *QQClient) UploadVoice(target message.Source, voice io.ReadSeeker) (*mes ext = c.buildGroupPttStoreBDHExt(target.PrimaryID, fh, int32(length), 0, int32(length)) } // multi-thread upload is no need - rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{ + rsp, err := c.highwaySession.UploadBDH(highway.Transaction{ CommandID: cmd, Body: voice, + Sum: fh, + Size: length, Ticket: c.highwaySession.SigSession, Ext: ext, - Encrypt: false, }) if err != nil { return nil, err @@ -88,7 +91,7 @@ func (c *QQClient) UploadVoice(target message.Source, voice io.ReadSeeker) (*mes FileType: proto.Int32(4), SrcUin: &c.Uin, FileMd5: fh, - FileName: proto.String(hex.EncodeToString(fh) + ".amr"), + FileName: proto.String(fmt.Sprintf("%x.amr", fh)), FileSize: proto.Int32(int32(length)), BoolValid: proto.Bool(true), } @@ -120,14 +123,17 @@ func (c *QQClient) UploadVoice(target message.Source, voice io.ReadSeeker) (*mes // UploadShortVideo 将视频和封面上传到服务器, 返回 message.ShortVideoElement 可直接发送 // thread 上传线程数 func (c *QQClient) UploadShortVideo(target message.Source, video, thumb io.ReadSeeker, thread int) (*message.ShortVideoElement, error) { - videoHash, videoLen := utils.ComputeMd5AndLength(video) - thumbHash, thumbLen := utils.ComputeMd5AndLength(thumb) + thumbHash := md5.New() + thumbLen, _ := io.Copy(thumbHash, thumb) + thumbSum := thumbHash.Sum(nil) + videoSum, videoLen := utils.ComputeMd5AndLength(io.TeeReader(video, thumbHash)) + sum := thumbHash.Sum(nil) - key := string(videoHash) + string(thumbHash) + key := string(sum) pttWaiter.Wait(key) defer pttWaiter.Done(key) - i, err := c.sendAndWait(c.buildPttGroupShortVideoUploadReqPacket(target, videoHash, thumbHash, videoLen, thumbLen)) + i, err := c.sendAndWait(c.buildPttGroupShortVideoUploadReqPacket(target, videoSum, thumbSum, videoLen, thumbLen)) if err != nil { return nil, errors.Wrap(err, "upload req error") } @@ -135,8 +141,8 @@ func (c *QQClient) UploadShortVideo(target message.Source, video, thumb io.ReadS videoElement := &message.ShortVideoElement{ Size: int32(videoLen), ThumbSize: int32(thumbLen), - Md5: videoHash, - ThumbMd5: thumbHash, + Md5: videoSum, + ThumbMd5: thumbSum, Guild: target.SourceType == message.SourceGuildChannel, } if rsp.FileExists == 1 { @@ -149,28 +155,22 @@ func (c *QQClient) UploadShortVideo(target message.Source, video, thumb io.ReadS if target.SourceType == message.SourceGuildChannel { cmd = 89 } - ext, _ := proto.Marshal(c.buildPttShortVideoProto(target, videoHash, thumbHash, videoLen, thumbLen).PttShortVideoUploadReq) + ext, _ := proto.Marshal(c.buildPttShortVideoProto(target, videoSum, thumbSum, videoLen, thumbLen).PttShortVideoUploadReq) + _, _ = thumb.Seek(0, io.SeekStart) + _, _ = video.Seek(0, io.SeekStart) + combined := io.MultiReader(thumb, video) + input := highway.Transaction{ + CommandID: cmd, + Body: combined, + Size: videoLen + thumbLen, + Sum: sum, + Ticket: c.highwaySession.SigSession, + Ext: ext, + Encrypt: true, + } if thread > 1 { - sum, _ := utils.ComputeMd5AndLength(utils.MultiReadSeeker(thumb, video)) - input := highway.BdhMultiThreadInput{ - CommandID: cmd, - Body: utils.ReaderAtFrom2ReadSeeker(thumb, video), - Size: videoLen + thumbLen, - Sum: sum, - Ticket: c.highwaySession.SigSession, - Ext: ext, - Encrypt: true, - } hwRsp, err = c.highwaySession.UploadBDHMultiThread(input, thread) } else { - multi := utils.MultiReadSeeker(thumb, video) - input := highway.BdhInput{ - CommandID: cmd, - Body: multi, - Ticket: c.highwaySession.SigSession, - Ext: ext, - Encrypt: true, - } hwRsp, err = c.highwaySession.UploadBDH(input) } if err != nil { @@ -262,7 +262,7 @@ func (c *QQClient) buildPttShortVideoProto(target message.Source, videoHash, thu ChatType: chatType, ClientType: 2, Info: &pttcenter.ShortVideoFileInfo{ - FileName: hex.EncodeToString(videoHash) + ".mp4", + FileName: fmt.Sprintf("%x.mp4", videoHash), FileMd5: videoHash, ThumbFileMd5: thumbHash, FileSize: videoSize, @@ -326,7 +326,7 @@ func (c *QQClient) buildC2CPttStoreBDHExt(target int64, md5 []byte, size, voiceL } // PttCenterSvr.ShortVideoDownReq -func decodePttShortVideoDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodePttShortVideoDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := pttcenter.ShortVideoRspBody{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -338,7 +338,7 @@ func decodePttShortVideoDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, } // PttCenterSvr.GroupShortVideoUpReq -func decodeGroupShortVideoUploadResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeGroupShortVideoUploadResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := pttcenter.ShortVideoRspBody{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") diff --git a/client/qidian.go b/client/qidian.go index 1054ac5d..37f0b148 100644 --- a/client/qidian.go +++ b/client/qidian.go @@ -52,7 +52,7 @@ func (c *QQClient) getQiDianAddressDetailList() ([]*FriendInfo, error) { ret := []*FriendInfo{} for _, detail := range rsp.GetAddressDetailListRspBody.AddressDetail { if len(detail.Qq) == 0 { - c.Warning("address detail %v QQ is 0", string(detail.Name)) + c.warning("address detail %v QQ is 0", string(detail.Name)) continue } ret = append(ret, &FriendInfo{ @@ -152,7 +152,7 @@ func (c *QQClient) bigDataRequest(subCmd uint32, req proto.Message) ([]byte, err return tea.Decrypt(payload), nil } -func decodeLoginExtraResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeLoginExtraResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := cmd0x3f6.C3F6RspBody{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -168,7 +168,7 @@ func decodeLoginExtraResponse(c *QQClient, _ *network.IncomingPacketInfo, payloa return nil, nil } -func decodeConnKeyResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeConnKeyResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := cmd0x6ff.C501RspBody{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") diff --git a/client/recall.go b/client/recall.go index a65104e9..a6f3546c 100644 --- a/client/recall.go +++ b/client/recall.go @@ -92,7 +92,7 @@ func (c *QQClient) buildPrivateRecallPacket(uin, ts int64, msgSeq, random int32) return c.uniPacket("PbMessageSvc.PbMsgWithDraw", payload) } -func decodeMsgWithDrawResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeMsgWithDrawResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := msg.MsgWithDrawResp{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") diff --git a/client/richmsg.go b/client/richmsg.go index efa3ba69..88356aaf 100644 --- a/client/richmsg.go +++ b/client/richmsg.go @@ -66,7 +66,7 @@ var musicType = [...]musicTypeInfo{ // SendGroupMusicShare 发送群聊音乐卡片 func (c *QQClient) SendGroupMusicShare(target int64, msg *message.MusicShareElement) (*message.GroupMessage, error) { - ch := make(chan *message.GroupMessage) + ch := make(chan *message.GroupMessage, 2) eid := utils.RandomString(6) c.onGroupMessageReceipt(eid, func(c *QQClient, e *groupMessageReceiptEvent) { for _, elem := range e.Msg.Elements { diff --git a/client/security.go b/client/security.go index 777a4031..e7bc3e5e 100644 --- a/client/security.go +++ b/client/security.go @@ -48,14 +48,11 @@ func (c *QQClient) buildUrlCheckRequest(url string) (uint16, []byte) { return c.uniPacket("OidbSvc.0xbcb_0", payload) } -func decodeUrlCheckResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := &oidb.OIDBSSOPkg{} +func decodeUrlCheckResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := &oidb.DBCBRspBody{} - if err := proto.Unmarshal(payload, pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } if rsp.CheckUrlRsp == nil || len(rsp.CheckUrlRsp.Results) == 0 { return nil, errors.New("response is empty") diff --git a/client/sync.go b/client/sync.go index cdb48647..2a75a986 100644 --- a/client/sync.go +++ b/client/sync.go @@ -77,9 +77,9 @@ func (c *QQClient) RefreshStatus() error { // SyncSessions 同步会话列表 func (c *QQClient) SyncSessions() (*SessionSyncResponse, error) { ret := &SessionSyncResponse{} - notifyChan := make(chan bool) + notifyChan := make(chan bool, 4) var groupNum int32 = -1 - stop := c.waitPacket("RegPrxySvc.PbSyncMsg", func(i interface{}, err error) { + stop := c.waitPacket("RegPrxySvc.PbSyncMsg", func(i any, err error) { if err != nil { return } @@ -90,7 +90,7 @@ func (c *QQClient) SyncSessions() (*SessionSyncResponse, error) { if e.GroupNum != -1 { groupNum = e.GroupNum } - c.Debug("sync session %v/%v", len(ret.GroupSessions), groupNum) + c.debug("sync session %v/%v", len(ret.GroupSessions), groupNum) if groupNum != -1 && len(ret.GroupSessions) >= int(groupNum) { notifyChan <- true } @@ -285,7 +285,7 @@ func (c *QQClient) buildPrivateMsgReadedPacket(uin, time int64) (uint16, []byte) } // StatSvc.GetDevLoginInfo -func decodeDevListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeDevListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -307,7 +307,7 @@ func decodeDevListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload [ } // RegPrxySvc.PushParam -func decodePushParamPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodePushParamPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -352,7 +352,7 @@ func decodePushParamPacket(c *QQClient, _ *network.IncomingPacketInfo, payload [ } // RegPrxySvc.PbSyncMsg -func decodeMsgSyncResponse(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeMsgSyncResponse(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := &msf.SvcRegisterProxyMsgResp{} if err := proto.Unmarshal(payload, rsp); err != nil { return nil, err @@ -396,7 +396,7 @@ func decodeMsgSyncResponse(c *QQClient, info *network.IncomingPacketInfo, payloa } // OnlinePush.PbC2CMsgSync -func decodeC2CSyncPacket(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeC2CSyncPacket(c *QQClient, info *network.IncomingPacketInfo, payload []byte) (any, error) { m := msg.PbPushMsg{} if err := proto.Unmarshal(payload, &m); err != nil { return nil, err @@ -406,7 +406,7 @@ func decodeC2CSyncPacket(c *QQClient, info *network.IncomingPacketInfo, payload return nil, nil } -func decodeMsgReadedResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeMsgReadedResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := msg.PbMsgReadedReportResp{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, errors.Wrap(err, "failed to unmarshal protobuf message") @@ -420,7 +420,7 @@ func decodeMsgReadedResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload var loginNotifyLock sync.Mutex // StatSvc.SvcReqMSFLoginNotify -func decodeLoginNotifyPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeLoginNotifyPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { request := &jce.RequestPacket{} request.ReadFrom(jce.NewJceReader(payload)) data := &jce.RequestDataVersion2{} @@ -443,7 +443,7 @@ func decodeLoginNotifyPacket(c *QQClient, _ *network.IncomingPacketInfo, payload t := ac if ac.AppId == notify.AppId { c.OnlineClients = append(c.OnlineClients, t) - c.dispatchOtherClientStatusChangedEvent(&OtherClientStatusChangedEvent{ + c.OtherClientStatusChangedEvent.dispatch(c, &OtherClientStatusChangedEvent{ Client: t, Online: true, }) @@ -462,7 +462,7 @@ func decodeLoginNotifyPacket(c *QQClient, _ *network.IncomingPacketInfo, payload if rmi != -1 { rmc := c.OnlineClients[rmi] c.OnlineClients = append(c.OnlineClients[:rmi], c.OnlineClients[rmi+1:]...) - c.dispatchOtherClientStatusChangedEvent(&OtherClientStatusChangedEvent{ + c.OtherClientStatusChangedEvent.dispatch(c, &OtherClientStatusChangedEvent{ Client: rmc, Online: false, }) diff --git a/client/system_msg.go b/client/system_msg.go index e6dbfdd6..c580df53 100644 --- a/client/system_msg.go +++ b/client/system_msg.go @@ -60,7 +60,7 @@ func (c *QQClient) GetGroupSystemMessages() (*GroupSystemMessages, error) { func (c *QQClient) exceptAndDispatchGroupSysMsg() { if c.groupSysMsgCache == nil { - c.Error("warning: groupSysMsgCache is nil") + c.error("warning: groupSysMsgCache is nil") c.groupSysMsgCache, _ = c.GetGroupSystemMessages() return } @@ -86,12 +86,12 @@ func (c *QQClient) exceptAndDispatchGroupSysMsg() { } for _, msg := range msgs.JoinRequests { if !joinExists(msg.RequestId) { - c.dispatchJoinGroupRequest(msg) + c.UserWantJoinGroupEvent.dispatch(c, msg) } } for _, msg := range msgs.InvitedRequests { if !invExists(msg.RequestId) { - c.dispatchGroupInvitedEvent(msg) + c.GroupInvitedEvent.dispatch(c, msg) } } c.groupSysMsgCache = msgs @@ -190,7 +190,7 @@ func (c *QQClient) buildSystemMsgFriendActionPacket(reqID, requester int64, acce } // ProfileService.Pb.ReqSystemMsgNew.Group -func decodeSystemMsgGroupPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { +func decodeSystemMsgGroupPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := structmsg.RspSystemMsgNew{} if err := proto.Unmarshal(payload, &rsp); err != nil { return nil, err @@ -243,12 +243,12 @@ func decodeSystemMsgGroupPacket(c *QQClient, _ *network.IncomingPacketInfo, payl ActionUinNick: st.Msg.ActionUinQqNick, }) default: - c.Debug("unknown group system message type: %v", st.Msg.GroupMsgType) + c.debug("unknown group system message type: %v", st.Msg.GroupMsgType) } case 3: // ? case 5: // 自身状态变更(管理员/加群退群) default: - c.Debug("unknown group system msg: %v", st.Msg.SubType) + c.debug("unknown group system msg: %v", st.Msg.SubType) } } return ret, nil diff --git a/client/tlv_decoders.go b/client/tlv_decoders.go index 6337abf4..030e7ee0 100644 --- a/client/tlv_decoders.go +++ b/client/tlv_decoders.go @@ -12,16 +12,16 @@ import ( // --- tlv decoders for qq client --- +/* func (c *QQClient) decodeT161(data []byte) { - /* - reader := binary.NewReader(data) - reader.ReadBytes(2) - t := reader.ReadTlvMap(2) - if t172, ok := t[0x172]; ok { - c.rollbackSig = t172 - } - */ + reader := binary.NewReader(data) + reader.ReadBytes(2) + t := reader.ReadTlvMap(2) + if t172, ok := t[0x172]; ok { + c.rollbackSig = t172 + } } +*/ func (c *QQClient) decodeT119(data, ek []byte) { tea := binary.NewTeaCipher(ek) @@ -63,32 +63,32 @@ func (c *QQClient) decodeT119(data, ek []byte) { pt4TokenMap map[string][]byte ) - if _, ok := m[0x125]; ok { - // openId, openKey = readT125(t125) - } - if t186, ok := m[0x186]; ok { - c.decodeT186(t186) - } if t11a, ok := m[0x11a]; ok { nick, age, gender = readT11A(t11a) } - if _, ok := m[0x199]; ok { - // openId, payToken = readT199(t199) - } - if _, ok := m[0x200]; ok { - // pf, pfkey = readT200(t200) - } + /* + if _, ok := m[0x125]; ok { + openId, openKey = readT125(t125) + } + if t186, ok := m[0x186]; ok { + c.decodeT186(t186) + } + if _, ok := m[0x199]; ok { + openId, payToken = readT199(t199) + } + if _, ok := m[0x200]; ok { + pf, pfkey = readT200(t200) + } + if _, ok := m[0x531]; ok { + a1, noPicSig = readT531(t531) + } + if _, ok := m[0x138]; ok { + readT138(t138) // chg time + } + */ if t512, ok := m[0x512]; ok { psKeyMap, pt4TokenMap = readT512(t512) } - if _, ok := m[0x531]; ok { - // a1, noPicSig = readT531(t531) - } - - if _, ok := m[0x138]; ok { - // readT138(t138) // chg time - } - c.oicq.WtSessionTicketKey = utils.Select(m[0x134], c.oicq.WtSessionTicketKey) // we don't use `c.sigInfo = &auth.SigInfo{...}` here, @@ -139,11 +139,11 @@ func (c *QQClient) decodeT119R(data []byte) { if t120, ok := m[0x120]; ok { c.sig.SKey = t120 c.sig.SKeyExpiredTime = time.Now().Unix() + 21600 - c.Debug("skey updated: %v", c.sig.SKey) + c.debug("skey updated: %v", c.sig.SKey) } if t11a, ok := m[0x11a]; ok { c.Nickname, c.Age, c.Gender = readT11A(t11a) - c.Debug("account info updated: " + c.Nickname) + c.debug("account info updated: " + c.Nickname) } } @@ -160,9 +160,11 @@ func (c *QQClient) decodeT113(data []byte) { fmt.Println("got t113 uin:", uin) } +/* func (c *QQClient) decodeT186(data []byte) { // c.pwdFlag = data[1] == 1 } +*/ // --- tlv readers --- diff --git a/client/translate.go b/client/translate.go index 946cec49..e0a4bb6b 100644 --- a/client/translate.go +++ b/client/translate.go @@ -5,7 +5,6 @@ import ( "github.com/Mrs4s/MiraiGo/client/internal/network" "github.com/Mrs4s/MiraiGo/client/pb/oidb" - "github.com/Mrs4s/MiraiGo/internal/proto" ) func (c *QQClient) buildTranslatePacket(src, dst, text string) (uint16, []byte) { @@ -16,13 +15,7 @@ func (c *QQClient) buildTranslatePacket(src, dst, text string) (uint16, []byte) SrcTextList: []string{text}, }, } - b, _ := proto.Marshal(body) - req := &oidb.OIDBSSOPkg{ - Command: 2448, - ServiceType: 2, - Bodybuffer: b, - } - payload, _ := proto.Marshal(req) + payload := c.packOIDBPackageProto(2448, 2, body) return c.uniPacket("OidbSvc.0x990", payload) } @@ -41,14 +34,11 @@ func (c *QQClient) Translate(src, dst, text string) (string, error) { } // OidbSvc.0x990 -func decodeTranslateResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) { - pkg := oidb.OIDBSSOPkg{} +func decodeTranslateResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) { rsp := oidb.TranslateRspBody{} - if err := proto.Unmarshal(payload, &pkg); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + err := unpackOIDBPackage(payload, &rsp) + if err != nil { + return nil, err } return rsp.BatchTranslateRsp, nil } diff --git a/go.mod b/go.mod index e265f018..7c3d577d 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,10 @@ module github.com/Mrs4s/MiraiGo -go 1.17 +go 1.18 require ( - github.com/RomiChan/protobuf v0.0.0-20220213164748-44b69c8bdec0 + github.com/RomiChan/protobuf v0.0.0-20220318113238-d8a99598f896 + github.com/RomiChan/syncx v0.0.0-20220320130821-c88644afda9c github.com/fumiama/imgsz v0.0.2 github.com/pierrec/lz4/v4 v4.1.11 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index dfef8f81..d00f0272 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -github.com/RomiChan/protobuf v0.0.0-20220213164748-44b69c8bdec0 h1:8CK7Hg+CRGTFhpjvp5V+7wd8/TkuZ6fSuztLVV3bwoQ= -github.com/RomiChan/protobuf v0.0.0-20220213164748-44b69c8bdec0/go.mod h1:CKKOWC7mBxd36zxsCB1V8DTrwlTNRQvkSVbYqyUiGEE= +github.com/RomiChan/protobuf v0.0.0-20220318113238-d8a99598f896 h1:UFAqSbH6VqW5mEzQV2HVB7+p3k9JfTbidWJ/9F15yz0= +github.com/RomiChan/protobuf v0.0.0-20220318113238-d8a99598f896/go.mod h1:CKKOWC7mBxd36zxsCB1V8DTrwlTNRQvkSVbYqyUiGEE= +github.com/RomiChan/syncx v0.0.0-20220320130821-c88644afda9c h1:zHWyqx7A71A/+mlzthPVcVrNGuTPyTpCW3meUPtaULU= +github.com/RomiChan/syncx v0.0.0-20220320130821-c88644afda9c/go.mod h1:KqZzu7slNKROh3TSYEH/IUMG6f4M+1qubZ5e52QypsE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/internal/generator/jce_gen/main.go b/internal/generator/jce_gen/main.go index ed418bd0..3439c0b2 100644 --- a/internal/generator/jce_gen/main.go +++ b/internal/generator/jce_gen/main.go @@ -173,7 +173,7 @@ func (g Generator) Generate(w io.Writer) { } } -func assert(cond bool, val interface{}) { +func assert(cond bool, val any) { if !cond { panic("assertion failed: " + fmt.Sprint(val)) } diff --git a/internal/proto/dynamic.go b/internal/proto/dynamic.go index c211c05d..a00339de 100644 --- a/internal/proto/dynamic.go +++ b/internal/proto/dynamic.go @@ -6,7 +6,7 @@ import ( "math" ) -type DynamicMessage map[uint64]interface{} +type DynamicMessage map[uint64]any type encoder struct { bytes.Buffer diff --git a/internal/proto/wrapper.go b/internal/proto/wrapper.go index c5865421..b43a2ac9 100644 --- a/internal/proto/wrapper.go +++ b/internal/proto/wrapper.go @@ -2,7 +2,7 @@ package proto import "github.com/RomiChan/protobuf/proto" -type Message = interface{} +type Message = any func Marshal(m Message) ([]byte, error) { return proto.Marshal(m) diff --git a/message/elements.go b/message/elements.go index c1d02ee0..c90e6015 100644 --- a/message/elements.go +++ b/message/elements.go @@ -103,8 +103,10 @@ type AnimatedSticker struct { Name string } -type RedBagMessageType int -type AtType int +type ( + RedBagMessageType int + AtType int +) // /com/tencent/mobileqq/data/MessageForQQWalletMsg.java const ( diff --git a/message/forward.go b/message/forward.go index 9062f11d..a1226bb2 100644 --- a/message/forward.go +++ b/message/forward.go @@ -1,9 +1,9 @@ package message import ( - "bytes" "crypto/md5" "regexp" + "strings" "sync" "github.com/Mrs4s/MiraiGo/binary" @@ -14,10 +14,9 @@ import ( // *----- Definitions -----* // -// ForwardMessage 添加 Node 请用 AddNode 方法 +// ForwardMessage 合并转发消息 type ForwardMessage struct { Nodes []*ForwardNode - items []*msg.PbMultiMsgItem } type ForwardNode struct { @@ -72,9 +71,6 @@ func (f *ForwardMessage) AddNode(node *ForwardNode) *ForwardMessage { if item.Type() != Forward { // quick path continue } - if forward, ok := item.(*ForwardElement); ok { - f.items = append(f.items, forward.Items...) - } } return f } @@ -83,7 +79,7 @@ func (f *ForwardMessage) AddNode(node *ForwardNode) *ForwardMessage { func (f *ForwardMessage) Length() int { return len(f.Nodes) } func (f *ForwardMessage) Brief() string { - var brief bytes.Buffer + var brief strings.Builder for _, n := range f.Nodes { brief.WriteString(ToReadableString(n.Message)) if brief.Len() >= 27 { @@ -94,7 +90,7 @@ func (f *ForwardMessage) Brief() string { } func (f *ForwardMessage) Preview() string { - var pv bytes.Buffer + var pv strings.Builder for i, node := range f.Nodes { if i >= 4 { break @@ -109,7 +105,7 @@ func (f *ForwardMessage) Preview() string { } func (f *ForwardMessage) CalculateValidationData(seq, random int32, groupCode int64) ([]byte, []byte) { - msgs := f.packForwardMsg(seq, random, groupCode) + msgs := f.PackForwardMessage(seq, random, groupCode) trans := &msg.PbMultiMsgTransmit{Msg: msgs, PbItemList: []*msg.PbMultiMsgItem{ { FileName: proto.String("MultiMsg"), @@ -122,23 +118,7 @@ func (f *ForwardMessage) CalculateValidationData(seq, random int32, groupCode in return data, hash[:] } -// CalculateValidationDataForward 屎代码 -func (f *ForwardMessage) CalculateValidationDataForward(seq, random int32, groupCode int64) ([]byte, []byte, []*msg.PbMultiMsgItem) { - msgs := f.packForwardMsg(seq, random, groupCode) - trans := &msg.PbMultiMsgTransmit{Msg: msgs, PbItemList: []*msg.PbMultiMsgItem{ - { - FileName: proto.String("MultiMsg"), - Buffer: &msg.PbMultiMsgNew{Msg: msgs}, - }, - }} - trans.PbItemList = append(trans.PbItemList, f.items...) - b, _ := proto.Marshal(trans) - data := binary.GZipCompress(b) - hash := md5.Sum(data) - return data, hash[:], trans.PbItemList -} - -func (f *ForwardMessage) packForwardMsg(seq int32, random int32, groupCode int64) []*msg.Message { +func (f *ForwardMessage) PackForwardMessage(seq int32, random int32, groupCode int64) []*msg.Message { ml := make([]*msg.Message, 0, len(f.Nodes)) for _, node := range f.Nodes { ml = append(ml, &msg.Message{ diff --git a/message/image.go b/message/image.go index 6e596b7e..173153d2 100644 --- a/message/image.go +++ b/message/image.go @@ -1,9 +1,8 @@ package message import ( - "strings" + "fmt" - "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client/pb/msg" "github.com/Mrs4s/MiraiGo/internal/proto" ) @@ -73,7 +72,7 @@ func NewGroupImage(id string, md5 []byte, fid int64, size, width, height, imageT ImageType: imageType, Width: width, Height: height, - Url: "https://gchat.qpic.cn/gchatpic_new/1/0-0-" + strings.ReplaceAll(binary.CalculateImageResourceId(md5)[1:37], "-", "") + "/0?term=2", + Url: fmt.Sprintf("https://gchat.qpic.cn/gchatpic_new/1/0-0-%X/0?term=2", md5), } } diff --git a/message/message.go b/message/message.go index 201fd169..2cb9673b 100644 --- a/message/message.go +++ b/message/message.go @@ -778,7 +778,7 @@ func splitPlainMessage(content string) []IMessageElement { } } if last != len(content) { - splittedMessage = append(splittedMessage, NewText(content[last:len(content)])) + splittedMessage = append(splittedMessage, NewText(content[last:])) } return splittedMessage diff --git a/message/pack.go b/message/pack.go index 37af9a7c..bb56ad70 100644 --- a/message/pack.go +++ b/message/pack.go @@ -1,7 +1,6 @@ package message import ( - "encoding/hex" "fmt" "github.com/Mrs4s/MiraiGo/binary" @@ -126,7 +125,7 @@ func (e *ShortVideoElement) Pack() (r []*msg.Elem) { video := &msg.VideoFile{ FileUuid: e.Uuid, FileMd5: e.Md5, - FileName: []byte(hex.EncodeToString(e.Md5) + ".mp4"), + FileName: []byte(fmt.Sprintf("%x.mp4", e.Md5)), FileFormat: proto.Int32(3), FileTime: proto.Int32(10), FileSize: proto.Int32(e.Size), diff --git a/topic/feed.go b/topic/feed.go index b7bfa146..8cb65363 100644 --- a/topic/feed.go +++ b/topic/feed.go @@ -55,10 +55,10 @@ type ( pack(patternId string, isPatternData bool) content } - content map[string]interface{} + content map[string]any ) -var globalBlockId int64 = 0 +var globalBlockId int64 func genBlockId() string { id := atomic.AddInt64(&globalBlockId, 1) diff --git a/utils/http.go b/utils/http.go index ea594e84..df1caa6c 100644 --- a/utils/http.go +++ b/utils/http.go @@ -8,7 +8,7 @@ import ( "strings" ) -var client = &http.Client{ +var Client = &http.Client{ Transport: &http.Transport{ ForceAttemptHTTP2: true, MaxConnsPerHost: 0, @@ -34,7 +34,7 @@ func HttpPostBytes(url string, data []byte) ([]byte, error) { } req.Header["User-Agent"] = []string{"QQ/8.2.0.1296 CFNetwork/1126"} req.Header["Net-Type"] = []string{"Wifi"} - resp, err := client.Do(req) + resp, err := Client.Do(req) if err != nil { return nil, err } @@ -67,7 +67,7 @@ func HttpPostBytesWithCookie(url string, data []byte, cookie string, contentType if cookie != "" { req.Header["Cookie"] = []string{cookie} } - resp, err := client.Do(req) + resp, err := Client.Do(req) if err != nil { return nil, err } @@ -125,7 +125,7 @@ func HTTPGetReadCloser(url string, cookie string) (io.ReadCloser, error) { if cookie != "" { req.Header["Cookie"] = []string{cookie} } - resp, err := client.Do(req) + resp, err := Client.Do(req) if err != nil { return nil, err } diff --git a/utils/string.go b/utils/string.go index eb702fe4..aa8b236d 100644 --- a/utils/string.go +++ b/utils/string.go @@ -1,11 +1,11 @@ package utils import ( - "encoding/xml" "math/rand" "reflect" "strconv" "strings" + "unicode/utf8" "unsafe" ) @@ -69,9 +69,63 @@ func S2B(s string) (b []byte) { return } -// XmlEscape xml escape string -func XmlEscape(c string) string { - buf := new(strings.Builder) - _ = xml.EscapeText(buf, []byte(c)) - return buf.String() +const ( + escQuot = """ // shorter than """ + escApos = "'" // shorter than "'" + escAmp = "&" + escLT = "<" + escGT = ">" + escTab = " " + escNL = " " + escCR = " " + escFFFD = "\uFFFD" // Unicode replacement character +) + +func isInCharacterRange(r rune) (inrange bool) { + return r == 0x09 || + r == 0x0A || + r == 0x0D || + r >= 0x20 && r <= 0xD7FF || + r >= 0xE000 && r <= 0xFFFD || + r >= 0x10000 && r <= 0x10FFFF +} + +// XmlEscape xml escape string +func XmlEscape(s string) string { + var esc string + var sb strings.Builder + sb.Grow(len(s)) + last := 0 + for i, r := range s { + width := utf8.RuneLen(r) + switch r { + case '"': + esc = escQuot + case '\'': + esc = escApos + case '&': + esc = escAmp + case '<': + esc = escLT + case '>': + esc = escGT + case '\t': + esc = escTab + case '\n': + esc = escNL + case '\r': + esc = escCR + default: + if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) { + esc = escFFFD + break + } + continue + } + sb.WriteString(s[last:i]) + sb.WriteString(esc) + last = i + width + } + sb.WriteString(s[last:]) + return sb.String() } diff --git a/utils/string_test.go b/utils/string_test.go new file mode 100644 index 00000000..23310da9 --- /dev/null +++ b/utils/string_test.go @@ -0,0 +1,14 @@ +package utils + +import ( + "testing" +) + +func TestXmlEscape(t *testing.T) { + input := "A \x00 terminated string." + expected := "A \uFFFD terminated string." + text := XmlEscape(input) + if text != expected { + t.Errorf("have %v, want %v", text, expected) + } +} diff --git a/utils/sys.go b/utils/sys.go index 7ef22b45..b3b11654 100644 --- a/utils/sys.go +++ b/utils/sys.go @@ -20,10 +20,10 @@ func ComputeMd5AndLength(r io.Reader) ([]byte, int64) { func (r *multiReadSeeker) Read(p []byte) (int, error) { if r.multiReader == nil { - var readers []io.Reader + readers := make([]io.Reader, len(r.readers)) for i := range r.readers { _, _ = r.readers[i].Seek(0, io.SeekStart) - readers = append(readers, r.readers[i]) + readers[i] = r.readers[i] } r.multiReader = io.MultiReader(readers...) } @@ -44,59 +44,6 @@ func MultiReadSeeker(r ...io.ReadSeeker) io.ReadSeeker { } } -type multiReadAt struct { - first io.ReadSeeker - second io.ReadSeeker - firstSize int64 - secondSize int64 -} - -func (m *multiReadAt) ReadAt(p []byte, off int64) (n int, err error) { - if m.second == nil { // quick path - _, _ = m.first.Seek(off, io.SeekStart) - return m.first.Read(p) - } - if off < m.firstSize && off+int64(len(p)) < m.firstSize { - _, err = m.first.Seek(off, io.SeekStart) - if err != nil { - return - } - return m.first.Read(p) - } else if off < m.firstSize && off+int64(len(p)) >= m.firstSize { - _, _ = m.first.Seek(off, io.SeekStart) - _, _ = m.second.Seek(0, io.SeekStart) - n, err = m.first.Read(p[:m.firstSize-off]) - if err != nil { - return - } - n2, err := m.second.Read(p[m.firstSize-off:]) - return n + n2, err - } - _, err = m.second.Seek(off-m.firstSize, io.SeekStart) - if err != nil { - return - } - return m.second.Read(p) -} - -func ReaderAtFrom2ReadSeeker(first, second io.ReadSeeker) io.ReaderAt { - firstSize, _ := first.Seek(0, io.SeekEnd) - if second == nil { - return &multiReadAt{ - first: first, - firstSize: firstSize, - secondSize: 0, - } - } - secondSize, _ := second.Seek(0, io.SeekEnd) - return &multiReadAt{ - first: first, - second: second, - firstSize: firstSize, - secondSize: secondSize, - } -} - // Select 如果A为nil 将会返回 B 否则返回A // 对应 ?? 语法 func Select(a, b []byte) []byte { diff --git a/utils/ttl.go b/utils/ttl.go index 1b589960..e81228af 100644 --- a/utils/ttl.go +++ b/utils/ttl.go @@ -7,26 +7,26 @@ import ( // https://github.com/Konstantin8105/SimpleTTL // entry - typical element of cache -type entry struct { +type entry[T any] struct { expiry time.Time - value interface{} + value T } // Cache - simple implementation of cache // More information: https://en.wikipedia.org/wiki/Time_to_live -type Cache struct { +type Cache[T any] struct { lock sync.RWMutex - cache map[string]*entry + cache map[string]*entry[T] } // NewCache - initialization of new cache. // For avoid mistake - minimal time to live is 1 minute. // For simplification, - key is string and cache haven`t stop method -func NewCache(interval time.Duration) *Cache { +func NewCache[T any](interval time.Duration) *Cache[T] { if interval < time.Second { interval = time.Second } - cache := &Cache{cache: make(map[string]*entry)} + cache := &Cache[T]{cache: make(map[string]*entry[T])} go func() { ticker := time.NewTicker(interval) for { @@ -47,7 +47,7 @@ func NewCache(interval time.Duration) *Cache { } // Count - return amount element of TTL map. -func (cache *Cache) Count() int { +func (cache *Cache[_]) Count() int { cache.lock.RLock() defer cache.lock.RUnlock() @@ -55,19 +55,18 @@ func (cache *Cache) Count() int { } // Get - return value from cache -func (cache *Cache) Get(key string) (interface{}, bool) { +func (cache *Cache[T]) Get(key string) (value T, _ bool) { cache.lock.RLock() defer cache.lock.RUnlock() e, ok := cache.cache[key] - if ok && e.expiry.After(time.Now()) { return e.value, true } - return nil, false + return } -func (cache *Cache) GetAndUpdate(key string, ttl time.Duration) (interface{}, bool) { +func (cache *Cache[T]) GetAndUpdate(key string, ttl time.Duration) (_ T, _ bool) { cache.lock.RLock() defer cache.lock.RUnlock() @@ -75,22 +74,22 @@ func (cache *Cache) GetAndUpdate(key string, ttl time.Duration) (interface{}, bo e.expiry = time.Now().Add(ttl) return e.value, true } - return nil, false + return } // Add - add key/value in cache -func (cache *Cache) Add(key string, value interface{}, ttl time.Duration) { +func (cache *Cache[T]) Add(key string, value T, ttl time.Duration) { cache.lock.Lock() defer cache.lock.Unlock() - cache.cache[key] = &entry{ + cache.cache[key] = &entry[T]{ value: value, expiry: time.Now().Add(ttl), } } // GetKeys - return all keys of cache map -func (cache *Cache) GetKeys() []string { +func (cache *Cache[_]) GetKeys() []string { cache.lock.RLock() defer cache.lock.RUnlock()