继上次幼儿园发的微信接龙打卡破解版后,现金牛区指定打卡地点,必须到教育局公众号打卡,其实既然都是程序,那么实现自动化的东西肯定是可以的,就看能不能爬出的接口了,通过Fiddler分析,发现接口既没加密、又没做限制,唯一需要有点遗憾的就是与微信OpenId绑定,但既然都能拿到Token了,还担心这个干嘛,跳过这步直接Token请求就完事。

先看看分析出来的API地址:

const URLS = {
    PATH: {
        base_api_post: "/base/api/request/http/post",
        platform_api_post: "/platform/api/request/http/post",
    },
    API: {
        authorize: "http://172.34.46.165:40003/api/user_relation/authorize",
        get_user_info: "http://172.34.46.165:40003/api/user/get_user_info",
        get_sign: "http://172.34.46.165:40002/api/minio/get_sign",
        get_record: "http://172.34.46.165:40002/api/health_records/get_record",
        add_record: "http://172.34.46.165:40002/api/health_records/add_record"
    }
}

请求参数都是统一封装形式:

        const params = {
            method: "POST",
            protocol: "https:",
            host: "smartedu.jnei.cn",
            path,
            https: {
                rejectUnauthorized: false // 网站数字证书设置不正确会导致验证失败
            },
            headers: {
                "Content-Type": "application/json"
            },
            json: {
                method,
                url,
                dataType: "json",
                headers: {
                    token: this.token
                },
                body
            }
        }

话不多说,直接上代码:

	
    async init() {
        // this.auth()
        // this.getUserInfo()
        // this.getRecord()

        const images = await this.genImage()
        const health = [], trip = []

        for (const idx in images.health) {
            health[idx] = await this.getUploadUrl()
            await this.uploadImg(images.health[idx], health[idx])
        }
        for (const idx in images.trip) {
            trip[idx] = await this.getUploadUrl(2)
            await this.uploadImg(images.trip[idx], trip[idx])
        }

        console.log(images, health, trip)
        this.addRecord(health, trip)
    }

    private async genImage(): Promise<{ health: string[], trip: string[] }> {

        const health = [
            path.join(__dirname, "tmp/health1.png"),
            path.join(__dirname, "tmp/health2.png"),
        ]
        const trip = [
            path.join(__dirname, "tmp/trip.png")
        ]

        const generator = new ImageGenerator()
        await generator.init()
        generator.health(health[0], false)
        // generator.health(health[1])
        await generator.owner(health[1])
        generator.trip(trip[0])

        return {health, trip}
    }

    /**
     * 通过微信code获取token、用户信息等
     */
    private async auth(code: string, userType: 1 | 2 | 3): Promise<object> {
        const result = await this.request({code, userType}, URLS.API.authorize, URLS.PATH.base_api_post)
        this.token = result.data.token
        return result.data
    }

    /**
     * 获取用户班级信息
     * @returns
     */
    private async getUserInfo(): Promise<object> {
        const result = await this.request(null, URLS.API.get_user_info, URLS.PATH.base_api_post, "GET")
        this.userInfo = result.data
        return this.userInfo
    }

    /**
     * 获取已打卡记录信息
     */
    private async getRecord(): Promise<object> {
        const param = {
            "createDay": dayjs().format('yyyy-MM-dd'),
            "userId": "学生身份证号"
        }
        const result = await this.request(param, URLS.API.get_record)
        return result.data
    }

    /**
     * 获取上传路径
     */
    private async getUploadUrl(type: 1 | 2 = 1): Promise<string> {
        const result = await this.request({"name": `${randomUUID()}.png`, type}, URLS.API.get_sign)
        return result.data.replace('http://ocr.jnei.cn:19000/', 'https://smartedu.jnei.cn/upload/');
    }

    /**
     * 上传图片
     */
    private async uploadImg(imgPath: string, uploadUrl: string) {
        const pipeline = promisify(stream.pipeline);
        await pipeline(
            fs.createReadStream(imgPath),
            got.stream.put(uploadUrl, {
                protocol: "https:",
                https: {
                    rejectUnauthorized: false
                },
            }),
            new stream.PassThrough()
        );
    }

    /**
     * 添加打卡信息
     */
    private async addRecord(healthUrls: string[], tripUrls: string[]): Promise<any> {
        let param = {
            "contactFlag": 0,
            "createDay": dayjs().format("YYYY-MM-DD"), // new Date().Format('yyyy-MM-dd')
            "healthFlag": 0,
            "healthUrl1": "", // 本人健康码
            "healthUrl2": "", // 同住人健康码
            "healthUrl3": "", // 同住人健康码
            "healthUrl4": "", // 同住人健康码
            "healthUrl5": "", // 同住人健康码
            "idcard": "学生身份证号", // user.idCard,
            "idcardUS": "6位前缀****4位后缀",
            "name": "学生名字", // user.userName
            "otherSymptomFlag": 0,
            "temperature": `36.${getRandomInt(9)}`, // 体温
            "tripDays": "",
            "tripFlag": 0,
            "tripUrl1": "", // 本人行程卡
            "tripUrl2": "", // 同住人行程卡
            "tripUrl3": "", // 同住人行程卡
            "tripUrl4": "", // 同住人行程卡
            "tripUrl5": "", // 同住人行程卡
            "userId": "626ba46c795a706c7f5c4499", // user.userId,
            "userType": 2 // userType
        }
        // @ts-ignore
        healthUrls.forEach((it: string, idx: number) => param[`healthUrl${idx + 1}`] = it.split("?")[0])
        // @ts-ignore
        tripUrls.forEach((it: string, idx: number) => param[`tripUrl${idx + 2}`] = it.split("?")[0])

        const result = await this.request(param, URLS.API.add_record)
        console.log("返回信息", result)
    }