校园的社团、实验室招新一般由是校领导会发一个QQ通知,让各个班的同学们进一个招新群。 群里面会有负责人提示大家报名,但是群成员不总是都会报名,我们需要的就是,找到那些,已经进群,但是没有报名的同学,然后私聊提醒一下。
大体思路:获取QQ群成员列表,获取已经报名的人员(从问卷星导出,使用go读取execl),根据两者(我们使用QQ群昵称 "计科203张三" 和 execl中的班级+姓名作为判断标准)即可求出我们想要的差集合并集。
获取QQ群成员列表
参考文章:js解密之QQ的bkn值,获取QQ群成员信息,获取QQ好友列表信息-腾讯云开发者社区-腾讯云
进入网站:https://qun.qq.com/member.html 登录,选择群,打开网络请求
解析参数:
- gc,应该是群id
- st: 开始下标
- end :结束下标
- sort:不知道
- bkn:加密用的应该是
解析response
- adm_num :管理员数量,不带群主
- count: 群成员总数
- mems : 群成员列表
- mems.role : 0是群主,1是管理员,2是群成员
- card: 群昵称
nick:QQ昵称
使用网络抓包即可,导入apifox即可
具体代码
Go
package main
import (
"encoding/json"
"fmt"
"github.com/360EntSecGroup-Skylar/excelize"
"io/ioutil"
"net/http"
"strings"
"time"
)
type QQUser struct {
Uin int `json:"uin"`
Role int `json:"role"`
G int `json:"g"`
JoinTime int `json:"join_time"`
LastSpeakTime int `json:"last_speak_time"`
Lv struct {
Point int `json:"point"`
Level int `json:"level"`
} `json:"lv"`
Card string `json:"card"`
Tags string `json:"tags"`
Flag int `json:"flag"`
Nick string `json:"nick"`
Qage int `json:"qage"`
Rm int `json:"rm"`
}
type JSONData struct {
Ec int `json:"ec"`
Errcode int `json:"errcode"`
Em string `json:"em"`
Cache int `json:"cache"`
AdmNum int `json:"adm_num"`
Levelname interface{} `json:"levelname"`
Mems []QQUser `json:"mems"`
Count int `json:"count"`
SvrTime int `json:"svr_time"`
MaxCount int `json:"max_count"`
SearchCount int `json:"search_count"`
Extmode int `json:"extmode"`
}
type WJXUser struct {
Class string
Name string
}
func ReadExcel(filename string) (RegisteredUserList []WJXUser, err error) {
f, err := excelize.OpenFile(filename)
if err != nil {
return
}
sheets := f.GetSheetMap() //获取Execl表的工作表
fmt.Println(sheets)
sheet1 := sheets[1] //sheets是一个map,map[1]就是获取到第一个工作表
fmt.Println("第一个工作表", sheet1)
rows := f.GetRows(sheet1) //获取工作表的所有数据,数据存储在一个二维数组中,二维数组中的每一个一位数组就是一行数据
fmt.Println(rows)
RegisteredUserList = make([]WJXUser, 0)
for i := 1; i < len(rows); i++ {
name := strings.ReplaceAll(rows[i][6], " ", "")
name = strings.ReplaceAll(name, " ", "")
class := strings.ReplaceAll(rows[i][9], " ", "")
class = strings.ReplaceAll(class, " ", "")
class = strings.ReplaceAll(class, "班", "")
RegisteredUser := WJXUser{Name: name, Class: class}
RegisteredUserList = append(RegisteredUserList, RegisteredUser)
}
return RegisteredUserList, err
}
func main() {
url := "https://qun.qq.com/cgi-bin/qun_mgr/search_group_members"
method := "POST"
client := &http.Client{}
NotedUserList := make([]QQUser, 0) // 已经进QQ群且已经备注人员 (已经剔除管理员)
NoNoteUserList := make([]QQUser, 0) // 已经进QQ群但是未备注人员(已经剔除管理员)
countMember := 0 // 群成员总数量
cycleIndex := 1 // 刚开始让循环一轮,后面根据群成员总数来确定到底循环多少轮
for i := 0; i < cycleIndex; i++ {
st := i * 21
end := st + 20
if i != 0 && i == cycleIndex-1 { // For the fifth iteration, set the end to 86
end = countMember
}
payload := strings.NewReader(fmt.Sprintf("gc=185335516&st=%d&end=%d&sort=0&bkn=336337277", st, end))
req, err := http.NewRequest(method, url, payload)
if err != nil {
return
}
req.Header.Add("Accept", "application/json, text/javascript, */*; q=0.01")
req.Header.Add("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Add("Connection", "keep-alive")
req.Header.Add("Origin", "https://qun.qq.com")
req.Header.Add("Referer", "https://qun.qq.com/member.html")
req.Header.Add("Sec-Fetch-Dest", "empty")
req.Header.Add("Sec-Fetch-Mode", "cors")
req.Header.Add("Sec-Fetch-Site", "same-origin")
req.Header.Add("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36")
req.Header.Add("X-Requested-With", "XMLHttpRequest")
req.Header.Add("sec-ch-ua", "\"Google Chrome\";v=\"125\", \"Chromium\";v=\"125\", \"Not.A/Brand\";v=\"24\"")
req.Header.Add("sec-ch-ua-mobile", "?0")
req.Header.Add("sec-ch-ua-platform", "\"macOS\"")
req.Header.Add("Cookie", "RK=Gdv1uMjH8g; ptcz=cc0188ccc671556dd56ea8ffb4ae59d282714e8e788826033f41c89d77b61b53; pgv_pvid=4845315920; tgw_l7_route=9d1d4698c4322116c7c255687ec1fe38; traceid=cfd4c1d7fb; uin=o3063360183; skey=@61HarNEbA; p_uin=o3063360183; pt4_token=e6WELkURyGZz0yu3RI3j1UwIQk5z9E7n9dUrbXmq4gE_; p_skey=GQ0pK0oDZsKCwOphrmWqZtFoCE5qsUVEqZfgDLHn7FU_")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
req.Header.Add("Host", "qun.qq.com")
res, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println(err)
return
}
var r JSONData
err = json.Unmarshal(body, &r)
countMember = r.Count
cycleIndex = countMember/21 + 1 // 每次查询是21人,不足21次的向上取整一下
if err != nil {
fmt.Println("Error unmarshalling response:", err)
return
}
for j := 0; j < len(r.Mems); j++ {
if r.Mems[j].Role == 2 {
if r.Mems[j].Card == "" {
NoNoteUserList = append(NoNoteUserList, r.Mems[j])
} else {
// 数据处理,让数据保持格式为 "计科221张三"
r.Mems[j].Card = strings.ReplaceAll(r.Mems[j].Card, " ", "")
r.Mems[j].Card = strings.ReplaceAll(r.Mems[j].Card, " ", "")
r.Mems[j].Card = strings.ReplaceAll(r.Mems[j].Card, "班", "")
NotedUserList = append(NotedUserList, r.Mems[j])
}
}
}
time.Sleep(2 * time.Second)
}
registeredUserList, err := ReadExcel("/Users/yjppjy/Downloads/263778738_按序号_π-Team报名表_54_51.xlsx") // 已经报名人员
if err != nil {
return
}
// 根据 NoteUserList中的.Card字段 和 registeredUserList的 Class + Name 作为判断依据,以QQUser为基准,找出来 未进群但是已报名、已进群已报名、已进群未报名的人员
// 将已报名人员存储在一个map中,以便快速查找
registeredUserMap := make(map[string]WJXUser)
for _, user := range registeredUserList {
key := user.Class + user.Name
registeredUserMap[key] = user
}
// 查找已进群已报名、已进群未报名
var notedAndRegistered []QQUser
var notedAndNotRegistered []QQUser
for _, qqUser := range NotedUserList {
key := qqUser.Card
if _, found := registeredUserMap[key]; found {
notedAndRegistered = append(notedAndRegistered, qqUser)
} else {
notedAndNotRegistered = append(notedAndNotRegistered, qqUser)
}
}
// 查找未进群但已报名
var notInGroupButRegistered []WJXUser
for key, wjxUser := range registeredUserMap {
found := false
for _, qqUser := range NotedUserList {
if qqUser.Card == key {
found = true
break
}
}
if !found {
notInGroupButRegistered = append(notInGroupButRegistered, wjxUser)
}
}
// 输出结果
//fmt.Println("已进群已报名:")
//for _, user := range notedAndRegistered {
// fmt.Println(user.Card, user.Nick)
//}
fmt.Println("已进群未报名:")
for _, user := range notedAndNotRegistered {
fmt.Println(user.Card, user.Nick)
}
fmt.Println("未进群但已报名:")
for _, user := range notInGroupButRegistered {
fmt.Println(user.Class, user.Name)
}
}