#21 让 Automation 与 Chat Bot 连动

今天我们要让之前的程序能跟 Chat Bot 连动。

需要 Worker 吗?

直接从程序中 POST 过去啊,难道还要透过 Worker?

对没错,不用,我们可以直接从程序中处理。

回顾

让我们回顾一下我们的 checker 函式:

async function checker(courses) {
    // 一样,确认子页面存在再操作
    await page.waitForSelector("#stfseldListDo");
    const child_page = await (await page.$("#stfseldListDo")).contentFrame();

    // 用无穷回圈重复执行,直到想停止时手动结束程序
    while (true) {
        // 遍历每个课程,检查是否有可加选的课程
        for (let i = 0; i < courses.length; i++) {
            // 在子页面中注入程序确认课程人数状态,後方是带入的查询参数
            const data = await child_page.evaluate(injection.getInfo, courses[i].code, courses[i].class, courses[i].type, courses[i].form);
            courses[i].seats = data.限修人数 - data.已分发人数;
        }

        // 印出所有可加选课程
        console.log(
            "---\n" +
                courses
                    .filter((course) => course.seats > 0)
                    .map((course) => `${course.name} 还有 ${course.seats} 个空位`)
                    .join("\n")
        );

        // 冷却时间,避免瘫痪系统
        await child_page.waitForTimeout(30 * 1000);
    }
}

我们这里会一直印出有空位的课以及空多少位置。

但你总不希望一直被讯息骚扰吧?所以我们会在位置发生变动时再发送讯息。

程序

我们需要先加上 node-fetch Package:

npm i node-fetch@2

然後引入:

const fetch = require("node-fetch");

send 函式

就是传讯息的函式:

async function send(msg) {
    // 发送讯息
    await fetch(`https://api.telegram.org/bot${process.env.TOKEN}/sendMessage`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ chat_id: +process.env.CHAT_ID, parse_mode: "MarkdownV2", text: msg }),
    });
}

这里我们用环境变数来储存 TOKEN 及 CHAT_ID。(当然你也可以一起放到 config 里面)
至於 CHAT_ID 要怎麽取得?你就在 Bot 上写个回传 Chat_ID 的东西就好啦。

// Bot
if (text === "id") {
    await fetch(`https://api.telegram.org/bot${TOKEN}/sendMessage`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        // 回覆 Chat ID
        body: JSON.stringify({ chat_id, parse_mode: "MarkdownV2", text: chat_id.toString() }),
    });
}

新的 checker

async function checker(courses) {
    // 一样,确认子页面存在再操作
    await page.waitForSelector("#stfseldListDo");
    const child_page = await (await page.$("#stfseldListDo")).contentFrame();

    // 这里多了个储存空位状态的东西
    const seats = {};

    // 用无穷回圈重复执行,直到想停止时手动结束程序
    while (true) {
        // 遍历每个课程,检查是否有可加选的课程
        for (let i = 0; i < courses.length; i++) {
            // 在子页面中注入程序确认课程人数状态,後方是带入的查询参数
            const data = await child_page.evaluate(injection.getInfo, courses[i].code, courses[i].class, courses[i].type, courses[i].form);
            courses[i].seats = data.限修人数 - data.已分发人数;
        }

        // 空位数有变化再放进来
        const changed = {};
        courses
            .forEach((course) => {
                if (seats[course.name] !== course.seats) {
                    // 空位数有变化
                    changed[course.name] = course.seats;
                    seats[course.name] = course.seats; // 更新空位数
                }
            });

        // 如果有空位数有变化,传送讯息
        if (Object.keys(changed).length > 0) {
            // 只发送有位置的讯息,可以发送 Markdown 格式的讯息
            let msg = Object.entries(changed)
                .map(([name, seats]) => (seats ? `${name} 目前有 **${seats}** 个空位` : ""))
                .filter((x) => !!x)
                .join("\n");
            // 没有 await 的必要,因为不影响
            send(msg);
        }

        // 冷却时间,避免瘫痪系统
        await child_page.waitForTimeout(30 * 1000);
    }
}

我们用一个 changed 来确认空位数是否有变化。
有变化的话,再传讯息。

完成!


每日铁人赛热门 Top 10 (1004)

以 10/04 20:00 ~ 10/05 20:00 文章观看数增加值排名

  1. +230 JS 07 - 原型方法:欲达则必速
    • 作者: Felix
    • 系列:JavaScript 从 50% 开始,打造函式库不是问题!
  2. +155 Day 18:数据蒐集、资料视觉化、数据分析
    • 作者: Broccoli Huang
    • 系列:30 天从麻瓜变 Android 工程师
  3. +134 Day20 Android - Retrofit(Get)
    • 作者: chu_jin02
    • 系列:Android 新手入门学习
  4. +133 [Day30] 完结洒花❀ 看完赛心得顺便用Python画 3D 渐层花朵!
    • 作者: lulu_meat
    • 系列:奇怪的知识增加了!原来程序还可以这样用?!
  5. +133 【Day 20】Google Apps Script - API 篇回顾整理
    • 作者: Jason Hung
    • 系列:「Google Apps Script」 学习笔记
  6. +132 Day 1 无限手套 AWS 版:掌控一切的 5 + 1 云端必学主题
    • 作者: 用图片高效学程序
    • 系列:无限手套 AWS 版:掌控一切的 5 + 1 云端必学主题
  7. +122 [面试][资料库]如何解决高并发情境的商品秒杀问题
    • 作者: 宝宝出头天
    • 系列:全端工程师生存笔记
  8. +120 [Day2] 抓取每日收盘价
    • 作者: JohnsonTheRock
    • 系列:从零开始使用python打造简易投资工具
  9. +117 Day 23【Tokens' Owner】FUN SIDE PROJECT
    • 作者: ALu
    • 系列:All In One NFT Website Development
  10. +112 从零开始的8-bit迷宫探险【Level 27】神助攻-老弟帮我配个音效
    • 作者: 雪花冰
    • 系列:从零开始的8-bit迷宫探险!Swift SpriteKit 游戏开发实战

<<:  Dialog 关闭後更新 Grid 资料 / 显示储存的图档 - day20

>>:  第一个 HOC (Higher-Order Components)( Day21 )

前端工程日记 25日 Flex 并排选单

附codepen网址: https://codepen.io/pwbzvqja/pen/GRWNV...

Day27 D3js 动画事件小技巧

D3js 动画事件小技巧 用途 在d3世界中,如果想使用起来不是透过事件驱动或是外部驱动,而是想要产...

ASP.NET MVC 从入门到放弃(Day13) -C# HttpClient 泛型功能介绍

接着来讲讲泛型的部分.... 简单来说泛型就是传入值、传回值不固定的情况下这时候就可以使用泛型......

非本科、半路转职的「软件科技职涯发展笔记」

from Unsplash 写了三十天的技术文章,最後一篇想谈谈「职涯发展」,毕竟这才是非本科转职...

30天轻松学会unity自制游戏-设定画面按钮

现在死亡後有了两个选项,一个重新开始游戏,一个是回到标题,目前只有一个场景,所以第一步快速制作一个开...