目录
背景说明
使用效果
参考代码
扫码登录(备份)
背景说明
Q群验证就是为了验证某个用户是否加入了指定的群聊。这可以有很多作用,比如限制软件的使用人群,以防滥用。
使用效果
参考代码
from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QMessageBox, QHBoxLayout, QLabel
import sys
class QQGroupLogin(QWidget):
def __init__(self, groupid="913182235"):
super().__init__() # 调用父类的构造函数
self.init_ui() # 初始化用户界面
self.groupid = groupid # 指定的QQ群ID
def init_ui(self):
self.setWindowTitle('QQ群验证登录 by 小锋学长生活大爆炸') # 设置窗口标题
layout = QVBoxLayout() # 创建一个垂直布局
self.loading_label = QLabel('页面加载中,请稍候...')
layout.addWidget(self.loading_label) # 添加加载提示到布局
# 创建并配置Web浏览器组件
self.browser = QWebEngineView()
# 加载指定的URL,即QQ登录页面
self.browser.load(QUrl("https://xui.ptlogin2.qq.com/cgi-bin/xlogin?pt_disable_pwd=1&appid=715030901&daid=73&hide_close_icon=1&pt_no_auth=1&s_url=https%3A%2F%2Fqun.qq.com%2Fmember.html%23"))
self.browser.setMinimumSize(600, 400) # 设置浏览器组件的最小大小
layout.addWidget(self.browser) # 将浏览器组件添加到布局中
# 连接页面加载信号
self.browser.loadStarted.connect(self.on_load_started)
self.browser.loadFinished.connect(self.on_load_finished)
# 创建一个水平布局来放置按钮
button_layout = QHBoxLayout()
# 创建“刷新页面”按钮,并绑定点击事件到浏览器的reload方法以刷新页面
self.refresh_button = QPushButton('刷新页面')
self.refresh_button.setStyleSheet("font-size: 16px; height: 36px;")
self.refresh_button.clicked.connect(self.browser.reload)
button_layout.addWidget(self.refresh_button) # 将按钮添加到按钮布局中
# 创建“检查验证”按钮,用于触发验证过程
self.check_button = QPushButton('检查验证')
self.check_button.setStyleSheet("font-size: 16px; height: 36px;")
self.check_button.clicked.connect(self.check_verification)
button_layout.addWidget(self.check_button) # 将按钮添加到按钮布局中
layout.addLayout(button_layout) # 将按钮布局添加到主布局中
self.setLayout(layout) # 设置窗口的布局为之前创建的布局
self.resize(800, 600) # 设置窗口的初始大小
self.center_on_screen() # 将窗口居中显示
self.show() # 显示窗口
def center_on_screen(self):
# 计算并应用窗口居中的位置,确保使用整数进行move调用
resolution = self.screen().availableGeometry()
self.move(int((resolution.width() / 2) - (self.frameSize().width() / 2)),
int((resolution.height() / 2) - (self.frameSize().height() / 2)))
def on_load_started(self):
self.loading_label.setVisible(True) # 显示加载提示
def on_load_finished(self, ok):
self.loading_label.setVisible(False) # 隐藏加载提示
def check_verification(self):
# 请求当前页面的HTML内容,用于后续处理
self.browser.page().toHtml(self.process_page_source)
def process_page_source(self, html):
# 处理页面源代码,检查用户是否已加入指定的QQ群
if "data-id=\"{}\"".format(self.groupid) in html:
QMessageBox.information(self, '验证结果', '恭喜,验证成功!')
else:
QMessageBox.warning(self, '验证结果', '您尚未加入指定的QQ群,请加群后再尝试。')
if __name__ == '__main__':
# 如果不需要调整缩放,可以注释掉下面这行
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True) # 新增加的,针对高DPI屏幕自动缩放
app = QApplication(sys.argv) # 创建应用程序实例
ex = QQGroupLogin() # 创建窗口实例
sys.exit(app.exec_()) # 启动应用程序的事件循环
扫码登录(备份)
这段代码只作为备份,因为扫码登录能用,但Q群验证这个不行了。Q群验证还得是用上面的方法。
import requests
from PIL import Image, ImageTk
import tkinter as tk
import time
import re
import sys
from io import BytesIO # 用于将二进制内容转换为文件类对象,以便Image.open使用
import base64
class QQGroupLogin:
def __init__(self):
# 初始化会话以跟踪cookies和headers
self.session = requests.Session()
@staticmethod
def compute_bkn(skey):
# 计算bkn,某些请求所需的参数
t = 5381
for character in skey:
t += (t << 5) + ord(character)
return t & 2147483647
@staticmethod
def compute_ptqrtoken(qrsig):
# 根据qrsig计算ptqrtoken,用于二维码验证
e = 0
for character in qrsig:
e += (e << 5) + ord(character)
return 2147483647 & e
def fetch_qr_code(self):
# 获取登录的二维码
print("正在获取登录二维码...")
url = f'https://ssl.ptlogin2.qq.com/ptqrshow?appid=715030901&e=2&l=M&s=3&d=72&v=4&t={time.time()}&daid=73&pt_3rd_aid=0'
response = self.session.get(url)
qrsig = response.cookies.get('qrsig')
return response.content, qrsig
def display_qr(self, content):
# 显示登录用的二维码
print("请扫描二维码登录。")
root = tk.Tk()
root.title("QQ扫码登录 by 小锋学长生活大爆炸") # 设置窗口标题
icon_base64 = 'iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAABQhJREFUWEe9lm1MU1ccxp9zS4HSYkenRgGpG2wCYgVkRLeBmGimcWNsswsMFxD2YfswlzmCMLKED2TTbdmWDLOXDxS7zLoNPigZzBgcW8ROgZkJBUFgo04l6GjB8tKXe89yL7ShtYVbJZ4PNzfnPOf//53nvBJd3w2KByyUUG1JYmzD/YQhPMCk034/fYU+K6RhWBaAq5NjQUPEyJSIiVixPABBZ5/vsCwOBJN8tGcMMxPeU2a9Pvmj9bqlb2EcCuauhHM11tcX/LNYfGENrIkIxUaVIqDu/C0r7CwntBu/7cKsD0CgjgSk/njdvgOiAHbHrQyoM1wb9QD8+skF0YZRirbvdNodogFMJhM6Ojo8+uLiYuF/IcD6GQ4JapVQT0H5D2QSxiyVMBNCJW8UMxfi4DtNKr1OGxsUAA+hVqvR0tKC6upqvwA5aYvG9OQrKmkw63VaddAAa9euRWtra0CAG72/ecUsLCxET08PmpubPfXl5eUoKm34W1+nfTwogN7eXo9eq9X6dcDcfQ4SiURoGxkZQW5urvBfVVWFgoICGAwGnDp1CkUlPw3pda8miAYIJPRdAzyAVCqF1WqFxWLB9PQ08vPz7wUobRzQ1+3bIAogXhkRUNc5Ninsgij6H5SzUeAB+JHzhXeC4zjk5eUJAO4iOFDa2Kev25e8JMBiAnfbBtYEDduFDnY/RvvPCyM+efIkUlJS0N7ejqSkJAwNDSE6OhpNTU2ora1FUUmDSa/TpiwJIOYyepp2IQmDMDoLsXPLOq+YNTU1HgfKysqQlZUltBeXNnQfr9NqRAEsdRlFMza8GXEZRvtL6GtrFEa90O7PPj3iLHl+3SU4rOGK2Cw2RKXJPHb02ER2xp10zc7q4UAQoq9jKZyIxShkjsfwZ6sBcXFxczvEYBBWvmTWfDtv7/ZVIcpEzAyfQOiqp4x1OmPy3q3j/ZGqFXuUGw+N+4MQAMSsgXT2IuLZfnSwr+PS2e/vcaCv43T/E/HqDZRzwXHzrBBywMxOrVS65Cpl6BkipQfkyVW3fHN5HFhqCrZJb+Dl8AGcm9Xihcx4v8wz/d8YObtlm6eRos08astRKcMgl0nbQyj2y9IqvW5HD8BdEa+ircxVuFxpeC4j8OnqBUHRBoKcCZsDSkUof2/8RTn6WmT6+57TTvQUhMGOXMcPwhTkpMUsOmtTps8HwNqfxDyAj1iv2FxZ5K4T7YAULEpDzqDT8cqiDvCB7cMnulw285YAALxkj2Jz5S/8j+g1wIs1IWOIIcmYdbKwO1kwHAOGBajNeYWzsxOy8BDJI1EyV6ry5zVhZOqmMEqCHF+7KMXXkamVb3kAxOwCt8b3QaJarUDZu88O3zRbzbVf/SEkq317aIhhaHwgBwjFBXlq5TNeDoiF6Pqiy0uasHE1PjiUjWuDd36v+agtex7gX4ahsQEBABsYZod80+HOoKaAD+4wWsBZnAJEpIRCSiSIflR+a/y2zTpumXbwS31X+gSzK2OcEoB/Jc2dy34KBf1Q9EnoL0AkppBJrmI9NyjWQF9duehtuFgGNdf/XiZ7MR0UhWJJCHBavrnyRUIp79SDF0IInTYdzaQsfYNSuhuA95W5IAUBLk+HMdtXJR6+uyzJ/eHbTB+nUpcrGxxVgWHyCeB+GY0SQkrkmoqWuV36EMp45xFlaChXAUoqGIYcjNhU8aU77UMBcCezXTmySaGp6F445v8BLSC+zybZQ60AAAAASUVORK5CYII='
# 将base64字符串转换回二进制数据,并设置为窗口图标
icon_data = base64.b64decode(icon_base64)
icon_image = Image.open(BytesIO(icon_data))
icon_photo = ImageTk.PhotoImage(icon_image)
root.tk.call('wm', 'iconphoto', root._w, icon_photo) # 设置窗口图标
# 设置窗口的背景颜色
root.configure(bg='white')
img = Image.open(BytesIO(content))
img = img.resize((350, 350), Image.Resampling.LANCZOS)
photo = ImageTk.PhotoImage(img)
# 创建一个Frame来放置内容,增加边距效果
frame = tk.Frame(root, bg='white', padx=20, pady=20)
frame.pack()
# 创建一个Label显示二维码,并放置到Frame中
label = tk.Label(frame, image=photo, bg='white')
label.image = photo # 保持对photo的引用
label.pack(pady=(0, 10)) # 在二维码和按钮之间添加一些垂直间距
# 创建一个“已扫码”按钮,点击后关闭窗口,并美化按钮样式
button = tk.Button(frame, text="已扫码", command=root.destroy, height=2, width=20, bg='#4CAF50', fg='white', font=('Arial', 12, 'bold'))
button.pack()
# 窗口居中显示
root.update_idletasks() # 更新窗口状态,以获取准确的窗口大小
width = root.winfo_width()
height = root.winfo_height()
x = (root.winfo_screenwidth() // 2) - (width // 2)
y = (root.winfo_screenheight() // 2) - (height // 2)
root.geometry(f'{width}x{height}+{x}+{y}') # 设置窗口大小和位置
root.mainloop()
def check_qr_status(self, qrsig, ptqrtoken):
# 检查二维码登录状态
print("正在检查二维码状态...")
while True:
url = f'https://ssl.ptlogin2.qq.com/ptqrlogin?u1=https%3A%2F%2Fqun.qq.com%2Fmanage.html%23click&ptqrtoken={ptqrtoken}&ptredirect=1&h=1&t=1&g=1&from_ui=1&ptlang=2052&action=0-0-{time.time()}&js_ver=20032614&js_type=1&login_sig=&pt_uistyle=40&aid=715030901&daid=73&'
response = self.session.get(url, cookies={'qrsig': qrsig})
if '二维码已失效' in response.text:
print('二维码已失效。')
break
elif '登录成功' in response.text:
print('登录成功。')
return True
time.sleep(3)
return False
def verify_group_membership(self, skey, group_id):
# 验证登录用户是否为指定群组的成员
print(f"正在验证群组 {group_id} 的成员资格...")
bkn = self.compute_bkn(skey)
url = 'https://qun.qq.com/cgi-bin/qun_mgr/get_group_list'
response = self.session.post(url, data={'bkn': bkn})
print(response.text)
groups = re.findall(r'"gc":(\d+),"gn', response.text)
return group_id in groups
if __name__ == '__main__':
group_id = '722072237' # 要检查成员资格的QQ群ID
qq_login = QQGroupLogin()
qr_content, qrsig = qq_login.fetch_qr_code()
qq_login.display_qr(qr_content)
ptqrtoken = QQGroupLogin.compute_ptqrtoken(qrsig)
if qq_login.check_qr_status(qrsig, ptqrtoken):
skey = qq_login.session.cookies.get('skey')
if skey and qq_login.verify_group_membership(skey, group_id):
print('恭喜您,验证成功!')
else:
print('很遗憾,验证失败。')
else:
print('登录失败或二维码已失效。')