Telegramボットの構築とデプロイ方法は?

この記事では、Node.jsとBack4appのbackend-as-a-serviceを使ってTelegramボットを構築します。そして、Back4appのコンテナを使ってデプロイします。

ボットとは、ネットワーク上やプラットフォーム上で自動化された反復タスクを実行できるソフトウェアのことです。Telegram ボットは Telegram ボット API を使用して作成され、Telegram で動作するように特別に設計されています。

Telegramボットをデザインして、Telegram上で様々なタスクを実行させることができる。例えば、天気予報やニュースのヘッドラインなどの情報を取得したり、娯楽のためのクイズを主催したりすることができる。

また、リマインダーのスケジューリングや簡単なユーザー認証などのタスクを自動化することもできる。

開発環境のセットアップ

この記事では、ユーザーが好きな都市の天気を6時間ごとに自動更新したり、選択した都市の天気情報を即座に入手したりできるTelegramボットを紹介する。

プロジェクト・ディレクトリを設定するには、以下のコマンドを実行する:

# プロジェクトディレクトリを作成する
mkdir telegram-weather-bot

# プロジェクトディレクトリにcdする
cd telegram-weather-bot

# npmを初期化する
npm init -y

上記のコマンドは、telegram-weather-botディレクトリを作成し、その中に移動し、その中でnpmを初期化する。

package.jsonファイルに、ボットを実行するための以下のスタートスクリプトを追加します:

"start": "node server.js"

次に、以下のコマンドを実行して、このプロジェクトに必要なパッケージをインストールする:

npm install axios node-telegram-bot-api dotenv node-schedule parse

上記のコマンドは以下のパッケージをインストールした:

  • axiosです:このライブラリを使って天気APIにHTTPリクエストを行う。
  • node-telegram-bot-api:このライブラリは Telegram Bot API とのやりとりを簡略化します。
  • dotenv: このパッケージは.envファイルから環境変数をロードする手助けを します。
  • node-schedule:タスクをスケジュールし、天気の更新を自動化するにはこのパッケージが必要です。
  • parse:このパッケージはBack4app Parse Backendとやり取りするために必要です。

これで開発環境のセットアップは完了です。次に、Telegramでボット・アカウントを作成する必要がある。

Telegram Botアカウントの作成

Telegramボットアカウントは、Telegramプラットフォーム上でのボットのアイデンティティとして機能します。ユーザーはこのアカウントを通じてボットとやりとりすることができます。

Telegramボットを作成するには、Telegramアプリを起動し、検索バーに「BotFather」と入力し、検証済みのトップ結果をクリックします。

ボットファーザーのスタートページ

BotFatherは、他のボットを素早く開発できるTelegramボットです。コマンド/newbotを入力して、ボットの作成プロセスを開始します。ボットの名前と固有のユーザー名を入力します。

テレグラムBotFather

ボットの名前とユーザー名を入力すると、BotFather がユニークなトークンを提供し、ボットのアカウントとのやり取りや管理ができるようになります。

ルートディレクトリに.envファイルを作成し、そのファイルにボットトークンを貼り付けて、ボットトークンをプロジェクトに保存します。このように:

TELEGRAM_BOT_TOKEN = <YOUR_BOT_TOKEN>

ボットトークンを取得し、プロジェクトに保存したら、Telegramボットを構築できます。

Telegram ボット構築

このセクションでは、Telegram天気ボットを作成し、コマンドの処理、ユーザーの状態の管理、天気APIからのデータの取得などの機能を実行するためにインストールしたライブラリを統合する手順を説明します。

ボットのディレクトリを以下のように構成し、ビルド・プロセスを開始します:

telegram-weather-bot/
├── node_modules/          
├── src/                        
|   ├── bot.js                    # ボットとユーザーのやり取りを処理するためのファイル
|   ├── weather.js                # 気象データを取得するためのモジュール 
|   ├── stateManager.js           # Back4Appでユーザー状態を管理するためのモジュール
|   └── cityManager.js            # ユーザーの都市設定を管理し、天気予報を送信します
├── .env                        
├── package.json                  
├── server.js                     # アプリケーションのエントリポイント
└── package-lock.json            

気象情報の入手

気象情報を取得するには、OpenWeatherMap APIを使用します。

このAPIへのリクエストにはAPIキーが必要です。このAPIキーを取得するには、アカウントにログインしてください(アカウントをお持ちでない場合は作成してください)。

次に、プロフィールの「My API Keys」セクションに移動し、API Keyをコピーします。

OpenWeatherMap API

OpenWeatherMap の API キーを.envファイルに保存します:

OPENWEATHERMAP_TOKEN = <YOUR_OPEN_WEATHER_MAP_APIKEY>

ボットの機能は天気情報の取得が中心なので、OpenWeatherMap の API に GET リクエストを行い、axios.getメソッドで天気情報を取得する関数を定義します。

以下のコードブロックをweather.jsファイルに追加して、OpenWeatherMap APIから天気情報のGETリクエストを行います:

const axios = require('axios');
const apiKey = process.env.OPENWEATHERMAP_TOKEN;

async function getWeather(city) {
  try {
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
    const response = await axios.get(url);
    const temp = response.data.main.temp;  
    const description = response.data.weather[0].description;  
    return `The weather in ${city} is ${temp}°C with ${description}.`;
  } catch (error) {
    console.error('Error fetching weather:', error);
    throw error;
  }
}

module.exports = { getWeather } 

上記のコード・ブロックは、都市名を引数にとり、APIのレスポンスから気温と天気の説明を返すgetWeather関数を定義している。

ユーザーに6時間ごとの定期的な天気更新を提供するためには、getWeather関数を実行して希望する都市の天気更新を返すスケジューラー関数を実装する必要があります。

ウェザー・アップデートのスケジューリング

希望する都市の天気の更新を返すジョブをスケジュールするには、cityManager.jsに以下のコードブロックを追加します:

const schedule = require('node-schedule');
const weather = require('./weather');

let userCities = {};

function setCity(chatId, city) {
  userCities[chatId] = city;
}

function init(bot) {
  schedule.scheduleJob('0 */6 * * *', function() {
    for (let chatId in userCities) {
      const city = userCities[chatId];
      weather.getWeather(city).then(response => {
        bot.sendMessage(chatId, response);
      }).catch(error => {
        bot.sendMessage(chatId, "Failed to retrieve weather.");
      });
    }
  });
}

module.exports = { setCity, init };

上のコード・ブロックは、setCityと initという2つの関数を作っている。

このコード・ブロックは、ボット・ユーザーのTelegramチャットIDをsetCity関数を使って都市名にマッピングすることで、ボット・ユーザーが興味を持っている都市を追跡するためのuserCitiesオブジェクトを宣言している。

この機能により、ボットはユーザーの好みに合わせて、どの都市の天気予報を受信するかを設定することができます。

コードブロックのinit関数は、userCitiesに格納されているすべてのチャットIDを反復処理する6時間ごとのスケジュールされたタスクを設定します。init関数はweatherモジュールを使用して、その都市の現在の天気を取得します。

あなたのボットは、異なるユーザーとその好みの都市を追跡するために、ユーザーの状態を永続化する必要があります。これは、Back4appのバックエンドをサービスとして利用することで実現できます。

ボットが様々なユーザーとその好みの都市を追跡できるようにするには、Back4appのバックエンドをサービスとして使用して、ボットユーザーの好みの都市を保存します。

Back4appバックエンドの作成

Back4appのバックエンドをサービスとして利用するには、Back4appのアカウントが必要です。お持ちでない場合は、無料でサインアップできます。

アカウントにログインし、右上の「NEW APP」ボタンをクリックします。アプリケーションに名前を付け、「作成」ボタンをクリックします。

新しいBack4appアプリを作成する

CREATE “ボタンをクリックすると、Back4appがアプリケーションを生成し、アプリケーションのダッシュボードに誘導します。

Back4app Dashboard

Node.jsプロジェクトからBack4Appのアプリインスタンスに接続するには、インストールしたparse SDKをBack4Appのアプリケーション認証情報で初期化する必要があります:アプリケーションIDとJavascript KEYです。

サイドバーの “App Settings “をクリックして “Security & Keys “セクションに移動し、Back4appからアプリケーションIDとJavaScript Keyを入手してください。

.envファイルに保存してください。現在の.envファイルは以下のようなものになっているはずです:

TELEGRAM_BOT_TOKEN= "<YOUR_TELEGRAM_BOT_TOKEN_HERE>"
OPENWEATHERMAP_TOKEN= "<YOUR_OPENWEATHERMAP_TOKEN_HERE>"
BACK4APP_APP_ID= "<YOUR_BACK4APP_APP_ID_HERE>"
BACK4APP_JAVASCRIPT_KEY= "<YOUR_BACK4APP_JAVASCRIPT_KEY_HERE>"

次に、userId(文字列)とstate(オブジェクト)の2つのフィールドを持つ新しい “UserState “クラスを作成する必要があります。このタスクは、Back4appのAIエージェントを使用して行います。

それぞれのフィールドを持つこのクラスを作成するには、画面の「AIエージェント」タブに移動し、エージェントに以下のプロンプトを与えます:

Create a new class, "UserState," in my Back4app application with the APP ID “<YOUR_BACK4APP_APP_ID_HERE>”. The "UserState" class will have two fields: userId (string) and state (object).

下の画像のような反応が返ってくるはずだ:

AIエージェントの反応

Back4appのダッシュボードを確認すると、UserStateクラスが正常に作成されているはずです。

Telegramボットでユーザーの状態を管理する

ボットのユーザーとボットの間のインタラクションの流れを管理するには、ボットがユーザーからどのようなコマンドを期待するかを示すユーザーステートを定義する必要があります。

startコマンドを除けば、ボットは主に2つのコマンドを理解します。各ユーザとの対話(コマンドを含む)は、UserStateクラスにチャットIDとともに格納されているユーザの状態を更新するトリガーとなります。

この状態管理によって、ボットは会話の中で各ユーザーの特定の詳細を記憶することができる。

Back4Appデータベースに作成したUserStateクラスでユーザのステータスのトラッキングを開始するには、Parse SDKを認証情報で初期化する必要があります。

以下のコードブロックをstateManager.jsに追加し、Back4app Parse Backendに接続するために必要な認証情報でParse SDKを初期化します:

// stateManager.js
const Parse = require('parse/node');

Parse.initialize(
  process.env.BACK4APP_APP_ID,
  process.env.BACK4APP_JAVASCRIPT_KEY
);

Parse.serverURL = '<https://parseapi.back4app.com/>';

次に、stateManager.jsファイルで、Back4app ParseバックエンドのUserStateクラスのスキーマを表すParseオブジェクトを定義し、ボットがデータベースに保存されたユーザーのステートを操作できるようにします。

こんな感じだ:

// stateManager.js
const UserState = Parse.Object.extend('UserState');

次に、データベースに保存されているユーザーIDに基づいて、ボットが特定のユーザーの現在の会話状態を取得できるようにする必要があります。

ユーザの状態を取得するには、UserStateクラスにユーザのIDを指定してクエリを実行し、データベースに保存されている状態を見つける必要があります。

こんな感じだ:

// stateManager.js
async function getUserState(userId) {
  const query = new Parse.Query(UserState);
  query.equalTo('userId', userId.toString());
  const userState = await query.first();
  return userState ? userState.get('state') : null;
}

上記のコードブロックは、非同期にユーザー状態データをフェッチするgetUserState関数を定義している。

この関数は、引数(userId)に基づいてユーザの状態オブジェクトを検索するクエリを作成し、見つかった場合はユーザの状態を取得します。

ユーザーの状態を取得した後、ボットがユーザーIDと提供された状態情報に基づいて、そのユーザーに関連する会話状態を更新できるようにする必要があります。

例えば、会話の状態を更新することで、ボットが希望する都市を更新し、定期的な天気予報を取得できるようになる。

こんな感じだ:

// stateManager.js
async function setUserState(userId, state) {
  const query = new Parse.Query(UserState);
  query.equalTo('userId', userId.toString());
  let userState = await query.first();

  if (!userState) {
    userState = new UserState();
    userState.set('userId', userId.toString());
  }

  userState.set('state', state);
  await userState.save();
}

上記のコードブロックの関数は、userIdに基づいてユーザー状態オブジェクトを検索するクエリを構築し、存在しない場合は新しいUserStateオブジェクトを作成し、state属性を設定し、オブジェクトをデータベースに保存します。

次に、以下のコード・ブロックをstateManager.jsファイルに追加して、ユーザーの状態をリセットする関数を定義する:

// stateManager.js
async function resetUserState(userId) {
  await setUserState(userId, {});
}

resetUserState関数は、非同期にユーザの状態を空のオブジェクト{}に設定し、データベース内のユーザの状態をリセットします。

ステートオブジェクトが常に存在するようにして、以前と新しいボットユーザーのアクションを追跡します。以下のコードブロックをstateManager.jsファイルに追加します:

// stateManager.js
async function ensureUserState(userId) {
  let state = await getUserState(userId);
  if (!state) {
    await setUserState(userId, {});
  }
}

このコードブロックは、ユーザーIDに基づいてステートオブジェクトを取得するsecureUserState関数を定義しており、Back4app ParseバックエンドのUserStateクラスに指定されたuserIdのユーザーステートデータが存在することを確認します。

getUserState関数でユーザーの状態を取得し、状態が存在しない場合は、setUserState関数で空のオブジェクト{}を設定します。

すべての関数をエクスポートして、他のJavascriptソースファイルで利用できるようにします:

// stateManager.js
module.exports = {
  getUserState,
  setUserState,
  resetUserState,
  ensureUserState,
};

ユーザーとボットのやり取り

ボットがstartgetWeathersetCityコマンドをリッスンできるようにするには、3つのコマンドにテキストをマッチさせてコールバック関数を実行するイベントリスナーを定義します。

これにより、ボットはコマンドに関連するタスクを実行できるようになる。

Telegram Bot APIとやりとりしてボットの動作を定義するには、node-telegram-bot-apiライブラリからTelegramBotクラスをインポートし、クラスの新しいインスタンスを作成します:

// bot.js
const TelegramBot = require('node-telegram-bot-api');
const token = process.env.TELEGRAM_BOT_TOKEN;
const bot = new TelegramBot(token, { polling: true });

上のコード・ブロックは、ボット・トークンとオブジェクトの2つの引数でTelegramBotクラスの新しいインスタンスを生成します。

このオブジェクトは、ボットがTelegramサーバーからのメッセージやアップデートを継続的にチェックできるように、ポーリング値をtrueに設定します。

次に、このプロジェクトで作成したモジュールをインポートします。これらのモジュールを使って、天気予報の更新を取得したり、希望する都市を設定したり、ユーザーの状態を管理したりといったボットの機能を実装します。

以下のコードを追加して、必要なモジュールをインポートする:

// bot.js
const weather = require('./weather');
const cityManager = require('./cityManager');
const stateManager = require('./stateManager');

上記のコードブロックは、すべてのソースファイルで作成した関数をインポートし、bot.jsファイルでこれらの関数を呼び出せるようにします。

コマンドをセットアップするには、TelegramBotクラスで利用可能なonTextメソッドを使用します。このメソッドはリスナーを設定し、コールバック関数を実装してロジックを実行します。

以下のコードブロックをbot.jsファイルに追加して、ボットがonTextメソッドで積極的にリッスンする/startコマンドハンドラを設定します:

// bot.js

bot.onText(/\\/start/, async (msg) => {
    const welcomeMessage = "Welcome to the Weather Bot! Use the commands below to interact:\\n" +
    "/setCity - Set your preferred city for weather updates.\\n" +
    "/getWeather - Get instant weather information for any city.\\n";
    await bot.sendMessage(msg.chat.id, welcomeMessage);
    stateManager.ensureUserState(msg.chat.id);
});

上のコードブロックは、ボットがコールバック関数を実行し、ボットの動作について詳しく説明するウェルカムメッセージをユーザーに送信するようにします。

ボットはsecureUserState関数でユーザーの初期状態を設定し、各ユーザーとのインタラクションでクリーンな状態を確保します。

次に、以下のコードブロックをbot.jsファイルに追加して、ボットがonTextメソッドで積極的にリッスンする/setCityコマンドハンドラを設定します:

// bot.js

bot.onText(/\\/setCity$/, async (msg) => {
    stateManager.setUserState(msg.chat.id, { expect: 'SET_CITY' });
    bot.sendMessage(msg.chat.id, "Which city do you want to set as your preferred city for weather updates?");
});

上のコード・ブロックでは、「/setCity」コマンドで、天気予報の更新に使用する都市を指定できる。

このコマンドを受信すると、ボットはsetUserState関数を使用してユーザーの状態を「SET_CITY」に更新し、ユーザーが希望する都市を入力するよう促します。

以下のコードブロックをbot.jsファイルに追加して、ボットがonTextメソッドで積極的にリッスンする/getWeatherコマンドハンドラを設定します:

// bot.js
bot.onText(/\\/getWeather$/, async (msg) => {
    stateManager.setUserState(msg.chat.id, { expect: 'GET_WEATHER' });
    bot.sendMessage(msg.chat.id, "Which city do you want to get weather information for?");
});

getWeather“コマンドは、ボットがユーザーの状態を “GET_WEATHER “に更新するトリガーとなり、即座に天気情報が欲しい都市を入力するよう促す。

着信メッセージの処理

Telegram ボットの受信メッセージを処理するには、ボットが新しいメッセージを受信するたびにコールバック関数をトリガーするイベントリスナーをセットアップする必要があります。

以下のコードブロックをbot.jsファイルに追加して、コマンドとユーザーの応答を聞くためのメッセージハンドラを設定します:

// bot.js

// Handle incoming messages
bot.on('message', async (msg) => {
    if (msg.text.startsWith('/')) {
        // If the message is a command, reset the user state
        stateManager.resetUserState(msg.chat.id);
    } else {
        // If it's not a command, check user state
        const state = await stateManager.getUserState(msg.chat.id);
        if (state && state.expect === 'SET_CITY') {
            // If expecting SET_CITY, set city and reset state
            const city = msg.text;
            cityManager.setCity(msg.chat.id, city);
            bot.sendMessage(msg.chat.id, `City set to ${city}. You will receive weather updates every 2 minutes.`);
            stateManager.resetUserState(msg.chat.id);
        } else if (state && state.expect === 'GET_WEATHER') {
            // If expecting GET_WEATHER, get weather and reset state
            const city = msg.text;
            weather.getWeather(city).then(response => {
                bot.sendMessage(msg.chat.id, response);
            }).catch(error => {
                bot.sendMessage(msg.chat.id, "Failed to retrieve weather.");
            });
            stateManager.resetUserState(msg.chat.id);
        }
    }
});

// Initialize the init function from cityManager.js for regular weather updates
cityManager.init(bot);

このコード・ブロックは、送られてきたテキスト・メッセージをリッスンする一般的なメッセージ・ハンドラをセットアップし、インタラクション・ロジックの中核として機能する。

メッセージがフォワード・スラッシュ(“/”)で始まる場合、それはコマンドとみなされる。この場合、ハンドラはユーザーの状態をリセットし、新しいコマンドのために再出発するようにします。

そうでない場合、ハンドラはユーザーの現在の状態(stateManager.getUserState)をチェックし、ユーザーの意図を理解する。

ユーザーが”/setCity “コマンドを渡すと、ボットハンドラーはcityManager.setCityを使用して選択した都市を保存し、更新を確認する。

ユーザーが”/getWeather“コマンドを渡した場合、ハンドラーはweather.getWeather関数を使用して指定された都市の天気情報を取得し、その応答をユーザーに送り返す。

上記のコードブロックは、次にcityManager.init(bot)を実行し、希望する都市の天気の更新を取得するスケジュールされたタスクを実行する。

ボットのロジック処理が完了したら、Back4appコンテナにデプロイする際に実行できるようにサーバーをセットアップします。

エントリーファイルの作成

ボットアプリケーション全体を初期化するエントリーファイルを作成します。このファイルは2つの機能を果たします。

  • エントリーファイルは、ボットがTelegramのAPIとどのようにやりとりし、ユーザーメッセージに応答するかを処理するJavaScriptファイルを要求することで、ボットのロジックを実行します。
  • エントリーファイルは、Back4appコンテナ上でボットを動作させるためのサーバーもセットアップします。Back4appコンテナは、アプリケーションのデプロイを成功させるために公開ポートを必要とします。

プロジェクト・ディレクトリ内のserver.jsファイルに移動する。このファイルがサーバーの作成を処理します。以下のコード・ブロックをserver.jsファイルに追加します:

const http = require('http');
require('./src/bot'); // Import bot logic
const port = 3000;

http
  .createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('My Telegram bot is running\\n');
  })
  .listen(port, () => {
    console.log(`Server running on port ${port}`);
  });

上のコード・ブロックは、シンプルなHTTPサーバーを作成することで、Telegramボットの実行をセットアップしている。このサーバーはエントリー・ポイントとして機能し、ボットをコンテナの環境内で機能させる。

スクリプトはボットのコアロジックをインポートし、Back4appのポートを指定することで、ボットをBack4appコンテナ上にデプロイできるようにします。

エントリーファイルを作成したら、Back4appコンテナにデプロイする前にローカルでTelegramボットをテストすることができます。

Telegramボットのテスト

以下のコマンドを実行してTelegramボットを起動します:

node server.js

ボットと対話するには、Telegramアプリを開き、検索バーでボットの名前を検索します。ボットとのチャットに入り、/startコマンドを送信します。

ボットは歓迎メッセージとコマンドのリストで応答するはずです。ボットにコマンドを送る。

テレグラムBotの天気情報

このボットでは、/setCityコマンドで好みの都市を設定し、定期的に天気予報の更新を受け取ることができる。

上の画像にあるように、/getWeatherコマンドは、特定の都市の天気情報を即座に取得することができる。

ボットは上の画像と同じように動作し、あなたのコマンドに反応し、意図したとおりにあなたと対話するはずです。

Telegram BotをBack4Appコンテナにデプロイする

ボットをテストし、正しく動作することを確認したら、Back4appコンテナにデプロイすることができます。そのためには、まずプロジェクト用のDockerfileを作成する必要があります。

プロジェクトのルート・ディレクトリにDockerfileファイルを作成し、以下のコード・ブロックを追加する:

# Specify base image
FROM node:18-alpine

# Specify the working directory
WORKDIR /app

# Copy package.json and package-lock.json
COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

# Run the app
CMD ["npm", "start"]

Dockerfileを作成したら、ローカルリポジトリをGitHubにプッシュし、Back4appコンテナにデプロイできるようにします。

次に、GitHubアカウントにBack4App Containers GitHub Appをインストールし、アプリケーションのコードリポジトリにアクセスするために必要なパーミッションを与えます。

GitHubの設定が完了したら、Back4appのホームページに移動し、画面右上の “New App“ボタンをクリックします。

初期化画面が表示されますので、作成したいアプリの種類を選択してください。下の画像のように、Back4appエージェントのオプションを選択します。

Back4appで新規アプリを作成する

Back4app Agentオプションを選択すると、Back4app AIエージェントのページにリダイレクトされます。

以下のプロンプトを入力し、アプリケーションをBack4appコンテナにデプロイします:

Deploy my "YOUR_REPOSITORY_URL" repository on GitHub to Back4App Containers.
Here are the required environmental variables:
TELEGRAM_BOT_TOKEN = "TELEGRAM_BOT_TOKEN"
OPENWEATHERMAP_TOKEN = "WEATHER_API_TOKEN"
BACK4APP_APP_ID = "BACK4APP_APP_ID"
BACK4APP_JAVASCRIPT_KEY = "BACK4APP_JAVASCRIPT_KEY"

プレースホルダを実際の値に置き換えます。上記のプロンプトが、デプロイメントプロセスを開始します。完了すると、AIエージェントが応答し、デプロイの成功または保留を示します。

以下のような返事が返ってくるはずだ:

AIエージェントの反応

保留中のデプロイが表示されたら、Back4app コンテナダッシュボードでアプリのデプロイ状況を監視できます。あるいは、Back4app コンテナ上に手動でアプリケーションをデプロイすることもできます。

結論

この記事では、Node.jsを使ってTelegramボットを作成する方法を学びました。Telegram ボットアカウントの作成方法、ボットコマンドの処理方法、ユーザーインタラクションの処理方法について説明しました。

また、Back4appのAIエージェントの助けを借りてボットのバックエンドを作成し、ユーザーの状態を効率的に管理したり、定期的に天気の更新を受け取るためにユーザーが希望する都市などの情報を会話に保存できるようにしました。

完全なコードはGitHubリポジトリにある。


Leave a reply

Your email address will not be published.