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 }