Telegram BotにはサーバーとWebhookの二つの立ち上げる方法があります。Lambdaはサーバーレスだから、今回はWebhookで使用します。
最初はCargoで新たなプロジェクトを作ります。
cargo new telegram-bot
必要な依存を追加します。
crgo add aws_lambda_events base64 lambda_runtime serde_json serde teloxide-macros tokio teloxide cargo-lambda
zigをインストール
# macosbrew install zig# lambda cliでzigのインストール方法を出力してcargo lambda system --install-zig
Telegramのコマンドを宣言します
#[derive(BotCommands, PartialEq, Clone, Debug)]// いろんなパラメータに対応しています。公式ドキュメントをチェックしてください。#[command( rename_rule = "lowercase", description = "These commands are supported:")]pub enum Command { #[command(description = "get current user id")] UserID,}
Telegramの処理関数を定義します
pub async fn answer( bot: teloxide::prelude::Bot, msg: Message, cmd: Command,) -> Result<(), RequestError> { let reply = match cmd { Command::UserID => format!("your id is: {}", from_user).to_string(), } // 返事を送信し bot.send_message(msg.chat.id, v) .reply_parameters(ReplyParameters::new(msg.id)) .parse_mode(teloxide::types::ParseMode::MarkdownV2) .disable_link_preview(true) .await?; Ok(())}
Telegramのルートを定義します
ここはちょっと難しいかもしれません
Update::filter_message()は一般的なメッセージUpdate::filter_edited_message()は更新したメッセージ- 他の種類のメッセージはtrait.UpdateFilterExtにご確認ください。
filter_command::<Command>()はCommandによってメッセージをフィルターする。
例えばfilter_commandを削除してdptree::entry().endpoint(answer)になったら、これはすべてのメッセージをマッチする。
pub fn handler() -> Handler<'static, Result<(), RequestError>, DpHandlerDescription> { return dptree::entry() .branch( Update::filter_message() .branch(dptree::entry().filter_command::<Command>().endpoint(answer)), ) .branch( Update::filter_edited_message() .branch(dptree::entry().filter_command::<Command>().endpoint(answer)), );}
Telegram Botサーバーなら次のコードで実行できます。Lambdaには他の方法を使います。
pub struct TErrorHandler {} impl ErrorHandler<RequestError> for TErrorHandler { fn handle_error(self: std::sync::Arc<Self>, error: RequestError) -> BoxFuture<'static, ()> { println!("error: {}", error); Box::pin(async move {}) }} pub async fn run_bot() -> Dispatcher<Bot, RequestError, DefaultKey> { let bot = Bot::from_env(); bot.set_my_commands(Command::bot_commands()) .send() .await .unwrap(); let handler = handler(); let dispatcher: Dispatcher<Bot, RequestError, DefaultKey> = Dispatcher::builder(bot, handler) .enable_ctrlc_handler() .error_handler(Arc::new(TErrorHandler {})) .build(); return dispatcher;}
まずLambdaのエントリー関数を作ります。
#[derive(Serialize, Deserialize)]struct Response { msg: String,} #[derive(Serialize, Deserialize)]struct LambdaRequest {} async fn handler(&self, event: LambdaEvent<Value>) -> Result<Response, lambda_runtime::Error> { let (payload, _) = event.into_parts(); // Telegram WebhookはFunction Urlにアクセスするので、ここではLambdaFunctionUrlRequestを使います if let Ok(v) = serde_json::from_value::<LambdaFunctionUrlRequest>(payload.clone()) { return bot_handler(v).await; // 他にはカスタマイズした構造体を使うこともできますので、自由に定義してください。 } else if let Ok(v) = serde_json::from_value::<LambdaRequest>(payload.clone()) { return request_handler(v).await; } else { return Err(lambda_runtime::Error::from("Unknown request")); }} async fn request_handler( &self, request: LambdaRequest,) -> Result<Response, lambda_runtime::Error> { // TODO ... return Ok(Response { msg: "Send successful.".to_string(), });}
Telegram Botのハンドラーを作ります。
async fn bot_handler(event: LambdaFunctionUrlRequest) -> Result<Response, lambda_runtime::Error> { let bot = Bot::from_env(); let me = bot.get_me().await?; println!("Bot: {}, {}", me.username.as_ref().unwrap(), me.id.0); match event.raw_path.ok_or("Path is None")?.as_str() { // Function UrlをTelegram BotのWebhookに設定し "/tgbot/register" => { // UrlのHostはHTTPリクエストのドメインです let url = format!( "https://{}/tgbot", event .request_context .domain_name .ok_or("domain name is none")? ); println!("Registering webhook: {}", url); // Commandを設定する let _ = bot.set_my_commands(Command::bot_commands()).await; // Webhookを設定する let _ = bot.set_webhook(url::Url::parse(&url)?).send().await?; return Ok(Response { msg: format!("register telegram bot to {} successful", url).to_string(), }); } // telegram Botのリクエストのhandler "/tgbot" => { let bytes = event.body.ok_or("body is none")?; // Function UrlのBodyはbase64エンコードされたものです let body = if event.is_base64_encoded { general_purpose::STANDARD.decode(bytes)? } else { bytes.as_bytes().to_vec() }; println!("body: {}", String::from_utf8_lossy(&body)); // UpdateはTelegram Botのリクストの構造体です, teloxide::types::Update let update: Update = serde_json::from_slice(&body)?; let handler = handler(); let dependencies = dptree::deps![me.clone(), bot.clone(), update]; // リクエスを処理する let result = handler.dispatch(dependencies).await; return match result { ControlFlow::Break(Ok(())) => Ok(Response { msg: "Update was handled by bot.".to_string(), }), ControlFlow::Break(Err(e)) => Err(lambda_runtime::Error::from(e)), ControlFlow::Continue(_) => Ok(Response { msg: "Update was not handled by bot.".to_string(), }), }; } _ => {} } Err(lambda_runtime::Error::from(format!("404 NOT FOUND")))}
次はTelegram Bot Fatherで新たなBotを作成して、Tokenを取得してください。
Lambdaに新たな環境変数を追加します
TELOXIDE_TOKEN=<Telegram Bot Token>
ビルド
cargo lambda build --release --bin lambda
デプロイ
cargo lambda deploy --binary-name lambda hj-telegram-bot
Webhookを登録します
curl https://<function url>/tgbot/register
これで完成しました。
Telegram Botに/useridを送信してテストしましょう。
0 件のコメント