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をインストール
# macos
brew 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
を送信してテストしましょう。