这是一个 2.0 版的免补签工具

from playwright.sync_api import sync_playwright
import json
import time
from bs4 import BeautifulSoup
from email.mime.text import MIMEText
from email.header import Header
from smtplib import SMTP_SSL
import traceback


class kq():
    def __init__(self, username, password, receiver=None):
        self.username = username
        self.password = password
        self.x_value = 0
        if receiver:
            self.receiver = receiver

    def send_email(self, msg, receiver):
        host_server = ''  # 邮箱smtp服务器
        user = '' # user 为发件人的邮箱号码
        pwd = '' #pwd 为邮箱的授权码
        sender_mail = '' #发件人的邮箱
        #邮件的正文内容
        mail_content = msg

        #添加机器人签名
        mail_sign = \
        '''
        \n
        ---

        No need to reply!
        Contact me: w-gx@qq.com
        '''

        #邮件标题
        mail_title = '【打卡消息推送】'
        #ssl登录
        smtp = SMTP_SSL(host_server)
        #set_debuglevel()是用来调试的。参数值为1表示开启调试模式,参数值为0关闭调试模式
        smtp.set_debuglevel(0)
        smtp.ehlo(host_server)
        smtp.login(user, pwd)

        msg = MIMEText(mail_content + mail_sign, "plain", 'utf-8')
        msg["Subject"] = Header(mail_title, 'utf-8')
        msg["From"] = sender_mail
        msg["To"] = receiver
        smtp.sendmail(sender_mail, receiver, msg.as_string())
        smtp.quit()

    def run(self, playwright):
        retry = 0
        browser = playwright.chromium.launch(headless=True)
        context = browser.new_context()

        # Open new page
        page = context.new_page()

        # Add response handler
        page.on('response', lambda response: self.handle_response(response))

        page.goto("")
        
        user_input_box = page.locator("xpath=/html/body/div/form/div[1]/div/div/div/div[2]/div[2]/input")
        pwd_input_box = page.locator("xpath=/html/body/div/form/div[1]/div/div/div/div[2]/div[3]/input")
        user_input_box.fill(self.username)  
        pwd_input_box.fill(self.password)  

        login_button = page.locator("xpath=/html/body/div/form/div[1]/div/div/div/div[2]/div[4]/input")
        login_button.click()

        # Wait for navigation or action to complete
        page.wait_for_timeout(5000)  
        while True:
            x_percent = (360 - self.x_value) / 360
            print(self.x_value)
            # Wait for the slider and track elements to appear and calculate the move distance based on the track width and x_value percentage
            slider_handle = page.wait_for_selector('.captcha-control-button')
            slider_track = page.wait_for_selector('.captcha-control-wrap')  # Assuming this is the slider track

            # Get bounding box of slider handle and track
            slider_handle_box = slider_handle.bounding_box()
            print(slider_handle_box['width'])
            slider_track_box = slider_track.bounding_box()
            print(slider_track_box['width'])

            # Calculating the move distance based on the track width and the percentage obtained from x_value
            move_distance = slider_track_box['width'] * x_percent

            # Starting point for the movement
            start_x = slider_handle_box['x'] + slider_handle_box['width'] / 2
            start_y = slider_handle_box['y'] + slider_handle_box['height'] / 2

            # Ending X coordinate based on the calculated distance
            end_x = start_x + move_distance - slider_handle_box['width'] / 2

            # Simulate the drag and drop action
            page.mouse.move(start_x, start_y)
            page.mouse.down()
            page.mouse.move(end_x, start_y, steps=20)  # Use steps to simulate a more realistic drag action
            page.mouse.up()

            time.sleep(3)

            try:
                daka_button = page.locator("xpath=/html/body/div/body/div[2]/div[2]/div[2]/div[2]/a[1]")
                daka_button.click()
                break
            except:
                retry += 1
                if retry >= 20:
                    print("Failed to click the daka button")
                    break
                print("Verification failed, try again.")
                continue

        page.wait_for_timeout(5000)
        msg = '【打卡成功】\n'
        msg += self.parse_daka(page.content())
        print(msg)

        if self.receiver:
            self.send_email(msg, self.receiver)
        context.close()
        browser.close()

    def parse_daka(self, html_content):
        # 使用BeautifulSoup解析HTML
        soup = BeautifulSoup(html_content, 'html.parser')

        # 初始化结果列表
        result = []

        # 找到表格
        table = soup.find('table', {'class': 'kq-message-table'})

        # 获取表头中的所有th标签,用于列名称
        headers = []
        for th in table.find('thead').find_all('th'):
            # 每个th标签,获取文本并消除空格和换行符
            headers.append(' '.join(th.stripped_strings))

        # 遍历表格的每一行
        for row in table.find('tbody').find_all('tr'):
            # 获取行中的所有td标签
            cells = row.find_all('td')
            # 每一行的数据都以字典形式存在,字典的键来自标题,值来自单元格数据
            data = {}
            for index, cell in enumerate(cells):
                key = headers[index]
                # 提取每个单元格的文本作为数据
                data[key] = cell.get_text(strip=True)
            # 将每行数据添加至结果列表
            result.append(data)

        # 将结果列表转换为JSON格式
        json_data = json.dumps(result, ensure_ascii=False, indent=4)
        return json_data

    def handle_response(self, response):
        if response.url == 'https://xxxx/attendance_api/services/login/imageRoute':
            json_body = response.json()
            self.x_value = int(json_body["data"]["imageVerificationVo"]["x"])
        if response.url == 'https://xxxx/attendance_api/services/login/verifyRoute':
            json_body = response.json()
            if json_body["code"] == 0:
                self.verification = 1
                print("Verification successful")
            else:
                self.verification = 0
                print("Verification failed")


with sync_playwright() as playwright:
    username = ""
    password = ""
    receiver = ""
    kq = kq(username, password, receiver)
    retry = 0
    while retry < 5:
        try:
            kq.run(playwright)
            break
        except Exception as e:
            retry += 1
            msg = "【打卡失败】\n" + traceback.format_exc()
            kq.send_email(msg,receiver)
            continue