【Rust・LINEBot】line-bot-sdk-rust が無かったから作ってみた。

## 2023.12.04 追記

`line-openapi` の公開により、OpenAPIベースのライブラリに更新しました。

https://qiita.com/nanato12/items/450d7c61b34ec83f0271

## はじめに
Rustに入門し、早1週間。

Rustの色々な機能やシステムが有能すぎることに気付く。
そして、LINEBotの公式SDKにRustがないことに気付く。

ということで、練習がてらに **line-bot-sdk-rust** 作りました。

**GitHub**

https://github.com/nanato12/line-bot-sdk-rust

**Crate**

https://crates.io/crates/line-bot-sdk-rust

**Documentation**

https://docs.rs/line-bot-sdk-rust/0.1.0/line_bot_sdk_rust/

## LINEBotを動かすための準備
基本的に、`rocket` や `actix-web` といったFWを用いて動かします。

LINEBotのためのアカウントの作成方法はこちら
[【初心者向け】LINEBOTを作るためのLINE公式アカウント作成・設定](https://qiita.com/nanato12/items/25e2db9461bb6ac2b8c5)

`Channel secret ` と `Channel access token (long-lived) ` の値を保存してください。

## 動かしてみよう
Rustのフレームワーク `rocket` を使用して行きます。

とりあえず、`cargo new`

```shell
$ cargo new rust-linebot
```

`Cargo.toml` に以下を追記。

```toml:Cargo.toml
[dependencies]
line-bot-sdk-rust = { version = "0.1", features = ["rocket_support"] }
rocket = "0.4"
dotenv = "0.15.0"
```

`.env` ファイルを作成

```config:.env
LINE_CHANNEL_RECRET="取得した Channel secret"
LINE_CHANNEL_ACCESS_TOKEN="取得した Channel access token (long-lived)"
```

`main.rs` にコードを書いてく。( EchoBot )
送られた文字をそのまま返すBotです。

```rust:src/main.rs
#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate rocket;

extern crate line_bot_sdk_rust as line;

use dotenv::dotenv;
use std::env;

use rocket::http::Status;

use line::bot::LineBot;
use line::events::messages::MessageType as EventMessageType;
use line::events::{EventType, Events};
use line::messages::{SendMessageType, TextMessage};
use line::support::rocket_support::{Body, Signature};

#[post("/callback", data = "<body>")]
fn callback(signature: Signature, body: Body) -> Status {
    // Get channel secret and access token by environment variable
    let channel_secret: &str =
        &env::var("LINE_CHANNEL_RECRET").expect("Failed getting LINE_CHANNEL_RECRET");
    let access_token: &str =
        &env::var("LINE_CHANNEL_ACCESS_TOKEN").expect("Failed getting LINE_CHANNEL_ACCESS_TOKEN");

    // LineBot
    let bot = LineBot::new(channel_secret, access_token);

    // Request body parse
    let result: Result<Events, &'static str> =
        bot.parse_event_request(&signature.key, &body.string);

    // Success parsing
    if let Ok(res) = result {
        for event in res.events {
            // MessageEvent only
            if let EventType::MessageEvent(message_event) = event.r#type {
                // TextMessageEvent only
                if let EventMessageType::TextMessage(text_message) = message_event.message.r#type {
                    // Create TextMessage
                    let message = SendMessageType::TextMessage(TextMessage {
                        text: text_message.text,
                        emojis: None,
                    });
                    // Reply message with reply_token
                    let _res = bot.reply_message(&message_event.reply_token, vec![message]);
                }
            }
        }
        return Status::new(200, "OK");
    }
    // Failed parsing
    else if let Err(msg) = result {
        return Status::new(500, msg);
    }
    Status::new(500, "Internal Server Error")
}

fn main() {
    dotenv().ok();
    rocket::ignite().mount("/", routes![callback]).launch();
}
```

そして実行。

```shell
$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.55s
     Running `target/debug/rust-linebot`
🔧 Configured for development.
    => address: localhost
    => port: 8000
    => log: normal
    => workers: 16
    => secret key: generated
    => limits: forms = 32KiB
    => keep-alive: 5s
    => read timeout: 5s
    => write timeout: 5s
    => tls: disabled
🛰  Mounting /:
    => POST /callback (callback)
🚀 Rocket has launched from http://localhost:8000
```

ローカルサーバーが起動したので、LINEBotのWebhookに設定する。

ローカルで建てたサーバーを一時的に外部に公開できる **ngrok** を使用します。
（ngrok の設定は調べてください。）

`8000` ポートを指定して起動します（一応、リージョンは `ap` ）。

```shell
$ ngrok http 8000 --region=ap
```
```shell
ngrok by @inconshreveable                                               (Ctrl+C to quit)
                                                                                                               
Session Status                online
Account                       ななといつ (Plan: Free)
Version                       2.3.35
Region                        Asia Pacific (ap)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://cc41bcab451d.ap.ngrok.io -> http://localhost:8000
Forwarding                    https://cc41bcab451d.ap.ngrok.io -> http://localhost:8000
Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00                  
```

`https` のURLをwebhookに設定します。

エンドポイントは `/callback` なので、
`https://cc41bcab451d.ap.ngrok.io/callback` を設定します。

<img width="500" alt="LINE" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437282/31cef853-48de-7c94-8c92-41533ec5bda9.png">

メッセージを送信してみる。

<img width="500" alt="LINE" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/437282/dd1db4f6-ece4-6a62-7117-b5d03365eeb1.png">

返ってきた。

`rocket` と `ngrok` はこんな感じ。

- rocket

```shell
POST /callback application/json; charset=utf-8:
    => Matched: POST /callback (callback)
    => Outcome: Success
    => Response succeeded.
```

- ngrok

```shell

HTTP Requests
-------------

POST /callback                 200 OK
```

ちゃんと、`200` のレスポンスを返してる。
おっけい。

書いたコードは一応、ここに置いてあります。
https://github.com/nanato12/rust-linebot

## 最後に
正直、実装できてない部分があります。
issue 建ててあるので、もし良かったら実装してみてください。

https://github.com/nanato12/line-bot-sdk-rust/issues

あと、Rust初心者なので、

- ここの書き方きめえよ！
- ここもっといい書き方がある！
- Rustの良いところ活かせてない！

とかあればPRください。。。