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 := &noticeImage{}
   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  }