带有node.js的电报机器人
#node #heroku #bottender

电报机器人(除了常规聊天机器人)外,还可以用作“仪表板”。身份验证已经存在,以及用于发送命令以接收特定数据的UI。另一个选择是实现一个执行一些命令并向用户发送响应的Cronjob。与其他平台相比,设置机器人的速度要快得多。

先决条件

  • Telegram应用程序已安装

电报设置

查找BotFather帐户并发送/newbot命令。选择名称和用户名,之后,您将获得新创建的机器人的访问令牌。要为其添加默认命令,请按说明中所述的/setcommands命令并发送命令。

开发环境的设置

引导机器人

Bottender是开发机器人的绝佳框架,它支持多个平台。从以下命令开始,然后选择平台和会话存储。使用BotFather发送的访问令牌更新.env文件。

npx create-bottender-app <bot-name>
cd <bot-name>
npm i express body-parser cron ngrok shelljs pino
npm i nodemon -D

公开暴露的URL

电报机器人需要Webhooks公开可用的URL。 Ngrok在Bottender启用Webhook集成时,创建了指向本地服务器的安全公共URL。要自动化此过程,必须实现自定义服务器。

服务器设置

Bellow实现启动了Cronjob和自定义服务器,并具有与隧道和Webhook URL设置的自动连接。

// server.js
const { bottender } = require('bottender');
const ngrok = require('ngrok');
const shell = require('shelljs');
const { setupCustomServer } = require('./src/custom-server');
const { logger } = require('./src/logger');
const { setupScheduler } = require('./src/scheduler');

const app = bottender({
  dev: process.env.NODE_ENV !== 'production',
});

const setWebhookUrl = (url) =>
  shell.exec(`npm run telegram-webhook:set ${url}/webhooks/telegram`);

const connectToTunnel = async (port) => {
  const url = await ngrok.connect({
    addr: port,
    onStatusChange: (status) => {
      switch (status) {
        case 'connected': {
          logger.info('Connected to tunnel...');
          break;
        }
        case 'closed': {
          logger.warn('Connection to tunnel is closed...');
          logger.info('Reconnecting...');
          return connectToTunnel(port);
        }
      }
    },
  });
  setWebhookUrl(url);
};

(async () => {
  try {
    await app.prepare();
    const port = Number(process.env.PORT) || 5000;
    setupCustomServer(app, port);

    if (process.env.NODE_ENV !== 'production') {
      await connectToTunnel(port);
    }
    setupScheduler();
  } catch (error) {
    logger.error(error, 'Setting up failed...');
  }
})();

Webhook的自定义服务器实现

// src/custom-server.js
const bodyParser = require('body-parser');
const express = require('express');
const { logger } = require('./logger');

const setupCustomServer = (app, port) => {
  // the request handler of the bottender app
  const handle = app.getRequestHandler();

  const server = express();

  const verify = (req, _, buf) => {
    req.rawBody = buf.toString();
  };
  server.use(bodyParser.json({ verify }));
  server.use(bodyParser.urlencoded({ extended: false, verify }));

  // route for webhook request
  server.all('*', (req, res) => {
    return handle(req, res);
  });

  server.listen(port, (err) => {
    if (err) throw err;
    logger.info(`Ready on http://localhost:${port}`);
  });
};

module.exports = {
  setupCustomServer,
};

自定义调度程序

// src/scheduler.js
const { getClient } = require('bottender');
const { CronJob } = require('cron');
const { CHAT_ID, CRONJOB_INTERVAL, replyMarkup, TIMEZONE } = require('./constants');
const { executeCustomCommand } = require('./services');

const client = getClient('telegram');

const setupScheduler = () =>
  new CronJob(
    CRONJOB_INTERVAL,
    async function () {
      const response = await executeCustomCommand();

      await client.sendMessage(CHAT_ID, response, {
        parseMode: 'HTML',
        replyMarkup,
      });
    },
    null,
    true,
    TIMEZONE,
  );

module.exports = {
  setupScheduler,
};

NPM脚本

// package.json
{
  // ...
  "scripts": {
    "dev": "nodemon server.js",
    "lint": "eslint . ",
    "lint:fix": "npm run lint -- --fix",
    "start": "node server.js",
    "telegram-webhook:set": "echo 'Y' | bottender telegram webhook set -w $1",
    "test": "jest"
  }
  // ...
}

林特

ESLINT配置中的ecmaVersion字段更新为2021

记录器

const logger = require('pino')();

module.exports = {
  logger,
};

机器人开发

以下是机器人入口点,可以指定多个处理程序。

// src/index.js
const { router, telegram } = require('bottender/router');
const { HandleMessage } = require('./handlers/message-handler');

module.exports = async function App() {
  return router([telegram.message(HandleMessage)]);
};

Bellow是基本消息处理程序实现。限制对机器人的访问可以通过聊天ID完成。对于具有HTML内容的消息,应将parseMode参数设置为HTML。可以在replyMarkup字段中添加快速答复。接收到的bot命令具有类型的bot_command

// src/handlers/message-handler.js
const { ADMIN_CHAT_ID } = require('../constants');
const { handleCustomLogic } = require('../services');

async function HandleMessage(context) {
  const chatId = context.event._rawEvent.message?.chat?.id;
  if (chatId !== ADMIN_CHAT_ID) {
    await context.sendMessage('Access denied!');
    return;
  }

  const isBotCommand = !!context.event._rawEvent.message?.entities?.find(
    (entity) => entity.type === 'bot_command'
  );
  const message = isBotCommand
    ? context.event.text.replace('/', '')
    : context.event.text;

  const response = await handleCustomLogic(message);

  await context.sendMessage(response, {
    parseMode: 'HTML',
    replyMarkup: {
      keyboard: [
        [
          {
            text: '/command',
          },
        ],
      ],
    },
  });
}

module.exports = {
  HandleMessage,
};

部署

部署电报机器人的选项之一是Heroku(必须将Heroku CLI作为先决条件安装)运行以下命令进行设置和部署。

// Procfile
web: npm start
heroku create -a <PROJECT_NAME>
git init
heroku git:remote -a <PROJECT_NAME>
git add .
git commit -m "Initial commit"
git push heroku master
heroku config:set TELEGRAM_ACCESS_TOKEN=<ACCESS_TOKEN>
npm run telegram:webhook-set https://<PROJECT_NAME>.herokuapp.com/webhooks/telegram