github.com/Mrs4s/MiraiGo@v0.0.0-20240226124653-54bdd873e3fe/client/group_info.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"math/rand"
     8  	"net/http"
     9  	"net/url"
    10  	"sort"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/pkg/errors"
    15  
    16  	"github.com/Mrs4s/MiraiGo/binary"
    17  	"github.com/Mrs4s/MiraiGo/binary/jce"
    18  	"github.com/Mrs4s/MiraiGo/client/internal/network"
    19  	"github.com/Mrs4s/MiraiGo/client/pb/oidb"
    20  	"github.com/Mrs4s/MiraiGo/client/pb/profilecard"
    21  	"github.com/Mrs4s/MiraiGo/internal/proto"
    22  	"github.com/Mrs4s/MiraiGo/utils"
    23  )
    24  
    25  type (
    26  	GroupInfo struct {
    27  		Uin             int64
    28  		Code            int64
    29  		Name            string
    30  		OwnerUin        int64
    31  		GroupCreateTime uint32
    32  		GroupLevel      uint32
    33  		MemberCount     uint16
    34  		MaxMemberCount  uint16
    35  		Members         []*GroupMemberInfo
    36  		// 最后一条信息的SEQ,只有通过 GetGroupInfo 函数获取的 GroupInfo 才会有
    37  		LastMsgSeq int64
    38  
    39  		client *QQClient
    40  
    41  		lock sync.RWMutex
    42  	}
    43  
    44  	GroupMemberInfo struct {
    45  		Group           *GroupInfo
    46  		Uin             int64
    47  		Nickname        string
    48  		CardName        string
    49  		JoinTime        int64
    50  		LastSpeakTime   int64
    51  		SpecialTitle    string
    52  		ShutUpTimestamp int64
    53  		Permission      MemberPermission
    54  		Level           uint16
    55  		Gender          byte
    56  	}
    57  
    58  	// GroupSearchInfo 通过搜索得到的群信息
    59  	GroupSearchInfo struct {
    60  		Code int64  // 群号
    61  		Name string // 群名
    62  		Memo string // 简介
    63  	}
    64  )
    65  
    66  func init() {
    67  	decoders["SummaryCard.ReqSearch"] = decodeGroupSearchResponse
    68  	decoders["OidbSvc.0x88d_0"] = decodeGroupInfoResponse
    69  }
    70  
    71  func (c *QQClient) GetGroupInfo(groupCode int64) (*GroupInfo, error) {
    72  	i, err := c.sendAndWait(c.buildGroupInfoRequestPacket(groupCode))
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	return i.(*GroupInfo), nil
    77  }
    78  
    79  // OidbSvc.0x88d_0
    80  func (c *QQClient) buildGroupInfoRequestPacket(groupCode int64) (uint16, []byte) {
    81  	body := &oidb.D88DReqBody{
    82  		AppId: proto.Uint32(c.version().AppId),
    83  		ReqGroupInfo: []*oidb.ReqGroupInfo{
    84  			{
    85  				GroupCode: proto.Uint64(uint64(groupCode)),
    86  				Stgroupinfo: &oidb.D88DGroupInfo{
    87  					GroupOwner:           proto.Uint64(0),
    88  					GroupUin:             proto.Uint64(0),
    89  					GroupCreateTime:      proto.Uint32(0),
    90  					GroupFlag:            proto.Uint32(0),
    91  					GroupMemberMaxNum:    proto.Uint32(0),
    92  					GroupMemberNum:       proto.Uint32(0),
    93  					GroupOption:          proto.Uint32(0),
    94  					GroupLevel:           proto.Uint32(0),
    95  					GroupFace:            proto.Uint32(0),
    96  					GroupName:            EmptyBytes,
    97  					GroupMemo:            EmptyBytes,
    98  					GroupFingerMemo:      EmptyBytes,
    99  					GroupLastMsgTime:     proto.Uint32(0),
   100  					GroupCurMsgSeq:       proto.Uint32(0),
   101  					GroupQuestion:        EmptyBytes,
   102  					GroupAnswer:          EmptyBytes,
   103  					GroupGrade:           proto.Uint32(0),
   104  					ActiveMemberNum:      proto.Uint32(0),
   105  					HeadPortraitSeq:      proto.Uint32(0),
   106  					MsgHeadPortrait:      &oidb.D88DGroupHeadPortrait{},
   107  					StGroupExInfo:        &oidb.D88DGroupExInfoOnly{},
   108  					GroupSecLevel:        proto.Uint32(0),
   109  					CmduinPrivilege:      proto.Uint32(0),
   110  					NoFingerOpenFlag:     proto.Uint32(0),
   111  					NoCodeFingerOpenFlag: proto.Uint32(0),
   112  				},
   113  			},
   114  		},
   115  		PcClientVersion: proto.Uint32(0),
   116  	}
   117  	payload := c.packOIDBPackageProto(2189, 0, body)
   118  	return c.uniPacket("OidbSvc.0x88d_0", payload)
   119  }
   120  
   121  // SearchGroupByKeyword 通过关键词搜索陌生群组
   122  func (c *QQClient) SearchGroupByKeyword(keyword string) ([]GroupSearchInfo, error) {
   123  	rsp, err := c.sendAndWait(c.buildGroupSearchPacket(keyword))
   124  	if err != nil {
   125  		return nil, errors.Wrap(err, "group search failed")
   126  	}
   127  	return rsp.([]GroupSearchInfo), nil
   128  }
   129  
   130  // SummaryCard.ReqSearch
   131  func (c *QQClient) buildGroupSearchPacket(keyword string) (uint16, []byte) {
   132  	comm, _ := proto.Marshal(&profilecard.BusiComm{
   133  		Ver:      proto.Int32(1),
   134  		Seq:      proto.Int32(rand.Int31()),
   135  		Service:  proto.Int32(80000001),
   136  		Platform: proto.Int32(2),
   137  		Qqver:    proto.String("8.5.0.5025"),
   138  		Build:    proto.Int32(5025),
   139  	})
   140  	search, _ := proto.Marshal(&profilecard.AccountSearch{
   141  		Start:     proto.Int32(0),
   142  		End:       proto.Uint32(4),
   143  		Keyword:   proto.Some(keyword),
   144  		Highlight: []string{keyword},
   145  		UserLocation: &profilecard.Location{
   146  			Latitude:  proto.Float64(0),
   147  			Longitude: proto.Float64(0),
   148  		},
   149  		Filtertype: proto.Int32(0),
   150  	})
   151  	req := &jce.SummaryCardReqSearch{
   152  		Keyword:     keyword,
   153  		CountryCode: "+86",
   154  		Version:     3,
   155  		ReqServices: [][]byte{
   156  			binary.NewWriterF(func(w *binary.Writer) {
   157  				w.WriteByte(0x28)
   158  				w.WriteUInt32(uint32(len(comm)))
   159  				w.WriteUInt32(uint32(len(search)))
   160  				w.Write(comm)
   161  				w.Write(search)
   162  				w.WriteByte(0x29)
   163  			}),
   164  		},
   165  	}
   166  	head := jce.NewJceWriter()
   167  	head.WriteInt32(2, 0)
   168  	buf := &jce.RequestDataVersion3{Map: map[string][]byte{
   169  		"ReqHead":   packUniRequestData(head.Bytes()),
   170  		"ReqSearch": packUniRequestData(req.ToBytes()),
   171  	}}
   172  	pkt := &jce.RequestPacket{
   173  		IVersion:     3,
   174  		SServantName: "SummaryCardServantObj",
   175  		SFuncName:    "ReqSearch",
   176  		SBuffer:      buf.ToBytes(),
   177  		Context:      make(map[string]string),
   178  		Status:       make(map[string]string),
   179  	}
   180  	return c.uniPacket("SummaryCard.ReqSearch", pkt.ToBytes())
   181  }
   182  
   183  // SummaryCard.ReqSearch
   184  func decodeGroupSearchResponse(_ *QQClient, pkt *network.Packet) (any, error) {
   185  	request := &jce.RequestPacket{}
   186  	request.ReadFrom(jce.NewJceReader(pkt.Payload))
   187  	data := &jce.RequestDataVersion2{}
   188  	data.ReadFrom(jce.NewJceReader(request.SBuffer))
   189  	if len(data.Map["RespHead"]["SummaryCard.RespHead"]) > 20 {
   190  		return nil, errors.New("not found")
   191  	}
   192  	rsp := data.Map["RespSearch"]["SummaryCard.RespSearch"][1:]
   193  	r := jce.NewJceReader(rsp)
   194  	// rspService := r.ReadAny(2).([]interface{})[0].([]byte)
   195  	rspService := r.ReadByteArrArr(2)[0]
   196  	sr := binary.NewReader(rspService)
   197  	sr.ReadByte()
   198  	ld1 := sr.ReadInt32()
   199  	ld2 := sr.ReadInt32()
   200  	if ld1 > 0 && ld2+9 < int32(len(rspService)) {
   201  		sr.ReadBytes(int(ld1)) // busi comm
   202  		searchPb := sr.ReadBytes(int(ld2))
   203  		searchRsp := profilecard.AccountSearch{}
   204  		err := proto.Unmarshal(searchPb, &searchRsp)
   205  		if err != nil {
   206  			return nil, errors.Wrap(err, "get search result failed")
   207  		}
   208  		var ret []GroupSearchInfo
   209  		for _, g := range searchRsp.List {
   210  			ret = append(ret, GroupSearchInfo{
   211  				Code: int64(g.Code.Unwrap()),
   212  				Name: g.Name.Unwrap(),
   213  				Memo: g.Brief.Unwrap(),
   214  			})
   215  		}
   216  		return ret, nil
   217  	}
   218  	return nil, nil
   219  }
   220  
   221  // OidbSvc.0x88d_0
   222  func decodeGroupInfoResponse(c *QQClient, pkt *network.Packet) (any, error) {
   223  	rsp := oidb.D88DRspBody{}
   224  	err := unpackOIDBPackage(pkt.Payload, &rsp)
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	if len(rsp.RspGroupInfo) == 0 {
   229  		return nil, errors.New(string(rsp.StrErrorInfo))
   230  	}
   231  	info := rsp.RspGroupInfo[0]
   232  	if info.GroupInfo == nil {
   233  		return nil, errors.New("group info not found")
   234  	}
   235  	return &GroupInfo{
   236  		Uin:             int64(info.GroupInfo.GroupUin.Unwrap()),
   237  		Code:            int64(info.GroupCode.Unwrap()),
   238  		Name:            string(info.GroupInfo.GroupName),
   239  		GroupCreateTime: info.GroupInfo.GroupCreateTime.Unwrap(),
   240  		GroupLevel:      info.GroupInfo.GroupLevel.Unwrap(),
   241  		OwnerUin:        int64(info.GroupInfo.GroupOwner.Unwrap()),
   242  		MemberCount:     uint16(info.GroupInfo.GroupMemberNum.Unwrap()),
   243  		MaxMemberCount:  uint16(info.GroupInfo.GroupMemberMaxNum.Unwrap()),
   244  		Members:         []*GroupMemberInfo{},
   245  		LastMsgSeq:      int64(info.GroupInfo.GroupCurMsgSeq.Unwrap()),
   246  		client:          c,
   247  	}, nil
   248  }
   249  
   250  func (c *QQClient) uploadGroupHeadPortrait(groupCode int64, img []byte) error {
   251  	url := fmt.Sprintf("http://htdata3.qq.com/cgi-bin/httpconn?htcmd=0x6ff0072&ver=5520&ukey=%v&range=0&uin=%v&seq=23&groupuin=%v&filetype=3&imagetype=5&userdata=0&subcmd=1&subver=101&clip=0_0_0_0&filesize=%v",
   252  		c.getSKey(), c.Uin, groupCode, len(img))
   253  	req, _ := http.NewRequest(http.MethodPost, url, bytes.NewReader(img))
   254  	req.Header["User-Agent"] = []string{"Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H)"}
   255  	req.Header["Content-Type"] = []string{"multipart/form-data;boundary=****"}
   256  	rsp, err := http.DefaultClient.Do(req)
   257  	if err != nil {
   258  		return errors.Wrap(err, "failed to upload group head portrait")
   259  	}
   260  	rsp.Body.Close()
   261  	return nil
   262  }
   263  
   264  func (g *GroupInfo) UpdateName(newName string) {
   265  	if g.AdministratorOrOwner() && newName != "" && strings.Count(newName, "") <= 20 {
   266  		g.client.updateGroupName(g.Code, newName)
   267  		g.Name = newName
   268  	}
   269  }
   270  
   271  func (g *GroupInfo) UpdateGroupHeadPortrait(img []byte) {
   272  	if g.AdministratorOrOwner() {
   273  		_ = g.client.uploadGroupHeadPortrait(g.Uin, img)
   274  	}
   275  }
   276  
   277  func (g *GroupInfo) MuteAll(mute bool) {
   278  	if g.AdministratorOrOwner() {
   279  		g.client.groupMuteAll(g.Code, mute)
   280  	}
   281  }
   282  
   283  func (g *GroupInfo) MuteAnonymous(id, nick string, seconds int32) error {
   284  	payload := fmt.Sprintf("anony_id=%v&group_code=%v&seconds=%v&anony_nick=%v&bkn=%v", url.QueryEscape(id), g.Code, seconds, nick, g.client.getCSRFToken())
   285  	rsp, err := utils.HttpPostBytesWithCookie("https://qqweb.qq.com/c/anonymoustalk/blacklist", []byte(payload), g.client.getCookies(), "application/x-www-form-urlencoded")
   286  	if err != nil {
   287  		return errors.Wrap(err, "failed to request blacklist")
   288  	}
   289  	var muteResp struct {
   290  		RetCode int `json:"retcode"`
   291  		CGICode int `json:"cgicode"`
   292  	}
   293  	err = json.Unmarshal(rsp, &muteResp)
   294  	if err != nil {
   295  		return errors.Wrap(err, "failed to parse muteResp")
   296  	}
   297  	if muteResp.RetCode != 0 {
   298  		return errors.Errorf("retcode %v", muteResp.RetCode)
   299  	}
   300  	if muteResp.CGICode != 0 {
   301  		return errors.Errorf("retcode %v", muteResp.CGICode)
   302  	}
   303  	return nil
   304  }
   305  
   306  func (g *GroupInfo) Quit() {
   307  	if g.SelfPermission() != Owner {
   308  		g.client.quitGroup(g.Code)
   309  	}
   310  }
   311  
   312  func (g *GroupInfo) SelfPermission() MemberPermission {
   313  	return g.FindMember(g.client.Uin).Permission
   314  }
   315  
   316  func (g *GroupInfo) AdministratorOrOwner() bool {
   317  	return g.SelfPermission() == Administrator || g.SelfPermission() == Owner
   318  }
   319  
   320  func (g *GroupInfo) FindMember(uin int64) *GroupMemberInfo {
   321  	r := g.Read(func(info *GroupInfo) any {
   322  		return info.FindMemberWithoutLock(uin)
   323  	})
   324  	if r == nil {
   325  		return nil
   326  	}
   327  	return r.(*GroupMemberInfo)
   328  }
   329  
   330  func (g *GroupInfo) FindMemberWithoutLock(uin int64) *GroupMemberInfo {
   331  	i := sort.Search(len(g.Members), func(i int) bool {
   332  		return g.Members[i].Uin >= uin
   333  	})
   334  	if i >= len(g.Members) || g.Members[i].Uin != uin {
   335  		return nil
   336  	}
   337  	return g.Members[i]
   338  }
   339  
   340  // sort call this method must hold the lock
   341  func (g *GroupInfo) sort() {
   342  	sort.Slice(g.Members, func(i, j int) bool {
   343  		return g.Members[i].Uin < g.Members[j].Uin
   344  	})
   345  }
   346  
   347  func (g *GroupInfo) Update(f func(*GroupInfo)) {
   348  	g.lock.Lock()
   349  	defer g.lock.Unlock()
   350  	f(g)
   351  }
   352  
   353  func (g *GroupInfo) Read(f func(*GroupInfo) any) any {
   354  	g.lock.RLock()
   355  	defer g.lock.RUnlock()
   356  	return f(g)
   357  }
   358  
   359  func (m *GroupMemberInfo) DisplayName() string {
   360  	if m.CardName == "" {
   361  		return m.Nickname
   362  	}
   363  	return m.CardName
   364  }
   365  
   366  func (m *GroupMemberInfo) EditCard(card string) {
   367  	if m.CardChangable() && len(card) <= 60 {
   368  		m.Group.client.editMemberCard(m.Group.Code, m.Uin, card)
   369  		m.CardName = card
   370  	}
   371  }
   372  
   373  func (m *GroupMemberInfo) Poke() {
   374  	m.Group.client.SendGroupPoke(m.Group.Code, m.Uin)
   375  }
   376  
   377  func (m *GroupMemberInfo) SetAdmin(flag bool) {
   378  	if m.Group.OwnerUin == m.Group.client.Uin {
   379  		m.Group.client.setGroupAdmin(m.Group.Code, m.Uin, flag)
   380  	}
   381  }
   382  
   383  func (m *GroupMemberInfo) EditSpecialTitle(title string) {
   384  	if m.Group.SelfPermission() == Owner && len(title) <= 18 {
   385  		m.Group.client.editMemberSpecialTitle(m.Group.Code, m.Uin, title)
   386  		m.SpecialTitle = title
   387  	}
   388  }
   389  
   390  func (m *GroupMemberInfo) Kick(msg string, block bool) error {
   391  	if m.Uin != m.Group.client.Uin && m.Manageable() {
   392  		m.Group.client.KickGroupMembers(m.Group.Code, msg, block, m.Uin)
   393  		return nil
   394  	} else {
   395  		return errors.New("not manageable")
   396  	}
   397  }
   398  
   399  func (m *GroupMemberInfo) Mute(time uint32) error {
   400  	if time >= 2592000 {
   401  		return errors.New("time is not in range")
   402  	}
   403  	if m.Uin != m.Group.client.Uin && m.Manageable() {
   404  		m.Group.client.groupMute(m.Group.Code, m.Uin, time)
   405  		return nil
   406  	} else {
   407  		return errors.New("not manageable")
   408  	}
   409  }
   410  
   411  func (g *GroupInfo) SetAnonymous(enable bool) {
   412  	if g.AdministratorOrOwner() {
   413  		g.client.setGroupAnonymous(g.Code, enable)
   414  	}
   415  }
   416  
   417  func (m *GroupMemberInfo) Manageable() bool {
   418  	if m.Uin == m.Group.client.Uin {
   419  		return true
   420  	}
   421  	self := m.Group.SelfPermission()
   422  	if self == Member || m.Permission == Owner {
   423  		return false
   424  	}
   425  	return m.Permission != Administrator || self == Owner
   426  }
   427  
   428  func (m *GroupMemberInfo) CardChangable() bool {
   429  	if m.Uin == m.Group.client.Uin {
   430  		return true
   431  	}
   432  	self := m.Group.SelfPermission()
   433  	if self == Member {
   434  		return false
   435  	}
   436  	return m.Permission != Owner
   437  }