diff --git a/client/client.go b/client/client.go index ab3937ea..dc8fe545 100644 --- a/client/client.go +++ b/client/client.go @@ -1,7 +1,6 @@ package client import ( - "bytes" "crypto/md5" "fmt" "math" @@ -9,8 +8,6 @@ import ( "net" "runtime/debug" "sort" - "strconv" - "strings" "sync" "sync/atomic" "time" @@ -472,54 +469,6 @@ func (c *QQClient) SetOnlineStatus(s UserOnlineStatus) { _, _ = c.sendAndWait(c.buildStatusSetPacket(11, int32(s))) } -func (c *QQClient) GetVipInfo(target int64) (*VipInfo, error) { - b, err := utils.HttpGetBytes(fmt.Sprintf("https://h5.vip.qq.com/p/mc/cardv2/other?platform=1&qq=%d&adtag=geren&aid=mvip.pingtai.mobileqq.androidziliaoka.fromqita", target), c.getCookiesWithDomain("h5.vip.qq.com")) - if err != nil { - return nil, err - } - ret := VipInfo{Uin: target} - b = b[bytes.Index(b, []byte(``))+24:] - t := b[:bytes.Index(b, []byte(``))] - ret.Name = string(t) - b = b[bytes.Index(b, []byte(`LV`))+17:] - t = b[:bytes.Index(b, []byte(`

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

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

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

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

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

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

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

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

`))] - ret.VipGrowthTotal, _ = strconv.Atoi(string(t)) - return &ret, nil -} - -func (c *QQClient) GetGroupHonorInfo(groupCode int64, honorType HonorType) (*GroupHonorInfo, error) { - b, err := utils.HttpGetBytes(fmt.Sprintf("https://qun.qq.com/interactive/honorlist?gc=%d&type=%d", groupCode, honorType), c.getCookiesWithDomain("qun.qq.com")) - if err != nil { - return nil, err - } - b = b[bytes.Index(b, []byte(`window.__INITIAL_STATE__=`))+25:] - b = b[:bytes.Index(b, []byte(""))] - ret := GroupHonorInfo{} - err = json.Unmarshal(b, &ret) - if err != nil { - return nil, err - } - return &ret, nil -} - func (c *QQClient) GetWordSegmentation(text string) ([]string, error) { rsp, err := c.sendAndWait(c.buildWordSegmentationPacket([]byte(text))) if err != nil { diff --git a/client/group_notice.go b/client/group_notice.go deleted file mode 100644 index a47654cc..00000000 --- a/client/group_notice.go +++ /dev/null @@ -1,114 +0,0 @@ -package client - -import ( - "bytes" - "fmt" - "html" - "io/ioutil" - "mime/multipart" - "net/http" - "net/textproto" - "net/url" - "strconv" - - "github.com/pkg/errors" - - "github.com/Mrs4s/MiraiGo/utils" -) - -type noticePicUpResponse struct { - ErrorCode int `json:"ec"` - ErrorMessage string `json:"em"` - ID string `json:"id"` -} - -type noticeImage struct { - Height string `json:"h"` - Width string `json:"w"` - ID string `json:"id"` -} - -func (c *QQClient) uploadGroupNoticePic(img []byte) (*noticeImage, error) { - buf := new(bytes.Buffer) - w := multipart.NewWriter(buf) - err := w.WriteField("bkn", strconv.Itoa(c.getCSRFToken())) - if err != nil { - return nil, errors.Wrap(err, "write multipart failed") - } - err = w.WriteField("source", "troopNotice") - if err != nil { - return nil, errors.Wrap(err, "write multipart failed") - } - err = w.WriteField("m", "0") - if err != nil { - return nil, errors.Wrap(err, "write multipart failed") - } - h := make(textproto.MIMEHeader) - h.Set("Content-Disposition", `form-data; name="pic_up"; filename="temp_uploadFile.png"`) - h.Set("Content-Type", "image/png") - fw, err := w.CreatePart(h) - if err != nil { - return nil, errors.Wrap(err, "create multipart field failed") - } - _, err = fw.Write(img) - if err != nil { - return nil, errors.Wrap(err, "write multipart failed") - } - err = w.Close() - if err != nil { - return nil, errors.Wrap(err, "close multipart failed") - } - req, err := http.NewRequest("POST", "https://web.qun.qq.com/cgi-bin/announce/upload_img", buf) - if err != nil { - return nil, errors.Wrap(err, "new request error") - } - req.Header.Set("Content-Type", w.FormDataContentType()) - req.Header.Set("Cookie", c.getCookies()) - resp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, errors.Wrap(err, "post error") - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, errors.Wrap(err, "read body error") - } - res := noticePicUpResponse{} - err = json.Unmarshal(body, &res) - if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal json") - } - if res.ErrorCode != 0 { - return nil, errors.New(res.ErrorMessage) - } - ret := ¬iceImage{} - err = json.UnmarshalFromString(html.UnescapeString(res.ID), &ret) - if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal json") - } - return ret, nil -} - -// AddGroupNoticeSimple 发群公告 -func (c *QQClient) AddGroupNoticeSimple(groupCode int64, text string) error { - body := fmt.Sprintf(`qid=%v&bkn=%v&text=%v&pinned=0&type=1&settings={"is_show_edit_card":0,"tip_window_type":1,"confirm_required":1}`, groupCode, c.getCSRFToken(), url.QueryEscape(text)) - _, err := utils.HttpPostBytesWithCookie("https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn="+fmt.Sprint(c.getCSRFToken()), []byte(body), c.getCookiesWithDomain("qun.qq.com")) - if err != nil { - return errors.Wrap(err, "request error") - } - return nil -} - -// AddGroupNoticeWithPic 发群公告带图片 -func (c *QQClient) AddGroupNoticeWithPic(groupCode int64, text string, pic []byte) error { - img, err := c.uploadGroupNoticePic(pic) - if err != nil { - return err - } - body := fmt.Sprintf(`qid=%v&bkn=%v&text=%v&pinned=0&type=1&settings={"is_show_edit_card":0,"tip_window_type":1,"confirm_required":1}&pic=%v&imgWidth=%v&imgHeight=%v`, groupCode, c.getCSRFToken(), url.QueryEscape(text), img.ID, img.Width, img.Height) - _, err = utils.HttpPostBytesWithCookie("https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn="+fmt.Sprint(c.getCSRFToken()), []byte(body), c.getCookiesWithDomain("qun.qq.com")) - if err != nil { - return errors.Wrap(err, "request error") - } - return nil -} diff --git a/client/honor.go b/client/honor.go deleted file mode 100644 index 3a3daa7a..00000000 --- a/client/honor.go +++ /dev/null @@ -1,39 +0,0 @@ -package client - -type ( - HonorType int - - GroupHonorInfo struct { - GroupCode string `json:"gc"` - Uin string `json:"uin"` - Type HonorType `json:"type"` - TalkativeList []HonorMemberInfo `json:"talkativeList"` - CurrentTalkative CurrentTalkative `json:"currentTalkative"` - ActorList []HonorMemberInfo `json:"actorList"` - LegendList []HonorMemberInfo `json:"legendList"` - StrongNewbieList []HonorMemberInfo `json:"strongnewbieList"` - EmotionList []HonorMemberInfo `json:"emotionList"` - } - - HonorMemberInfo struct { - Uin int64 `json:"uin"` - Avatar string `json:"avatar"` - Name string `json:"name"` - Desc string `json:"desc"` - } - - CurrentTalkative struct { - Uin int64 `json:"uin"` - DayCount int32 `json:"day_count"` - Avatar string `json:"avatar"` - Name string `json:"nick"` - } -) - -const ( - Talkative HonorType = 1 // 龙王 - Performer HonorType = 2 // 群聊之火 - Legend HonorType = 3 // 群聊炙焰 - StrongNewbie HonorType = 5 // 冒尖小春笋 - Emotion HonorType = 6 // 快乐源泉 -) diff --git a/client/http_api.go b/client/http_api.go new file mode 100644 index 00000000..b4ae2ff9 --- /dev/null +++ b/client/http_api.go @@ -0,0 +1,263 @@ +package client + +import ( + "bytes" + "fmt" + "html" + "io/ioutil" + "mime/multipart" + "net/http" + "net/textproto" + "net/url" + "strconv" + "strings" + + "github.com/pkg/errors" + "google.golang.org/protobuf/proto" + + "github.com/Mrs4s/MiraiGo/binary" + "github.com/Mrs4s/MiraiGo/client/pb/richmedia" + "github.com/Mrs4s/MiraiGo/utils" +) + +/* -------- VipInfo -------- */ + +type VipInfo struct { + Uin int64 + Name string + Level int + LevelSpeed float64 + VipLevel string + VipGrowthSpeed int + VipGrowthTotal int +} + +func (c *QQClient) GetVipInfo(target int64) (*VipInfo, error) { + b, err := utils.HttpGetBytes(fmt.Sprintf("https://h5.vip.qq.com/p/mc/cardv2/other?platform=1&qq=%d&adtag=geren&aid=mvip.pingtai.mobileqq.androidziliaoka.fromqita", target), c.getCookiesWithDomain("h5.vip.qq.com")) + if err != nil { + return nil, err + } + ret := VipInfo{Uin: target} + b = b[bytes.Index(b, []byte(``))+24:] + t := b[:bytes.Index(b, []byte(``))] + ret.Name = string(t) + b = b[bytes.Index(b, []byte(`LV`))+17:] + t = b[:bytes.Index(b, []byte(`

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

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

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

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

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

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

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

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

`))] + ret.VipGrowthTotal, _ = strconv.Atoi(string(t)) + return &ret, nil +} + +/* -------- GroupHonorInfo -------- */ + +type ( + HonorType int + + GroupHonorInfo struct { + GroupCode string `json:"gc"` + Uin string `json:"uin"` + Type HonorType `json:"type"` + TalkativeList []HonorMemberInfo `json:"talkativeList"` + CurrentTalkative CurrentTalkative `json:"currentTalkative"` + ActorList []HonorMemberInfo `json:"actorList"` + LegendList []HonorMemberInfo `json:"legendList"` + StrongNewbieList []HonorMemberInfo `json:"strongnewbieList"` + EmotionList []HonorMemberInfo `json:"emotionList"` + } + + HonorMemberInfo struct { + Uin int64 `json:"uin"` + Avatar string `json:"avatar"` + Name string `json:"name"` + Desc string `json:"desc"` + } + + CurrentTalkative struct { + Uin int64 `json:"uin"` + DayCount int32 `json:"day_count"` + Avatar string `json:"avatar"` + Name string `json:"nick"` + } +) + +const ( + Talkative HonorType = 1 // 龙王 + Performer HonorType = 2 // 群聊之火 + Legend HonorType = 3 // 群聊炙焰 + StrongNewbie HonorType = 5 // 冒尖小春笋 + Emotion HonorType = 6 // 快乐源泉 +) + +func (c *QQClient) GetGroupHonorInfo(groupCode int64, honorType HonorType) (*GroupHonorInfo, error) { + b, err := utils.HttpGetBytes(fmt.Sprintf("https://qun.qq.com/interactive/honorlist?gc=%d&type=%d", groupCode, honorType), c.getCookiesWithDomain("qun.qq.com")) + if err != nil { + return nil, err + } + b = b[bytes.Index(b, []byte(`window.__INITIAL_STATE__=`))+25:] + b = b[:bytes.Index(b, []byte(""))] + ret := GroupHonorInfo{} + err = json.Unmarshal(b, &ret) + if err != nil { + return nil, err + } + return &ret, nil +} + +/* -------- TextToSpeech -------- */ + +func (c *QQClient) GetTts(text string) ([]byte, error) { + url := "https://textts.qq.com/cgi-bin/tts" + text, _ = json.MarshalToString(text) + data := fmt.Sprintf(`{"appid": "201908021016","sendUin": %v,"text": %v}`, c.Uin, text) + rsp, err := utils.HttpPostBytesWithCookie(url, []byte(data), c.getCookies()) + if err != nil { + return nil, errors.Wrap(err, "failed to post to tts server") + } + ttsReader := binary.NewReader(rsp) + ttsWriter := binary.NewWriter() + for { + // 数据格式 69e(字符串) 十六进制 数据长度 0 为结尾 + // 0D 0A (分隔符) payload 0D 0A + var dataLen []byte + for b := ttsReader.ReadByte(); b != byte(0x0d); b = ttsReader.ReadByte() { + dataLen = append(dataLen, b) + } + ttsReader.ReadByte() + var length int + _, _ = fmt.Sscan("0x"+string(dataLen), &length) + if length == 0 { + break + } + ttsRsp := &richmedia.TtsRspBody{} + err := proto.Unmarshal(ttsReader.ReadBytes(length), ttsRsp) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal protobuf message") + } + if ttsRsp.RetCode != 0 { + return nil, errors.New("can't convert text to voice") + } + for _, voiceItem := range ttsRsp.VoiceData { + ttsWriter.Write(voiceItem.Voice) + } + ttsReader.ReadBytes(2) + } + ret := ttsWriter.Bytes() + ret[0] = '\x02' + return ret, nil +} + +/* -------- GroupNotice -------- */ + +type noticePicUpResponse struct { + ErrorCode int `json:"ec"` + ErrorMessage string `json:"em"` + ID string `json:"id"` +} + +type noticeImage struct { + Height string `json:"h"` + Width string `json:"w"` + ID string `json:"id"` +} + +func (c *QQClient) uploadGroupNoticePic(img []byte) (*noticeImage, error) { + buf := new(bytes.Buffer) + w := multipart.NewWriter(buf) + err := w.WriteField("bkn", strconv.Itoa(c.getCSRFToken())) + if err != nil { + return nil, errors.Wrap(err, "write multipart failed") + } + err = w.WriteField("source", "troopNotice") + if err != nil { + return nil, errors.Wrap(err, "write multipart failed") + } + err = w.WriteField("m", "0") + if err != nil { + return nil, errors.Wrap(err, "write multipart failed") + } + h := make(textproto.MIMEHeader) + h.Set("Content-Disposition", `form-data; name="pic_up"; filename="temp_uploadFile.png"`) + h.Set("Content-Type", "image/png") + fw, err := w.CreatePart(h) + if err != nil { + return nil, errors.Wrap(err, "create multipart field failed") + } + _, err = fw.Write(img) + if err != nil { + return nil, errors.Wrap(err, "write multipart failed") + } + err = w.Close() + if err != nil { + return nil, errors.Wrap(err, "close multipart failed") + } + req, err := http.NewRequest("POST", "https://web.qun.qq.com/cgi-bin/announce/upload_img", buf) + if err != nil { + return nil, errors.Wrap(err, "new request error") + } + req.Header.Set("Content-Type", w.FormDataContentType()) + req.Header.Set("Cookie", c.getCookies()) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, errors.Wrap(err, "post error") + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrap(err, "read body error") + } + res := noticePicUpResponse{} + err = json.Unmarshal(body, &res) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal json") + } + if res.ErrorCode != 0 { + return nil, errors.New(res.ErrorMessage) + } + ret := ¬iceImage{} + err = json.UnmarshalFromString(html.UnescapeString(res.ID), &ret) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal json") + } + return ret, nil +} + +// AddGroupNoticeSimple 发群公告 +func (c *QQClient) AddGroupNoticeSimple(groupCode int64, text string) error { + body := fmt.Sprintf(`qid=%v&bkn=%v&text=%v&pinned=0&type=1&settings={"is_show_edit_card":0,"tip_window_type":1,"confirm_required":1}`, groupCode, c.getCSRFToken(), url.QueryEscape(text)) + _, err := utils.HttpPostBytesWithCookie("https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn="+fmt.Sprint(c.getCSRFToken()), []byte(body), c.getCookiesWithDomain("qun.qq.com")) + if err != nil { + return errors.Wrap(err, "request error") + } + return nil +} + +// AddGroupNoticeWithPic 发群公告带图片 +func (c *QQClient) AddGroupNoticeWithPic(groupCode int64, text string, pic []byte) error { + img, err := c.uploadGroupNoticePic(pic) + if err != nil { + return err + } + body := fmt.Sprintf(`qid=%v&bkn=%v&text=%v&pinned=0&type=1&settings={"is_show_edit_card":0,"tip_window_type":1,"confirm_required":1}&pic=%v&imgWidth=%v&imgHeight=%v`, groupCode, c.getCSRFToken(), url.QueryEscape(text), img.ID, img.Width, img.Height) + _, err = utils.HttpPostBytesWithCookie("https://web.qun.qq.com/cgi-bin/announce/add_qun_notice?bkn="+fmt.Sprint(c.getCSRFToken()), []byte(body), c.getCookiesWithDomain("qun.qq.com")) + if err != nil { + return errors.Wrap(err, "request error") + } + return nil +} diff --git a/client/tts.go b/client/tts.go deleted file mode 100644 index 33a0191c..00000000 --- a/client/tts.go +++ /dev/null @@ -1,53 +0,0 @@ -package client - -import ( - "fmt" - - "github.com/pkg/errors" - "google.golang.org/protobuf/proto" - - "github.com/Mrs4s/MiraiGo/binary" - "github.com/Mrs4s/MiraiGo/client/pb/richmedia" - "github.com/Mrs4s/MiraiGo/utils" -) - -func (c *QQClient) GetTts(text string) ([]byte, error) { - url := "https://textts.qq.com/cgi-bin/tts" - text, _ = json.MarshalToString(text) - data := fmt.Sprintf(`{"appid": "201908021016","sendUin": %v,"text": %v}`, c.Uin, text) - rsp, err := utils.HttpPostBytesWithCookie(url, []byte(data), c.getCookies()) - if err != nil { - return nil, errors.Wrap(err, "failed to post to tts server") - } - ttsReader := binary.NewReader(rsp) - ttsWriter := binary.NewWriter() - for { - // 数据格式 69e(字符串) 十六进制 数据长度 0 为结尾 - // 0D 0A (分隔符) payload 0D 0A - var dataLen []byte - for b := ttsReader.ReadByte(); b != byte(0x0d); b = ttsReader.ReadByte() { - dataLen = append(dataLen, b) - } - ttsReader.ReadByte() - var length int - _, _ = fmt.Sscan("0x"+string(dataLen), &length) - if length == 0 { - break - } - ttsRsp := &richmedia.TtsRspBody{} - err := proto.Unmarshal(ttsReader.ReadBytes(length), ttsRsp) - if err != nil { - return nil, errors.Wrap(err, "failed to unmarshal protobuf message") - } - if ttsRsp.RetCode != 0 { - return nil, errors.New("can't convert text to voice") - } - for _, voiceItem := range ttsRsp.VoiceData { - ttsWriter.Write(voiceItem.Voice) - } - ttsReader.ReadBytes(2) - } - ret := ttsWriter.Bytes() - ret[0] = '\x02' - return ret, nil -} diff --git a/client/vip.go b/client/vip.go deleted file mode 100644 index 532d563c..00000000 --- a/client/vip.go +++ /dev/null @@ -1,13 +0,0 @@ -package client - -type ( - VipInfo struct { - Uin int64 - Name string - Level int - LevelSpeed float64 - VipLevel string - VipGrowthSpeed int - VipGrowthTotal int - } -)