这是一个 2.0 版的 Fuck信仰不息🗽

from selenium.webdriver.chrome.service import Service
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
from email.mime.text import MIMEText
from email.header import Header
from smtplib import SMTP_SSL
import random
import json
import requests
import os
import time
import cv2
import numpy as np


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

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

No need to reply!
Contact me: wgx@dijk.eu.org
    '''

    #邮件标题
    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 bin_img(img):
    # 设定纯白色的阈值为255(注意:OpenCV使用BGR而不是RGB)
    white_threshold = 246
    # 通过比较找出不是纯白色的像素位置
    not_white = np.any(img < white_threshold, axis=-1)
    # 创建一个全黑色的图像
    black_image = np.zeros_like(img)
    # 将非纯白色的像素设为黑色,而其他的保持不变(可选,如果您想只保留纯白色的像素)
    img[not_white] = [0, 0, 0]
    return img

def download_image(url, filename):
    response = requests.get(url)
    if response.status_code == 200:
        with open(filename, 'wb') as file:
            file.write(response.content)

def match_img(big_image, small_image):
    # 二值图像
    big_image = bin_img(big_image)
    small_image = bin_img(small_image)

    # 使用模板匹配方法寻找小图在大图中的位置
    result = cv2.matchTemplate(big_image, small_image, cv2.TM_CCOEFF_NORMED)

    # 获取匹配结果中的最大值和它的位置
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)

    # 滑块的顶部左侧点
    top_left = max_loc

    # 由于我们知道小图的大小,我们可以计算出匹配区域的右下角位置
    # bottom_right = (top_left[0] + small_image.shape[1], top_left[1] + small_image.shape[0])

    # # 在大图上绘制匹配到的区域
    # cv2.rectangle(big_image, top_left, bottom_right, color=(0, 255, 0), thickness=2)

    # 显示结果
    # cv2.imshow('Match', big_image)
    # cv2.waitKey(0)
    # cv2.destroyAllWindows()

    xcenter_x = top_left[0] + small_image.shape[1] / 2
    image_width = big_image.shape[1]  # image.shape[1] 给出图像宽度

    # 计算百分比
    percentage = (xcenter_x / image_width)

    return percentage

def wait_for_page_load(driver, timeout=10):
    try:
        # 使用WebDriverWait等待页面加载,通过执行JavaScript返回document.readyState == 'complete'
        page_loaded = WebDriverWait(driver, timeout).until(
            lambda d: d.execute_script('return document.readyState') == 'complete'
        )
    except TimeoutException:
        print("在给定的时间内,页面未完全加载!")
        return False
    return True

def verify(percentage, driver):
    slider_button = driver.find_element(By.CSS_SELECTOR, ".ui-slider-btn.init")
    slider_track = driver.find_element(By.CSS_SELECTOR, ".ui-slider-wrap")  # 假定滑道元素类名是 "ui-slider-wrap"
    # 获取滑道的长度
    track_width = slider_track.size['width']
    # 计算需要拖动的像素距离
    offset = track_width * percentage
    # 初始化ActionChains对象
    action_chains = ActionChains(driver)
    # 点击并按住滑块按钮
    action_chains.click_and_hold(slider_button).perform()
    # 这里我们添加一个非常短暂的延时来模拟人非常平滑的拖动滑块
    for step in range(int(offset // 10)):
        action_chains.move_by_offset(10, 0).perform()  # 小步移动
        time.sleep(0.05)  # 稍微等待,让动作可见
    remaining_offset = offset % 10
    action_chains.move_by_offset(remaining_offset, 0).perform()  # 移动剩余的距离
    time.sleep(0.5)  # 完成拖动后稍作停顿
    action_chains.release().perform()  # 释放滑块

def is_passed(driver):
    # 找到包含指定文本的元素
    text_element = driver.find_element(By.CLASS_NAME, "ui-slider-text")
    #   提取并打印文本
    slider_text = text_element.text
    print(slider_text)
    if slider_text[:4] == "验证成功":
        return True
    else:
        return False

def login_recheck(driver):
    elements = driver.find_elements(By.XPATH, '/html/body/form/div/div[2]/div[2]/table')
    if len(elements) > 0:
        return True
    else:
        return False

def parse_daka(driver):
    # 假设html_content是包含上面所示HTML的字符串
    html_content = driver.page_source
    # 使用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 daka(user,password, receiver):
    options = Options()
    options.binary_location = '/opt/google/chrome/google-chrome'
    options.add_argument('--headless')
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument("--no-sandbox")
    service = Service(executable_path='/daka/chromedriver')
    driver = webdriver.Chrome(service=service,options=options)

    retry = 0
    verify_adjust = [0.03, 0.02, 0.01, -0.01, -0.02, -0.03]
    adjust_index = 0
    while True:
        driver.get('http://kq.neusoft.com')
        while not is_passed(driver):
            big_image_elem = driver.find_element(By.ID, "bigImage")
            small_image_elem = driver.find_element(By.ID, "smallImage")

            big_image_url = big_image_elem.get_attribute("src")
            small_image_url = small_image_elem.get_attribute("src")

            download_image(big_image_url, "big.png")
            download_image(small_image_url, "small.png")
            time.sleep(5)

            big_image = cv2.imread('big.png')
            small_image = cv2.imread('small.png')

            percent = match_img(big_image, small_image)
            print(percent)
            verify(percent - verify_adjust[adjust_index], driver)
            time.sleep(2)

            retry += 1
            adjust_index = retry // 10
            if adjust_index > 5:
                send_email("打卡失败:重试次数过多,放弃重试!",receiver)
                return -1

        print("通过验证码!")
        user_input_element = driver.find_element(By.XPATH, '/html/body/div[1]/form/div[2]/div/div/div[2]/div[2]/input')
        user_input_element.send_keys(user)

        pass_input_element = driver.find_element(By.XPATH, "/html/body/div[1]/form/div[2]/div/div/div[2]/div[3]/input")
        pass_input_element.send_keys(password)

        login_button = driver.find_element(By.XPATH, '/html/body/div[1]/form/div[2]/div/div/div[2]/div[6]/input')
        login_button.click()
        time.sleep(1)
        if login_recheck(driver):
            print("登陆成功!")
            break

    time.sleep(2)
    msg = parse_daka(driver)    
    button = driver.find_element(By.XPATH, '/html/body/form/div/div[2]/div[2]/div[1]/a[1]')
    button.click()
    msg = "打卡成功!\n请查看打卡记录:\n"
    msg += parse_daka(driver)
    print(msg)
    send_email(msg, receiver)  

if __name__ == '__main__':
    user = ''
    password = ''
    receiver = ''
    sleep_time = random.randint(1, 300)
    print(f"{sleep_time} 秒后启动程序...")
    time.sleep(sleep_time)
    try:
        daka(user, password, receiver)
    except Exception as e:
        msg = "未知异常,请检查!"
        send_email(msg, receiver)