mirror of
https://github.com/Mrs4s/MiraiGo.git
synced 2025-05-04 19:17:38 +08:00
Merge branch 'Mrs4s:master' into master
This commit is contained in:
commit
0fd0b02e41
6
.github/workflows/go.yml
vendored
6
.github/workflows/go.yml
vendored
@ -2,9 +2,9 @@ name: Go
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master ]
|
branches: [ master, typeparam ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master, typeparam ]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ jobs:
|
|||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.13
|
go-version: 1.18
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
2
.github/workflows/goimports.yml
vendored
2
.github/workflows/goimports.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
|||||||
- name: Set up Go 1.x
|
- name: Set up Go 1.x
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: ^1.13
|
go-version: 1.18
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
@ -49,7 +49,7 @@ func (r *JceReader) skipHead() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *JceReader) skip(l int) {
|
func (r *JceReader) skip(l int) {
|
||||||
r.skipBytes(l)
|
r.off += l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *JceReader) skipField(t byte) {
|
func (r *JceReader) skipField(t byte) {
|
||||||
@ -105,17 +105,6 @@ func (r *JceReader) readBytes(n int) []byte {
|
|||||||
return b
|
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 {
|
func (r *JceReader) readByte() byte {
|
||||||
if r.off >= len(r.buf) {
|
if r.off >= len(r.buf) {
|
||||||
panic("readByte: EOF")
|
panic("readByte: EOF")
|
||||||
@ -126,15 +115,21 @@ func (r *JceReader) readByte() byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *JceReader) readUInt16() uint16 {
|
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 {
|
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 {
|
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 {
|
func (r *JceReader) readFloat32() float32 {
|
||||||
|
@ -99,7 +99,7 @@ func TestJceReader_ReadBytes(t *testing.T) {
|
|||||||
assert.Equal(t, b, rb)
|
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)
|
t := reflect.TypeOf(i)
|
||||||
if t.Kind() == reflect.Map {
|
if t.Kind() == reflect.Map {
|
||||||
w.WriteMap(i, tag)
|
w.WriteMap(i, tag)
|
||||||
@ -192,7 +192,7 @@ type decoder struct {
|
|||||||
var decoderCache = sync.Map{}
|
var decoderCache = sync.Map{}
|
||||||
|
|
||||||
// WriteJceStructRaw 写入 Jce 结构体
|
// WriteJceStructRaw 写入 Jce 结构体
|
||||||
func (w *JceWriter) WriteJceStructRaw(s interface{}) {
|
func (w *JceWriter) WriteJceStructRaw(s any) {
|
||||||
t := reflect.TypeOf(s)
|
t := reflect.TypeOf(s)
|
||||||
if t.Kind() != reflect.Ptr {
|
if t.Kind() != reflect.Ptr {
|
||||||
return
|
return
|
||||||
@ -234,7 +234,7 @@ func (w *JceWriter) WriteJceStruct(s IJceStruct, tag byte) {
|
|||||||
w.writeHead(11, 0)
|
w.writeHead(11, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *JceWriter) WriteSlice(i interface{}, tag byte) {
|
func (w *JceWriter) WriteSlice(i any, tag byte) {
|
||||||
va := reflect.ValueOf(i)
|
va := reflect.ValueOf(i)
|
||||||
if va.Kind() != reflect.Slice {
|
if va.Kind() != reflect.Slice {
|
||||||
panic("JceWriter.WriteSlice: not a 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)
|
va := reflect.ValueOf(m)
|
||||||
if va.Kind() != reflect.Map {
|
if va.Kind() != reflect.Map {
|
||||||
panic("JceWriter.WriteMap: not a map")
|
panic("JceWriter.WriteMap: not a map")
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var bufferPool = sync.Pool{
|
var bufferPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() any {
|
||||||
return new(Writer)
|
return new(Writer)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ func SelectWriter() *Writer {
|
|||||||
// PutWriter 将 Writer 放回池中
|
// PutWriter 将 Writer 放回池中
|
||||||
func PutWriter(w *Writer) {
|
func PutWriter(w *Writer) {
|
||||||
// See https://golang.org/issue/23199
|
// See https://golang.org/issue/23199
|
||||||
const maxSize = 1 << 16
|
const maxSize = 32 * 1024
|
||||||
if (*bytes.Buffer)(w).Cap() < maxSize { // 对于大Buffer直接丢弃
|
if (*bytes.Buffer)(w).Cap() < maxSize { // 对于大Buffer直接丢弃
|
||||||
w.Reset()
|
w.Reset()
|
||||||
bufferPool.Put(w)
|
bufferPool.Put(w)
|
||||||
@ -32,7 +32,7 @@ func PutWriter(w *Writer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var gzipPool = sync.Pool{
|
var gzipPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() any {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
w := gzip.NewWriter(buf)
|
w := gzip.NewWriter(buf)
|
||||||
return &GzipWriter{
|
return &GzipWriter{
|
||||||
@ -64,7 +64,7 @@ type zlibWriter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var zlibPool = sync.Pool{
|
var zlibPool = sync.Pool{
|
||||||
New: func() interface{} {
|
New: func() any {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
w := zlib.NewWriter(buf)
|
w := zlib.NewWriter(buf)
|
||||||
return &zlibWriter{
|
return &zlibWriter{
|
||||||
|
@ -52,17 +52,20 @@ func (r *Reader) ReadBytesShort() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) ReadUInt16() uint16 {
|
func (r *Reader) ReadUInt16() uint16 {
|
||||||
b := r.ReadBytes(2)
|
b := make([]byte, 2)
|
||||||
|
_, _ = r.buf.Read(b)
|
||||||
return binary.BigEndian.Uint16(b)
|
return binary.BigEndian.Uint16(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) ReadInt32() int32 {
|
func (r *Reader) ReadInt32() int32 {
|
||||||
b := r.ReadBytes(4)
|
b := make([]byte, 4)
|
||||||
|
_, _ = r.buf.Read(b)
|
||||||
return int32(binary.BigEndian.Uint32(b))
|
return int32(binary.BigEndian.Uint32(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) ReadInt64() int64 {
|
func (r *Reader) ReadInt64() int64 {
|
||||||
b := r.ReadBytes(8)
|
b := make([]byte, 8)
|
||||||
|
_, _ = r.buf.Read(b)
|
||||||
return int64(binary.BigEndian.Uint64(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) {
|
func (r *NetworkReader) ReadInt32() (int32, error) {
|
||||||
b, err := r.ReadBytes(4)
|
b := make([]byte, 4)
|
||||||
|
_, err := r.conn.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import (
|
|||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
binary2 "encoding/binary"
|
binary2 "encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/utils"
|
"github.com/Mrs4s/MiraiGo/utils"
|
||||||
@ -34,7 +33,7 @@ func ZlibUncompress(src []byte) []byte {
|
|||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
r, _ := zlib.NewReader(b)
|
r, _ := zlib.NewReader(b)
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
io.Copy(&out, r)
|
_, _ = out.ReadFrom(r)
|
||||||
return out.Bytes()
|
return out.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +41,8 @@ func ZlibCompress(data []byte) []byte {
|
|||||||
zw := acquireZlibWriter()
|
zw := acquireZlibWriter()
|
||||||
_, _ = zw.w.Write(data)
|
_, _ = zw.w.Write(data)
|
||||||
_ = zw.w.Close()
|
_ = zw.w.Close()
|
||||||
ret := append([]byte(nil), zw.buf.Bytes()...)
|
ret := make([]byte, len(zw.buf.Bytes()))
|
||||||
|
copy(ret, zw.buf.Bytes())
|
||||||
releaseZlibWriter(zw)
|
releaseZlibWriter(zw)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@ -51,7 +51,8 @@ func GZipCompress(data []byte) []byte {
|
|||||||
gw := AcquireGzipWriter()
|
gw := AcquireGzipWriter()
|
||||||
_, _ = gw.Write(data)
|
_, _ = gw.Write(data)
|
||||||
_ = gw.Close()
|
_ = gw.Close()
|
||||||
ret := append([]byte(nil), gw.buf.Bytes()...)
|
ret := make([]byte, len(gw.buf.Bytes()))
|
||||||
|
copy(ret, gw.buf.Bytes())
|
||||||
ReleaseGzipWriter(gw)
|
ReleaseGzipWriter(gw)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
@ -61,7 +62,7 @@ func GZipUncompress(src []byte) []byte {
|
|||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
r, _ := gzip.NewReader(b)
|
r, _ := gzip.NewReader(b)
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
_, _ = io.Copy(&out, r)
|
_, _ = out.ReadFrom(r)
|
||||||
return out.Bytes()
|
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) {
|
return NewWriterF(func(w *Writer) {
|
||||||
// TODO: more types
|
// TODO: more types
|
||||||
switch t := i.(type) {
|
switch t := i.(type) {
|
||||||
|
@ -12,7 +12,8 @@ type Writer bytes.Buffer
|
|||||||
func NewWriterF(f func(writer *Writer)) []byte {
|
func NewWriterF(f func(writer *Writer)) []byte {
|
||||||
w := SelectWriter()
|
w := SelectWriter()
|
||||||
f(w)
|
f(w)
|
||||||
b := append([]byte(nil), w.Bytes()...)
|
b := make([]byte, len(w.Bytes()))
|
||||||
|
copy(b, w.Bytes())
|
||||||
w.put()
|
w.put()
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -903,7 +903,7 @@ func (c *QQClient) buildOffPicUpPacket(target int64, md5 []byte, size int32) (ui
|
|||||||
DstUin: proto.Uint64(uint64(target)),
|
DstUin: proto.Uint64(uint64(target)),
|
||||||
FileMd5: md5,
|
FileMd5: md5,
|
||||||
FileSize: proto.Uint64(uint64(size)),
|
FileSize: proto.Uint64(uint64(size)),
|
||||||
FileName: []byte(hex.EncodeToString(md5) + ".jpg"),
|
FileName: []byte(fmt.Sprintf("%x.jpg", md5)),
|
||||||
SrcTerm: proto.Uint32(5),
|
SrcTerm: proto.Uint32(5),
|
||||||
PlatformType: proto.Uint32(9),
|
PlatformType: proto.Uint32(9),
|
||||||
BuType: proto.Uint32(1),
|
BuType: proto.Uint32(1),
|
||||||
|
@ -71,7 +71,7 @@ func (c *QQClient) c2cMessageSyncProcessor(rsp *msg.GetMessageResponse, info *ne
|
|||||||
_, _ = c.sendAndWait(c.buildDeleteMessageRequestPacket(delItems))
|
_, _ = c.sendAndWait(c.buildDeleteMessageRequestPacket(delItems))
|
||||||
}
|
}
|
||||||
if rsp.GetSyncFlag() != msg.SyncFlag_STOP {
|
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())
|
seq, pkt := c.buildGetMessageRequestPacket(rsp.GetSyncFlag(), time.Now().Unix())
|
||||||
_, _ = c.sendAndWait(seq, pkt, info.Params)
|
_, _ = 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) {
|
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())
|
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 {
|
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
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
c.lastC2CMsgTime = int64(pMsg.Head.GetMsgTime())
|
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 {
|
if decoder, _ := peekC2CDecoder(pMsg.Head.GetMsgType()); decoder != nil {
|
||||||
decoder(c, pMsg, info)
|
decoder(c, pMsg, info)
|
||||||
} else {
|
} 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 {
|
if pMsg.Head.GetFromUin() == c.Uin {
|
||||||
c.dispatchPrivateMessageSelf(c.parsePrivateMessage(pMsg))
|
c.SelfPrivateMessageEvent.dispatch(c, c.parsePrivateMessage(pMsg))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.dispatchPrivateMessage(c.parsePrivateMessage(pMsg))
|
c.PrivateMessageEvent.dispatch(c, c.parsePrivateMessage(pMsg))
|
||||||
default:
|
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)
|
// m := binary.NewReader(pMsg.Body.RichText.Ptt.Reserve[1:]).ReadTlvMap(1)
|
||||||
// T3 -> timestamp T8 -> voiceType T9 -> voiceLength T10 -> PbReserveStruct
|
// 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) {
|
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 {
|
if pMsg.Head.GetFromUin() == c.Uin {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.dispatchTempMessage(&TempMessageEvent{
|
c.TempMessageEvent.dispatch(c, &TempMessageEvent{
|
||||||
Message: c.parseTempMessage(pMsg),
|
Message: c.parseTempMessage(pMsg),
|
||||||
Session: session,
|
Session: session,
|
||||||
})
|
})
|
||||||
@ -219,20 +219,20 @@ func troopAddMemberBroadcastDecoder(c *QQClient, pMsg *msg.Message, _ *network.I
|
|||||||
group := c.FindGroupByUin(pMsg.Head.GetFromUin())
|
group := c.FindGroupByUin(pMsg.Head.GetFromUin())
|
||||||
if pMsg.Head.GetAuthUin() == c.Uin {
|
if pMsg.Head.GetAuthUin() == c.Uin {
|
||||||
if group == nil && c.ReloadGroupList() == nil {
|
if group == nil && c.ReloadGroupList() == nil {
|
||||||
c.dispatchJoinGroupEvent(c.FindGroupByUin(pMsg.Head.GetFromUin()))
|
c.GroupJoinEvent.dispatch(c, c.FindGroupByUin(pMsg.Head.GetFromUin()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if group != nil && group.FindMember(pMsg.Head.GetAuthUin()) == nil {
|
if group != nil && group.FindMember(pMsg.Head.GetAuthUin()) == nil {
|
||||||
mem, err := c.GetMemberInfo(group.Code, pMsg.Head.GetAuthUin())
|
mem, err := c.GetMemberInfo(group.Code, pMsg.Head.GetAuthUin())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Debug("error to fetch new member info: %v", err)
|
c.debug("error to fetch new member info: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
group.Update(func(info *GroupInfo) {
|
group.Update(func(info *GroupInfo) {
|
||||||
info.Members = append(info.Members, mem)
|
info.Members = append(info.Members, mem)
|
||||||
info.sort()
|
info.sort()
|
||||||
})
|
})
|
||||||
c.dispatchNewMemberEvent(&MemberJoinGroupEvent{
|
c.GroupMemberJoinEvent.dispatch(c, &MemberJoinGroupEvent{
|
||||||
Group: group,
|
Group: group,
|
||||||
Member: mem,
|
Member: mem,
|
||||||
})
|
})
|
||||||
@ -255,7 +255,7 @@ func troopSystemMessageDecoder(c *QQClient, pMsg *msg.Message, info *network.Inc
|
|||||||
reader := binary.NewReader(pMsg.Body.MsgContent)
|
reader := binary.NewReader(pMsg.Body.MsgContent)
|
||||||
groupCode := uint32(reader.ReadInt32())
|
groupCode := uint32(reader.ReadInt32())
|
||||||
if info := c.FindGroup(int64(groupCode)); info != nil && pMsg.Head.GetGroupName() != "" && info.Name != pMsg.Head.GetGroupName() {
|
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()
|
info.Name = pMsg.Head.GetGroupName()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ func msgType0x211Decoder(c *QQClient, pMsg *msg.Message, info *network.IncomingP
|
|||||||
sub4 := msg.SubMsgType0X4Body{}
|
sub4 := msg.SubMsgType0X4Body{}
|
||||||
if err := proto.Unmarshal(pMsg.Body.MsgContent, &sub4); err != nil {
|
if err := proto.Unmarshal(pMsg.Body.MsgContent, &sub4); err != nil {
|
||||||
err = errors.Wrap(err, "unmarshal sub msg 0x4 error")
|
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
|
return
|
||||||
}
|
}
|
||||||
if sub4.NotOnlineFile != nil && sub4.NotOnlineFile.GetSubcmd() == 1 { // subcmd: 1 -> sendPacket, 2-> recv
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.dispatchOfflineFileEvent(&OfflineFileEvent{
|
c.OfflineFileEvent.dispatch(c, &OfflineFileEvent{
|
||||||
FileName: string(sub4.NotOnlineFile.FileName),
|
FileName: string(sub4.NotOnlineFile.FileName),
|
||||||
FileSize: sub4.NotOnlineFile.GetFileSize(),
|
FileSize: sub4.NotOnlineFile.GetFileSize(),
|
||||||
Sender: pMsg.Head.GetFromUin(),
|
Sender: pMsg.Head.GetFromUin(),
|
||||||
|
105
client/client.go
105
client/client.go
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
@ -13,17 +14,18 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/atomic"
|
"go.uber.org/atomic"
|
||||||
|
|
||||||
|
"github.com/RomiChan/syncx"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
"github.com/Mrs4s/MiraiGo/client/internal/auth"
|
"github.com/Mrs4s/MiraiGo/client/internal/auth"
|
||||||
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/internal/network"
|
"github.com/Mrs4s/MiraiGo/client/internal/network"
|
||||||
"github.com/Mrs4s/MiraiGo/client/internal/oicq"
|
"github.com/Mrs4s/MiraiGo/client/internal/oicq"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||||
|
"github.com/Mrs4s/MiraiGo/message"
|
||||||
"github.com/Mrs4s/MiraiGo/utils"
|
"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 {
|
type QQClient struct {
|
||||||
Uin int64
|
Uin int64
|
||||||
PasswordMd5 [16]byte
|
PasswordMd5 [16]byte
|
||||||
@ -48,17 +50,17 @@ type QQClient struct {
|
|||||||
// protocol public field
|
// protocol public field
|
||||||
SequenceId atomic.Int32
|
SequenceId atomic.Int32
|
||||||
SessionId []byte
|
SessionId []byte
|
||||||
RandomKey []byte
|
|
||||||
TCP *network.TCPListener // todo: combine other protocol state into one struct
|
TCP *network.TCPListener // todo: combine other protocol state into one struct
|
||||||
ConnectTime time.Time
|
ConnectTime time.Time
|
||||||
|
|
||||||
transport *network.Transport
|
transport *network.Transport
|
||||||
oicq *oicq.Codec
|
oicq *oicq.Codec
|
||||||
|
logger Logger
|
||||||
|
|
||||||
// internal state
|
// internal state
|
||||||
handlers HandlerMap
|
handlers syncx.Map[uint16, *handlerInfo]
|
||||||
waiters sync.Map
|
waiters syncx.Map[string, func(any, error)]
|
||||||
servers []*net.TCPAddr
|
servers []netip.AddrPort
|
||||||
currServerIndex int
|
currServerIndex int
|
||||||
retryTimes int
|
retryTimes int
|
||||||
version *auth.AppVersion
|
version *auth.AppVersion
|
||||||
@ -76,19 +78,47 @@ type QQClient struct {
|
|||||||
// otherSrvAddrs []string
|
// otherSrvAddrs []string
|
||||||
// fileStorageInfo *jce.FileStoragePushFSSvcList
|
// 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
|
// message state
|
||||||
msgSvcCache *utils.Cache
|
msgSvcCache *utils.Cache[unit]
|
||||||
lastC2CMsgTime int64
|
lastC2CMsgTime int64
|
||||||
transCache *utils.Cache
|
transCache *utils.Cache[unit]
|
||||||
groupSysMsgCache *GroupSystemMessages
|
groupSysMsgCache *GroupSystemMessages
|
||||||
msgBuilders sync.Map
|
msgBuilders syncx.Map[int32, *messageBuilder]
|
||||||
onlinePushCache *utils.Cache
|
onlinePushCache *utils.Cache[unit]
|
||||||
heartbeatEnabled bool
|
heartbeatEnabled bool
|
||||||
requestPacketRequestID atomic.Int32
|
requestPacketRequestID atomic.Int32
|
||||||
groupSeq atomic.Int32
|
groupSeq atomic.Int32
|
||||||
friendSeq atomic.Int32
|
friendSeq atomic.Int32
|
||||||
highwayApplyUpSeq atomic.Int32
|
highwayApplyUpSeq atomic.Int32
|
||||||
eventHandlers eventHandlers
|
|
||||||
|
|
||||||
groupListLock sync.Mutex
|
groupListLock sync.Mutex
|
||||||
}
|
}
|
||||||
@ -103,7 +133,7 @@ type QiDianAccountInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type handlerInfo struct {
|
type handlerInfo struct {
|
||||||
fun func(i interface{}, err error)
|
fun func(i any, err error)
|
||||||
dynamic bool
|
dynamic bool
|
||||||
params network.RequestParams
|
params network.RequestParams
|
||||||
}
|
}
|
||||||
@ -115,7 +145,7 @@ func (h *handlerInfo) getParams() network.RequestParams {
|
|||||||
return h.params
|
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.login": decodeLoginResponse,
|
||||||
"wtlogin.exchange_emp": decodeExchangeEmpResponse,
|
"wtlogin.exchange_emp": decodeExchangeEmpResponse,
|
||||||
"wtlogin.trans_emp": decodeTransEmpResponse,
|
"wtlogin.trans_emp": decodeTransEmpResponse,
|
||||||
@ -164,10 +194,9 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
|
|||||||
sig: &auth.SigInfo{
|
sig: &auth.SigInfo{
|
||||||
OutPacketSessionID: []byte{0x02, 0xB0, 0x5B, 0x8B},
|
OutPacketSessionID: []byte{0x02, 0xB0, 0x5B, 0x8B},
|
||||||
},
|
},
|
||||||
msgSvcCache: utils.NewCache(time.Second * 15),
|
msgSvcCache: utils.NewCache[unit](time.Second * 15),
|
||||||
transCache: utils.NewCache(time.Second * 15),
|
transCache: utils.NewCache[unit](time.Second * 15),
|
||||||
onlinePushCache: utils.NewCache(time.Second * 15),
|
onlinePushCache: utils.NewCache[unit](time.Second * 15),
|
||||||
servers: []*net.TCPAddr{},
|
|
||||||
alive: true,
|
alive: true,
|
||||||
highwaySession: new(highway.Session),
|
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
|
adds, err := net.LookupIP("msfwifi.3g.qq.com") // host servers
|
||||||
if err == nil && len(adds) > 0 {
|
if err == nil && len(adds) > 0 {
|
||||||
var hostAddrs []*net.TCPAddr
|
var hostAddrs []netip.AddrPort
|
||||||
for _, addr := range adds {
|
for _, addr := range adds {
|
||||||
hostAddrs = append(hostAddrs, &net.TCPAddr{
|
ip, ok := netip.AddrFromSlice(addr.To4())
|
||||||
IP: addr,
|
if ok {
|
||||||
Port: 8080,
|
hostAddrs = append(hostAddrs, netip.AddrPortFrom(ip, 8080))
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
cli.servers = append(hostAddrs, cli.servers...)
|
cli.servers = append(hostAddrs, cli.servers...)
|
||||||
}
|
}
|
||||||
if len(cli.servers) == 0 {
|
if len(cli.servers) == 0 {
|
||||||
cli.servers = []*net.TCPAddr{ // default servers
|
cli.servers = []netip.AddrPort{ // default servers
|
||||||
{IP: net.IP{42, 81, 172, 81}, Port: 80},
|
netip.AddrPortFrom(netip.AddrFrom4([4]byte{42, 81, 172, 81}), 80),
|
||||||
{IP: net.IP{114, 221, 148, 59}, Port: 14000},
|
netip.AddrPortFrom(netip.AddrFrom4([4]byte{114, 221, 148, 59}), 14000),
|
||||||
{IP: net.IP{42, 81, 172, 147}, Port: 443},
|
netip.AddrPortFrom(netip.AddrFrom4([4]byte{42, 81, 172, 147}), 443),
|
||||||
{IP: net.IP{125, 94, 60, 146}, Port: 80},
|
netip.AddrPortFrom(netip.AddrFrom4([4]byte{125, 94, 60, 146}), 80),
|
||||||
{IP: net.IP{114, 221, 144, 215}, Port: 80},
|
netip.AddrPortFrom(netip.AddrFrom4([4]byte{114, 221, 144, 215}), 80),
|
||||||
{IP: net.IP{42, 81, 172, 22}, Port: 80},
|
netip.AddrPortFrom(netip.AddrFrom4([4]byte{42, 81, 172, 22}), 80),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pings := make([]int64, len(cli.servers))
|
pings := make([]int64, len(cli.servers))
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(len(cli.servers))
|
wg.Add(len(cli.servers))
|
||||||
|
// println(len(cli.servers))
|
||||||
for i := range cli.servers {
|
for i := range cli.servers {
|
||||||
go func(index int) {
|
go func(index int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@ -239,7 +269,6 @@ func NewClientMd5(uin int64, passwordMd5 [16]byte) *QQClient {
|
|||||||
}
|
}
|
||||||
cli.TCP.PlannedDisconnect(cli.plannedDisconnect)
|
cli.TCP.PlannedDisconnect(cli.plannedDisconnect)
|
||||||
cli.TCP.UnexpectedDisconnect(cli.unexpectedDisconnect)
|
cli.TCP.UnexpectedDisconnect(cli.unexpectedDisconnect)
|
||||||
rand.Read(cli.RandomKey)
|
|
||||||
return cli
|
return cli
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,7 +420,7 @@ func (c *QQClient) SubmitSMS(code string) (*LoginResponse, error) {
|
|||||||
func (c *QQClient) RequestSMS() bool {
|
func (c *QQClient) RequestSMS() bool {
|
||||||
rsp, err := c.sendAndWait(c.buildSMSRequestPacket())
|
rsp, err := c.sendAndWait(c.buildSMSRequestPacket())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error("request sms error: %v", err)
|
c.error("request sms error: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return rsp.(LoginResponse).Error == SMSNeededError
|
return rsp.(LoginResponse).Error == SMSNeededError
|
||||||
@ -399,18 +428,18 @@ func (c *QQClient) RequestSMS() bool {
|
|||||||
|
|
||||||
func (c *QQClient) init(tokenLogin bool) error {
|
func (c *QQClient) init(tokenLogin bool) error {
|
||||||
if len(c.sig.G) == 0 {
|
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)
|
c.highwaySession.Uin = strconv.FormatInt(c.Uin, 10)
|
||||||
if err := c.registerClient(); err != nil {
|
if err := c.registerClient(); err != nil {
|
||||||
return errors.Wrap(err, "register error")
|
return errors.Wrap(err, "register error")
|
||||||
}
|
}
|
||||||
if tokenLogin {
|
if tokenLogin {
|
||||||
notify := make(chan struct{})
|
notify := make(chan struct{}, 2)
|
||||||
d := c.waitPacket("StatSvc.ReqMSFOffline", func(i interface{}, err error) {
|
d := c.waitPacket("StatSvc.ReqMSFOffline", func(i any, err error) {
|
||||||
notify <- struct{}{}
|
notify <- struct{}{}
|
||||||
})
|
})
|
||||||
d2 := c.waitPacket("MessageSvc.PushForceOffline", func(i interface{}, err error) {
|
d2 := c.waitPacket("MessageSvc.PushForceOffline", func(i any, err error) {
|
||||||
notify <- struct{}{}
|
notify <- struct{}{}
|
||||||
})
|
})
|
||||||
select {
|
select {
|
||||||
@ -651,7 +680,7 @@ func (c *QQClient) FindGroup(code int64) *GroupInfo {
|
|||||||
return nil
|
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 {
|
if accept {
|
||||||
block = false
|
block = false
|
||||||
reason = ""
|
reason = ""
|
||||||
@ -680,7 +709,7 @@ func (c *QQClient) SolveFriendRequest(req *NewFriendRequest, accept bool) {
|
|||||||
|
|
||||||
func (c *QQClient) getSKey() string {
|
func (c *QQClient) getSKey() string {
|
||||||
if c.sig.SKeyExpiredTime < time.Now().Unix() && len(c.sig.G) > 0 {
|
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())
|
_, _ = c.sendAndWait(c.buildRequestTgtgtNopicsigPacket())
|
||||||
}
|
}
|
||||||
return string(c.sig.SKey)
|
return string(c.sig.SKey)
|
||||||
@ -761,7 +790,7 @@ func (c *QQClient) UpdateProfile(profile ProfileDetailUpdate) {
|
|||||||
_, _ = c.sendAndWait(c.buildUpdateProfileDetailPacket(profile))
|
_, _ = 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...)
|
c.servers = append(servers, c.servers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"net/netip"
|
||||||
"net"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -31,7 +30,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// wtlogin.login
|
// 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 := binary.NewReader(payload)
|
||||||
reader.ReadUInt16() // sub command
|
reader.ReadUInt16() // sub command
|
||||||
t := reader.ReadByte()
|
t := reader.ReadByte()
|
||||||
@ -47,9 +46,9 @@ func decodeLoginResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []b
|
|||||||
// if t150, ok := m[0x150]; ok {
|
// if t150, ok := m[0x150]; ok {
|
||||||
// c.t150 = t150
|
// c.t150 = t150
|
||||||
// }
|
// }
|
||||||
if t161, ok := m[0x161]; ok {
|
// if t161, ok := m[0x161]; ok {
|
||||||
c.decodeT161(t161)
|
// c.decodeT161(t161)
|
||||||
}
|
// }
|
||||||
if m.Exists(0x403) {
|
if m.Exists(0x403) {
|
||||||
c.sig.RandSeed = m[0x403]
|
c.sig.RandSeed = m[0x403]
|
||||||
}
|
}
|
||||||
@ -171,15 +170,15 @@ func decodeLoginResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []b
|
|||||||
ErrorMessage: t146r.ReadStringShort(),
|
ErrorMessage: t146r.ReadStringShort(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
c.Debug("unknown login response: %v", t)
|
c.debug("unknown login response: %v", t)
|
||||||
for k, v := range m {
|
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) // ?
|
return nil, errors.Errorf("unknown login response: %v", t) // ?
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatSvc.register
|
// 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 := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -188,7 +187,7 @@ func decodeClientRegisterResponse(c *QQClient, _ *network.IncomingPacketInfo, pa
|
|||||||
svcRsp.ReadFrom(jce.NewJceReader(data.Map["SvcRespRegister"]["QQService.SvcRespRegister"][1:]))
|
svcRsp.ReadFrom(jce.NewJceReader(data.Map["SvcRespRegister"]["QQService.SvcRespRegister"][1:]))
|
||||||
if svcRsp.Result != "" || svcRsp.ReplyCode != 0 {
|
if svcRsp.Result != "" || svcRsp.ReplyCode != 0 {
|
||||||
if svcRsp.Result != "" {
|
if svcRsp.Result != "" {
|
||||||
c.Error("reg error: %v", svcRsp.Result)
|
c.error("reg error: %v", svcRsp.Result)
|
||||||
}
|
}
|
||||||
return nil, errors.New("reg failed")
|
return nil, errors.New("reg failed")
|
||||||
}
|
}
|
||||||
@ -196,7 +195,7 @@ func decodeClientRegisterResponse(c *QQClient, _ *network.IncomingPacketInfo, pa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wtlogin.exchange_emp
|
// 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)
|
reader := binary.NewReader(payload)
|
||||||
cmd := reader.ReadUInt16()
|
cmd := reader.ReadUInt16()
|
||||||
t := reader.ReadByte()
|
t := reader.ReadByte()
|
||||||
@ -216,7 +215,7 @@ func decodeExchangeEmpResponse(c *QQClient, _ *network.IncomingPacketInfo, paylo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wtlogin.trans_emp
|
// 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 {
|
if len(payload) < 48 {
|
||||||
return nil, errors.New("missing payload length")
|
return nil, errors.New("missing payload length")
|
||||||
}
|
}
|
||||||
@ -299,7 +298,7 @@ func decodeTransEmpResponse(c *QQClient, _ *network.IncomingPacketInfo, payload
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ConfigPushSvc.PushReq
|
// 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 := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -313,16 +312,16 @@ func decodePushReqPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []b
|
|||||||
ssoPkt := jce.NewJceReader(jceBuf)
|
ssoPkt := jce.NewJceReader(jceBuf)
|
||||||
servers := ssoPkt.ReadSsoServerInfos(1)
|
servers := ssoPkt.ReadSsoServerInfos(1)
|
||||||
if len(servers) > 0 {
|
if len(servers) > 0 {
|
||||||
var adds []*net.TCPAddr
|
var adds []netip.AddrPort
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
if strings.Contains(s.Server, "com") {
|
if strings.Contains(s.Server, "com") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.Debug("got new server addr: %v location: %v", s.Server, s.Location)
|
c.debug("got new server addr: %v location: %v", s.Server, s.Location)
|
||||||
adds = append(adds, &net.TCPAddr{
|
addr, err := netip.ParseAddr(s.Server)
|
||||||
IP: net.ParseIP(s.Server),
|
if err == nil {
|
||||||
Port: int(s.Port),
|
adds = append(adds, netip.AddrPortFrom(addr, uint16(s.Port)))
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
f := true
|
f := true
|
||||||
for _, e := range c.eventHandlers.serverUpdatedHandlers {
|
for _, e := range c.eventHandlers.serverUpdatedHandlers {
|
||||||
@ -341,7 +340,7 @@ func decodePushReqPacket(c *QQClient, _ *network.IncomingPacketInfo, payload []b
|
|||||||
fmtPkt := jce.NewJceReader(jceBuf)
|
fmtPkt := jce.NewJceReader(jceBuf)
|
||||||
list := &jce.FileStoragePushFSSvcList{}
|
list := &jce.FileStoragePushFSSvcList{}
|
||||||
list.ReadFrom(fmtPkt)
|
list.ReadFrom(fmtPkt)
|
||||||
c.Debug("got file storage svc push.")
|
c.debug("got file storage svc push.")
|
||||||
// c.fileStorageInfo = list
|
// c.fileStorageInfo = list
|
||||||
rsp := cmd0x6ff.C501RspBody{}
|
rsp := cmd0x6ff.C501RspBody{}
|
||||||
if err := proto.Unmarshal(list.BigDataChannel.PbBuf, &rsp); err == nil && rsp.RspBody != nil {
|
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
|
// 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{}
|
rsp := msg.GetMessageResponse{}
|
||||||
err := proto.Unmarshal(payload, &rsp)
|
err := proto.Unmarshal(payload, &rsp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -383,7 +382,7 @@ func decodeMessageSvcPacket(c *QQClient, info *network.IncomingPacketInfo, paylo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MessageSvc.PushNotify
|
// 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 := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload[4:]))
|
request.ReadFrom(jce.NewJceReader(payload[4:]))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -410,7 +409,7 @@ func decodeSvcNotify(c *QQClient, _ *network.IncomingPacketInfo, payload []byte)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SummaryCard.ReqSummaryCard
|
// SummaryCard.ReqSummaryCard
|
||||||
func decodeSummaryCardResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeSummaryCardResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
request := &jce.RequestPacket{}
|
request := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -458,7 +457,7 @@ func decodeSummaryCardResponse(_ *QQClient, _ *network.IncomingPacketInfo, paylo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// friendlist.getFriendGroupList
|
// friendlist.getFriendGroupList
|
||||||
func decodeFriendGroupListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeFriendGroupListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
request := &jce.RequestPacket{}
|
request := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion3{}
|
data := &jce.RequestDataVersion3{}
|
||||||
@ -483,7 +482,7 @@ func decodeFriendGroupListResponse(_ *QQClient, _ *network.IncomingPacketInfo, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// friendlist.delFriend
|
// friendlist.delFriend
|
||||||
func decodeFriendDeleteResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeFriendDeleteResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
request := &jce.RequestPacket{}
|
request := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion3{}
|
data := &jce.RequestDataVersion3{}
|
||||||
@ -496,7 +495,7 @@ func decodeFriendDeleteResponse(_ *QQClient, _ *network.IncomingPacketInfo, payl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// friendlist.GetTroopListReqV2
|
// 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 := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion3{}
|
data := &jce.RequestDataVersion3{}
|
||||||
@ -528,7 +527,7 @@ func decodeGroupListResponse(c *QQClient, _ *network.IncomingPacketInfo, payload
|
|||||||
}
|
}
|
||||||
|
|
||||||
// friendlist.GetTroopMemberListReq
|
// friendlist.GetTroopMemberListReq
|
||||||
func decodeGroupMemberListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeGroupMemberListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
request := &jce.RequestPacket{}
|
request := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion3{}
|
data := &jce.RequestDataVersion3{}
|
||||||
@ -564,7 +563,7 @@ func decodeGroupMemberListResponse(_ *QQClient, _ *network.IncomingPacketInfo, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// group_member_card.get_group_member_card_info
|
// 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{}
|
rsp := pb.GroupMemberRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
@ -597,7 +596,7 @@ func decodeGroupMemberInfoResponse(c *QQClient, _ *network.IncomingPacketInfo, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LongConn.OffPicUp
|
// LongConn.OffPicUp
|
||||||
func decodeOffPicUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeOffPicUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
rsp := cmd0x352.RspBody{}
|
rsp := cmd0x352.RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
@ -635,7 +634,7 @@ func decodeOffPicUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OnlinePush.PbPushTransMsg
|
// 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{}
|
info := msg.TransMsgInfo{}
|
||||||
err := proto.Unmarshal(payload, &info)
|
err := proto.Unmarshal(payload, &info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -646,7 +645,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay
|
|||||||
if _, ok := c.transCache.Get(idStr); ok {
|
if _, ok := c.transCache.Get(idStr); ok {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
c.transCache.Add(idStr, "", time.Second*15)
|
c.transCache.Add(idStr, unit{}, time.Second*15)
|
||||||
if info.GetMsgType() == 34 {
|
if info.GetMsgType() == 34 {
|
||||||
data.ReadInt32()
|
data.ReadInt32()
|
||||||
data.ReadByte()
|
data.ReadByte()
|
||||||
@ -659,10 +658,10 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay
|
|||||||
switch typ {
|
switch typ {
|
||||||
case 0x02:
|
case 0x02:
|
||||||
if target == c.Uin {
|
if target == c.Uin {
|
||||||
c.dispatchLeaveGroupEvent(&GroupLeaveEvent{Group: g})
|
c.GroupLeaveEvent.dispatch(c, &GroupLeaveEvent{Group: g})
|
||||||
} else if m := g.FindMember(target); m != nil {
|
} else if m := g.FindMember(target); m != nil {
|
||||||
g.removeMember(target)
|
g.removeMember(target)
|
||||||
c.dispatchMemberLeaveEvent(&MemberLeaveGroupEvent{
|
c.GroupMemberLeaveEvent.dispatch(c, &MemberLeaveGroupEvent{
|
||||||
Group: g,
|
Group: g,
|
||||||
Member: m,
|
Member: m,
|
||||||
})
|
})
|
||||||
@ -672,13 +671,13 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if target == c.Uin {
|
if target == c.Uin {
|
||||||
c.dispatchLeaveGroupEvent(&GroupLeaveEvent{
|
c.GroupLeaveEvent.dispatch(c, &GroupLeaveEvent{
|
||||||
Group: g,
|
Group: g,
|
||||||
Operator: g.FindMember(operator),
|
Operator: g.FindMember(operator),
|
||||||
})
|
})
|
||||||
} else if m := g.FindMember(target); m != nil {
|
} else if m := g.FindMember(target); m != nil {
|
||||||
g.removeMember(target)
|
g.removeMember(target)
|
||||||
c.dispatchMemberLeaveEvent(&MemberLeaveGroupEvent{
|
c.GroupMemberLeaveEvent.dispatch(c, &MemberLeaveGroupEvent{
|
||||||
Group: g,
|
Group: g,
|
||||||
Member: m,
|
Member: m,
|
||||||
Operator: g.FindMember(operator),
|
Operator: g.FindMember(operator),
|
||||||
@ -687,7 +686,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay
|
|||||||
case 0x82:
|
case 0x82:
|
||||||
if m := g.FindMember(target); m != nil {
|
if m := g.FindMember(target); m != nil {
|
||||||
g.removeMember(target)
|
g.removeMember(target)
|
||||||
c.dispatchMemberLeaveEvent(&MemberLeaveGroupEvent{
|
c.GroupMemberLeaveEvent.dispatch(c, &MemberLeaveGroupEvent{
|
||||||
Group: g,
|
Group: g,
|
||||||
Member: m,
|
Member: m,
|
||||||
})
|
})
|
||||||
@ -695,7 +694,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay
|
|||||||
case 0x83:
|
case 0x83:
|
||||||
if m := g.FindMember(target); m != nil {
|
if m := g.FindMember(target); m != nil {
|
||||||
g.removeMember(target)
|
g.removeMember(target)
|
||||||
c.dispatchMemberLeaveEvent(&MemberLeaveGroupEvent{
|
c.GroupMemberLeaveEvent.dispatch(c, &MemberLeaveGroupEvent{
|
||||||
Group: g,
|
Group: g,
|
||||||
Member: m,
|
Member: m,
|
||||||
Operator: g.FindMember(operator),
|
Operator: g.FindMember(operator),
|
||||||
@ -724,7 +723,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay
|
|||||||
if mem.Permission != newPermission {
|
if mem.Permission != newPermission {
|
||||||
old := mem.Permission
|
old := mem.Permission
|
||||||
mem.Permission = newPermission
|
mem.Permission = newPermission
|
||||||
c.dispatchPermissionChanged(&MemberPermissionChangedEvent{
|
c.GroupMemberPermissionChangedEvent.dispatch(c, &MemberPermissionChangedEvent{
|
||||||
Group: g,
|
Group: g,
|
||||||
Member: mem,
|
Member: mem,
|
||||||
OldPermission: old,
|
OldPermission: old,
|
||||||
@ -738,7 +737,7 @@ func decodeOnlinePushTransPacket(c *QQClient, _ *network.IncomingPacketInfo, pay
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProfileService.Pb.ReqSystemMsgNew.Friend
|
// 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{}
|
rsp := structmsg.RspSystemMsgNew{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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]
|
st := rsp.Friendmsgs[0]
|
||||||
if st.Msg != nil {
|
if st.Msg != nil {
|
||||||
c.dispatchNewFriendRequest(&NewFriendRequest{
|
c.NewFriendRequestEvent.dispatch(c, &NewFriendRequest{
|
||||||
RequestId: st.MsgSeq,
|
RequestId: st.MsgSeq,
|
||||||
Message: st.Msg.MsgAdditional,
|
Message: st.Msg.MsgAdditional,
|
||||||
RequesterUin: st.ReqUin,
|
RequesterUin: st.ReqUin,
|
||||||
@ -760,7 +759,7 @@ func decodeSystemMsgFriendPacket(c *QQClient, _ *network.IncomingPacketInfo, pay
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MessageSvc.PushForceOffline
|
// 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 := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -768,28 +767,25 @@ func decodeForceOfflinePacket(c *QQClient, _ *network.IncomingPacketInfo, payloa
|
|||||||
r := jce.NewJceReader(data.Map["req_PushForceOffline"]["PushNotifyPack.RequestPushForceOffline"][1:])
|
r := jce.NewJceReader(data.Map["req_PushForceOffline"]["PushNotifyPack.RequestPushForceOffline"][1:])
|
||||||
tips := r.ReadString(2)
|
tips := r.ReadString(2)
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
go c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: tips})
|
go c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: tips})
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatSvc.ReqMSFOffline
|
// StatSvc.ReqMSFOffline
|
||||||
func decodeMSFOfflinePacket(c *QQClient, _ *network.IncomingPacketInfo, _ []byte) (interface{}, error) {
|
func decodeMSFOfflinePacket(c *QQClient, _ *network.IncomingPacketInfo, _ []byte) (any, error) {
|
||||||
// c.lastLostMsg = "服务器端强制下线."
|
// c.lastLostMsg = "服务器端强制下线."
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
// 这个decoder不能消耗太多时间, event另起线程处理
|
// 这个decoder不能消耗太多时间, event另起线程处理
|
||||||
go c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "服务端强制下线."})
|
go c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "服务端强制下线."})
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OidbSvc.0xd79
|
// OidbSvc.0xd79
|
||||||
func decodeWordSegmentation(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeWordSegmentation(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := &oidb.D79RspBody{}
|
rsp := &oidb.D79RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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 rsp.Content != nil {
|
if rsp.Content != nil {
|
||||||
return rsp.Content.SliceContent, nil
|
return rsp.Content.SliceContent, nil
|
||||||
@ -797,7 +793,7 @@ func decodeWordSegmentation(_ *QQClient, _ *network.IncomingPacketInfo, payload
|
|||||||
return nil, errors.New("no word received")
|
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))
|
_, err := c.sendAndWait(c.buildRequestChangeSigPacket(3554528))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "resign client error")
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -175,12 +175,6 @@ type (
|
|||||||
client *QQClient
|
client *QQClient
|
||||||
}
|
}
|
||||||
|
|
||||||
LogEvent struct {
|
|
||||||
Type string
|
|
||||||
Message string
|
|
||||||
Dump []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
ServerUpdatedEvent struct {
|
ServerUpdatedEvent struct {
|
||||||
Servers []jce.SsoServerInfo
|
Servers []jce.SsoServerInfo
|
||||||
}
|
}
|
||||||
@ -296,6 +290,9 @@ type (
|
|||||||
SigSession []byte
|
SigSession []byte
|
||||||
SessionKey []byte
|
SessionKey []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unit is an alias for struct{}, like `()` in rust
|
||||||
|
unit = struct{}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
477
client/events.go
477
client/events.go
@ -8,12 +8,40 @@ import (
|
|||||||
"github.com/Mrs4s/MiraiGo/message"
|
"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 {
|
type eventHandlers struct {
|
||||||
privateMessageHandlers []func(*QQClient, *message.PrivateMessage)
|
// todo: move to event handle
|
||||||
tempMessageHandlers []func(*QQClient, *TempMessageEvent)
|
|
||||||
groupMessageHandlers []func(*QQClient, *message.GroupMessage)
|
|
||||||
selfPrivateMessageHandlers []func(*QQClient, *message.PrivateMessage)
|
|
||||||
selfGroupMessageHandlers []func(*QQClient, *message.GroupMessage)
|
|
||||||
guildChannelMessageHandlers []func(*QQClient, *message.GuildChannelMessage)
|
guildChannelMessageHandlers []func(*QQClient, *message.GuildChannelMessage)
|
||||||
guildMessageReactionsUpdatedHandlers []func(*QQClient, *GuildMessageReactionsUpdatedEvent)
|
guildMessageReactionsUpdatedHandlers []func(*QQClient, *GuildMessageReactionsUpdatedEvent)
|
||||||
guildMessageRecalledHandlers []func(*QQClient, *GuildMessageRecalledEvent)
|
guildMessageRecalledHandlers []func(*QQClient, *GuildMessageRecalledEvent)
|
||||||
@ -21,60 +49,11 @@ type eventHandlers struct {
|
|||||||
guildChannelCreatedHandlers []func(*QQClient, *GuildChannelOperationEvent)
|
guildChannelCreatedHandlers []func(*QQClient, *GuildChannelOperationEvent)
|
||||||
guildChannelDestroyedHandlers []func(*QQClient, *GuildChannelOperationEvent)
|
guildChannelDestroyedHandlers []func(*QQClient, *GuildChannelOperationEvent)
|
||||||
memberJoinedGuildHandlers []func(*QQClient, *MemberJoinGuildEvent)
|
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
|
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
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *GuildService) OnGuildChannelMessage(f func(*QQClient, *message.GuildChannelMessage)) {
|
func (s *GuildService) OnGuildChannelMessage(f func(*QQClient, *message.GuildChannelMessage)) {
|
||||||
s.c.eventHandlers.guildChannelMessageHandlers = append(s.c.eventHandlers.guildChannelMessageHandlers, f)
|
s.c.eventHandlers.guildChannelMessageHandlers = append(s.c.eventHandlers.guildChannelMessageHandlers, f)
|
||||||
}
|
}
|
||||||
@ -103,99 +82,10 @@ func (s *GuildService) OnMemberJoinedGuild(f func(*QQClient, *MemberJoinGuildEve
|
|||||||
s.c.eventHandlers.memberJoinedGuildHandlers = append(s.c.eventHandlers.memberJoinedGuildHandlers, f)
|
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) {
|
func (c *QQClient) OnServerUpdated(f func(*QQClient, *ServerUpdatedEvent) bool) {
|
||||||
c.eventHandlers.serverUpdatedHandlers = append(c.eventHandlers.serverUpdatedHandlers, f)
|
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 {
|
func NewUinFilterPrivate(uin int64) func(*message.PrivateMessage) bool {
|
||||||
return func(msg *message.PrivateMessage) bool {
|
return func(msg *message.PrivateMessage) bool {
|
||||||
return msg.Sender.Uin == uin
|
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])
|
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) {
|
func (c *QQClient) dispatchGuildChannelMessage(msg *message.GuildChannelMessage) {
|
||||||
if msg == nil {
|
if msg == nil {
|
||||||
return
|
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) {
|
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)
|
go f.(func(*QQClient, *groupMessageReceiptEvent))(c, e)
|
||||||
return true
|
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()) {
|
func cover(f func()) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if pan := recover(); pan != nil {
|
if pan := recover(); pan != nil {
|
||||||
|
@ -41,7 +41,7 @@ func (c *QQClient) buildFaceroamRequestPacket() (uint16, []byte) {
|
|||||||
return c.uniPacket("Faceroam.OpReq", payload)
|
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{}
|
rsp := faceroam.FaceroamRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -114,7 +115,7 @@ func GenIMEI() string {
|
|||||||
return final.String()
|
return final.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSSOAddress() ([]*net.TCPAddr, error) {
|
func getSSOAddress() ([]netip.AddrPort, error) {
|
||||||
protocol := SystemDeviceInfo.Protocol.Version()
|
protocol := SystemDeviceInfo.Protocol.Version()
|
||||||
key, _ := hex.DecodeString("F0441F5FF42DA58FDCF7949ABA62D411")
|
key, _ := hex.DecodeString("F0441F5FF42DA58FDCF7949ABA62D411")
|
||||||
payload := jce.NewJceWriter(). // see ServerConfig.d
|
payload := jce.NewJceWriter(). // see ServerConfig.d
|
||||||
@ -122,7 +123,7 @@ func getSSOAddress() ([]*net.TCPAddr, error) {
|
|||||||
WriteString("00000", 4).WriteInt32(100, 5).
|
WriteString("00000", 4).WriteInt32(100, 5).
|
||||||
WriteInt32(int32(protocol.AppId), 6).WriteString(SystemDeviceInfo.IMEI, 7).
|
WriteInt32(int32(protocol.AppId), 6).WriteString(SystemDeviceInfo.IMEI, 7).
|
||||||
WriteInt64(0, 8).WriteInt64(0, 9).WriteInt64(0, 10).
|
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{
|
buf := &jce.RequestDataVersion3{
|
||||||
Map: map[string][]byte{"HttpServerListReq": packUniRequestData(payload)},
|
Map: map[string][]byte{"HttpServerListReq": packUniRequestData(payload)},
|
||||||
}
|
}
|
||||||
@ -140,7 +141,7 @@ func getSSOAddress() ([]*net.TCPAddr, error) {
|
|||||||
tea := binary.NewTeaCipher(key)
|
tea := binary.NewTeaCipher(key)
|
||||||
encpkt := tea.Encrypt(b)
|
encpkt := tea.Encrypt(b)
|
||||||
cl()
|
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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "unable to fetch server list")
|
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))
|
data.ReadFrom(jce.NewJceReader(rspPkt.SBuffer))
|
||||||
reader := jce.NewJceReader(data.Map["HttpServerListRes"][1:])
|
reader := jce.NewJceReader(data.Map["HttpServerListRes"][1:])
|
||||||
servers := reader.ReadSsoServerInfos(2)
|
servers := reader.ReadSsoServerInfos(2)
|
||||||
adds := make([]*net.TCPAddr, 0, len(servers))
|
adds := make([]netip.AddrPort, 0, len(servers))
|
||||||
for _, s := range servers {
|
for _, s := range servers {
|
||||||
if strings.Contains(s.Server, "com") {
|
if strings.Contains(s.Server, "com") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
adds = append(adds, &net.TCPAddr{
|
ip, ok := netip.AddrFromSlice(net.ParseIP(s.Server))
|
||||||
IP: net.ParseIP(s.Server),
|
if ok {
|
||||||
Port: int(s.Port),
|
adds = append(adds, netip.AddrPortFrom(ip, uint16(s.Port)))
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
return adds, nil
|
return adds, nil
|
||||||
}
|
}
|
||||||
@ -244,14 +245,15 @@ func (c *QQClient) parseTempMessage(msg *msg.Message) *message.TempMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *QQClient) messageBuilder(seq int32) *messageBuilder {
|
func (c *QQClient) messageBuilder(seq int32) *messageBuilder {
|
||||||
builder := &messageBuilder{}
|
actual, ok := c.msgBuilders.Load(seq)
|
||||||
actual, ok := c.msgBuilders.LoadOrStore(seq, builder)
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
builder := &messageBuilder{}
|
||||||
|
actual, _ = c.msgBuilders.LoadOrStore(seq, builder)
|
||||||
time.AfterFunc(time.Minute, func() {
|
time.AfterFunc(time.Minute, func() {
|
||||||
c.msgBuilders.Delete(seq) // delete avoid memory leak
|
c.msgBuilders.Delete(seq) // delete avoid memory leak
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return actual.(*messageBuilder)
|
return actual
|
||||||
}
|
}
|
||||||
|
|
||||||
type messageBuilder struct {
|
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 {
|
func genForwardTemplate(resID, preview, summary string, ts int64, items []*msg.PbMultiMsgItem) *message.ForwardElement {
|
||||||
template := forwardDisplay(resID, strconv.FormatInt(ts, 10), preview, summary)
|
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{
|
return &message.ForwardElement{
|
||||||
FileName: strconv.FormatInt(ts, 10),
|
FileName: strconv.FormatInt(ts, 10),
|
||||||
Content: template,
|
Content: template,
|
||||||
@ -356,59 +353,16 @@ func (c *QQClient) packOIDBPackageProto(cmd, serviceType int32, msg proto.Messag
|
|||||||
return c.packOIDBPackage(cmd, serviceType, b)
|
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)
|
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")
|
return errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
}
|
}
|
||||||
if pkg.Result != 0 {
|
if pkg.Result != 0 {
|
||||||
return errors.Errorf("oidb result unsuccessful: %v msg: %v", pkg.Result, pkg.ErrorMsg)
|
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 errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
}
|
}
|
||||||
return nil
|
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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -71,7 +71,7 @@ func init() {
|
|||||||
func (c *QQClient) GetGroupFileSystem(groupCode int64) (fs *GroupFileSystem, err error) {
|
func (c *QQClient) GetGroupFileSystem(groupCode int64) (fs *GroupFileSystem, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if pan := recover(); pan != nil {
|
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")
|
err = errors.New("fs error")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -104,7 +104,7 @@ func (c *QQClient) GetGroupFileUrl(groupCode int64, fileId string, busId int32)
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
url := i.(string)
|
url := i.(string)
|
||||||
url += "?fname=" + hex.EncodeToString([]byte(fileId))
|
url += fmt.Sprintf("?fname=%x", fileId)
|
||||||
return url
|
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) {
|
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,
|
GroupCode: &groupCode,
|
||||||
AppId: proto.Int32(3),
|
AppId: proto.Int32(3),
|
||||||
BusId: proto.Int32(102),
|
BusId: proto.Int32(102),
|
||||||
@ -288,14 +288,8 @@ func (c *QQClient) buildGroupFileUploadReqPacket(parentFolderID, fileName string
|
|||||||
Sha: sha1,
|
Sha: sha1,
|
||||||
Md5: md5,
|
Md5: md5,
|
||||||
SupportMultiUpload: proto.Bool(true),
|
SupportMultiUpload: proto.Bool(true),
|
||||||
}})
|
}}
|
||||||
req := &oidb.OIDBSSOPkg{
|
payload := c.packOIDBPackageProto(1750, 0, body)
|
||||||
Command: 1750,
|
|
||||||
ServiceType: 0,
|
|
||||||
Bodybuffer: b,
|
|
||||||
ClientVersion: "android 8.4.8",
|
|
||||||
}
|
|
||||||
payload, _ := proto.Marshal(req)
|
|
||||||
return c.uniPacket("OidbSvc.0x6d6_0", payload)
|
return c.uniPacket("OidbSvc.0x6d6_0", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,31 +322,19 @@ func (c *QQClient) buildGroupFileListRequestPacket(groupCode int64, folderID str
|
|||||||
StartIndex: &startIndex,
|
StartIndex: &startIndex,
|
||||||
Context: EmptyBytes,
|
Context: EmptyBytes,
|
||||||
}}
|
}}
|
||||||
b, _ := proto.Marshal(body)
|
payload := c.packOIDBPackageProto(1752, 1, body)
|
||||||
req := &oidb.OIDBSSOPkg{
|
|
||||||
Command: 1752,
|
|
||||||
ServiceType: 1,
|
|
||||||
Bodybuffer: b,
|
|
||||||
ClientVersion: "android 8.4.8",
|
|
||||||
}
|
|
||||||
payload, _ := proto.Marshal(req)
|
|
||||||
return c.uniPacket("OidbSvc.0x6d8_1", payload)
|
return c.uniPacket("OidbSvc.0x6d8_1", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *QQClient) buildGroupFileCountRequestPacket(groupCode int64) (uint16, []byte) {
|
func (c *QQClient) buildGroupFileCountRequestPacket(groupCode int64) (uint16, []byte) {
|
||||||
body := &oidb.D6D8ReqBody{GroupFileCountReq: &oidb.GetFileCountReqBody{
|
body := &oidb.D6D8ReqBody{
|
||||||
|
GroupFileCountReq: &oidb.GetFileCountReqBody{
|
||||||
GroupCode: proto.Uint64(uint64(groupCode)),
|
GroupCode: proto.Uint64(uint64(groupCode)),
|
||||||
AppId: proto.Uint32(3),
|
AppId: proto.Uint32(3),
|
||||||
BusId: proto.Uint32(0),
|
BusId: proto.Uint32(0),
|
||||||
}}
|
},
|
||||||
b, _ := proto.Marshal(body)
|
|
||||||
req := &oidb.OIDBSSOPkg{
|
|
||||||
Command: 1752,
|
|
||||||
ServiceType: 2,
|
|
||||||
Bodybuffer: b,
|
|
||||||
ClientVersion: "android 8.4.8",
|
|
||||||
}
|
}
|
||||||
payload, _ := proto.Marshal(req)
|
payload := c.packOIDBPackageProto(1752, 2, body)
|
||||||
return c.uniPacket("OidbSvc.0x6d8_1", payload)
|
return c.uniPacket("OidbSvc.0x6d8_1", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,14 +343,7 @@ func (c *QQClient) buildGroupFileSpaceRequestPacket(groupCode int64) (uint16, []
|
|||||||
GroupCode: proto.Uint64(uint64(groupCode)),
|
GroupCode: proto.Uint64(uint64(groupCode)),
|
||||||
AppId: proto.Uint32(3),
|
AppId: proto.Uint32(3),
|
||||||
}}
|
}}
|
||||||
b, _ := proto.Marshal(body)
|
payload := c.packOIDBPackageProto(1752, 3, body)
|
||||||
req := &oidb.OIDBSSOPkg{
|
|
||||||
Command: 1752,
|
|
||||||
ServiceType: 3,
|
|
||||||
Bodybuffer: b,
|
|
||||||
ClientVersion: "android 8.4.8",
|
|
||||||
}
|
|
||||||
payload, _ := proto.Marshal(req)
|
|
||||||
return c.uniPacket("OidbSvc.0x6d8_1", payload)
|
return c.uniPacket("OidbSvc.0x6d8_1", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -411,13 +386,7 @@ func (c *QQClient) buildGroupFileDownloadReqPacket(groupCode int64, fileId strin
|
|||||||
FileId: &fileId,
|
FileId: &fileId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
b, _ := proto.Marshal(body)
|
payload := c.packOIDBPackageProto(1750, 2, body)
|
||||||
req := &oidb.OIDBSSOPkg{
|
|
||||||
Command: 1750,
|
|
||||||
ServiceType: 2,
|
|
||||||
Bodybuffer: b,
|
|
||||||
}
|
|
||||||
payload, _ := proto.Marshal(req)
|
|
||||||
return c.uniPacket("OidbSvc.0x6d6_2", payload)
|
return c.uniPacket("OidbSvc.0x6d6_2", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,38 +398,25 @@ func (c *QQClient) buildGroupFileDeleteReqPacket(groupCode int64, parentFolderId
|
|||||||
ParentFolderId: &parentFolderId,
|
ParentFolderId: &parentFolderId,
|
||||||
FileId: &fileId,
|
FileId: &fileId,
|
||||||
}}
|
}}
|
||||||
b, _ := proto.Marshal(body)
|
payload := c.packOIDBPackageProto(1750, 3, body)
|
||||||
req := &oidb.OIDBSSOPkg{
|
|
||||||
Command: 1750,
|
|
||||||
ServiceType: 3,
|
|
||||||
Bodybuffer: b,
|
|
||||||
ClientVersion: "android 8.4.8",
|
|
||||||
}
|
|
||||||
payload, _ := proto.Marshal(req)
|
|
||||||
return c.uniPacket("OidbSvc.0x6d6_3", payload)
|
return c.uniPacket("OidbSvc.0x6d6_3", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeOIDB6d81Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeOIDB6d81Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.D6D8RspBody{}
|
rsp := oidb.D6D8RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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")
|
|
||||||
}
|
}
|
||||||
return &rsp, nil
|
return &rsp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OidbSvc.0x6d6_2
|
// OidbSvc.0x6d6_2
|
||||||
func decodeOIDB6d62Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeOIDB6d62Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.D6D6RspBody{}
|
rsp := oidb.D6D6RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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 rsp.DownloadFileRsp.DownloadUrl == nil {
|
if rsp.DownloadFileRsp.DownloadUrl == nil {
|
||||||
return nil, errors.New(rsp.DownloadFileRsp.GetClientWording())
|
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
|
return fmt.Sprintf("http://%s/ftn_handler/%s/", ip, url), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeOIDB6d63Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeOIDB6d63Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.D6D6RspBody{}
|
rsp := oidb.D6D6RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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 rsp.DeleteFileRsp == nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
}
|
||||||
return rsp.DeleteFileRsp.GetClientWording(), nil
|
return rsp.DeleteFileRsp.GetClientWording(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeOIDB6d60Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeOIDB6d60Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.D6D6RspBody{}
|
rsp := oidb.D6D6RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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")
|
|
||||||
}
|
}
|
||||||
return rsp.UploadFileRsp, nil
|
return rsp.UploadFileRsp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeOIDB6d7Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeOIDB6d7Response(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.D6D7RspBody{}
|
rsp := oidb.D6D7RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := proto.Unmarshal(pkg.Bodybuffer, &rsp); err != nil {
|
if retCode := rsp.CreateFolderRsp.GetRetCode(); retCode != 0 {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Errorf("create folder error: %v", retCode)
|
||||||
}
|
}
|
||||||
if rsp.CreateFolderRsp != nil && rsp.CreateFolderRsp.GetRetCode() != 0 {
|
if retCode := rsp.RenameFolderRsp.GetRetCode(); retCode != 0 {
|
||||||
return nil, errors.Errorf("create folder error: %v", rsp.CreateFolderRsp.GetRetCode())
|
return nil, errors.Errorf("rename folder error: %v", retCode)
|
||||||
}
|
}
|
||||||
if rsp.RenameFolderRsp != nil && rsp.RenameFolderRsp.GetRetCode() != 0 {
|
if retCode := rsp.DeleteFolderRsp.GetRetCode(); retCode != 0 {
|
||||||
return nil, errors.Errorf("rename folder error: %v", rsp.CreateFolderRsp.GetRetCode())
|
return nil, errors.Errorf("delete folder error: %v", retCode)
|
||||||
}
|
|
||||||
if rsp.DeleteFolderRsp != nil && rsp.DeleteFolderRsp.GetRetCode() != 0 {
|
|
||||||
return nil, errors.Errorf("delete folder error: %v", rsp.CreateFolderRsp.GetRetCode())
|
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -116,12 +116,7 @@ func (c *QQClient) buildGroupInfoRequestPacket(groupCode int64) (uint16, []byte)
|
|||||||
},
|
},
|
||||||
PcClientVersion: proto.Uint32(0),
|
PcClientVersion: proto.Uint32(0),
|
||||||
}
|
}
|
||||||
b, _ := proto.Marshal(body)
|
payload := c.packOIDBPackageProto(2189, 0, body)
|
||||||
req := &oidb.OIDBSSOPkg{
|
|
||||||
Command: 2189,
|
|
||||||
Bodybuffer: b,
|
|
||||||
}
|
|
||||||
payload, _ := proto.Marshal(req)
|
|
||||||
return c.uniPacket("OidbSvc.0x88d_0", payload)
|
return c.uniPacket("OidbSvc.0x88d_0", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +183,7 @@ func (c *QQClient) buildGroupSearchPacket(keyword string) (uint16, []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SummaryCard.ReqSearch
|
// SummaryCard.ReqSearch
|
||||||
func decodeGroupSearchResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeGroupSearchResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
request := &jce.RequestPacket{}
|
request := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -226,14 +221,11 @@ func decodeGroupSearchResponse(_ *QQClient, _ *network.IncomingPacketInfo, paylo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OidbSvc.0x88d_0
|
// OidbSvc.0x88d_0
|
||||||
func decodeGroupInfoResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeGroupInfoResponse(c *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.D88DRspBody{}
|
rsp := oidb.D88DRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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 len(rsp.RspGroupInfo) == 0 {
|
if len(rsp.RspGroupInfo) == 0 {
|
||||||
return nil, errors.New(string(rsp.StrErrorInfo))
|
return nil, errors.New(string(rsp.StrErrorInfo))
|
||||||
@ -336,7 +328,7 @@ func (g *GroupInfo) AdministratorOrOwner() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GroupInfo) FindMember(uin int64) *GroupMemberInfo {
|
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)
|
return info.FindMemberWithoutLock(uin)
|
||||||
})
|
})
|
||||||
if r == nil {
|
if r == nil {
|
||||||
@ -368,7 +360,7 @@ func (g *GroupInfo) Update(f func(*GroupInfo)) {
|
|||||||
f(g)
|
f(g)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GroupInfo) Read(f func(*GroupInfo) interface{}) interface{} {
|
func (g *GroupInfo) Read(f func(*GroupInfo) any) any {
|
||||||
g.lock.RLock()
|
g.lock.RLock()
|
||||||
defer g.lock.RUnlock()
|
defer g.lock.RUnlock()
|
||||||
return f(g)
|
return f(g)
|
||||||
|
@ -2,9 +2,9 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -61,7 +61,7 @@ func (c *QQClient) SendGroupMessage(groupCode int64, m *message.SendingMessage,
|
|||||||
Message: m.Elements,
|
Message: m.Elements,
|
||||||
}))
|
}))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error("%v", err)
|
c.error("%v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ret := c.sendGroupMessage(groupCode, false, &message.SendingMessage{Elements: []message.IMessageElement{lmsg}})
|
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 {
|
for i, ip := range rsp.Uint32UpIp {
|
||||||
addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])}
|
addr := highway.Addr{IP: uint32(ip), Port: int(rsp.Uint32UpPort[i])}
|
||||||
input := highway.Input{
|
hash := md5.Sum(body)
|
||||||
|
input := highway.Transaction{
|
||||||
CommandID: 27,
|
CommandID: 27,
|
||||||
Key: rsp.MsgSig,
|
Ticket: rsp.MsgSig,
|
||||||
Body: bytes.NewReader(body),
|
Body: bytes.NewReader(body),
|
||||||
|
Size: int64(len(body)),
|
||||||
|
Sum: hash[:],
|
||||||
}
|
}
|
||||||
err := c.highwaySession.Upload(addr, input)
|
err := c.highwaySession.Upload(addr, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error("highway upload long message error: %v", err)
|
c.error("highway upload long message error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return genLongTemplate(rsp.MsgResid, m.Brief(), ts), nil
|
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.")
|
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) {
|
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)))
|
i, err := c.sendAndWait(c.buildMultiApplyUpPacket(data, hash, buType, utils.ToGroupUin(groupCode)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -297,7 +273,7 @@ func (c *QQClient) buildAtAllRemainRequestPacket(groupCode int64) (uint16, []byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OnlinePush.PbPushGroupMsg
|
// 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{}
|
pkt := msg.PushMessagePacket{}
|
||||||
err := proto.Unmarshal(payload, &pkt)
|
err := proto.Unmarshal(payload, &pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -317,22 +293,22 @@ func decodeGroupMessagePacket(c *QQClient, _ *network.IncomingPacketInfo, payloa
|
|||||||
if builder.len() >= pkt.Message.Content.GetPkgNum() {
|
if builder.len() >= pkt.Message.Content.GetPkgNum() {
|
||||||
c.msgBuilders.Delete(seq)
|
c.msgBuilders.Delete(seq)
|
||||||
if pkt.Message.Head.GetFromUin() == c.Uin {
|
if pkt.Message.Head.GetFromUin() == c.Uin {
|
||||||
c.dispatchGroupMessageSelf(c.parseGroupMessage(builder.build()))
|
c.SelfGroupMessageEvent.dispatch(c, c.parseGroupMessage(builder.build()))
|
||||||
} else {
|
} else {
|
||||||
c.dispatchGroupMessage(c.parseGroupMessage(builder.build()))
|
c.GroupMessageEvent.dispatch(c, c.parseGroupMessage(builder.build()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if pkt.Message.Head.GetFromUin() == c.Uin {
|
if pkt.Message.Head.GetFromUin() == c.Uin {
|
||||||
c.dispatchGroupMessageSelf(c.parseGroupMessage(pkt.Message))
|
c.SelfGroupMessageEvent.dispatch(c, c.parseGroupMessage(pkt.Message))
|
||||||
} else {
|
} else {
|
||||||
c.dispatchGroupMessage(c.parseGroupMessage(pkt.Message))
|
c.GroupMessageEvent.dispatch(c, c.parseGroupMessage(pkt.Message))
|
||||||
}
|
}
|
||||||
return nil, nil
|
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{}
|
rsp := msg.SendMessageResponse{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
@ -340,20 +316,20 @@ func decodeMsgSendResponse(c *QQClient, _ *network.IncomingPacketInfo, payload [
|
|||||||
switch rsp.GetResult() {
|
switch rsp.GetResult() {
|
||||||
case 0: // OK.
|
case 0: // OK.
|
||||||
case 55:
|
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:
|
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
|
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{}
|
rsp := msg.GetGroupMsgResp{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
}
|
}
|
||||||
if rsp.GetResult() != 0 {
|
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())
|
return nil, errors.Errorf("get msg error: %v msg: %v", rsp.GetResult(), rsp.GetErrmsg())
|
||||||
}
|
}
|
||||||
var ret []*message.GroupMessage
|
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 != nil && m.Content.GetPkgNum() > 1 && !info.Params.Bool("raw") {
|
||||||
if m.Content.GetPkgIndex() == 0 {
|
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()
|
i := m.Head.GetMsgSeq() - m.Content.GetPkgNum()
|
||||||
builder := &messageBuilder{}
|
builder := &messageBuilder{}
|
||||||
for {
|
for {
|
||||||
@ -396,14 +372,11 @@ func decodeGetGroupMsgResponse(c *QQClient, info *network.IncomingPacketInfo, pa
|
|||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeAtAllRemainResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeAtAllRemainResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.D8A7RspBody{}
|
rsp := oidb.D8A7RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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")
|
|
||||||
}
|
}
|
||||||
return &AtAllRemainInfo{
|
return &AtAllRemainInfo{
|
||||||
CanAtAll: rsp.GetCanAtAll(),
|
CanAtAll: rsp.GetCanAtAll(),
|
||||||
@ -415,10 +388,10 @@ func decodeAtAllRemainResponse(_ *QQClient, _ *network.IncomingPacketInfo, paylo
|
|||||||
func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage {
|
func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage {
|
||||||
group := c.FindGroup(m.Head.GroupInfo.GetGroupCode())
|
group := c.FindGroup(m.Head.GroupInfo.GetGroupCode())
|
||||||
if group == nil {
|
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())
|
info, err := c.GetGroupInfo(m.Head.GroupInfo.GetGroupCode())
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
group = info
|
group = info
|
||||||
@ -427,7 +400,7 @@ func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage {
|
|||||||
if len(group.Members) == 0 {
|
if len(group.Members) == 0 {
|
||||||
mem, err := c.GetGroupMembers(group)
|
mem, err := c.GetGroupMembers(group)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
group.Members = mem
|
group.Members = mem
|
||||||
@ -463,7 +436,7 @@ func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage {
|
|||||||
mem = info
|
mem = info
|
||||||
group.Members = append(group.Members, mem)
|
group.Members = append(group.Members, mem)
|
||||||
group.sort()
|
group.sort()
|
||||||
go c.dispatchNewMemberEvent(&MemberJoinGroupEvent{
|
go c.GroupMemberJoinEvent.dispatch(c, &MemberJoinGroupEvent{
|
||||||
Group: group,
|
Group: group,
|
||||||
Member: info,
|
Member: info,
|
||||||
})
|
})
|
||||||
@ -531,7 +504,7 @@ func (c *QQClient) parseGroupMessage(m *msg.Message) *message.GroupMessage {
|
|||||||
mem.CardName = groupCard
|
mem.CardName = groupCard
|
||||||
}
|
}
|
||||||
if old != mem.CardName {
|
if old != mem.CardName {
|
||||||
go c.dispatchMemberCardUpdatedEvent(&MemberCardUpdatedEvent{
|
c.MemberCardUpdatedEvent.dispatch(c, &MemberCardUpdatedEvent{
|
||||||
Group: group,
|
Group: group,
|
||||||
OldCard: old,
|
OldCard: old,
|
||||||
Member: mem,
|
Member: mem,
|
||||||
@ -592,14 +565,11 @@ func (c *QQClient) buildEssenceMsgOperatePacket(groupCode int64, msgSeq, msgRand
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OidbSvc.0xeac_1/2
|
// OidbSvc.0xeac_1/2
|
||||||
func decodeEssenceMsgResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeEssenceMsgResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := &oidb.EACRspBody{}
|
rsp := &oidb.EACRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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")
|
|
||||||
}
|
}
|
||||||
return rsp, nil
|
return rsp, nil
|
||||||
}
|
}
|
||||||
|
@ -710,7 +710,7 @@ func convertChannelInfo(info *channel.GuildChannelInfo) *ChannelInfo {
|
|||||||
func (c *QQClient) syncChannelFirstView() {
|
func (c *QQClient) syncChannelFirstView() {
|
||||||
rsp, err := c.sendAndWaitDynamic(c.buildSyncChannelFirstViewPacket())
|
rsp, err := c.sendAndWaitDynamic(c.buildSyncChannelFirstViewPacket())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error("sync channel error: %v", err)
|
c.error("sync channel error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
firstViewRsp := new(channel.FirstViewRsp)
|
firstViewRsp := new(channel.FirstViewRsp)
|
||||||
@ -723,7 +723,7 @@ func (c *QQClient) syncChannelFirstView() {
|
|||||||
c.GuildService.Nickname = self.Nickname
|
c.GuildService.Nickname = self.Nickname
|
||||||
c.GuildService.AvatarUrl = self.AvatarUrl
|
c.GuildService.AvatarUrl = self.AvatarUrl
|
||||||
} else {
|
} 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)
|
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)
|
firstViewMsg := new(channel.FirstViewMsg)
|
||||||
if err := proto.Unmarshal(payload, firstViewMsg); err != nil {
|
if err := proto.Unmarshal(payload, firstViewMsg); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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)
|
channels, err := c.GuildService.FetchChannelList(info.GuildId)
|
||||||
if err != nil {
|
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 {
|
for _, node := range guild.ChannelNodes {
|
||||||
meta := new(channel.ChannelMsgMeta)
|
meta := new(channel.ChannelMsgMeta)
|
||||||
_ = proto.Unmarshal(node.Meta, meta)
|
_ = proto.Unmarshal(node.Meta, meta)
|
||||||
|
@ -27,7 +27,7 @@ type tipsPushInfo struct {
|
|||||||
ChannelId uint64
|
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)
|
push := new(channel.MsgOnlinePush)
|
||||||
if err := proto.Unmarshal(payload, push); err != nil {
|
if err := proto.Unmarshal(payload, push); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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)
|
eventBody := new(channel.EventBody)
|
||||||
if err := proto.Unmarshal(common.PbElem, eventBody); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
c.processGuildEventBody(m, eventBody)
|
c.processGuildEventBody(m, eventBody)
|
||||||
@ -106,7 +106,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody
|
|||||||
var guild *GuildInfo
|
var guild *GuildInfo
|
||||||
if m.Head.RoutingHead.GetGuildId() != 0 {
|
if m.Head.RoutingHead.GetGuildId() != 0 {
|
||||||
if guild = c.GuildService.FindGuild(m.Head.RoutingHead.GetGuildId()); guild == nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,7 +118,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody
|
|||||||
}
|
}
|
||||||
channelInfo, err := c.GuildService.FetchChannelInfo(guild.GuildId, chanId.GetChanId())
|
channelInfo, err := c.GuildService.FetchChannelInfo(guild.GuildId, chanId.GetChanId())
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
guild.Channels = append(guild.Channels, channelInfo)
|
guild.Channels = append(guild.Channels, channelInfo)
|
||||||
@ -148,7 +148,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody
|
|||||||
if oldInfo == nil {
|
if oldInfo == nil {
|
||||||
info, err := c.GuildService.FetchChannelInfo(m.Head.RoutingHead.GetGuildId(), eventBody.ChangeChanInfo.GetChanId())
|
info, err := c.GuildService.FetchChannelInfo(m.Head.RoutingHead.GetGuildId(), eventBody.ChangeChanInfo.GetChanId())
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
guild.Channels = append(guild.Channels, info)
|
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())
|
newInfo, err := c.GuildService.FetchChannelInfo(m.Head.RoutingHead.GetGuildId(), eventBody.ChangeChanInfo.GetChanId())
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
for i := range guild.Channels {
|
for i := range guild.Channels {
|
||||||
@ -178,13 +178,13 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody
|
|||||||
case eventBody.JoinGuild != nil:
|
case eventBody.JoinGuild != nil:
|
||||||
/* 应该不会重复推送把, 不会吧不会吧
|
/* 应该不会重复推送把, 不会吧不会吧
|
||||||
if mem := guild.FindMember(eventBody.JoinGuild.GetMemberTinyid()); mem != 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
|
return
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
profile, err := c.GuildService.FetchGuildMemberProfileInfo(guild.GuildId, eventBody.JoinGuild.GetMemberTinyid())
|
profile, err := c.GuildService.FetchGuildMemberProfileInfo(guild.GuildId, eventBody.JoinGuild.GetMemberTinyid())
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
info := &GuildMemberInfo{
|
info := &GuildMemberInfo{
|
||||||
@ -210,7 +210,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody
|
|||||||
if eventBody.UpdateMsg.GetEventType() == 4 { // 消息贴表情更新 (包含添加或删除)
|
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)
|
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 {
|
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
|
return
|
||||||
}
|
}
|
||||||
// 自己的消息被贴表情会单独推送一个tips, 这里不需要解析
|
// 自己的消息被贴表情会单独推送一个tips, 这里不需要解析
|
||||||
@ -223,7 +223,7 @@ func (c *QQClient) processGuildEventBody(m *channel.ChannelMsgContent, eventBody
|
|||||||
MessageId: t[0].Head.ContentHead.GetSeq(),
|
MessageId: t[0].Head.ContentHead.GetSeq(),
|
||||||
CurrentReactions: decodeGuildMessageEmojiReactions(t[0]),
|
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 {
|
if i == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -88,7 +88,7 @@ func (s *GuildService) QueryImage(guildId, channelId uint64, hash []byte, size u
|
|||||||
if body.IsExists {
|
if body.IsExists {
|
||||||
return &message.GuildImageElement{
|
return &message.GuildImageElement{
|
||||||
FileId: body.FileId,
|
FileId: body.FileId,
|
||||||
FilePath: hex.EncodeToString(hash) + ".jpg",
|
FilePath: fmt.Sprintf("%x.jpg", hash),
|
||||||
Size: int32(size),
|
Size: int32(size),
|
||||||
DownloadIndex: body.DownloadIndex,
|
DownloadIndex: body.DownloadIndex,
|
||||||
Width: body.Width,
|
Width: body.Width,
|
||||||
@ -177,7 +177,7 @@ func (c *QQClient) buildGuildImageStorePacket(guildId, channelId uint64, hash []
|
|||||||
FileId: proto.Uint64(0),
|
FileId: proto.Uint64(0),
|
||||||
FileMd5: hash,
|
FileMd5: hash,
|
||||||
FileSize: &size,
|
FileSize: &size,
|
||||||
FileName: []byte(hex.EncodeToString(hash) + ".jpg"),
|
FileName: []byte(fmt.Sprintf("%x.jpg", hash)),
|
||||||
SrcTerm: proto.Uint32(5),
|
SrcTerm: proto.Uint32(5),
|
||||||
PlatformType: proto.Uint32(9),
|
PlatformType: proto.Uint32(9),
|
||||||
BuType: proto.Uint32(211),
|
BuType: proto.Uint32(211),
|
||||||
@ -230,7 +230,7 @@ func decodeGuildMessageEmojiReactions(content *channel.ChannelMsgContent) (r []*
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeGuildImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeGuildImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
body := new(cmd0x388.D388RspBody)
|
body := new(cmd0x388.D388RspBody)
|
||||||
if err := proto.Unmarshal(payload, body); err != nil {
|
if err := proto.Unmarshal(payload, body); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -5,13 +5,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
@ -21,51 +19,6 @@ import (
|
|||||||
"github.com/Mrs4s/MiraiGo/utils"
|
"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(`<span class="ui-nowrap">`))+24:]
|
|
||||||
t := b[:bytes.Index(b, []byte(`</span>`))]
|
|
||||||
ret.Name = string(t)
|
|
||||||
b = b[bytes.Index(b, []byte(`<small>LV</small>`))+17:]
|
|
||||||
t = b[:bytes.Index(b, []byte(`</p>`))]
|
|
||||||
ret.Level, _ = strconv.Atoi(string(t))
|
|
||||||
b = b[bytes.Index(b, []byte(`<div class="pk-line pk-line-guest">`))+35:]
|
|
||||||
b = b[bytes.Index(b, []byte(`<p>`))+3:]
|
|
||||||
t = b[:bytes.Index(b, []byte(`<small>倍`))]
|
|
||||||
ret.LevelSpeed, _ = strconv.ParseFloat(string(t), 64)
|
|
||||||
b = b[bytes.Index(b, []byte(`<div class="pk-line pk-line-guest">`))+35:]
|
|
||||||
b = b[bytes.Index(b, []byte(`<p>`))+3:]
|
|
||||||
st := string(b[:bytes.Index(b, []byte(`</p>`))])
|
|
||||||
st = strings.Replace(st, "<small>", "", 1)
|
|
||||||
st = strings.Replace(st, "</small>", "", 1)
|
|
||||||
ret.VipLevel = st
|
|
||||||
b = b[bytes.Index(b, []byte(`<div class="pk-line pk-line-guest">`))+35:]
|
|
||||||
b = b[bytes.Index(b, []byte(`<p>`))+3:]
|
|
||||||
t = b[:bytes.Index(b, []byte(`</p>`))]
|
|
||||||
ret.VipGrowthSpeed, _ = strconv.Atoi(string(t))
|
|
||||||
b = b[bytes.Index(b, []byte(`<div class="pk-line pk-line-guest">`))+35:]
|
|
||||||
b = b[bytes.Index(b, []byte(`<p>`))+3:]
|
|
||||||
t = b[:bytes.Index(b, []byte(`</p>`))]
|
|
||||||
ret.VipGrowthTotal, _ = strconv.Atoi(string(t))
|
|
||||||
return &ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------- GroupHonorInfo -------- */
|
/* -------- GroupHonorInfo -------- */
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@ -165,6 +118,32 @@ func (c *QQClient) GetTts(text string) ([]byte, error) {
|
|||||||
|
|
||||||
/* -------- GroupNotice -------- */
|
/* -------- 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 {
|
type noticePicUpResponse struct {
|
||||||
ErrorCode int `json:"ec"`
|
ErrorCode int `json:"ec"`
|
||||||
ErrorMessage string `json:"em"`
|
ErrorMessage string `json:"em"`
|
||||||
@ -177,36 +156,77 @@ type noticeImage struct {
|
|||||||
ID string `json:"id"`
|
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) {
|
func (c *QQClient) uploadGroupNoticePic(img []byte) (*noticeImage, error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
w := multipart.NewWriter(buf)
|
w := multipart.NewWriter(buf)
|
||||||
err := w.WriteField("bkn", strconv.Itoa(c.getCSRFToken()))
|
_ = w.WriteField("bkn", strconv.Itoa(c.getCSRFToken()))
|
||||||
if err != nil {
|
_ = w.WriteField("source", "troopNotice")
|
||||||
return nil, errors.Wrap(err, "write multipart<bkn> failed")
|
_ = w.WriteField("m", "0")
|
||||||
}
|
|
||||||
err = w.WriteField("source", "troopNotice")
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "write multipart<source> failed")
|
|
||||||
}
|
|
||||||
err = w.WriteField("m", "0")
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "write multipart<m> failed")
|
|
||||||
}
|
|
||||||
h := make(textproto.MIMEHeader)
|
h := make(textproto.MIMEHeader)
|
||||||
h.Set("Content-Disposition", `form-data; name="pic_up"; filename="temp_uploadFile.png"`)
|
h.Set("Content-Disposition", `form-data; name="pic_up"; filename="temp_uploadFile.png"`)
|
||||||
h.Set("Content-Type", "image/png")
|
h.Set("Content-Type", "image/png")
|
||||||
fw, err := w.CreatePart(h)
|
fw, _ := w.CreatePart(h)
|
||||||
if err != nil {
|
_, _ = fw.Write(img)
|
||||||
return nil, errors.Wrap(err, "create multipart field<pic_up> failed")
|
_ = w.Close()
|
||||||
}
|
|
||||||
_, err = fw.Write(img)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "write multipart<pic_up> failed")
|
|
||||||
}
|
|
||||||
err = w.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "close multipart failed")
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("POST", "https://web.qun.qq.com/cgi-bin/announce/upload_img", buf)
|
req, err := http.NewRequest("POST", "https://web.qun.qq.com/cgi-bin/announce/upload_img", buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "new request error")
|
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")
|
return nil, errors.Wrap(err, "post error")
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(resp.Body)
|
var res noticePicUpResponse
|
||||||
if err != nil {
|
err = json.NewDecoder(resp.Body).Decode(&res)
|
||||||
return nil, errors.Wrap(err, "read body error")
|
|
||||||
}
|
|
||||||
res := noticePicUpResponse{}
|
|
||||||
err = json.Unmarshal(body, &res)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal json")
|
return nil, errors.Wrap(err, "failed to unmarshal json")
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"fmt"
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
@ -91,8 +90,9 @@ func (c *QQClient) uploadGroupOrGuildImage(target message.Source, img io.ReadSee
|
|||||||
}.Encode()
|
}.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
var r interface{}
|
var r any
|
||||||
var err error
|
var err error
|
||||||
|
var input highway.Transaction
|
||||||
switch target.SourceType {
|
switch target.SourceType {
|
||||||
case message.SourceGroup:
|
case message.SourceGroup:
|
||||||
r, err = c.sendAndWait(c.buildGroupImageStorePacket(target.PrimaryID, fh, int32(length)))
|
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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if tc > 1 && length > 3*1024*1024 {
|
input = highway.Transaction{
|
||||||
_, err = c.highwaySession.UploadBDHMultiThread(highway.BdhMultiThreadInput{
|
|
||||||
CommandID: cmd,
|
CommandID: cmd,
|
||||||
Body: utils.ReaderAtFrom2ReadSeeker(img, nil),
|
Body: img,
|
||||||
Size: length,
|
Size: length,
|
||||||
Sum: fh,
|
Sum: fh,
|
||||||
Ticket: rsp.UploadKey,
|
Ticket: rsp.UploadKey,
|
||||||
Ext: ext,
|
Ext: ext,
|
||||||
}, 4)
|
}
|
||||||
|
if tc > 1 && length > 3*1024*1024 {
|
||||||
|
_, err = c.highwaySession.UploadBDHMultiThread(input, tc)
|
||||||
} else {
|
} else {
|
||||||
_, err = c.highwaySession.UploadBDH(highway.BdhInput{
|
_, err = c.highwaySession.UploadBDH(input)
|
||||||
CommandID: cmd,
|
|
||||||
Body: img,
|
|
||||||
Ticket: rsp.UploadKey,
|
|
||||||
Ext: ext,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "upload failed")
|
return nil, errors.Wrap(err, "upload failed")
|
||||||
@ -145,7 +141,7 @@ ok:
|
|||||||
width := int32(i.Width)
|
width := int32(i.Width)
|
||||||
height := int32(i.Height)
|
height := int32(i.Height)
|
||||||
if err != nil && target.SourceType != message.SourceGroup {
|
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
|
width = 200
|
||||||
height = 200
|
height = 200
|
||||||
}
|
}
|
||||||
@ -158,7 +154,7 @@ ok:
|
|||||||
}
|
}
|
||||||
return &message.GuildImageElement{
|
return &message.GuildImageElement{
|
||||||
FileId: rsp.FileId,
|
FileId: rsp.FileId,
|
||||||
FilePath: hex.EncodeToString(fh) + ".jpg",
|
FilePath: fmt.Sprintf("%x.jpg", fh),
|
||||||
Size: int32(length),
|
Size: int32(length),
|
||||||
DownloadIndex: rsp.DownloadIndex,
|
DownloadIndex: rsp.DownloadIndex,
|
||||||
Width: width,
|
Width: width,
|
||||||
@ -207,18 +203,18 @@ func (c *QQClient) uploadPrivateImage(target int64, img io.ReadSeeker, count int
|
|||||||
return e, nil
|
return e, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *QQClient) ImageOcr(img interface{}) (*OcrResponse, error) {
|
func (c *QQClient) ImageOcr(img any) (*OcrResponse, error) {
|
||||||
url := ""
|
url := ""
|
||||||
switch e := img.(type) {
|
switch e := img.(type) {
|
||||||
case *message.GroupImageElement:
|
case *message.GroupImageElement:
|
||||||
url = e.Url
|
url = e.Url
|
||||||
if b, err := utils.HTTPGetReadCloser(e.Url, ""); err == nil {
|
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
|
url = e.Url
|
||||||
}
|
}
|
||||||
_ = b.Close()
|
_ = 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -318,7 +314,7 @@ func (c *QQClient) buildGroupImageDownloadPacket(fileId, groupCode int64, fileMd
|
|||||||
return c.uniPacket("ImgStore.GroupPicDown", payload)
|
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)
|
r := make([]byte, 16)
|
||||||
rand.Read(r)
|
rand.Read(r)
|
||||||
ext, _ := proto.Marshal(&highway2.CommFileExtReq{
|
ext, _ := proto.Marshal(&highway2.CommFileExtReq{
|
||||||
@ -326,13 +322,13 @@ func (c *QQClient) uploadOcrImage(img io.Reader) (string, error) {
|
|||||||
Uuid: binary.GenUUID(r),
|
Uuid: binary.GenUUID(r),
|
||||||
})
|
})
|
||||||
|
|
||||||
buf, _ := io.ReadAll(img)
|
rsp, err := c.highwaySession.UploadBDH(highway.Transaction{
|
||||||
rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
|
|
||||||
CommandID: 76,
|
CommandID: 76,
|
||||||
Body: bytes.NewReader(buf),
|
Body: img,
|
||||||
|
Size: int64(size),
|
||||||
|
Sum: sum,
|
||||||
Ticket: c.highwaySession.SigSession,
|
Ticket: c.highwaySession.SigSession,
|
||||||
Ext: ext,
|
Ext: ext,
|
||||||
Encrypt: false,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "upload ocr image error")
|
return "", errors.Wrap(err, "upload ocr image error")
|
||||||
@ -365,7 +361,7 @@ func (c *QQClient) buildImageOcrRequestPacket(url, md5 string, size, weight, hei
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ImgStore.GroupPicUp
|
// ImgStore.GroupPicUp
|
||||||
func decodeGroupImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeGroupImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkt := cmd0x388.D388RspBody{}
|
pkt := cmd0x388.D388RspBody{}
|
||||||
err := proto.Unmarshal(payload, &pkt)
|
err := proto.Unmarshal(payload, &pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -392,7 +388,7 @@ func decodeGroupImageStoreResponse(_ *QQClient, _ *network.IncomingPacketInfo, p
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeGroupImageDownloadResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeGroupImageDownloadResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkt := cmd0x388.D388RspBody{}
|
pkt := cmd0x388.D388RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkt); err != nil {
|
if err := proto.Unmarshal(payload, &pkt); err != nil {
|
||||||
return nil, errors.Wrap(err, "unmarshal protobuf message error")
|
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 {
|
if len(pkt.GetimgUrlRsp[0].FailMsg) != 0 {
|
||||||
return nil, errors.New(utils.B2S(pkt.GetimgUrlRsp[0].FailMsg))
|
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
|
// OidbSvc.0xe07_0
|
||||||
func decodeImageOcrResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeImageOcrResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.DE07RspBody{}
|
rsp := oidb.DE07RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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 rsp.Wording != "" {
|
if rsp.Wording != "" {
|
||||||
if strings.Contains(rsp.Wording, "服务忙") {
|
if strings.Contains(rsp.Wording, "服务忙") {
|
||||||
|
@ -12,31 +12,21 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
"github.com/Mrs4s/MiraiGo/client/internal/network"
|
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb"
|
"github.com/Mrs4s/MiraiGo/client/pb"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/proto"
|
"github.com/Mrs4s/MiraiGo/internal/proto"
|
||||||
"github.com/Mrs4s/MiraiGo/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BdhInput struct {
|
type Transaction struct {
|
||||||
CommandID int32
|
CommandID int32
|
||||||
Body io.ReadSeeker
|
Body io.Reader
|
||||||
|
Sum []byte // md5 sum of body
|
||||||
|
Size int64 // body size
|
||||||
Ticket []byte
|
Ticket []byte
|
||||||
Ext []byte
|
Ext []byte
|
||||||
Encrypt bool
|
Encrypt bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type BdhMultiThreadInput struct {
|
func (bdh *Transaction) encrypt(key []byte) error {
|
||||||
CommandID int32
|
|
||||||
Body io.ReaderAt
|
|
||||||
Sum []byte
|
|
||||||
Size int64
|
|
||||||
Ticket []byte
|
|
||||||
Ext []byte
|
|
||||||
Encrypt bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bdh *BdhInput) encrypt(key []byte) error {
|
|
||||||
if bdh.Encrypt {
|
if bdh.Encrypt {
|
||||||
if len(key) == 0 {
|
if len(key) == 0 {
|
||||||
return errors.New("session key not found. maybe miss some packet?")
|
return errors.New("session key not found. maybe miss some packet?")
|
||||||
@ -46,25 +36,13 @@ func (bdh *BdhInput) encrypt(key []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bdh *BdhMultiThreadInput) encrypt(key []byte) error {
|
func (s *Session) UploadBDH(trans Transaction) ([]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) {
|
|
||||||
if len(s.SsoAddr) == 0 {
|
if len(s.SsoAddr) == 0 {
|
||||||
return nil, errors.New("srv addrs not found. maybe miss some packet?")
|
return nil, errors.New("srv addrs not found. maybe miss some packet?")
|
||||||
}
|
}
|
||||||
addr := s.SsoAddr[0].String()
|
addr := s.SsoAddr[0].String()
|
||||||
|
|
||||||
sum, length := utils.ComputeMd5AndLength(input.Body)
|
if err := trans.encrypt(s.SessionKey); err != nil {
|
||||||
_, _ = input.Body.Seek(0, io.SeekStart)
|
|
||||||
if err := input.encrypt(s.SessionKey); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conn, err := net.DialTimeout("tcp", addr, time.Second*20)
|
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
|
const chunkSize = 256 * 1024
|
||||||
var rspExt []byte
|
var rspExt, chunk []byte
|
||||||
offset := 0
|
offset := 0
|
||||||
chunk := make([]byte, chunkSize)
|
if trans.Size > chunkSize {
|
||||||
|
chunk = make([]byte, chunkSize)
|
||||||
|
} else {
|
||||||
|
chunk = make([]byte, trans.Size)
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
chunk = chunk[:chunkSize]
|
chunk = chunk[:cap(chunk)]
|
||||||
rl, err := io.ReadFull(input.Body, chunk)
|
rl, err := io.ReadFull(trans.Body, chunk)
|
||||||
if errors.Is(err, io.EOF) {
|
if rl == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
@ -93,19 +75,20 @@ func (s *Session) UploadBDH(input BdhInput) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
ch := md5.Sum(chunk)
|
ch := md5.Sum(chunk)
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
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{
|
MsgSeghead: &pb.SegHead{
|
||||||
Filesize: length,
|
Filesize: trans.Size,
|
||||||
Dataoffset: int64(offset),
|
Dataoffset: int64(offset),
|
||||||
Datalength: int32(rl),
|
Datalength: int32(rl),
|
||||||
Serviceticket: input.Ticket,
|
Serviceticket: trans.Ticket,
|
||||||
Md5: ch[:],
|
Md5: ch[:],
|
||||||
FileMd5: sum,
|
FileMd5: trans.Sum,
|
||||||
},
|
},
|
||||||
ReqExtendinfo: input.Ext,
|
ReqExtendinfo: trans.Ext,
|
||||||
})
|
})
|
||||||
offset += rl
|
offset += rl
|
||||||
_, err = io.Copy(conn, network.HeadBodyFrame(head, chunk))
|
frame := newFrame(head, chunk)
|
||||||
|
_, err = frame.WriteTo(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "write conn error")
|
return nil, errors.Wrap(err, "write conn error")
|
||||||
}
|
}
|
||||||
@ -120,23 +103,17 @@ func (s *Session) UploadBDH(input BdhInput) ([]byte, error) {
|
|||||||
rspExt = rspHead.RspExtendinfo
|
rspExt = rspHead.RspExtendinfo
|
||||||
}
|
}
|
||||||
if rspHead.MsgSeghead != nil && rspHead.MsgSeghead.Serviceticket != nil {
|
if rspHead.MsgSeghead != nil && rspHead.MsgSeghead.Serviceticket != nil {
|
||||||
input.Ticket = rspHead.MsgSeghead.Serviceticket
|
trans.Ticket = rspHead.MsgSeghead.Serviceticket
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rspExt, nil
|
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,
|
// for small file and small thread count,
|
||||||
// use UploadBDH instead of UploadBDHMultiThread
|
// use UploadBDH instead of UploadBDHMultiThread
|
||||||
if input.Size < 1024*1024*3 || threadCount < 2 {
|
if trans.Size < 1024*1024*3 || threadCount < 2 {
|
||||||
return s.UploadBDH(BdhInput{
|
return s.UploadBDH(trans)
|
||||||
CommandID: input.CommandID,
|
|
||||||
Body: io.NewSectionReader(input.Body, 0, input.Size),
|
|
||||||
Ticket: input.Ticket,
|
|
||||||
Ext: input.Ext,
|
|
||||||
Encrypt: input.Encrypt,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(s.SsoAddr) == 0 {
|
if len(s.SsoAddr) == 0 {
|
||||||
@ -144,40 +121,25 @@ func (s *Session) UploadBDHMultiThread(input BdhMultiThreadInput, threadCount in
|
|||||||
}
|
}
|
||||||
addr := s.SsoAddr[0].String()
|
addr := s.SsoAddr[0].String()
|
||||||
|
|
||||||
if err := input.encrypt(s.SessionKey); err != nil {
|
if err := trans.encrypt(s.SessionKey); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlockMetaData struct {
|
const blockSize int64 = 256 * 1024
|
||||||
Id int
|
|
||||||
Offset int64
|
|
||||||
}
|
|
||||||
const blockSize int64 = 1024 * 512
|
|
||||||
var (
|
var (
|
||||||
blocks []BlockMetaData
|
|
||||||
rspExt []byte
|
rspExt []byte
|
||||||
BlockId = ^uint32(0) // -1
|
completedThread uint32
|
||||||
uploadedCount uint32
|
|
||||||
cond = sync.NewCond(&sync.Mutex{})
|
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 {
|
doUpload := func() error {
|
||||||
// send signal complete uploading
|
// send signal complete uploading
|
||||||
defer cond.Signal()
|
defer func() {
|
||||||
|
atomic.AddUint32(&completedThread, 1)
|
||||||
|
cond.Signal()
|
||||||
|
}()
|
||||||
|
|
||||||
conn, err := net.DialTimeout("tcp", addr, time.Second*20)
|
conn, err := net.DialTimeout("tcp", addr, time.Second*20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -189,50 +151,45 @@ func (s *Session) UploadBDHMultiThread(input BdhMultiThreadInput, threadCount in
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer := make([]byte, blockSize)
|
chunk := make([]byte, blockSize)
|
||||||
for {
|
for {
|
||||||
nextId := atomic.AddUint32(&BlockId, 1)
|
cond.L.Lock() // lock protect reading
|
||||||
if nextId >= uint32(len(blocks)) {
|
off := offset
|
||||||
break
|
offset += blockSize
|
||||||
}
|
id++
|
||||||
block := blocks[nextId]
|
if int64(id) == count { // last
|
||||||
if block.Id == len(blocks)-1 {
|
for atomic.LoadUint32(&completedThread) != uint32(threadCount-1) {
|
||||||
cond.L.Lock()
|
|
||||||
for atomic.LoadUint32(&uploadedCount) != uint32(len(blocks))-1 {
|
|
||||||
cond.Wait()
|
cond.Wait()
|
||||||
}
|
}
|
||||||
|
} else if int64(id) > count {
|
||||||
cond.L.Unlock()
|
cond.L.Unlock()
|
||||||
}
|
|
||||||
buffer = buffer[:blockSize]
|
|
||||||
|
|
||||||
cond.L.Lock() // lock protect reading
|
|
||||||
n, err := input.Body.ReadAt(buffer, block.Offset)
|
|
||||||
cond.L.Unlock()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err == io.ErrUnexpectedEOF {
|
chunk = chunk[:blockSize]
|
||||||
buffer = buffer[:n]
|
n, err := io.ReadFull(trans.Body, chunk)
|
||||||
} else {
|
cond.L.Unlock()
|
||||||
return err
|
|
||||||
|
if n == 0 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||||
|
chunk = chunk[:n]
|
||||||
}
|
}
|
||||||
ch := md5.Sum(buffer)
|
ch := md5.Sum(chunk)
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
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{
|
MsgSeghead: &pb.SegHead{
|
||||||
Filesize: input.Size,
|
Filesize: trans.Size,
|
||||||
Dataoffset: block.Offset,
|
Dataoffset: off,
|
||||||
Datalength: int32(n),
|
Datalength: int32(n),
|
||||||
Serviceticket: input.Ticket,
|
Serviceticket: trans.Ticket,
|
||||||
Md5: ch[:],
|
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 {
|
if err != nil {
|
||||||
return errors.Wrap(err, "write conn error")
|
return errors.Wrap(err, "write conn error")
|
||||||
}
|
}
|
||||||
@ -246,7 +203,6 @@ func (s *Session) UploadBDHMultiThread(input BdhMultiThreadInput, threadCount in
|
|||||||
if rspHead.RspExtendinfo != nil {
|
if rspHead.RspExtendinfo != nil {
|
||||||
rspExt = rspHead.RspExtendinfo
|
rspExt = rspHead.RspExtendinfo
|
||||||
}
|
}
|
||||||
atomic.AddUint32(&uploadedCount, 1)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
33
client/internal/highway/frame.go
Normal file
33
client/internal/highway/frame.go
Normal file
@ -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
|
||||||
|
}
|
@ -12,12 +12,17 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
"github.com/Mrs4s/MiraiGo/client/internal/network"
|
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb"
|
"github.com/Mrs4s/MiraiGo/client/pb"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/proto"
|
"github.com/Mrs4s/MiraiGo/internal/proto"
|
||||||
"github.com/Mrs4s/MiraiGo/utils"
|
"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 {
|
type Session struct {
|
||||||
Uin string
|
Uin string
|
||||||
AppID int32
|
AppID int32
|
||||||
@ -42,15 +47,7 @@ func (s *Session) AppendAddr(ip, port uint32) {
|
|||||||
s.SsoAddr = append(s.SsoAddr, addr)
|
s.SsoAddr = append(s.SsoAddr, addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Input struct {
|
func (s *Session) Upload(addr Addr, trans Transaction) error {
|
||||||
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)
|
|
||||||
conn, err := net.DialTimeout("tcp", addr.String(), time.Second*3)
|
conn, err := net.DialTimeout("tcp", addr.String(), time.Second*3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "connect error")
|
return errors.Wrap(err, "connect error")
|
||||||
@ -61,11 +58,9 @@ func (s *Session) Upload(addr Addr, input Input) error {
|
|||||||
chunk := make([]byte, chunkSize)
|
chunk := make([]byte, chunkSize)
|
||||||
offset := 0
|
offset := 0
|
||||||
reader := binary.NewNetworkReader(conn)
|
reader := binary.NewNetworkReader(conn)
|
||||||
w := binary.SelectWriter()
|
|
||||||
defer binary.PutWriter(w)
|
|
||||||
for {
|
for {
|
||||||
chunk = chunk[:chunkSize]
|
chunk = chunk[:chunkSize]
|
||||||
rl, err := io.ReadFull(input.Body, chunk)
|
rl, err := io.ReadFull(trans.Body, chunk)
|
||||||
if errors.Is(err, io.EOF) {
|
if errors.Is(err, io.EOF) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -74,19 +69,20 @@ func (s *Session) Upload(addr Addr, input Input) error {
|
|||||||
}
|
}
|
||||||
ch := md5.Sum(chunk)
|
ch := md5.Sum(chunk)
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
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{
|
MsgSeghead: &pb.SegHead{
|
||||||
Filesize: length,
|
Filesize: trans.Size,
|
||||||
Dataoffset: int64(offset),
|
Dataoffset: int64(offset),
|
||||||
Datalength: int32(rl),
|
Datalength: int32(rl),
|
||||||
Serviceticket: input.Key,
|
Serviceticket: trans.Ticket,
|
||||||
Md5: ch[:],
|
Md5: ch[:],
|
||||||
FileMd5: fh,
|
FileMd5: trans.Sum,
|
||||||
},
|
},
|
||||||
ReqExtendinfo: []byte{},
|
ReqExtendinfo: []byte{},
|
||||||
})
|
})
|
||||||
offset += rl
|
offset += rl
|
||||||
_, err = io.Copy(conn, network.HeadBodyFrame(head, chunk))
|
frame := newFrame(head, chunk)
|
||||||
|
_, err = frame.WriteTo(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "write conn error")
|
return errors.Wrap(err, "write conn error")
|
||||||
}
|
}
|
||||||
@ -133,7 +129,7 @@ func (s *Session) UploadExciting(input ExcitingInput) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
ch := md5.Sum(chunk)
|
ch := md5.Sum(chunk)
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
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{
|
MsgSeghead: &pb.SegHead{
|
||||||
Filesize: fileLength,
|
Filesize: fileLength,
|
||||||
Dataoffset: offset,
|
Dataoffset: offset,
|
||||||
@ -145,7 +141,8 @@ func (s *Session) UploadExciting(input ExcitingInput) ([]byte, error) {
|
|||||||
ReqExtendinfo: input.Ext,
|
ReqExtendinfo: input.Ext,
|
||||||
})
|
})
|
||||||
offset += int64(rl)
|
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("Accept", "*/*")
|
||||||
req.Header.Set("Connection", "Keep-Alive")
|
req.Header.Set("Connection", "Keep-Alive")
|
||||||
req.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)")
|
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)
|
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{
|
return &pb.DataHighwayHead{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Uin: s.Uin,
|
Uin: s.Uin,
|
||||||
Command: "PicUp.DataUp",
|
Command: cmd,
|
||||||
Seq: s.nextSeq(),
|
Seq: s.nextSeq(),
|
||||||
Appid: s.AppID,
|
Appid: s.AppID,
|
||||||
Dataflag: flag,
|
Dataflag: flag,
|
||||||
CommandId: cmd,
|
CommandId: cmdID,
|
||||||
LocaleId: locale,
|
LocaleId: locale,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) sendHeartbreak(conn net.Conn) error {
|
func (s *Session) sendHeartbreak(conn net.Conn) error {
|
||||||
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
head, _ := proto.Marshal(&pb.ReqDataHighwayHead{
|
||||||
MsgBasehead: &pb.DataHighwayHead{
|
MsgBasehead: s.dataHighwayHead(_REQ_CMD_HEART_BREAK, 4096, 0, 2052),
|
||||||
Version: 1,
|
|
||||||
Uin: s.Uin,
|
|
||||||
Command: "PicUp.Echo",
|
|
||||||
Seq: s.nextSeq(),
|
|
||||||
Appid: s.AppID,
|
|
||||||
Dataflag: 4096,
|
|
||||||
CommandId: 0,
|
|
||||||
LocaleId: 2052,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
_, err := io.Copy(conn, network.HeadBodyFrame(head, nil))
|
frame := newFrame(head, nil)
|
||||||
|
_, err := frame.WriteTo(conn)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,9 +34,9 @@ func (t *TCPListener) UnexpectedDisconnect(f func(*TCPListener, error)) {
|
|||||||
t.unexpectedDisconnect = f
|
t.unexpectedDisconnect = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TCPListener) Connect(addr *net.TCPAddr) error {
|
func (t *TCPListener) Connect(addr string) error {
|
||||||
t.Close()
|
t.Close()
|
||||||
conn, err := net.DialTCP("tcp", nil, addr)
|
conn, err := net.Dial("tcp", addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "dial tcp error")
|
return errors.Wrap(err, "dial tcp error")
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -6,7 +6,7 @@ type IncomingPacketInfo struct {
|
|||||||
Params RequestParams
|
Params RequestParams
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestParams map[string]interface{}
|
type RequestParams map[string]any
|
||||||
|
|
||||||
func (p RequestParams) Bool(k string) bool {
|
func (p RequestParams) Bool(k string) bool {
|
||||||
if p == nil {
|
if p == nil {
|
||||||
|
@ -7,8 +7,6 @@ const (
|
|||||||
RequestTypeSimple = 0x0B
|
RequestTypeSimple = 0x0B
|
||||||
)
|
)
|
||||||
|
|
||||||
var emptyKey = make([]byte, 16)
|
|
||||||
|
|
||||||
type EncryptType uint32
|
type EncryptType uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -46,6 +46,7 @@ func (t *Transport) ReadResponse(head []byte) (*Response, error) {
|
|||||||
case EncryptTypeD2Key:
|
case EncryptTypeD2Key:
|
||||||
body = binary.NewTeaCipher(t.Sig.D2Key).Decrypt(body)
|
body = binary.NewTeaCipher(t.Sig.D2Key).Decrypt(body)
|
||||||
case EncryptTypeEmptyKey:
|
case EncryptTypeEmptyKey:
|
||||||
|
emptyKey := make([]byte, 16)
|
||||||
body = binary.NewTeaCipher(emptyKey).Decrypt(body)
|
body = binary.NewTeaCipher(emptyKey).Decrypt(body)
|
||||||
}
|
}
|
||||||
err := t.readSSOFrame(resp, body)
|
err := t.readSSOFrame(resp, body)
|
||||||
|
@ -89,6 +89,7 @@ func (t *Transport) PackPacket(req *Request) []byte {
|
|||||||
case EncryptTypeD2Key:
|
case EncryptTypeD2Key:
|
||||||
body = binary.NewTeaCipher(t.Sig.D2Key).Encrypt(body)
|
body = binary.NewTeaCipher(t.Sig.D2Key).Encrypt(body)
|
||||||
case EncryptTypeEmptyKey:
|
case EncryptTypeEmptyKey:
|
||||||
|
emptyKey := make([]byte, 16)
|
||||||
body = binary.NewTeaCipher(emptyKey).Encrypt(body)
|
body = binary.NewTeaCipher(emptyKey).Encrypt(body)
|
||||||
}
|
}
|
||||||
w.Write(body)
|
w.Write(body)
|
||||||
|
@ -84,7 +84,8 @@ func (c *Codec) Marshal(m *Message) []byte {
|
|||||||
}
|
}
|
||||||
w.WriteByte(0x03)
|
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)))
|
goBinary.BigEndian.PutUint16(buf[1:3], uint16(len(buf)))
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
43
client/log.go
Normal file
43
client/log.go
Normal file
@ -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...)
|
||||||
|
}
|
||||||
|
}
|
@ -48,9 +48,8 @@ func (c *QQClient) getGtk(domain string) int {
|
|||||||
accu = accu + (accu << 5) + int(b)
|
accu = accu + (accu << 5) + int(b)
|
||||||
}
|
}
|
||||||
return 2147483647 & accu
|
return 2147483647 & accu
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *QQClient) GetModelShow(modelName string) ([]*ModelVariant, error) {
|
func (c *QQClient) GetModelShow(modelName string) ([]*ModelVariant, error) {
|
||||||
|
@ -2,14 +2,18 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
|
"github.com/Mrs4s/MiraiGo/client/internal/highway"
|
||||||
"github.com/Mrs4s/MiraiGo/client/internal/network"
|
"github.com/Mrs4s/MiraiGo/client/internal/network"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/longmsg"
|
"github.com/Mrs4s/MiraiGo/client/pb/longmsg"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||||
@ -47,7 +51,7 @@ func (c *QQClient) buildMultiApplyUpPacket(data, hash []byte, buType int32, grou
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MultiMsg.ApplyUp
|
// MultiMsg.ApplyUp
|
||||||
func decodeMultiApplyUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeMultiApplyUpResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
body := multimsg.MultiRspBody{}
|
body := multimsg.MultiRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &body); err != nil {
|
if err := proto.Unmarshal(payload, &body); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
@ -87,7 +91,7 @@ func (c *QQClient) buildMultiApplyDownPacket(resID string) (uint16, []byte) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MultiMsg.ApplyDown
|
// MultiMsg.ApplyDown
|
||||||
func decodeMultiApplyDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeMultiApplyDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
body := multimsg.MultiRspBody{}
|
body := multimsg.MultiRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &body); err != nil {
|
if err := proto.Unmarshal(payload, &body); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
@ -225,6 +229,88 @@ func forwardDisplay(resID, fileName, preview, summary string) string {
|
|||||||
sb.WriteString(`<hr></hr><summary size="26" color="#808080">`)
|
sb.WriteString(`<hr></hr><summary size="26" color="#808080">`)
|
||||||
sb.WriteString(summary)
|
sb.WriteString(summary)
|
||||||
// todo: 私聊的聊天记录?
|
// todo: 私聊的聊天记录?
|
||||||
sb.WriteString(`</summary></item><source name="群聊的聊天记录"></source></msg>`)
|
sb.WriteString(`</summary></item><source name="聊天记录"></source></msg>`)
|
||||||
return sb.String()
|
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
|
||||||
|
}
|
||||||
|
@ -40,34 +40,36 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo {
|
|||||||
r := &ConnectionQualityInfo{}
|
r := &ConnectionQualityInfo{}
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(2)
|
wg.Add(2)
|
||||||
|
|
||||||
|
currentServerAddr := c.servers[c.currServerIndex].String()
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if r.ChatServerLatency, err = qualityTest(c.servers[c.currServerIndex].String()); err != nil {
|
if r.ChatServerLatency, err = qualityTest(currentServerAddr); err != nil {
|
||||||
c.Error("test chat server latency error: %v", err)
|
c.error("test chat server latency error: %v", err)
|
||||||
r.ChatServerLatency = 9999
|
r.ChatServerLatency = 9999
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr, err := net.ResolveIPAddr("ip", "ssl.htdata.qq.com"); err == nil {
|
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 {
|
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
|
r.LongMessageServerLatency = 9999
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
c.Error("resolve long message server error: %v", err)
|
c.error("resolve long message server error: %v", err)
|
||||||
r.LongMessageServerLatency = 9999
|
r.LongMessageServerLatency = 9999
|
||||||
}
|
}
|
||||||
if c.highwaySession.AddrLength() > 0 {
|
if c.highwaySession.AddrLength() > 0 {
|
||||||
if r.SrvServerLatency, err = qualityTest(c.highwaySession.SsoAddr[0].String()); err != nil {
|
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
|
r.SrvServerLatency = 9999
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
res := utils.RunTCPPingLoop(c.servers[c.currServerIndex].String(), 10)
|
res := utils.RunTCPPingLoop(currentServerAddr, 10)
|
||||||
r.ChatServerPacketLoss = res.PacketsLoss
|
r.ChatServerPacketLoss = res.PacketsLoss
|
||||||
if c.highwaySession.AddrLength() > 0 {
|
if c.highwaySession.AddrLength() > 0 {
|
||||||
res = utils.RunTCPPingLoop(c.highwaySession.SsoAddr[0].String(), 10)
|
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 {
|
if _, err := utils.HttpGetBytes("https://ssl.htdata.qq.com", ""); err == nil {
|
||||||
r.LongMessageServerResponseLatency = time.Since(start).Milliseconds()
|
r.LongMessageServerResponseLatency = time.Since(start).Milliseconds()
|
||||||
} else {
|
} 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
|
r.LongMessageServerResponseLatency = 9999
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
@ -87,8 +89,9 @@ func (c *QQClient) ConnectionQualityTest() *ConnectionQualityInfo {
|
|||||||
|
|
||||||
// connect 连接到 QQClient.servers 中的服务器
|
// connect 连接到 QQClient.servers 中的服务器
|
||||||
func (c *QQClient) connect() error {
|
func (c *QQClient) connect() error {
|
||||||
c.Info("connect to server: %v", c.servers[c.currServerIndex].String())
|
addr := c.servers[c.currServerIndex].String()
|
||||||
err := c.TCP.Connect(c.servers[c.currServerIndex])
|
c.info("connect to server: %v", addr)
|
||||||
|
err := c.TCP.Connect(addr)
|
||||||
c.currServerIndex++
|
c.currServerIndex++
|
||||||
if c.currServerIndex == len(c.servers) {
|
if c.currServerIndex == len(c.servers) {
|
||||||
c.currServerIndex = 0
|
c.currServerIndex = 0
|
||||||
@ -98,19 +101,19 @@ func (c *QQClient) connect() error {
|
|||||||
if c.retryTimes > len(c.servers) {
|
if c.retryTimes > len(c.servers) {
|
||||||
return errors.New("All servers are unreachable")
|
return errors.New("All servers are unreachable")
|
||||||
}
|
}
|
||||||
c.Error("connect server error: %v", err)
|
c.error("connect server error: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.once.Do(func() {
|
c.once.Do(func() {
|
||||||
c.OnGroupMessage(func(_ *QQClient, _ *message.GroupMessage) {
|
c.GroupMessageEvent.Subscribe(func(_ *QQClient, _ *message.GroupMessage) {
|
||||||
c.stat.MessageReceived.Add(1)
|
c.stat.MessageReceived.Add(1)
|
||||||
c.stat.LastMessageTime.Store(time.Now().Unix())
|
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.MessageReceived.Add(1)
|
||||||
c.stat.LastMessageTime.Store(time.Now().Unix())
|
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.MessageReceived.Add(1)
|
||||||
c.stat.LastMessageTime.Store(time.Now().Unix())
|
c.stat.LastMessageTime.Store(time.Now().Unix())
|
||||||
})
|
})
|
||||||
@ -129,14 +132,14 @@ func (c *QQClient) quickReconnect() {
|
|||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
time.Sleep(time.Millisecond * 200)
|
time.Sleep(time.Millisecond * 200)
|
||||||
if err := c.connect(); err != nil {
|
if err := c.connect(); err != nil {
|
||||||
c.Error("connect server error: %v", err)
|
c.error("connect server error: %v", err)
|
||||||
c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "quick reconnect failed"})
|
c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "quick reconnect failed"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := c.registerClient(); err != nil {
|
if err := c.registerClient(); err != nil {
|
||||||
c.Error("register client failed: %v", err)
|
c.error("register client failed: %v", err)
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "register error"})
|
c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "register error"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,9 +151,9 @@ func (c *QQClient) Disconnect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sendAndWait 向服务器发送一个数据包, 并等待返回
|
// 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 {
|
type T struct {
|
||||||
Response interface{}
|
Response any
|
||||||
Error error
|
Error error
|
||||||
}
|
}
|
||||||
ch := make(chan T, 1)
|
ch := make(chan T, 1)
|
||||||
@ -160,7 +163,7 @@ func (c *QQClient) sendAndWait(seq uint16, pkt []byte, params ...network.Request
|
|||||||
p = params[0]
|
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{
|
ch <- T{
|
||||||
Response: i,
|
Response: i,
|
||||||
Error: err,
|
Error: err,
|
||||||
@ -204,7 +207,7 @@ func (c *QQClient) sendPacket(pkt []byte) error {
|
|||||||
// waitPacket
|
// waitPacket
|
||||||
// 等待一个或多个数据包解析, 优先级低于 sendAndWait
|
// 等待一个或多个数据包解析, 优先级低于 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)
|
c.waiters.Store(cmd, f)
|
||||||
return func() {
|
return func() {
|
||||||
c.waiters.Delete(cmd)
|
c.waiters.Delete(cmd)
|
||||||
@ -213,9 +216,9 @@ func (c *QQClient) waitPacket(cmd string, f func(interface{}, error)) func() {
|
|||||||
|
|
||||||
// waitPacketTimeoutSyncF
|
// waitPacketTimeoutSyncF
|
||||||
// 等待一个数据包解析, 优先级低于 sendAndWait
|
// 等待一个数据包解析, 优先级低于 sendAndWait
|
||||||
func (c *QQClient) waitPacketTimeoutSyncF(cmd string, timeout time.Duration, filter func(interface{}) bool) (r interface{}, e error) {
|
func (c *QQClient) waitPacketTimeoutSyncF(cmd string, timeout time.Duration, filter func(any) bool) (r any, e error) {
|
||||||
notifyChan := make(chan bool)
|
notifyChan := make(chan bool, 4)
|
||||||
defer c.waitPacket(cmd, func(i interface{}, err error) {
|
defer c.waitPacket(cmd, func(i any, err error) {
|
||||||
if filter(i) {
|
if filter(i) {
|
||||||
r = i
|
r = i
|
||||||
e = err
|
e = err
|
||||||
@ -234,7 +237,7 @@ func (c *QQClient) waitPacketTimeoutSyncF(cmd string, timeout time.Duration, fil
|
|||||||
// 发送数据包并返回需要解析的 response
|
// 发送数据包并返回需要解析的 response
|
||||||
func (c *QQClient) sendAndWaitDynamic(seq uint16, pkt []byte) ([]byte, error) {
|
func (c *QQClient) sendAndWaitDynamic(seq uint16, pkt []byte) ([]byte, error) {
|
||||||
ch := make(chan []byte, 1)
|
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)
|
err := c.sendPacket(pkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.handlers.Delete(seq)
|
c.handlers.Delete(seq)
|
||||||
@ -251,25 +254,25 @@ func (c *QQClient) sendAndWaitDynamic(seq uint16, pkt []byte) ([]byte, error) {
|
|||||||
|
|
||||||
// plannedDisconnect 计划中断线事件
|
// plannedDisconnect 计划中断线事件
|
||||||
func (c *QQClient) plannedDisconnect(_ *network.TCPListener) {
|
func (c *QQClient) plannedDisconnect(_ *network.TCPListener) {
|
||||||
c.Debug("planned disconnect.")
|
c.debug("planned disconnect.")
|
||||||
c.stat.DisconnectTimes.Add(1)
|
c.stat.DisconnectTimes.Add(1)
|
||||||
c.Online.Store(false)
|
c.Online.Store(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// unexpectedDisconnect 非预期断线事件
|
// unexpectedDisconnect 非预期断线事件
|
||||||
func (c *QQClient) unexpectedDisconnect(_ *network.TCPListener, e error) {
|
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.stat.DisconnectTimes.Add(1)
|
||||||
c.Online.Store(false)
|
c.Online.Store(false)
|
||||||
if err := c.connect(); err != nil {
|
if err := c.connect(); err != nil {
|
||||||
c.Error("connect server error: %v", err)
|
c.error("connect server error: %v", err)
|
||||||
c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "connection dropped by server."})
|
c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "connection dropped by server."})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := c.registerClient(); err != nil {
|
if err := c.registerClient(); err != nil {
|
||||||
c.Error("register client failed: %v", err)
|
c.error("register client failed: %v", err)
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "register error"})
|
c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "register error"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,7 +287,7 @@ func (c *QQClient) netLoop() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if l < 4 || l > 1024*1024*10 { // max 10MB
|
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++
|
errCount++
|
||||||
if errCount > 2 {
|
if errCount > 2 {
|
||||||
go c.quickReconnect()
|
go c.quickReconnect()
|
||||||
@ -295,10 +298,10 @@ func (c *QQClient) netLoop() {
|
|||||||
resp, err := c.transport.ReadResponse(data)
|
resp, err := c.transport.ReadResponse(data)
|
||||||
// pkt, err := packets.ParseIncomingPacket(data, c.sig.D2Key)
|
// pkt, err := packets.ParseIncomingPacket(data, c.sig.D2Key)
|
||||||
if err != nil {
|
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) {
|
if errors.Is(err, network.ErrSessionExpired) || errors.Is(err, network.ErrPacketDropped) {
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
go c.dispatchDisconnectEvent(&ClientDisconnectedEvent{Message: "session expired"})
|
go c.DisconnectedEvent.dispatch(c, &ClientDisconnectedEvent{Message: "session expired"})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
errCount++
|
errCount++
|
||||||
@ -310,7 +313,7 @@ func (c *QQClient) netLoop() {
|
|||||||
if resp.EncryptType == network.EncryptTypeEmptyKey {
|
if resp.EncryptType == network.EncryptTypeEmptyKey {
|
||||||
m, err := c.oicq.Unmarshal(resp.Body)
|
m, err := c.oicq.Unmarshal(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error("decrypt payload error: %v", err)
|
c.error("decrypt payload error: %v", err)
|
||||||
if errors.Is(err, oicq.ErrUnknownFlag) {
|
if errors.Is(err, oicq.ErrUnknownFlag) {
|
||||||
go c.quickReconnect()
|
go c.quickReconnect()
|
||||||
}
|
}
|
||||||
@ -319,7 +322,7 @@ func (c *QQClient) netLoop() {
|
|||||||
resp.Body = m.Body
|
resp.Body = m.Body
|
||||||
}
|
}
|
||||||
errCount = 0
|
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)
|
c.stat.PacketReceived.Add(1)
|
||||||
pkt := &packets.IncomingPacket{
|
pkt := &packets.IncomingPacket{
|
||||||
SequenceId: uint16(resp.SequenceID),
|
SequenceId: uint16(resp.SequenceID),
|
||||||
@ -329,15 +332,15 @@ func (c *QQClient) netLoop() {
|
|||||||
go func(pkt *packets.IncomingPacket) {
|
go func(pkt *packets.IncomingPacket) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if pan := recover(); pan != nil {
|
if pan := recover(); pan != nil {
|
||||||
c.Error("panic on decoder %v : %v\n%s", pkt.CommandName, pan, debug.Stack())
|
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.dump("packet decode error: %v - %v", pkt.Payload, pkt.CommandName, pan)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if decoder, ok := decoders[pkt.CommandName]; ok {
|
if decoder, ok := decoders[pkt.CommandName]; ok {
|
||||||
// found predefined decoder
|
// found predefined decoder
|
||||||
info, ok := c.handlers.LoadAndDelete(pkt.SequenceId)
|
info, ok := c.handlers.LoadAndDelete(pkt.SequenceId)
|
||||||
var decoded interface{}
|
var decoded any
|
||||||
decoded = pkt.Payload
|
decoded = pkt.Payload
|
||||||
if info == nil || !info.dynamic {
|
if info == nil || !info.dynamic {
|
||||||
decoded, err = decoder(c, &network.IncomingPacketInfo{
|
decoded, err = decoder(c, &network.IncomingPacketInfo{
|
||||||
@ -346,19 +349,19 @@ func (c *QQClient) netLoop() {
|
|||||||
Params: info.getParams(),
|
Params: info.getParams(),
|
||||||
}, pkt.Payload)
|
}, pkt.Payload)
|
||||||
if err != nil {
|
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 {
|
if ok {
|
||||||
info.fun(decoded, err)
|
info.fun(decoded, err)
|
||||||
} else if f, ok := c.waiters.Load(pkt.CommandName); ok { // 在不存在handler的情况下触发wait
|
} 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 {
|
} else if f, ok := c.handlers.LoadAndDelete(pkt.SequenceId); ok {
|
||||||
// does not need decoder
|
// does not need decoder
|
||||||
f.fun(pkt.Payload, nil)
|
f.fun(pkt.Payload, nil)
|
||||||
} else {
|
} 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)
|
}(pkt)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func (c *QQClient) grayTipProcessor(groupCode int64, tipInfo *notify.GeneralGray
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if sender != 0 {
|
if sender != 0 {
|
||||||
c.dispatchGroupNotifyEvent(&GroupPokeNotifyEvent{
|
c.GroupNotifyEvent.dispatch(c, &GroupPokeNotifyEvent{
|
||||||
GroupCode: groupCode,
|
GroupCode: groupCode,
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
Receiver: receiver,
|
Receiver: receiver,
|
||||||
@ -81,7 +81,7 @@ func (c *QQClient) grayTipProcessor(groupCode int64, tipInfo *notify.GeneralGray
|
|||||||
uin, _ = strconv.ParseInt(templ.Value, 10, 64)
|
uin, _ = strconv.ParseInt(templ.Value, 10, 64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.dispatchGroupNotifyEvent(&MemberHonorChangedNotifyEvent{
|
c.GroupNotifyEvent.dispatch(c, &MemberHonorChangedNotifyEvent{
|
||||||
GroupCode: groupCode,
|
GroupCode: groupCode,
|
||||||
Honor: func() HonorType {
|
Honor: func() HonorType {
|
||||||
switch tipInfo.TemplId {
|
switch tipInfo.TemplId {
|
||||||
@ -139,13 +139,13 @@ func (c *QQClient) msgGrayTipProcessor(groupCode int64, tipInfo *notify.AIOGrayT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if event.Uin == 0 {
|
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
|
return
|
||||||
}
|
}
|
||||||
if mem := c.FindGroup(groupCode).FindMember(event.Uin); mem != nil {
|
if mem := c.FindGroup(groupCode).FindMember(event.Uin); mem != nil {
|
||||||
mem.SpecialTitle = event.NewTitle
|
mem.SpecialTitle = event.NewTitle
|
||||||
}
|
}
|
||||||
c.dispatchMemberSpecialTitleUpdateEvent(event)
|
c.MemberSpecialTitleUpdatedEvent.dispatch(c, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,18 +33,18 @@ func (c *QQClient) buildOfflineFileDownloadRequestPacket(uuid []byte) (uint16, [
|
|||||||
return seq, packet
|
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{}
|
rsp := cmd0x346.C346RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
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")
|
return nil, errors.Wrap(err, "unmarshal cmd0x346 rsp body error")
|
||||||
}
|
}
|
||||||
if rsp.ApplyDownloadRsp == nil {
|
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")
|
return nil, errors.New("apply rsp is nil")
|
||||||
}
|
}
|
||||||
if rsp.ApplyDownloadRsp.RetCode != 0 {
|
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 nil, errors.Errorf("apply download rsp error: %d", rsp.ApplyDownloadRsp.RetCode)
|
||||||
}
|
}
|
||||||
return "http://" + rsp.ApplyDownloadRsp.DownloadInfo.DownloadDomain + rsp.ApplyDownloadRsp.DownloadInfo.DownloadUrl, nil
|
return "http://" + rsp.ApplyDownloadRsp.DownloadInfo.DownloadDomain + rsp.ApplyDownloadRsp.DownloadInfo.DownloadUrl, nil
|
||||||
|
@ -23,7 +23,7 @@ var msg0x210Decoders = map[int64]func(*QQClient, []byte) error{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OnlinePush.ReqPush
|
// 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 := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -37,7 +37,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa
|
|||||||
if _, ok := c.onlinePushCache.Get(k); ok {
|
if _, ok := c.onlinePushCache.Get(k); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.onlinePushCache.Add(k, "", time.Second*30)
|
c.onlinePushCache.Add(k, unit{}, time.Second*30)
|
||||||
// 0x2dc
|
// 0x2dc
|
||||||
if m.MsgType == 732 {
|
if m.MsgType == 732 {
|
||||||
r := binary.NewReader(m.VMsg)
|
r := binary.NewReader(m.VMsg)
|
||||||
@ -53,7 +53,17 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa
|
|||||||
r.ReadBytes(6)
|
r.ReadBytes(6)
|
||||||
target := int64(uint32(r.ReadInt32()))
|
target := int64(uint32(r.ReadInt32()))
|
||||||
t := 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,
|
GroupCode: groupCode,
|
||||||
OperatorUin: operator,
|
OperatorUin: operator,
|
||||||
TargetUin: target,
|
TargetUin: target,
|
||||||
@ -68,7 +78,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa
|
|||||||
if rm.MsgType == 2 {
|
if rm.MsgType == 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.dispatchGroupMessageRecalledEvent(&GroupMessageRecalledEvent{
|
c.GroupMessageRecalledEvent.dispatch(c, &GroupMessageRecalledEvent{
|
||||||
GroupCode: groupCode,
|
GroupCode: groupCode,
|
||||||
OperatorUin: b.OptMsgRecall.Uin,
|
OperatorUin: b.OptMsgRecall.Uin,
|
||||||
AuthorUin: rm.AuthorUin,
|
AuthorUin: rm.AuthorUin,
|
||||||
@ -82,7 +92,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa
|
|||||||
}
|
}
|
||||||
if b.OptMsgRedTips != nil {
|
if b.OptMsgRedTips != nil {
|
||||||
if b.OptMsgRedTips.LuckyFlag == 1 { // 运气王提示
|
if b.OptMsgRedTips.LuckyFlag == 1 { // 运气王提示
|
||||||
c.dispatchGroupNotifyEvent(&GroupRedBagLuckyKingNotifyEvent{
|
c.GroupNotifyEvent.dispatch(c, &GroupRedBagLuckyKingNotifyEvent{
|
||||||
GroupCode: groupCode,
|
GroupCode: groupCode,
|
||||||
Sender: int64(b.OptMsgRedTips.SenderUin),
|
Sender: int64(b.OptMsgRedTips.SenderUin),
|
||||||
LuckyKing: int64(b.OptMsgRedTips.LuckyUin),
|
LuckyKing: int64(b.OptMsgRedTips.LuckyUin),
|
||||||
@ -91,7 +101,7 @@ func decodeOnlinePushReqPacket(c *QQClient, info *network.IncomingPacketInfo, pa
|
|||||||
}
|
}
|
||||||
if b.QqGroupDigestMsg != nil {
|
if b.QqGroupDigestMsg != nil {
|
||||||
digest := b.QqGroupDigestMsg
|
digest := b.QqGroupDigestMsg
|
||||||
c.dispatchGroupDigestEvent(&GroupDigestEvent{
|
c.GroupDigestEvent.dispatch(c, &GroupDigestEvent{
|
||||||
GroupCode: int64(digest.GroupCode),
|
GroupCode: int64(digest.GroupCode),
|
||||||
MessageID: int32(digest.Seq),
|
MessageID: int32(digest.Seq),
|
||||||
InternalMessageID: int32(digest.Random),
|
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")
|
return nil, errors.Wrap(err, "decode online push 0x210 error")
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
for _, m := range s8a.MsgInfo {
|
||||||
if m.ToUin == c.Uin {
|
if m.ToUin == c.Uin {
|
||||||
c.dispatchFriendMessageRecalledEvent(&FriendMessageRecalledEvent{
|
c.FriendMessageRecalledEvent.dispatch(c, &FriendMessageRecalledEvent{
|
||||||
FriendUin: m.FromUin,
|
FriendUin: m.FromUin,
|
||||||
MessageId: m.MsgSeq,
|
MessageId: m.MsgSeq,
|
||||||
Time: m.MsgTime,
|
Time: m.MsgTime,
|
||||||
@ -152,7 +162,7 @@ func msgType0x210SubB3Decoder(c *QQClient, protobuf []byte) error {
|
|||||||
Nickname: b3.MsgAddFrdNotify.Nick,
|
Nickname: b3.MsgAddFrdNotify.Nick,
|
||||||
}
|
}
|
||||||
c.FriendList = append(c.FriendList, frd)
|
c.FriendList = append(c.FriendList, frd)
|
||||||
c.dispatchNewFriendEvent(&NewFriendEvent{Friend: frd})
|
c.NewFriendEvent.dispatch(c, &NewFriendEvent{Friend: frd})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +177,7 @@ func msgType0x210SubD4Decoder(c *QQClient, protobuf []byte) error {
|
|||||||
groupLeaveLock.Unlock()
|
groupLeaveLock.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.dispatchLeaveGroupEvent(&GroupLeaveEvent{Group: g})
|
c.GroupLeaveEvent.dispatch(c, &GroupLeaveEvent{Group: g})
|
||||||
}
|
}
|
||||||
groupLeaveLock.Unlock()
|
groupLeaveLock.Unlock()
|
||||||
return nil
|
return nil
|
||||||
@ -185,7 +195,7 @@ func msgType0x210Sub27Decoder(c *QQClient, protobuf []byte) error {
|
|||||||
if g := c.FindGroup(int64(m.ModGroupProfile.GetGroupCode())); g != nil {
|
if g := c.FindGroup(int64(m.ModGroupProfile.GetGroupCode())); g != nil {
|
||||||
old := g.Name
|
old := g.Name
|
||||||
g.Name = string(info.Value)
|
g.Name = string(info.Value)
|
||||||
c.dispatchGroupNameUpdatedEvent(&GroupNameUpdatedEvent{
|
c.GroupNameUpdatedEvent.dispatch(c, &GroupNameUpdatedEvent{
|
||||||
Group: g,
|
Group: g,
|
||||||
OldName: old,
|
OldName: old,
|
||||||
NewName: g.Name,
|
NewName: g.Name,
|
||||||
@ -221,7 +231,7 @@ func msgType0x210Sub122Decoder(c *QQClient, protobuf []byte) error {
|
|||||||
if sender == 0 {
|
if sender == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
c.dispatchFriendNotifyEvent(&FriendPokeNotifyEvent{
|
c.FriendNotifyEvent.dispatch(c, &FriendPokeNotifyEvent{
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
Receiver: receiver,
|
Receiver: receiver,
|
||||||
})
|
})
|
||||||
@ -241,7 +251,7 @@ func msgType0x210Sub44Decoder(c *QQClient, protobuf []byte) error {
|
|||||||
if s44.GroupSyncMsg.GrpCode == 0 { // member sync
|
if s44.GroupSyncMsg.GrpCode == 0 { // member sync
|
||||||
return errors.New("invalid group code")
|
return errors.New("invalid group code")
|
||||||
}
|
}
|
||||||
c.Debug("syncing members.")
|
c.debug("syncing members.")
|
||||||
if group := c.FindGroup(s44.GroupSyncMsg.GrpCode); group != nil {
|
if group := c.FindGroup(s44.GroupSyncMsg.GrpCode); group != nil {
|
||||||
group.lock.Lock()
|
group.lock.Lock()
|
||||||
defer group.lock.Unlock()
|
defer group.lock.Unlock()
|
||||||
@ -257,7 +267,7 @@ func msgType0x210Sub44Decoder(c *QQClient, protobuf []byte) error {
|
|||||||
group.Members = newMem
|
group.Members = newMem
|
||||||
for _, m := range newMem {
|
for _, m := range newMem {
|
||||||
if lastJoinTime < m.JoinTime {
|
if lastJoinTime < m.JoinTime {
|
||||||
go c.dispatchNewMemberEvent(&MemberJoinGroupEvent{
|
c.GroupMemberJoinEvent.dispatch(c, &MemberJoinGroupEvent{
|
||||||
Group: group,
|
Group: group,
|
||||||
Member: m,
|
Member: m,
|
||||||
})
|
})
|
||||||
|
@ -61,7 +61,7 @@ func (c *QQClient) SendPrivateMessage(target int64, m *message.SendingMessage) *
|
|||||||
},
|
},
|
||||||
Elements: m.Elements,
|
Elements: m.Elements,
|
||||||
}
|
}
|
||||||
go c.dispatchPrivateMessageSelf(ret)
|
go c.SelfPrivateMessageEvent.dispatch(c, ret)
|
||||||
return 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) {
|
func (c *QQClient) buildWPATempSendingPacket(uin int64, sig []byte, msgSeq, r int32, time int64, m *message.SendingMessage) (uint16, []byte) {
|
||||||
|
|
||||||
req := &msg.SendMessageRequest{
|
req := &msg.SendMessageRequest{
|
||||||
RoutingHead: &msg.RoutingHead{WpaTmp: &msg.WPATmp{
|
RoutingHead: &msg.RoutingHead{WpaTmp: &msg.WPATmp{
|
||||||
ToUin: proto.Uint64(uint64(uin)),
|
ToUin: proto.Uint64(uint64(uin)),
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"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))
|
ext = c.buildGroupPttStoreBDHExt(target.PrimaryID, fh, int32(length), 0, int32(length))
|
||||||
}
|
}
|
||||||
// multi-thread upload is no need
|
// multi-thread upload is no need
|
||||||
rsp, err := c.highwaySession.UploadBDH(highway.BdhInput{
|
rsp, err := c.highwaySession.UploadBDH(highway.Transaction{
|
||||||
CommandID: cmd,
|
CommandID: cmd,
|
||||||
Body: voice,
|
Body: voice,
|
||||||
|
Sum: fh,
|
||||||
|
Size: length,
|
||||||
Ticket: c.highwaySession.SigSession,
|
Ticket: c.highwaySession.SigSession,
|
||||||
Ext: ext,
|
Ext: ext,
|
||||||
Encrypt: false,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -88,7 +91,7 @@ func (c *QQClient) UploadVoice(target message.Source, voice io.ReadSeeker) (*mes
|
|||||||
FileType: proto.Int32(4),
|
FileType: proto.Int32(4),
|
||||||
SrcUin: &c.Uin,
|
SrcUin: &c.Uin,
|
||||||
FileMd5: fh,
|
FileMd5: fh,
|
||||||
FileName: proto.String(hex.EncodeToString(fh) + ".amr"),
|
FileName: proto.String(fmt.Sprintf("%x.amr", fh)),
|
||||||
FileSize: proto.Int32(int32(length)),
|
FileSize: proto.Int32(int32(length)),
|
||||||
BoolValid: proto.Bool(true),
|
BoolValid: proto.Bool(true),
|
||||||
}
|
}
|
||||||
@ -120,14 +123,17 @@ func (c *QQClient) UploadVoice(target message.Source, voice io.ReadSeeker) (*mes
|
|||||||
// UploadShortVideo 将视频和封面上传到服务器, 返回 message.ShortVideoElement 可直接发送
|
// UploadShortVideo 将视频和封面上传到服务器, 返回 message.ShortVideoElement 可直接发送
|
||||||
// thread 上传线程数
|
// thread 上传线程数
|
||||||
func (c *QQClient) UploadShortVideo(target message.Source, video, thumb io.ReadSeeker, thread int) (*message.ShortVideoElement, error) {
|
func (c *QQClient) UploadShortVideo(target message.Source, video, thumb io.ReadSeeker, thread int) (*message.ShortVideoElement, error) {
|
||||||
videoHash, videoLen := utils.ComputeMd5AndLength(video)
|
thumbHash := md5.New()
|
||||||
thumbHash, thumbLen := utils.ComputeMd5AndLength(thumb)
|
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)
|
pttWaiter.Wait(key)
|
||||||
defer pttWaiter.Done(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 {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "upload req error")
|
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{
|
videoElement := &message.ShortVideoElement{
|
||||||
Size: int32(videoLen),
|
Size: int32(videoLen),
|
||||||
ThumbSize: int32(thumbLen),
|
ThumbSize: int32(thumbLen),
|
||||||
Md5: videoHash,
|
Md5: videoSum,
|
||||||
ThumbMd5: thumbHash,
|
ThumbMd5: thumbSum,
|
||||||
Guild: target.SourceType == message.SourceGuildChannel,
|
Guild: target.SourceType == message.SourceGuildChannel,
|
||||||
}
|
}
|
||||||
if rsp.FileExists == 1 {
|
if rsp.FileExists == 1 {
|
||||||
@ -149,28 +155,22 @@ func (c *QQClient) UploadShortVideo(target message.Source, video, thumb io.ReadS
|
|||||||
if target.SourceType == message.SourceGuildChannel {
|
if target.SourceType == message.SourceGuildChannel {
|
||||||
cmd = 89
|
cmd = 89
|
||||||
}
|
}
|
||||||
ext, _ := proto.Marshal(c.buildPttShortVideoProto(target, videoHash, thumbHash, videoLen, thumbLen).PttShortVideoUploadReq)
|
ext, _ := proto.Marshal(c.buildPttShortVideoProto(target, videoSum, thumbSum, videoLen, thumbLen).PttShortVideoUploadReq)
|
||||||
if thread > 1 {
|
_, _ = thumb.Seek(0, io.SeekStart)
|
||||||
sum, _ := utils.ComputeMd5AndLength(utils.MultiReadSeeker(thumb, video))
|
_, _ = video.Seek(0, io.SeekStart)
|
||||||
input := highway.BdhMultiThreadInput{
|
combined := io.MultiReader(thumb, video)
|
||||||
|
input := highway.Transaction{
|
||||||
CommandID: cmd,
|
CommandID: cmd,
|
||||||
Body: utils.ReaderAtFrom2ReadSeeker(thumb, video),
|
Body: combined,
|
||||||
Size: videoLen + thumbLen,
|
Size: videoLen + thumbLen,
|
||||||
Sum: sum,
|
Sum: sum,
|
||||||
Ticket: c.highwaySession.SigSession,
|
Ticket: c.highwaySession.SigSession,
|
||||||
Ext: ext,
|
Ext: ext,
|
||||||
Encrypt: true,
|
Encrypt: true,
|
||||||
}
|
}
|
||||||
|
if thread > 1 {
|
||||||
hwRsp, err = c.highwaySession.UploadBDHMultiThread(input, thread)
|
hwRsp, err = c.highwaySession.UploadBDHMultiThread(input, thread)
|
||||||
} else {
|
} 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)
|
hwRsp, err = c.highwaySession.UploadBDH(input)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -262,7 +262,7 @@ func (c *QQClient) buildPttShortVideoProto(target message.Source, videoHash, thu
|
|||||||
ChatType: chatType,
|
ChatType: chatType,
|
||||||
ClientType: 2,
|
ClientType: 2,
|
||||||
Info: &pttcenter.ShortVideoFileInfo{
|
Info: &pttcenter.ShortVideoFileInfo{
|
||||||
FileName: hex.EncodeToString(videoHash) + ".mp4",
|
FileName: fmt.Sprintf("%x.mp4", videoHash),
|
||||||
FileMd5: videoHash,
|
FileMd5: videoHash,
|
||||||
ThumbFileMd5: thumbHash,
|
ThumbFileMd5: thumbHash,
|
||||||
FileSize: videoSize,
|
FileSize: videoSize,
|
||||||
@ -326,7 +326,7 @@ func (c *QQClient) buildC2CPttStoreBDHExt(target int64, md5 []byte, size, voiceL
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PttCenterSvr.ShortVideoDownReq
|
// PttCenterSvr.ShortVideoDownReq
|
||||||
func decodePttShortVideoDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodePttShortVideoDownResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
rsp := pttcenter.ShortVideoRspBody{}
|
rsp := pttcenter.ShortVideoRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
@ -338,7 +338,7 @@ func decodePttShortVideoDownResponse(_ *QQClient, _ *network.IncomingPacketInfo,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PttCenterSvr.GroupShortVideoUpReq
|
// PttCenterSvr.GroupShortVideoUpReq
|
||||||
func decodeGroupShortVideoUploadResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeGroupShortVideoUploadResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
rsp := pttcenter.ShortVideoRspBody{}
|
rsp := pttcenter.ShortVideoRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
|
@ -52,7 +52,7 @@ func (c *QQClient) getQiDianAddressDetailList() ([]*FriendInfo, error) {
|
|||||||
ret := []*FriendInfo{}
|
ret := []*FriendInfo{}
|
||||||
for _, detail := range rsp.GetAddressDetailListRspBody.AddressDetail {
|
for _, detail := range rsp.GetAddressDetailListRspBody.AddressDetail {
|
||||||
if len(detail.Qq) == 0 {
|
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
|
continue
|
||||||
}
|
}
|
||||||
ret = append(ret, &FriendInfo{
|
ret = append(ret, &FriendInfo{
|
||||||
@ -152,7 +152,7 @@ func (c *QQClient) bigDataRequest(subCmd uint32, req proto.Message) ([]byte, err
|
|||||||
return tea.Decrypt(payload), nil
|
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{}
|
rsp := cmd0x3f6.C3F6RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
@ -168,7 +168,7 @@ func decodeLoginExtraResponse(c *QQClient, _ *network.IncomingPacketInfo, payloa
|
|||||||
return nil, nil
|
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{}
|
rsp := cmd0x6ff.C501RspBody{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
|
@ -92,7 +92,7 @@ func (c *QQClient) buildPrivateRecallPacket(uin, ts int64, msgSeq, random int32)
|
|||||||
return c.uniPacket("PbMessageSvc.PbMsgWithDraw", payload)
|
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{}
|
rsp := msg.MsgWithDrawResp{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
|
@ -66,7 +66,7 @@ var musicType = [...]musicTypeInfo{
|
|||||||
|
|
||||||
// SendGroupMusicShare 发送群聊音乐卡片
|
// SendGroupMusicShare 发送群聊音乐卡片
|
||||||
func (c *QQClient) SendGroupMusicShare(target int64, msg *message.MusicShareElement) (*message.GroupMessage, error) {
|
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)
|
eid := utils.RandomString(6)
|
||||||
c.onGroupMessageReceipt(eid, func(c *QQClient, e *groupMessageReceiptEvent) {
|
c.onGroupMessageReceipt(eid, func(c *QQClient, e *groupMessageReceiptEvent) {
|
||||||
for _, elem := range e.Msg.Elements {
|
for _, elem := range e.Msg.Elements {
|
||||||
|
@ -48,14 +48,11 @@ func (c *QQClient) buildUrlCheckRequest(url string) (uint16, []byte) {
|
|||||||
return c.uniPacket("OidbSvc.0xbcb_0", payload)
|
return c.uniPacket("OidbSvc.0xbcb_0", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeUrlCheckResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeUrlCheckResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := &oidb.OIDBSSOPkg{}
|
|
||||||
rsp := &oidb.DBCBRspBody{}
|
rsp := &oidb.DBCBRspBody{}
|
||||||
if err := proto.Unmarshal(payload, pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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 rsp.CheckUrlRsp == nil || len(rsp.CheckUrlRsp.Results) == 0 {
|
if rsp.CheckUrlRsp == nil || len(rsp.CheckUrlRsp.Results) == 0 {
|
||||||
return nil, errors.New("response is empty")
|
return nil, errors.New("response is empty")
|
||||||
|
@ -77,9 +77,9 @@ func (c *QQClient) RefreshStatus() error {
|
|||||||
// SyncSessions 同步会话列表
|
// SyncSessions 同步会话列表
|
||||||
func (c *QQClient) SyncSessions() (*SessionSyncResponse, error) {
|
func (c *QQClient) SyncSessions() (*SessionSyncResponse, error) {
|
||||||
ret := &SessionSyncResponse{}
|
ret := &SessionSyncResponse{}
|
||||||
notifyChan := make(chan bool)
|
notifyChan := make(chan bool, 4)
|
||||||
var groupNum int32 = -1
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ func (c *QQClient) SyncSessions() (*SessionSyncResponse, error) {
|
|||||||
if e.GroupNum != -1 {
|
if e.GroupNum != -1 {
|
||||||
groupNum = e.GroupNum
|
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) {
|
if groupNum != -1 && len(ret.GroupSessions) >= int(groupNum) {
|
||||||
notifyChan <- true
|
notifyChan <- true
|
||||||
}
|
}
|
||||||
@ -285,7 +285,7 @@ func (c *QQClient) buildPrivateMsgReadedPacket(uin, time int64) (uint16, []byte)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StatSvc.GetDevLoginInfo
|
// StatSvc.GetDevLoginInfo
|
||||||
func decodeDevListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeDevListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
request := &jce.RequestPacket{}
|
request := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -307,7 +307,7 @@ func decodeDevListResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload [
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegPrxySvc.PushParam
|
// 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 := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -352,7 +352,7 @@ func decodePushParamPacket(c *QQClient, _ *network.IncomingPacketInfo, payload [
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegPrxySvc.PbSyncMsg
|
// 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{}
|
rsp := &msf.SvcRegisterProxyMsgResp{}
|
||||||
if err := proto.Unmarshal(payload, rsp); err != nil {
|
if err := proto.Unmarshal(payload, rsp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -396,7 +396,7 @@ func decodeMsgSyncResponse(c *QQClient, info *network.IncomingPacketInfo, payloa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OnlinePush.PbC2CMsgSync
|
// 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{}
|
m := msg.PbPushMsg{}
|
||||||
if err := proto.Unmarshal(payload, &m); err != nil {
|
if err := proto.Unmarshal(payload, &m); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -406,7 +406,7 @@ func decodeC2CSyncPacket(c *QQClient, info *network.IncomingPacketInfo, payload
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeMsgReadedResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeMsgReadedResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
rsp := msg.PbMsgReadedReportResp{}
|
rsp := msg.PbMsgReadedReportResp{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
||||||
@ -420,7 +420,7 @@ func decodeMsgReadedResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload
|
|||||||
var loginNotifyLock sync.Mutex
|
var loginNotifyLock sync.Mutex
|
||||||
|
|
||||||
// StatSvc.SvcReqMSFLoginNotify
|
// 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 := &jce.RequestPacket{}
|
||||||
request.ReadFrom(jce.NewJceReader(payload))
|
request.ReadFrom(jce.NewJceReader(payload))
|
||||||
data := &jce.RequestDataVersion2{}
|
data := &jce.RequestDataVersion2{}
|
||||||
@ -443,7 +443,7 @@ func decodeLoginNotifyPacket(c *QQClient, _ *network.IncomingPacketInfo, payload
|
|||||||
t := ac
|
t := ac
|
||||||
if ac.AppId == notify.AppId {
|
if ac.AppId == notify.AppId {
|
||||||
c.OnlineClients = append(c.OnlineClients, t)
|
c.OnlineClients = append(c.OnlineClients, t)
|
||||||
c.dispatchOtherClientStatusChangedEvent(&OtherClientStatusChangedEvent{
|
c.OtherClientStatusChangedEvent.dispatch(c, &OtherClientStatusChangedEvent{
|
||||||
Client: t,
|
Client: t,
|
||||||
Online: true,
|
Online: true,
|
||||||
})
|
})
|
||||||
@ -462,7 +462,7 @@ func decodeLoginNotifyPacket(c *QQClient, _ *network.IncomingPacketInfo, payload
|
|||||||
if rmi != -1 {
|
if rmi != -1 {
|
||||||
rmc := c.OnlineClients[rmi]
|
rmc := c.OnlineClients[rmi]
|
||||||
c.OnlineClients = append(c.OnlineClients[:rmi], c.OnlineClients[rmi+1:]...)
|
c.OnlineClients = append(c.OnlineClients[:rmi], c.OnlineClients[rmi+1:]...)
|
||||||
c.dispatchOtherClientStatusChangedEvent(&OtherClientStatusChangedEvent{
|
c.OtherClientStatusChangedEvent.dispatch(c, &OtherClientStatusChangedEvent{
|
||||||
Client: rmc,
|
Client: rmc,
|
||||||
Online: false,
|
Online: false,
|
||||||
})
|
})
|
||||||
|
@ -60,7 +60,7 @@ func (c *QQClient) GetGroupSystemMessages() (*GroupSystemMessages, error) {
|
|||||||
|
|
||||||
func (c *QQClient) exceptAndDispatchGroupSysMsg() {
|
func (c *QQClient) exceptAndDispatchGroupSysMsg() {
|
||||||
if c.groupSysMsgCache == nil {
|
if c.groupSysMsgCache == nil {
|
||||||
c.Error("warning: groupSysMsgCache is nil")
|
c.error("warning: groupSysMsgCache is nil")
|
||||||
c.groupSysMsgCache, _ = c.GetGroupSystemMessages()
|
c.groupSysMsgCache, _ = c.GetGroupSystemMessages()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -86,12 +86,12 @@ func (c *QQClient) exceptAndDispatchGroupSysMsg() {
|
|||||||
}
|
}
|
||||||
for _, msg := range msgs.JoinRequests {
|
for _, msg := range msgs.JoinRequests {
|
||||||
if !joinExists(msg.RequestId) {
|
if !joinExists(msg.RequestId) {
|
||||||
c.dispatchJoinGroupRequest(msg)
|
c.UserWantJoinGroupEvent.dispatch(c, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, msg := range msgs.InvitedRequests {
|
for _, msg := range msgs.InvitedRequests {
|
||||||
if !invExists(msg.RequestId) {
|
if !invExists(msg.RequestId) {
|
||||||
c.dispatchGroupInvitedEvent(msg)
|
c.GroupInvitedEvent.dispatch(c, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.groupSysMsgCache = msgs
|
c.groupSysMsgCache = msgs
|
||||||
@ -190,7 +190,7 @@ func (c *QQClient) buildSystemMsgFriendActionPacket(reqID, requester int64, acce
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ProfileService.Pb.ReqSystemMsgNew.Group
|
// 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{}
|
rsp := structmsg.RspSystemMsgNew{}
|
||||||
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
if err := proto.Unmarshal(payload, &rsp); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -243,12 +243,12 @@ func decodeSystemMsgGroupPacket(c *QQClient, _ *network.IncomingPacketInfo, payl
|
|||||||
ActionUinNick: st.Msg.ActionUinQqNick,
|
ActionUinNick: st.Msg.ActionUinQqNick,
|
||||||
})
|
})
|
||||||
default:
|
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 3: // ?
|
||||||
case 5: // 自身状态变更(管理员/加群退群)
|
case 5: // 自身状态变更(管理员/加群退群)
|
||||||
default:
|
default:
|
||||||
c.Debug("unknown group system msg: %v", st.Msg.SubType)
|
c.debug("unknown group system msg: %v", st.Msg.SubType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret, nil
|
return ret, nil
|
||||||
|
@ -12,16 +12,16 @@ import (
|
|||||||
|
|
||||||
// --- tlv decoders for qq client ---
|
// --- tlv decoders for qq client ---
|
||||||
|
|
||||||
|
/*
|
||||||
func (c *QQClient) decodeT161(data []byte) {
|
func (c *QQClient) decodeT161(data []byte) {
|
||||||
/*
|
|
||||||
reader := binary.NewReader(data)
|
reader := binary.NewReader(data)
|
||||||
reader.ReadBytes(2)
|
reader.ReadBytes(2)
|
||||||
t := reader.ReadTlvMap(2)
|
t := reader.ReadTlvMap(2)
|
||||||
if t172, ok := t[0x172]; ok {
|
if t172, ok := t[0x172]; ok {
|
||||||
c.rollbackSig = t172
|
c.rollbackSig = t172
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (c *QQClient) decodeT119(data, ek []byte) {
|
func (c *QQClient) decodeT119(data, ek []byte) {
|
||||||
tea := binary.NewTeaCipher(ek)
|
tea := binary.NewTeaCipher(ek)
|
||||||
@ -63,32 +63,32 @@ func (c *QQClient) decodeT119(data, ek []byte) {
|
|||||||
pt4TokenMap map[string][]byte
|
pt4TokenMap map[string][]byte
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if t11a, ok := m[0x11a]; ok {
|
||||||
|
nick, age, gender = readT11A(t11a)
|
||||||
|
}
|
||||||
|
/*
|
||||||
if _, ok := m[0x125]; ok {
|
if _, ok := m[0x125]; ok {
|
||||||
// openId, openKey = readT125(t125)
|
openId, openKey = readT125(t125)
|
||||||
}
|
}
|
||||||
if t186, ok := m[0x186]; ok {
|
if t186, ok := m[0x186]; ok {
|
||||||
c.decodeT186(t186)
|
c.decodeT186(t186)
|
||||||
}
|
}
|
||||||
if t11a, ok := m[0x11a]; ok {
|
|
||||||
nick, age, gender = readT11A(t11a)
|
|
||||||
}
|
|
||||||
if _, ok := m[0x199]; ok {
|
if _, ok := m[0x199]; ok {
|
||||||
// openId, payToken = readT199(t199)
|
openId, payToken = readT199(t199)
|
||||||
}
|
}
|
||||||
if _, ok := m[0x200]; ok {
|
if _, ok := m[0x200]; ok {
|
||||||
// pf, pfkey = readT200(t200)
|
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 {
|
if t512, ok := m[0x512]; ok {
|
||||||
psKeyMap, pt4TokenMap = readT512(t512)
|
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)
|
c.oicq.WtSessionTicketKey = utils.Select(m[0x134], c.oicq.WtSessionTicketKey)
|
||||||
|
|
||||||
// we don't use `c.sigInfo = &auth.SigInfo{...}` here,
|
// 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 {
|
if t120, ok := m[0x120]; ok {
|
||||||
c.sig.SKey = t120
|
c.sig.SKey = t120
|
||||||
c.sig.SKeyExpiredTime = time.Now().Unix() + 21600
|
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 {
|
if t11a, ok := m[0x11a]; ok {
|
||||||
c.Nickname, c.Age, c.Gender = readT11A(t11a)
|
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)
|
fmt.Println("got t113 uin:", uin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (c *QQClient) decodeT186(data []byte) {
|
func (c *QQClient) decodeT186(data []byte) {
|
||||||
// c.pwdFlag = data[1] == 1
|
// c.pwdFlag = data[1] == 1
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// --- tlv readers ---
|
// --- tlv readers ---
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/client/internal/network"
|
"github.com/Mrs4s/MiraiGo/client/internal/network"
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *QQClient) buildTranslatePacket(src, dst, text string) (uint16, []byte) {
|
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},
|
SrcTextList: []string{text},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
b, _ := proto.Marshal(body)
|
payload := c.packOIDBPackageProto(2448, 2, body)
|
||||||
req := &oidb.OIDBSSOPkg{
|
|
||||||
Command: 2448,
|
|
||||||
ServiceType: 2,
|
|
||||||
Bodybuffer: b,
|
|
||||||
}
|
|
||||||
payload, _ := proto.Marshal(req)
|
|
||||||
return c.uniPacket("OidbSvc.0x990", payload)
|
return c.uniPacket("OidbSvc.0x990", payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,14 +34,11 @@ func (c *QQClient) Translate(src, dst, text string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// OidbSvc.0x990
|
// OidbSvc.0x990
|
||||||
func decodeTranslateResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (interface{}, error) {
|
func decodeTranslateResponse(_ *QQClient, _ *network.IncomingPacketInfo, payload []byte) (any, error) {
|
||||||
pkg := oidb.OIDBSSOPkg{}
|
|
||||||
rsp := oidb.TranslateRspBody{}
|
rsp := oidb.TranslateRspBody{}
|
||||||
if err := proto.Unmarshal(payload, &pkg); err != nil {
|
err := unpackOIDBPackage(payload, &rsp)
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal protobuf message")
|
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")
|
|
||||||
}
|
}
|
||||||
return rsp.BatchTranslateRsp, nil
|
return rsp.BatchTranslateRsp, nil
|
||||||
}
|
}
|
||||||
|
5
go.mod
5
go.mod
@ -1,9 +1,10 @@
|
|||||||
module github.com/Mrs4s/MiraiGo
|
module github.com/Mrs4s/MiraiGo
|
||||||
|
|
||||||
go 1.17
|
go 1.18
|
||||||
|
|
||||||
require (
|
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/fumiama/imgsz v0.0.2
|
||||||
github.com/pierrec/lz4/v4 v4.1.11
|
github.com/pierrec/lz4/v4 v4.1.11
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
|
6
go.sum
6
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-20220318113238-d8a99598f896 h1:UFAqSbH6VqW5mEzQV2HVB7+p3k9JfTbidWJ/9F15yz0=
|
||||||
github.com/RomiChan/protobuf v0.0.0-20220213164748-44b69c8bdec0/go.mod h1:CKKOWC7mBxd36zxsCB1V8DTrwlTNRQvkSVbYqyUiGEE=
|
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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
@ -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 {
|
if !cond {
|
||||||
panic("assertion failed: " + fmt.Sprint(val))
|
panic("assertion failed: " + fmt.Sprint(val))
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DynamicMessage map[uint64]interface{}
|
type DynamicMessage map[uint64]any
|
||||||
|
|
||||||
type encoder struct {
|
type encoder struct {
|
||||||
bytes.Buffer
|
bytes.Buffer
|
||||||
|
@ -2,7 +2,7 @@ package proto
|
|||||||
|
|
||||||
import "github.com/RomiChan/protobuf/proto"
|
import "github.com/RomiChan/protobuf/proto"
|
||||||
|
|
||||||
type Message = interface{}
|
type Message = any
|
||||||
|
|
||||||
func Marshal(m Message) ([]byte, error) {
|
func Marshal(m Message) ([]byte, error) {
|
||||||
return proto.Marshal(m)
|
return proto.Marshal(m)
|
||||||
|
@ -103,8 +103,10 @@ type AnimatedSticker struct {
|
|||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RedBagMessageType int
|
type (
|
||||||
type AtType int
|
RedBagMessageType int
|
||||||
|
AtType int
|
||||||
|
)
|
||||||
|
|
||||||
// /com/tencent/mobileqq/data/MessageForQQWalletMsg.java
|
// /com/tencent/mobileqq/data/MessageForQQWalletMsg.java
|
||||||
const (
|
const (
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package message
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
@ -14,10 +14,9 @@ import (
|
|||||||
|
|
||||||
// *----- Definitions -----* //
|
// *----- Definitions -----* //
|
||||||
|
|
||||||
// ForwardMessage 添加 Node 请用 AddNode 方法
|
// ForwardMessage 合并转发消息
|
||||||
type ForwardMessage struct {
|
type ForwardMessage struct {
|
||||||
Nodes []*ForwardNode
|
Nodes []*ForwardNode
|
||||||
items []*msg.PbMultiMsgItem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ForwardNode struct {
|
type ForwardNode struct {
|
||||||
@ -72,9 +71,6 @@ func (f *ForwardMessage) AddNode(node *ForwardNode) *ForwardMessage {
|
|||||||
if item.Type() != Forward { // quick path
|
if item.Type() != Forward { // quick path
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if forward, ok := item.(*ForwardElement); ok {
|
|
||||||
f.items = append(f.items, forward.Items...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return f
|
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) Length() int { return len(f.Nodes) }
|
||||||
|
|
||||||
func (f *ForwardMessage) Brief() string {
|
func (f *ForwardMessage) Brief() string {
|
||||||
var brief bytes.Buffer
|
var brief strings.Builder
|
||||||
for _, n := range f.Nodes {
|
for _, n := range f.Nodes {
|
||||||
brief.WriteString(ToReadableString(n.Message))
|
brief.WriteString(ToReadableString(n.Message))
|
||||||
if brief.Len() >= 27 {
|
if brief.Len() >= 27 {
|
||||||
@ -94,7 +90,7 @@ func (f *ForwardMessage) Brief() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *ForwardMessage) Preview() string {
|
func (f *ForwardMessage) Preview() string {
|
||||||
var pv bytes.Buffer
|
var pv strings.Builder
|
||||||
for i, node := range f.Nodes {
|
for i, node := range f.Nodes {
|
||||||
if i >= 4 {
|
if i >= 4 {
|
||||||
break
|
break
|
||||||
@ -109,7 +105,7 @@ func (f *ForwardMessage) Preview() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *ForwardMessage) CalculateValidationData(seq, random int32, groupCode int64) ([]byte, []byte) {
|
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{
|
trans := &msg.PbMultiMsgTransmit{Msg: msgs, PbItemList: []*msg.PbMultiMsgItem{
|
||||||
{
|
{
|
||||||
FileName: proto.String("MultiMsg"),
|
FileName: proto.String("MultiMsg"),
|
||||||
@ -122,23 +118,7 @@ func (f *ForwardMessage) CalculateValidationData(seq, random int32, groupCode in
|
|||||||
return data, hash[:]
|
return data, hash[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateValidationDataForward 屎代码
|
func (f *ForwardMessage) PackForwardMessage(seq int32, random int32, groupCode int64) []*msg.Message {
|
||||||
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 {
|
|
||||||
ml := make([]*msg.Message, 0, len(f.Nodes))
|
ml := make([]*msg.Message, 0, len(f.Nodes))
|
||||||
for _, node := range f.Nodes {
|
for _, node := range f.Nodes {
|
||||||
ml = append(ml, &msg.Message{
|
ml = append(ml, &msg.Message{
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
package message
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
|
||||||
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
||||||
"github.com/Mrs4s/MiraiGo/internal/proto"
|
"github.com/Mrs4s/MiraiGo/internal/proto"
|
||||||
)
|
)
|
||||||
@ -73,7 +72,7 @@ func NewGroupImage(id string, md5 []byte, fid int64, size, width, height, imageT
|
|||||||
ImageType: imageType,
|
ImageType: imageType,
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -778,7 +778,7 @@ func splitPlainMessage(content string) []IMessageElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if last != len(content) {
|
if last != len(content) {
|
||||||
splittedMessage = append(splittedMessage, NewText(content[last:len(content)]))
|
splittedMessage = append(splittedMessage, NewText(content[last:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return splittedMessage
|
return splittedMessage
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package message
|
package message
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Mrs4s/MiraiGo/binary"
|
"github.com/Mrs4s/MiraiGo/binary"
|
||||||
@ -126,7 +125,7 @@ func (e *ShortVideoElement) Pack() (r []*msg.Elem) {
|
|||||||
video := &msg.VideoFile{
|
video := &msg.VideoFile{
|
||||||
FileUuid: e.Uuid,
|
FileUuid: e.Uuid,
|
||||||
FileMd5: e.Md5,
|
FileMd5: e.Md5,
|
||||||
FileName: []byte(hex.EncodeToString(e.Md5) + ".mp4"),
|
FileName: []byte(fmt.Sprintf("%x.mp4", e.Md5)),
|
||||||
FileFormat: proto.Int32(3),
|
FileFormat: proto.Int32(3),
|
||||||
FileTime: proto.Int32(10),
|
FileTime: proto.Int32(10),
|
||||||
FileSize: proto.Int32(e.Size),
|
FileSize: proto.Int32(e.Size),
|
||||||
|
@ -55,10 +55,10 @@ type (
|
|||||||
pack(patternId string, isPatternData bool) content
|
pack(patternId string, isPatternData bool) content
|
||||||
}
|
}
|
||||||
|
|
||||||
content map[string]interface{}
|
content map[string]any
|
||||||
)
|
)
|
||||||
|
|
||||||
var globalBlockId int64 = 0
|
var globalBlockId int64
|
||||||
|
|
||||||
func genBlockId() string {
|
func genBlockId() string {
|
||||||
id := atomic.AddInt64(&globalBlockId, 1)
|
id := atomic.AddInt64(&globalBlockId, 1)
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var client = &http.Client{
|
var Client = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
MaxConnsPerHost: 0,
|
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["User-Agent"] = []string{"QQ/8.2.0.1296 CFNetwork/1126"}
|
||||||
req.Header["Net-Type"] = []string{"Wifi"}
|
req.Header["Net-Type"] = []string{"Wifi"}
|
||||||
resp, err := client.Do(req)
|
resp, err := Client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -67,7 +67,7 @@ func HttpPostBytesWithCookie(url string, data []byte, cookie string, contentType
|
|||||||
if cookie != "" {
|
if cookie != "" {
|
||||||
req.Header["Cookie"] = []string{cookie}
|
req.Header["Cookie"] = []string{cookie}
|
||||||
}
|
}
|
||||||
resp, err := client.Do(req)
|
resp, err := Client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -125,7 +125,7 @@ func HTTPGetReadCloser(url string, cookie string) (io.ReadCloser, error) {
|
|||||||
if cookie != "" {
|
if cookie != "" {
|
||||||
req.Header["Cookie"] = []string{cookie}
|
req.Header["Cookie"] = []string{cookie}
|
||||||
}
|
}
|
||||||
resp, err := client.Do(req)
|
resp, err := Client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -69,9 +69,63 @@ func S2B(s string) (b []byte) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// XmlEscape xml escape string
|
const (
|
||||||
func XmlEscape(c string) string {
|
escQuot = """ // shorter than """
|
||||||
buf := new(strings.Builder)
|
escApos = "'" // shorter than "'"
|
||||||
_ = xml.EscapeText(buf, []byte(c))
|
escAmp = "&"
|
||||||
return buf.String()
|
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()
|
||||||
}
|
}
|
||||||
|
14
utils/string_test.go
Normal file
14
utils/string_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
57
utils/sys.go
57
utils/sys.go
@ -20,10 +20,10 @@ func ComputeMd5AndLength(r io.Reader) ([]byte, int64) {
|
|||||||
|
|
||||||
func (r *multiReadSeeker) Read(p []byte) (int, error) {
|
func (r *multiReadSeeker) Read(p []byte) (int, error) {
|
||||||
if r.multiReader == nil {
|
if r.multiReader == nil {
|
||||||
var readers []io.Reader
|
readers := make([]io.Reader, len(r.readers))
|
||||||
for i := range r.readers {
|
for i := range r.readers {
|
||||||
_, _ = r.readers[i].Seek(0, io.SeekStart)
|
_, _ = r.readers[i].Seek(0, io.SeekStart)
|
||||||
readers = append(readers, r.readers[i])
|
readers[i] = r.readers[i]
|
||||||
}
|
}
|
||||||
r.multiReader = io.MultiReader(readers...)
|
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
|
// Select 如果A为nil 将会返回 B 否则返回A
|
||||||
// 对应 ?? 语法
|
// 对应 ?? 语法
|
||||||
func Select(a, b []byte) []byte {
|
func Select(a, b []byte) []byte {
|
||||||
|
29
utils/ttl.go
29
utils/ttl.go
@ -7,26 +7,26 @@ import (
|
|||||||
|
|
||||||
// https://github.com/Konstantin8105/SimpleTTL
|
// https://github.com/Konstantin8105/SimpleTTL
|
||||||
// entry - typical element of cache
|
// entry - typical element of cache
|
||||||
type entry struct {
|
type entry[T any] struct {
|
||||||
expiry time.Time
|
expiry time.Time
|
||||||
value interface{}
|
value T
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache - simple implementation of cache
|
// Cache - simple implementation of cache
|
||||||
// More information: https://en.wikipedia.org/wiki/Time_to_live
|
// More information: https://en.wikipedia.org/wiki/Time_to_live
|
||||||
type Cache struct {
|
type Cache[T any] struct {
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
cache map[string]*entry
|
cache map[string]*entry[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCache - initialization of new cache.
|
// NewCache - initialization of new cache.
|
||||||
// For avoid mistake - minimal time to live is 1 minute.
|
// For avoid mistake - minimal time to live is 1 minute.
|
||||||
// For simplification, - key is string and cache haven`t stop method
|
// 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 {
|
if interval < time.Second {
|
||||||
interval = time.Second
|
interval = time.Second
|
||||||
}
|
}
|
||||||
cache := &Cache{cache: make(map[string]*entry)}
|
cache := &Cache[T]{cache: make(map[string]*entry[T])}
|
||||||
go func() {
|
go func() {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
for {
|
for {
|
||||||
@ -47,7 +47,7 @@ func NewCache(interval time.Duration) *Cache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Count - return amount element of TTL map.
|
// Count - return amount element of TTL map.
|
||||||
func (cache *Cache) Count() int {
|
func (cache *Cache[_]) Count() int {
|
||||||
cache.lock.RLock()
|
cache.lock.RLock()
|
||||||
defer cache.lock.RUnlock()
|
defer cache.lock.RUnlock()
|
||||||
|
|
||||||
@ -55,19 +55,18 @@ func (cache *Cache) Count() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get - return value from cache
|
// 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()
|
cache.lock.RLock()
|
||||||
defer cache.lock.RUnlock()
|
defer cache.lock.RUnlock()
|
||||||
|
|
||||||
e, ok := cache.cache[key]
|
e, ok := cache.cache[key]
|
||||||
|
|
||||||
if ok && e.expiry.After(time.Now()) {
|
if ok && e.expiry.After(time.Now()) {
|
||||||
return e.value, true
|
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()
|
cache.lock.RLock()
|
||||||
defer cache.lock.RUnlock()
|
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)
|
e.expiry = time.Now().Add(ttl)
|
||||||
return e.value, true
|
return e.value, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add - add key/value in cache
|
// 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()
|
cache.lock.Lock()
|
||||||
defer cache.lock.Unlock()
|
defer cache.lock.Unlock()
|
||||||
|
|
||||||
cache.cache[key] = &entry{
|
cache.cache[key] = &entry[T]{
|
||||||
value: value,
|
value: value,
|
||||||
expiry: time.Now().Add(ttl),
|
expiry: time.Now().Add(ttl),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetKeys - return all keys of cache map
|
// GetKeys - return all keys of cache map
|
||||||
func (cache *Cache) GetKeys() []string {
|
func (cache *Cache[_]) GetKeys() []string {
|
||||||
cache.lock.RLock()
|
cache.lock.RLock()
|
||||||
defer cache.lock.RUnlock()
|
defer cache.lock.RUnlock()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user