mirror of
https://github.com/Mrs4s/MiraiGo.git
synced 2025-05-05 03:23:50 +08:00
700 lines
20 KiB
Go
700 lines
20 KiB
Go
package client
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"github.com/Mrs4s/MiraiGo/binary"
|
|
"github.com/Mrs4s/MiraiGo/binary/jce"
|
|
devinfo "github.com/Mrs4s/MiraiGo/client/pb"
|
|
"github.com/Mrs4s/MiraiGo/client/pb/msg"
|
|
"github.com/Mrs4s/MiraiGo/client/pb/oidb"
|
|
"github.com/Mrs4s/MiraiGo/message"
|
|
"github.com/Mrs4s/MiraiGo/utils"
|
|
)
|
|
|
|
type (
|
|
DeviceInfo struct {
|
|
Display []byte
|
|
Product []byte
|
|
Device []byte
|
|
Board []byte
|
|
Brand []byte
|
|
Model []byte
|
|
Bootloader []byte
|
|
FingerPrint []byte
|
|
BootId []byte
|
|
ProcVersion []byte
|
|
BaseBand []byte
|
|
SimInfo []byte
|
|
OSType []byte
|
|
MacAddress []byte
|
|
IpAddress []byte
|
|
WifiBSSID []byte
|
|
WifiSSID []byte
|
|
IMSIMd5 []byte
|
|
IMEI string
|
|
AndroidId []byte
|
|
APN []byte
|
|
VendorName []byte
|
|
VendorOSName []byte
|
|
Guid []byte
|
|
TgtgtKey []byte
|
|
Protocol ClientProtocol
|
|
Version *Version
|
|
}
|
|
|
|
Version struct {
|
|
Incremental []byte
|
|
Release []byte
|
|
CodeName []byte
|
|
Sdk uint32
|
|
}
|
|
|
|
DeviceInfoFile struct {
|
|
Display string `json:"display"`
|
|
Product string `json:"product"`
|
|
Device string `json:"device"`
|
|
Board string `json:"board"`
|
|
Model string `json:"model"`
|
|
FingerPrint string `json:"finger_print"`
|
|
BootId string `json:"boot_id"`
|
|
ProcVersion string `json:"proc_version"`
|
|
Protocol int `json:"protocol"` // 0: Pad 1: Phone 2: Watch
|
|
IMEI string `json:"imei"`
|
|
Brand string `json:"brand"`
|
|
Bootloader string `json:"bootloader"`
|
|
BaseBand string `json:"base_band"`
|
|
Version *VersionFile `json:"version"`
|
|
SimInfo string `json:"sim_info"`
|
|
OsType string `json:"os_type"`
|
|
MacAddress string `json:"mac_address"`
|
|
IpAddress []int32 `json:"ip_address"`
|
|
WifiBSSID string `json:"wifi_bssid"`
|
|
WifiSSID string `json:"wifi_ssid"`
|
|
ImsiMd5 string `json:"imsi_md5"`
|
|
AndroidId string `json:"android_id"`
|
|
Apn string `json:"apn"`
|
|
VendorName string `json:"vendor_name"`
|
|
VendorOSName string `json:"vendor_os_name"`
|
|
}
|
|
|
|
VersionFile struct {
|
|
Incremental string `json:"incremental"`
|
|
Release string `json:"release"`
|
|
Codename string `json:"codename"`
|
|
Sdk uint32 `json:"sdk"`
|
|
}
|
|
|
|
groupMessageBuilder struct {
|
|
MessageSlices []*msg.Message
|
|
}
|
|
|
|
versionInfo struct {
|
|
ApkSign []byte
|
|
ApkId string
|
|
SortVersionName string
|
|
SdkVersion string
|
|
AppId uint32
|
|
SubAppId uint32
|
|
BuildTime uint32
|
|
SSOVersion uint32
|
|
MiscBitmap uint32
|
|
SubSigmap uint32
|
|
MainSigMap uint32
|
|
Protocol ClientProtocol
|
|
}
|
|
|
|
incomingPacketInfo struct {
|
|
CommandName string
|
|
SequenceId uint16
|
|
Params requestParams
|
|
}
|
|
|
|
requestParams map[string]interface{}
|
|
)
|
|
|
|
var SystemDeviceInfo = &DeviceInfo{
|
|
Display: []byte("MIRAI.123456.001"),
|
|
Product: []byte("mirai"),
|
|
Device: []byte("mirai"),
|
|
Board: []byte("mirai"),
|
|
Brand: []byte("mamoe"),
|
|
Model: []byte("mirai"),
|
|
Bootloader: []byte("unknown"),
|
|
FingerPrint: []byte("mamoe/mirai/mirai:10/MIRAI.200122.001/1234567:user/release-keys"),
|
|
BootId: []byte("cb886ae2-00b6-4d68-a230-787f111d12c7"),
|
|
ProcVersion: []byte("Linux version 3.0.31-cb886ae2 (android-build@xxx.xxx.xxx.xxx.com)"),
|
|
BaseBand: []byte{},
|
|
SimInfo: []byte("T-Mobile"),
|
|
OSType: []byte("android"),
|
|
MacAddress: []byte("00:50:56:C0:00:08"),
|
|
IpAddress: []byte{10, 0, 1, 3}, // 10.0.1.3
|
|
WifiBSSID: []byte("00:50:56:C0:00:08"),
|
|
WifiSSID: []byte("<unknown ssid>"),
|
|
IMEI: "468356291846738",
|
|
AndroidId: []byte("MIRAI.123456.001"),
|
|
APN: []byte("wifi"),
|
|
VendorName: []byte("MIUI"),
|
|
VendorOSName: []byte("mirai"),
|
|
Protocol: IPad,
|
|
Version: &Version{
|
|
Incremental: []byte("5891938"),
|
|
Release: []byte("10"),
|
|
CodeName: []byte("REL"),
|
|
Sdk: 29,
|
|
},
|
|
}
|
|
|
|
var (
|
|
EmptyBytes = []byte{}
|
|
NumberRange = "0123456789"
|
|
)
|
|
|
|
func init() {
|
|
r := make([]byte, 16)
|
|
rand.Read(r)
|
|
t := md5.Sum(r)
|
|
SystemDeviceInfo.IMSIMd5 = t[:]
|
|
SystemDeviceInfo.GenNewGuid()
|
|
SystemDeviceInfo.GenNewTgtgtKey()
|
|
}
|
|
|
|
func GenRandomDevice() {
|
|
r := make([]byte, 16)
|
|
rand.Read(r)
|
|
SystemDeviceInfo.Display = []byte("MIRAI." + utils.RandomStringRange(6, NumberRange) + ".001")
|
|
SystemDeviceInfo.FingerPrint = []byte("mamoe/mirai/mirai:10/MIRAI.200122.001/" + utils.RandomStringRange(7, NumberRange) + ":user/release-keys")
|
|
SystemDeviceInfo.BootId = binary.GenUUID(r)
|
|
SystemDeviceInfo.ProcVersion = []byte("Linux version 3.0.31-" + utils.RandomString(8) + " (android-build@xxx.xxx.xxx.xxx.com)")
|
|
rand.Read(r)
|
|
t := md5.Sum(r)
|
|
SystemDeviceInfo.IMSIMd5 = t[:]
|
|
SystemDeviceInfo.IMEI = GenIMEI()
|
|
r = make([]byte, 8)
|
|
rand.Read(r)
|
|
hex.Encode(SystemDeviceInfo.AndroidId, r)
|
|
SystemDeviceInfo.GenNewGuid()
|
|
SystemDeviceInfo.GenNewTgtgtKey()
|
|
}
|
|
|
|
func genVersionInfo(p ClientProtocol) *versionInfo {
|
|
switch p {
|
|
case AndroidPhone: // Dumped by mirai from qq android v8.8.38
|
|
return &versionInfo{
|
|
ApkId: "com.tencent.mobileqq",
|
|
AppId: 537100432,
|
|
SubAppId: 537100432,
|
|
SortVersionName: "8.8.38",
|
|
BuildTime: 1634310940,
|
|
ApkSign: []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D},
|
|
SdkVersion: "6.0.0.2487",
|
|
SSOVersion: 16,
|
|
MiscBitmap: 184024956,
|
|
SubSigmap: 0x10400,
|
|
MainSigMap: 34869472,
|
|
Protocol: p,
|
|
}
|
|
case AndroidWatch:
|
|
return &versionInfo{
|
|
ApkId: "com.tencent.qqlite",
|
|
AppId: 537064446,
|
|
SubAppId: 537064446,
|
|
SortVersionName: "2.0.5",
|
|
BuildTime: 1559564731,
|
|
ApkSign: []byte{0xA6, 0xB7, 0x45, 0xBF, 0x24, 0xA2, 0xC2, 0x77, 0x52, 0x77, 0x16, 0xF6, 0xF3, 0x6E, 0xB6, 0x8D},
|
|
SdkVersion: "6.0.0.236",
|
|
SSOVersion: 5,
|
|
MiscBitmap: 16252796,
|
|
SubSigmap: 0x10400,
|
|
MainSigMap: 34869472,
|
|
Protocol: p,
|
|
}
|
|
case IPad:
|
|
return &versionInfo{
|
|
ApkId: "com.tencent.minihd.qq",
|
|
AppId: 537097188,
|
|
SubAppId: 537097188,
|
|
SortVersionName: "8.8.35",
|
|
BuildTime: 1595836208,
|
|
ApkSign: []byte{170, 57, 120, 244, 31, 217, 111, 249, 145, 74, 102, 158, 24, 100, 116, 199},
|
|
SdkVersion: "6.0.0.2433",
|
|
SSOVersion: 12,
|
|
MiscBitmap: 150470524,
|
|
SubSigmap: 66560,
|
|
MainSigMap: 1970400,
|
|
Protocol: p,
|
|
}
|
|
case MacOS:
|
|
return &versionInfo{
|
|
ApkId: "com.tencent.minihd.qq",
|
|
AppId: 537064315,
|
|
SubAppId: 537064315,
|
|
SortVersionName: "5.8.9",
|
|
BuildTime: 1595836208,
|
|
ApkSign: []byte{170, 57, 120, 244, 31, 217, 111, 249, 145, 74, 102, 158, 24, 100, 116, 199},
|
|
SdkVersion: "6.0.0.2433",
|
|
SSOVersion: 12,
|
|
MiscBitmap: 150470524,
|
|
SubSigmap: 66560,
|
|
MainSigMap: 1970400,
|
|
Protocol: p,
|
|
}
|
|
case QiDian:
|
|
return &versionInfo{
|
|
ApkId: "com.tencent.qidian",
|
|
AppId: 537061386,
|
|
SubAppId: 537036590,
|
|
SortVersionName: "3.8.6",
|
|
BuildTime: 1556628836,
|
|
ApkSign: []byte{160, 30, 236, 171, 133, 233, 227, 186, 43, 15, 106, 21, 140, 133, 92, 41},
|
|
SdkVersion: "6.0.0.2365",
|
|
SSOVersion: 5,
|
|
MiscBitmap: 49807228,
|
|
SubSigmap: 66560,
|
|
MainSigMap: 34869472,
|
|
Protocol: p,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (info *DeviceInfo) ToJson() []byte {
|
|
f := &DeviceInfoFile{
|
|
Display: string(info.Display),
|
|
Product: string(info.Product),
|
|
Device: string(info.Device),
|
|
Board: string(info.Board),
|
|
Model: string(info.Model),
|
|
FingerPrint: string(info.FingerPrint),
|
|
BootId: string(info.BootId),
|
|
ProcVersion: string(info.ProcVersion),
|
|
IMEI: info.IMEI,
|
|
Brand: string(info.Brand),
|
|
Bootloader: string(info.Bootloader),
|
|
BaseBand: string(info.BaseBand),
|
|
AndroidId: string(info.AndroidId),
|
|
Version: &VersionFile{
|
|
Incremental: string(info.Version.Incremental),
|
|
Release: string(info.Version.Release),
|
|
Codename: string(info.Version.CodeName),
|
|
Sdk: info.Version.Sdk,
|
|
},
|
|
SimInfo: string(info.SimInfo),
|
|
OsType: string(info.OSType),
|
|
MacAddress: string(info.MacAddress),
|
|
IpAddress: []int32{int32(info.IpAddress[0]), int32(info.IpAddress[1]), int32(info.IpAddress[2]), int32(info.IpAddress[3])},
|
|
WifiBSSID: string(info.WifiBSSID),
|
|
WifiSSID: string(info.WifiSSID),
|
|
ImsiMd5: hex.EncodeToString(info.IMSIMd5),
|
|
Apn: string(info.APN),
|
|
VendorName: string(info.VendorName),
|
|
VendorOSName: string(info.VendorOSName),
|
|
Protocol: func() int {
|
|
switch info.Protocol {
|
|
case IPad:
|
|
return 0
|
|
case AndroidPhone:
|
|
return 1
|
|
case AndroidWatch:
|
|
return 2
|
|
case MacOS:
|
|
return 3
|
|
case QiDian:
|
|
return 4
|
|
}
|
|
return 0
|
|
}(),
|
|
}
|
|
d, _ := json.Marshal(f)
|
|
return d
|
|
}
|
|
|
|
func (info *DeviceInfo) ReadJson(d []byte) error {
|
|
var f DeviceInfoFile
|
|
if err := json.Unmarshal(d, &f); err != nil {
|
|
return errors.Wrap(err, "failed to unmarshal json message")
|
|
}
|
|
SetIfNotEmpty := func(trg *[]byte, str string) {
|
|
if str != "" {
|
|
*trg = []byte(str)
|
|
}
|
|
}
|
|
SetIfNotEmpty(&info.Display, f.Display)
|
|
SetIfNotEmpty(&info.Product, f.Product)
|
|
SetIfNotEmpty(&info.Device, f.Device)
|
|
SetIfNotEmpty(&info.Board, f.Board)
|
|
SetIfNotEmpty(&info.Brand, f.Brand)
|
|
SetIfNotEmpty(&info.Model, f.Model)
|
|
SetIfNotEmpty(&info.Bootloader, f.Bootloader)
|
|
SetIfNotEmpty(&info.FingerPrint, f.FingerPrint)
|
|
SetIfNotEmpty(&info.BootId, f.BootId)
|
|
SetIfNotEmpty(&info.ProcVersion, f.ProcVersion)
|
|
SetIfNotEmpty(&info.BaseBand, f.BaseBand)
|
|
SetIfNotEmpty(&info.SimInfo, f.SimInfo)
|
|
SetIfNotEmpty(&info.OSType, f.OsType)
|
|
SetIfNotEmpty(&info.MacAddress, f.MacAddress)
|
|
if len(f.IpAddress) == 4 {
|
|
info.IpAddress = []byte{byte(f.IpAddress[0]), byte(f.IpAddress[1]), byte(f.IpAddress[2]), byte(f.IpAddress[3])}
|
|
}
|
|
SetIfNotEmpty(&info.WifiBSSID, f.WifiBSSID)
|
|
SetIfNotEmpty(&info.WifiSSID, f.WifiSSID)
|
|
if len(f.ImsiMd5) != 0 {
|
|
imsiMd5, err := hex.DecodeString(f.ImsiMd5)
|
|
if err != nil {
|
|
info.IMSIMd5 = imsiMd5
|
|
}
|
|
}
|
|
if f.IMEI != "" {
|
|
info.IMEI = f.IMEI
|
|
}
|
|
SetIfNotEmpty(&info.APN, f.Apn)
|
|
SetIfNotEmpty(&info.VendorName, f.VendorName)
|
|
SetIfNotEmpty(&info.VendorOSName, f.VendorOSName)
|
|
|
|
SetIfNotEmpty(&info.AndroidId, f.AndroidId)
|
|
if f.AndroidId == "" {
|
|
info.AndroidId = info.Display // ?
|
|
}
|
|
|
|
switch f.Protocol {
|
|
case 1:
|
|
info.Protocol = AndroidPhone
|
|
case 2:
|
|
info.Protocol = AndroidWatch
|
|
case 3:
|
|
info.Protocol = MacOS
|
|
case 4:
|
|
info.Protocol = QiDian
|
|
default:
|
|
info.Protocol = IPad
|
|
}
|
|
SystemDeviceInfo.GenNewGuid()
|
|
SystemDeviceInfo.GenNewTgtgtKey()
|
|
return nil
|
|
}
|
|
|
|
func (info *DeviceInfo) GenNewGuid() {
|
|
t := md5.Sum(append(info.AndroidId, info.MacAddress...))
|
|
info.Guid = t[:]
|
|
}
|
|
|
|
func (info *DeviceInfo) GenNewTgtgtKey() {
|
|
r := make([]byte, 16)
|
|
rand.Read(r)
|
|
h := md5.New()
|
|
h.Write(r)
|
|
h.Write(info.Guid)
|
|
info.TgtgtKey = h.Sum(nil)
|
|
}
|
|
|
|
func (info *DeviceInfo) GenDeviceInfoData() []byte {
|
|
m := &devinfo.DeviceInfo{
|
|
Bootloader: string(info.Bootloader),
|
|
ProcVersion: string(info.ProcVersion),
|
|
Codename: string(info.Version.CodeName),
|
|
Incremental: string(info.Version.Incremental),
|
|
Fingerprint: string(info.FingerPrint),
|
|
BootId: string(info.BootId),
|
|
AndroidId: string(info.AndroidId),
|
|
BaseBand: string(info.BaseBand),
|
|
InnerVersion: string(info.Version.Incremental),
|
|
}
|
|
data, err := proto.Marshal(m)
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "failed to unmarshal protobuf message"))
|
|
}
|
|
return data
|
|
}
|
|
|
|
func GenIMEI() string {
|
|
sum := 0 // the control sum of digits
|
|
var final strings.Builder
|
|
|
|
randSrc := rand.NewSource(time.Now().UnixNano())
|
|
randGen := rand.New(randSrc)
|
|
|
|
for i := 0; i < 14; i++ { // generating all the base digits
|
|
toAdd := randGen.Intn(10)
|
|
if (i+1)%2 == 0 { // special proc for every 2nd one
|
|
toAdd *= 2
|
|
if toAdd >= 10 {
|
|
toAdd = (toAdd % 10) + 1
|
|
}
|
|
}
|
|
sum += toAdd
|
|
final.WriteString(fmt.Sprintf("%d", toAdd)) // and even printing them here!
|
|
}
|
|
ctrlDigit := (sum * 9) % 10 // calculating the control digit
|
|
final.WriteString(fmt.Sprintf("%d", ctrlDigit))
|
|
return final.String()
|
|
}
|
|
|
|
func getSSOAddress() ([]*net.TCPAddr, error) {
|
|
protocol := genVersionInfo(SystemDeviceInfo.Protocol)
|
|
key, _ := hex.DecodeString("F0441F5FF42DA58FDCF7949ABA62D411")
|
|
payload := jce.NewJceWriter(). // see ServerConfig.d
|
|
WriteInt64(0, 1).WriteInt64(0, 2).WriteByte(1, 3).
|
|
WriteString("00000", 4).WriteInt32(100, 5).
|
|
WriteInt32(int32(protocol.AppId), 6).WriteString(SystemDeviceInfo.IMEI, 7).
|
|
WriteInt64(0, 8).WriteInt64(0, 9).WriteInt64(0, 10).
|
|
WriteInt64(0, 11).WriteByte(0, 12).WriteInt64(0, 13).WriteByte(1, 14).Bytes()
|
|
buf := &jce.RequestDataVersion3{
|
|
Map: map[string][]byte{"HttpServerListReq": packUniRequestData(payload)},
|
|
}
|
|
pkt := &jce.RequestPacket{
|
|
IVersion: 3,
|
|
SServantName: "ConfigHttp",
|
|
SFuncName: "HttpServerListReq",
|
|
SBuffer: buf.ToBytes(),
|
|
}
|
|
tea := binary.NewTeaCipher(key)
|
|
rsp, err := utils.HttpPostBytes("https://configsvr.msf.3g.qq.com/configsvr/serverlist.jsp", tea.Encrypt(binary.NewWriterF(func(w *binary.Writer) {
|
|
w.WriteIntLvPacket(0, func(w *binary.Writer) {
|
|
w.Write(pkt.ToBytes())
|
|
})
|
|
})))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "unable to fetch server list")
|
|
}
|
|
rspPkt := &jce.RequestPacket{}
|
|
data := &jce.RequestDataVersion3{}
|
|
rspPkt.ReadFrom(jce.NewJceReader(tea.Decrypt(rsp)[4:]))
|
|
data.ReadFrom(jce.NewJceReader(rspPkt.SBuffer))
|
|
reader := jce.NewJceReader(data.Map["HttpServerListRes"][1:])
|
|
servers := []jce.SsoServerInfo{}
|
|
reader.ReadSlice(&servers, 2)
|
|
adds := make([]*net.TCPAddr, 0, len(servers))
|
|
for _, s := range servers {
|
|
if strings.Contains(s.Server, "com") {
|
|
continue
|
|
}
|
|
adds = append(adds, &net.TCPAddr{
|
|
IP: net.ParseIP(s.Server),
|
|
Port: int(s.Port),
|
|
})
|
|
}
|
|
return adds, nil
|
|
}
|
|
|
|
func qualityTest(addr string) (int64, error) {
|
|
// see QualityTestManager
|
|
start := time.Now()
|
|
conn, err := net.DialTimeout("tcp", addr, time.Second*5)
|
|
if err != nil {
|
|
return 0, errors.Wrap(err, "failed to connect to server during quality test")
|
|
}
|
|
_ = conn.Close()
|
|
end := time.Now()
|
|
return end.Sub(start).Milliseconds(), nil
|
|
}
|
|
|
|
func (c *QQClient) parsePrivateMessage(msg *msg.Message) *message.PrivateMessage {
|
|
friend := c.FindFriend(msg.Head.GetFromUin())
|
|
var sender *message.Sender
|
|
if friend == nil {
|
|
sender = &message.Sender{
|
|
Uin: msg.Head.GetFromUin(),
|
|
Nickname: msg.Head.GetFromNick(),
|
|
}
|
|
} else {
|
|
sender = &message.Sender{
|
|
Uin: friend.Uin,
|
|
Nickname: friend.Nickname,
|
|
IsFriend: true,
|
|
}
|
|
}
|
|
ret := &message.PrivateMessage{
|
|
Id: msg.Head.GetMsgSeq(),
|
|
Target: msg.Head.GetToUin(),
|
|
Time: msg.Head.GetMsgTime(),
|
|
Sender: sender,
|
|
Self: c.Uin,
|
|
Elements: func() []message.IMessageElement {
|
|
if msg.Body.RichText.Ptt != nil {
|
|
return []message.IMessageElement{
|
|
&message.VoiceElement{
|
|
Name: msg.Body.RichText.Ptt.GetFileName(),
|
|
Md5: msg.Body.RichText.Ptt.FileMd5,
|
|
Size: msg.Body.RichText.Ptt.GetFileSize(),
|
|
Url: string(msg.Body.RichText.Ptt.DownPara),
|
|
},
|
|
}
|
|
}
|
|
return message.ParseMessageElems(msg.Body.RichText.Elems)
|
|
}(),
|
|
}
|
|
if msg.Body.RichText.Attr != nil {
|
|
ret.InternalId = msg.Body.RichText.Attr.GetRandom()
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (c *QQClient) parseTempMessage(msg *msg.Message) *message.TempMessage {
|
|
var groupCode int64
|
|
var groupName string
|
|
group := c.FindGroupByUin(msg.Head.C2CTmpMsgHead.GetGroupUin())
|
|
sender := &message.Sender{
|
|
Uin: msg.Head.GetFromUin(),
|
|
Nickname: "Unknown",
|
|
IsFriend: false,
|
|
}
|
|
if group != nil {
|
|
groupCode = group.Code
|
|
groupName = group.Name
|
|
mem := group.FindMember(msg.Head.GetFromUin())
|
|
if mem != nil {
|
|
sender.Nickname = mem.Nickname
|
|
sender.CardName = mem.CardName
|
|
}
|
|
}
|
|
return &message.TempMessage{
|
|
Id: msg.Head.GetMsgSeq(),
|
|
GroupCode: groupCode,
|
|
GroupName: groupName,
|
|
Self: c.Uin,
|
|
Sender: sender,
|
|
Elements: message.ParseMessageElems(msg.Body.RichText.Elems),
|
|
}
|
|
}
|
|
|
|
func (b *groupMessageBuilder) build() *msg.Message {
|
|
sort.Slice(b.MessageSlices, func(i, j int) bool {
|
|
return b.MessageSlices[i].Content.GetPkgIndex() < b.MessageSlices[j].Content.GetPkgIndex()
|
|
})
|
|
base := b.MessageSlices[0]
|
|
for _, m := range b.MessageSlices[1:] {
|
|
base.Body.RichText.Elems = append(base.Body.RichText.Elems, m.Body.RichText.Elems...)
|
|
}
|
|
return base
|
|
}
|
|
|
|
func packUniRequestData(data []byte) (r []byte) {
|
|
r = append([]byte{0x0A}, data...)
|
|
r = append(r, 0x0B)
|
|
return
|
|
}
|
|
|
|
func genForwardTemplate(resID, preview, title, brief, source, summary string, ts int64, items []*msg.PbMultiMsgItem) *message.ForwardElement {
|
|
template := fmt.Sprintf(`<?xml version='1.0' encoding='UTF-8'?><msg serviceID="35" templateID="1" action="viewMultiMsg" brief="%s" m_resid="%s" m_fileName="%d" tSum="3" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="0"><item layout="1"><title color="#000000" size="34">%s</title> %s<hr></hr><summary size="26" color="#808080">%s</summary></item><source name="%s"></source></msg>`,
|
|
brief, resID, ts, title, preview, summary, source,
|
|
)
|
|
for _, item := range items {
|
|
if item.GetFileName() == "MultiMsg" {
|
|
*item.FileName = strconv.FormatInt(ts, 10)
|
|
}
|
|
}
|
|
return &message.ForwardElement{
|
|
FileName: strconv.FormatInt(ts, 10),
|
|
Content: template,
|
|
ResId: resID,
|
|
Items: items,
|
|
}
|
|
}
|
|
|
|
func genLongTemplate(resID, brief string, ts int64) *message.ServiceElement {
|
|
limited := func() string {
|
|
if len(brief) > 30 {
|
|
return brief[:30] + "…"
|
|
}
|
|
return brief
|
|
}()
|
|
template := fmt.Sprintf(`<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="35" templateID="1" action="viewMultiMsg" brief="%s" m_resid="%s" m_fileName="%d" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="1"> <item layout="1"> <title>%s</title> <hr hidden="false" style="0"/> <summary>点击查看完整消息</summary> </item> <source name="聊天记录" icon="" action="" appid="-1"/> </msg>`,
|
|
utils.XmlEscape(limited), resID, ts, utils.XmlEscape(limited),
|
|
)
|
|
return &message.ServiceElement{
|
|
Id: 35,
|
|
Content: template,
|
|
ResId: resID,
|
|
SubType: "Long",
|
|
}
|
|
}
|
|
|
|
func (p requestParams) bool(k string) bool {
|
|
if p == nil {
|
|
return false
|
|
}
|
|
i, ok := p[k]
|
|
if !ok {
|
|
return false
|
|
}
|
|
return i.(bool)
|
|
}
|
|
|
|
func (p requestParams) int32(k string) int32 {
|
|
if p == nil {
|
|
return 0
|
|
}
|
|
i, ok := p[k]
|
|
if !ok {
|
|
return 0
|
|
}
|
|
return i.(int32)
|
|
}
|
|
|
|
func (c *QQClient) packOIDBPackage(cmd, serviceType int32, body []byte) []byte {
|
|
pkg := &oidb.OIDBSSOPkg{
|
|
Command: cmd,
|
|
ServiceType: serviceType,
|
|
Bodybuffer: body,
|
|
ClientVersion: "Android " + c.version.SortVersionName,
|
|
}
|
|
r, _ := proto.Marshal(pkg)
|
|
return r
|
|
}
|
|
|
|
func (c *QQClient) packOIDBPackageDynamically(cmd, serviceType int32, msg binary.DynamicProtoMessage) []byte {
|
|
return c.packOIDBPackage(cmd, serviceType, msg.Encode())
|
|
}
|
|
|
|
func (c *QQClient) packOIDBPackageProto(cmd, serviceType int32, msg proto.Message) []byte {
|
|
b, _ := proto.Marshal(msg)
|
|
return c.packOIDBPackage(cmd, serviceType, b)
|
|
}
|
|
|
|
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...),
|
|
})
|
|
}
|