校园招新之获取进QQ群但未报名人员

校园的社团、实验室招新一般由是校领导会发一个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, "&nbsp;", "")
		class := strings.ReplaceAll(rows[i][9], " ", "")
		class = strings.ReplaceAll(class, "&nbsp;", "")
		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, "&nbsp;", "")
					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)
	}
}
相关推荐
吴佳浩14 小时前
Go史上最大“打脸”现场来了:泛型方法终于实现了
后端·go
明月_清风21 小时前
深入 Go 并发编程:从 Goroutine 到 Channel 的系统性避坑指南
后端·go
用户34232323763172 天前
开源!Go+Wails+Vue3 手搓一个 PLC 实时监控桌面工具
go
止语Lab2 天前
为什么你的 Go TCP server P99 延迟这么高
go
Andy Dennis2 天前
nsq学习记录
消息队列·go·nsq
韦胖漫谈IT2 天前
选语言不是站队,是选适合问题的工具
java·python·ai·rust·go·技术落地
喵个咪2 天前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
夜悊3 天前
Go网络编程的学习代码示例:客户端/服务端(C/S)模型
go
审判长烧鸡3 天前
【AI问答】GO代码循环返值
go
捧 花3 天前
Eino框架记忆功能实现指南
go·agent·eino