github.com/Mrs4s/MiraiGo@v0.0.0-20240226124653-54bdd873e3fe/client/http_api.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "html" 8 "mime/multipart" 9 "net/http" 10 "net/textproto" 11 "net/url" 12 "regexp" 13 "strconv" 14 15 "github.com/pkg/errors" 16 17 "github.com/Mrs4s/MiraiGo/binary" 18 "github.com/Mrs4s/MiraiGo/client/pb/richmedia" 19 "github.com/Mrs4s/MiraiGo/internal/proto" 20 "github.com/Mrs4s/MiraiGo/utils" 21 ) 22 23 /* -------- GroupHonorInfo -------- */ 24 25 type ( 26 HonorType int 27 28 GroupHonorInfo struct { 29 GroupCode string `json:"gc"` 30 Uin string `json:"uin"` 31 Type HonorType `json:"type"` 32 TalkativeList []HonorMemberInfo `json:"talkativeList"` 33 CurrentTalkative CurrentTalkative `json:"currentTalkative"` 34 ActorList []HonorMemberInfo `json:"actorList"` 35 LegendList []HonorMemberInfo `json:"legendList"` 36 StrongNewbieList []HonorMemberInfo `json:"strongnewbieList"` 37 EmotionList []HonorMemberInfo `json:"emotionList"` 38 } 39 40 HonorMemberInfo struct { 41 Uin int64 `json:"uin"` 42 Avatar string `json:"avatar"` 43 Name string `json:"name"` 44 Desc string `json:"desc"` 45 } 46 47 CurrentTalkative struct { 48 Uin int64 `json:"uin"` 49 DayCount int32 `json:"day_count"` 50 Avatar string `json:"avatar"` 51 Name string `json:"nick"` 52 } 53 ) 54 55 const ( 56 Talkative HonorType = 1 // 龙王 57 Performer HonorType = 2 // 群聊之火 58 Legend HonorType = 3 // 群聊炙焰 59 StrongNewbie HonorType = 5 // 冒尖小春笋 60 Emotion HonorType = 6 // 快乐源泉 61 ) 62 63 // 匹配 window.__INITIAL_STATE__ = 后的内容 64 var honorRe = regexp.MustCompile(`window\.__INITIAL_STATE__\s*?=\s*?(\{.*\})`) 65 66 func (c *QQClient) GetGroupHonorInfo(groupCode int64, honorType HonorType) (*GroupHonorInfo, error) { 67 b, err := utils.HttpGetBytes(fmt.Sprintf("https://qun.qq.com/interactive/honorlist?gc=%d&type=%d", groupCode, honorType), c.getCookiesWithDomain("qun.qq.com")) 68 if err != nil { 69 return nil, err 70 } 71 matched := honorRe.FindSubmatch(b) 72 if len(matched) == 0 { 73 return nil, errors.New("无匹配结果") 74 } 75 ret := GroupHonorInfo{} 76 err = json.NewDecoder(bytes.NewReader(matched[1])).Decode(&ret) 77 if err != nil { 78 return nil, err 79 } 80 return &ret, nil 81 } 82 83 /* -------- TextToSpeech -------- */ 84 85 func (c *QQClient) GetTts(text string) ([]byte, error) { 86 apiUrl := "https://textts.qq.com/cgi-bin/tts" 87 data := fmt.Sprintf(`{"appid": "201908021016","sendUin": %v,"text": %q}`, c.Uin, text) 88 rsp, err := utils.HttpPostBytesWithCookie(apiUrl, []byte(data), c.getCookies()) 89 if err != nil { 90 return nil, errors.Wrap(err, "failed to post to tts server") 91 } 92 ttsReader := binary.NewReader(rsp) 93 ttsWriter := binary.SelectWriter() 94 for { 95 // 数据格式 69e(字符串) 十六进制 数据长度 0 为结尾 96 // 0D 0A (分隔符) payload 0D 0A 97 var dataLen []byte 98 for b := ttsReader.ReadByte(); b != byte(0x0d); b = ttsReader.ReadByte() { 99 dataLen = append(dataLen, b) 100 } 101 ttsReader.ReadByte() 102 var length int 103 _, _ = fmt.Sscan("0x"+string(dataLen), &length) 104 if length == 0 { 105 break 106 } 107 ttsRsp := &richmedia.TtsRspBody{} 108 err := proto.Unmarshal(ttsReader.ReadBytes(length), ttsRsp) 109 if err != nil { 110 return nil, errors.Wrap(err, "failed to unmarshal protobuf message") 111 } 112 if ttsRsp.RetCode != 0 { 113 return nil, errors.New("can't convert text to voice") 114 } 115 for _, voiceItem := range ttsRsp.VoiceData { 116 ttsWriter.Write(voiceItem.Voice) 117 } 118 ttsReader.ReadBytes(2) 119 } 120 ret := ttsWriter.Bytes() 121 ret[0] = '\x02' 122 return ret, nil 123 } 124 125 /* -------- GroupNotice -------- */ 126 127 type groupNoticeRsp struct { 128 Feeds []*GroupNoticeFeed `json:"feeds"` 129 Inst []*GroupNoticeFeed `json:"inst"` 130 } 131 132 type GroupNoticeFeed struct { 133 NoticeId string `json:"fid"` 134 SenderId uint32 `json:"u"` 135 PublishTime uint64 `json:"pubt"` 136 Message struct { 137 Text string `json:"text"` 138 Images []noticeImage `json:"pics"` 139 } `json:"msg"` 140 } 141 142 type GroupNoticeMessage struct { 143 NoticeId string `json:"notice_id"` 144 SenderId uint32 `json:"sender_id"` 145 PublishTime uint64 `json:"publish_time"` 146 Message struct { 147 Text string `json:"text"` 148 Images []GroupNoticeImage `json:"images"` 149 } `json:"message"` 150 } 151 152 type GroupNoticeImage struct { 153 Height string `json:"height"` 154 Width string `json:"width"` 155 ID string `json:"id"` 156 } 157 158 type noticePicUpResponse struct { 159 ErrorCode int `json:"ec"` 160 ErrorMessage string `json:"em"` 161 ID string `json:"id"` 162 } 163 164 type noticeImage struct { 165 Height string `json:"h"` 166 Width string `json:"w"` 167 ID string `json:"id"` 168 } 169 170 type noticeSendResp struct { 171 NoticeId string `json:"new_fid"` 172 } 173 174 func (c *QQClient) GetGroupNotice(groupCode int64) (l []*GroupNoticeMessage, err error) { 175 v := url.Values{} 176 v.Set("bkn", strconv.Itoa(c.getCSRFToken())) 177 v.Set("qid", strconv.FormatInt(groupCode, 10)) 178 v.Set("ft", "23") 179 v.Set("ni", "1") 180 v.Set("n", "1") 181 v.Set("i", "1") 182 v.Set("log_read", "1") 183 v.Set("platform", "1") 184 v.Set("s", "-1") 185 v.Set("n", "20") 186 187 req, _ := http.NewRequest(http.MethodGet, "https://web.qun.qq.com/cgi-bin/announce/get_t_list?"+v.Encode(), nil) 188 req.Header.Set("Cookie", c.getCookies()) 189 rsp, err := utils.Client.Do(req) 190 if err != nil { 191 return 192 } 193 defer rsp.Body.Close() 194 195 r := groupNoticeRsp{} 196 err = json.NewDecoder(rsp.Body).Decode(&r) 197 if err != nil { 198 return 199 } 200 201 return c.parseGroupNoticeJson(&r), nil 202 } 203 204 func (c *QQClient) parseGroupNoticeJson(s *groupNoticeRsp) []*GroupNoticeMessage { 205 o := make([]*GroupNoticeMessage, 0, len(s.Feeds)+len(s.Inst)) 206 parse := func(v *GroupNoticeFeed) { 207 ims := make([]GroupNoticeImage, 0, len(v.Message.Images)) 208 for i := 0; i < len(v.Message.Images); i++ { 209 ims = append(ims, GroupNoticeImage{ 210 Height: v.Message.Images[i].Height, 211 Width: v.Message.Images[i].Width, 212 ID: v.Message.Images[i].ID, 213 }) 214 } 215 216 o = append(o, &GroupNoticeMessage{ 217 NoticeId: v.NoticeId, 218 SenderId: v.SenderId, 219 PublishTime: v.PublishTime, 220 Message: struct { 221 Text string `json:"text"` 222 Images []GroupNoticeImage `json:"images"` 223 }{ 224 Text: v.Message.Text, 225 Images: ims, 226 }, 227 }) 228 } 229 for _, v := range s.Feeds { 230 parse(v) 231 } 232 for _, v := range s.Inst { 233 parse(v) 234 } 235 return o 236 } 237 238 func (c *QQClient) uploadGroupNoticePic(img []byte) (*noticeImage, error) { 239 buf := new(bytes.Buffer) 240 w := multipart.NewWriter(buf) 241 _ = w.WriteField("bkn", strconv.Itoa(c.getCSRFToken())) 242 _ = w.WriteField("source", "troopNotice") 243 _ = w.WriteField("m", "0") 244 h := make(textproto.MIMEHeader) 245 h.Set("Content-Disposition", `form-data; name="pic_up"; filename="temp_uploadFile.png"`) 246 h.Set("Content-Type", "image/png") 247 fw, _ := w.CreatePart(h) 248 _, _ = fw.Write(img) 249 _ = w.Close() 250 req, err := http.NewRequest(http.MethodPost, "https://web.qun.qq.com/cgi-bin/announce/upload_img", buf) 251 if err != nil { 252 return nil, errors.Wrap(err, "new request error") 253 } 254 req.Header.Set("Content-Type", w.FormDataContentType()) 255 req.Header.Set("Cookie", c.getCookies()) 256 resp, err := http.DefaultClient.Do(req) 257 if err != nil { 258 return nil, errors.Wrap(err, "post error") 259 } 260 defer resp.Body.Close() 261 var res noticePicUpResponse 262 err = json.NewDecoder(resp.Body).Decode(&res) 263 if err != nil { 264 return nil, errors.Wrap(err, "failed to unmarshal json") 265 } 266 if res.ErrorCode != 0 { 267 return nil, errors.New(res.ErrorMessage) 268 } 269 ret := ¬iceImage{} 270 err = json.Unmarshal([]byte(html.UnescapeString(res.ID)), &ret) 271 if err != nil { 272 return nil, errors.Wrap(err, "failed to unmarshal json") 273 } 274 return ret, nil 275 } 276 277 // AddGroupNoticeSimple 发群公告 278 func (c *QQClient) AddGroupNoticeSimple(groupCode int64, text string) (noticeId string, err error) { 279 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)) 280 resp, 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")) 281 if err != nil { 282 return "", errors.Wrap(err, "request error") 283 } 284 var res noticeSendResp 285 err = json.Unmarshal(resp, &res) 286 if err != nil { 287 return "", errors.Wrap(err, "json unmarshal error") 288 } 289 return res.NoticeId, nil 290 } 291 292 // AddGroupNoticeWithPic 发群公告带图片 293 func (c *QQClient) AddGroupNoticeWithPic(groupCode int64, text string, pic []byte) (noticeId string, err error) { 294 img, err := c.uploadGroupNoticePic(pic) 295 if err != nil { 296 return "", err 297 } 298 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) 299 resp, 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")) 300 if err != nil { 301 return "", errors.Wrap(err, "request error") 302 } 303 var res noticeSendResp 304 err = json.Unmarshal(resp, &res) 305 if err != nil { 306 return "", errors.Wrap(err, "json unmarshal error") 307 } 308 return res.NoticeId, nil 309 } 310 311 func (c *QQClient) DelGroupNotice(groupCode int64, fid string) error { 312 body := fmt.Sprintf(`fid=%s&qid=%v&bkn=%v&ft=23&op=1`, fid, groupCode, c.getCSRFToken()) 313 _, err := utils.HttpPostBytesWithCookie("https://web.qun.qq.com/cgi-bin/announce/del_feed", []byte(body), c.getCookiesWithDomain("qun.qq.com")) 314 if err != nil { 315 return errors.Wrap(err, "request error") 316 } 317 return nil 318 }