Zexian Li

Python实现微信小程序自动打卡

2021-03-16 · 5 min read
Python

每天手动打卡实在太繁琐了,作为coder,有没有捷径可走?理论上可以有!
前边的话:本博客仅讨论技术实现,疫情防控需谨慎,如有异常请及时上报。

Charles + iPhone抓包

将iPhone和电脑连在同一wifi下,设置手机http代理,安装SSL证书,安装并信任描述文件。
点击手机端打卡界面,在Charles上得到手机端的HTTP请求列表;在手机端填写打卡页面并提交,在对应HTTP使用Charles的compose功能获取手机发送的json文件。
(具体细节可参考y2777an的博客)

打卡代码

代码思路为,先登录北航sso并验证(login()函数),获取上次提交的信息并进行修改(get_info()函数),随后向服务器提交json文件并验证(post()函数)。bark()函数基于ios APP bark,实现了打卡失败时向iPhone自动发送提醒的功能。代码添加了日志功能。
具体代码如下所示:

# -*- coding: utf-8 -*-
# /usr/bin/python

import requests
import json
import time
import sys
import urllib.request

login_url = "https://app.buaa.edu.cn/uc/wap/login?redirect=https%3A%2F%2Fapp.buaa.edu.cn%2Fncov%2Fwap%2Fdefault%2Findex%3Ffrom%3Dhistory"
login_check_url = "https://app.buaa.edu.cn/uc/wap/login/check"
base_url = "https://app.buaa.edu.cn/xisuncov/wap/open-report/index"
save_url = "https://app.buaa.edu.cn/xisuncov/wap/open-report/save"


class ClockIn(object):

    def __init__(self, username, password, bark_id):

        self.username = username
        self.password = password
        self.bark_id = bark_id
        self.log_path = './log.txt'
        self.sess = requests.Session()


    def login(self):

        """ Login to BUAA platform."""

        res = self.sess.get(login_url)
        if res.status_code != 200:
            error_1 = "{} failed to login platform,fail status code is {}.".format(self.username, res.status_code)
            self.bark(error_1)
            #print(error_1)
            raise Exception(error_1)

        data = {'username': self.username, 'password': self.password, }

        responce = self.sess.post(url=login_check_url, data=data)
        responce_decode = json.loads(responce.content.decode())

        if responce_decode['e'] != 0:
            error_2 = "{} failed in the login process and the reason is {}.".format(self.username, responce_decode['m'])
            self.bark(error_2)
            #print(error_2)
            raise Exception(error_2)

        return responce_decode


    def bark(self, message):

        """ Send message to iPhone if fails in clock-in, then log. """

        url = 'https://api.day.app/' + self.bark_id + '/ClockInFalse/' + self.username
        p = urllib.request.urlopen(url)

        with open(self.log_path, 'a') as f:
            f.write(self.get_date() + '\n')
            f.write(message + '\n')


    def get_info(self, html=None):

        """Get hitcard information, which is the old info with updated new time."""

        if not html:
            res = self.sess.get(base_url)
            if res.status_code != 200:
                error_3 = "{} get information failed, status code = {}".format(self.username, res.status_code)
                self.bark(error_3)
                #print(error_3)
                raise Exception(error_3)
            html = res.content.decode()
            raw_json = json.loads(html)
        
        return_dict = {}
        for data in ('sfzx', 'tw', 'area', 'city', 'province', 'address', 'geo_api_info', 'sfcyglq', 'sfyzz', 'qtqk', 'askforleave'):
            return_dict.update({data: raw_json['d']['info'][data]})
        self.info = return_dict
        return return_dict


    def post(self):

        """ Post the hitcard information."""

        res = self.sess.post(save_url, data=self.info)
        if res.status_code != 200:
            error_4 = "{} post information failed, status code = {}.".format(self.username, res.status_code)
            self.bark(error_4)
            #print(error_4)
            raise Exception(error_4)
        return json.loads(res.text)


    def main(self):

        self.login()
        time.sleep(0.5)
        self.get_info()
        time.sleep(0.5)
        ret = self.post()
        return ret


def clock_in_one_person(username, password, bark_id):

    op = ClockIn(username, password, bark_id)
    try:
        ret = op.main()
        if ret['e'] != 0:
            error_5 = "fail in the end and the reason is {}.".format(ret['m'])
            #print(error_5)
            op.bark(error_5)
    except Exception as e:
        op.bark(e)
    finally:
        return 0


if __name__ == "__main__":

    # username = sys.argv[1]
    # password = sys.argv[2]
    # bark_id = sys.argv[3]

    username = "your username"
    password = "your password"
    bark_id = "your bark id"

    clock_in_one_person(username, password, bark_id)

定时启动

我们选用cron+shell脚本的方式来实现打卡程序的定时启动。
使用crontab -e指令创建一个以当前用户运行的新cron任务,每个cron任务的格式如下:
<分钟> <小时> <日> <月> <星期> <命令or脚本路径>
cron任务中的操作符有*/-,*代表取值范围内的所有数字,/代表每过多少个数字,-表示从起始到终止,,表示散列数字。例如,每隔两天的上午8点到11点的第3和第15分钟执行任务可以写成3,15 8-11 */2 * * command
依此方式进行配置,最后使用crontab -l查看已经存在的任务。

至此,实现了微信小程序的自动打卡🥂

Bad decisions make good stories.