vxtwitterに自動で変換するBot

## TL;DR
`x.com`で始まるリンクを読み取ると`vxtwitter.com`に変換してくれるDiscord Botを作ったよ
ホスティングは[shuttle](https://shuttle.rs)でやったよ

## 作成経緯

Twitterが色々変更されていく中で､Discordでリンクを自動で展開する機能がなくなった(埋め込み)
ので､埋め込みを表示してくれるリダイレクトのリンクができた(`fxtwitter` `fixup`とか)
でもわざわざ打ち直すの面倒
→Botにしようぜ

## 機能

### イベントハンドラ

Messageにリンクが含まれていたら､
- Bot でない
- 正規表現にマッチする文字列が存在するか

を判定する｡

それらが通った場合､マッチした条件にあう文字列を使って`http://vxtwitter.com`のリンクに加工して､そのメッセージにリプを返す

```rs:EventHandler::message
#[async_trait]
impl EventHandler for Bot {
    async fn message(&self, _ctx: Context, msg: Message) {
        if msg.author.bot {
            return;
        }

        let Some((username, hash)) = match_url(&msg.content) else {
            return;
        };
        let reply = format!("https://vxtwitter.com/{}/status/{}\n", username, hash);
        check_msg(msg.reply(&_ctx.http, reply).await);
    }

    async fn ready(&self, _: Context, ready: Ready) {
        info!("{} is connected!", ready.user.name);
    }
}
```

### 正規表現で必要なパラメータを抽出する

正規表現をすると厳密に必要なパラメータを取り出せる(ただしセンスがいる)

```rs:match_url
// 正規表現マッチングを行う関数
fn match_url(content: &str) -> Option<(String, String)> {
    let regex = Regex::new(
        r"https:\/\/(x|twitter)\.com\/(?<username>[a-zA-Z0-9_]{1,16})\/status\/(?<hash>[0-9]+)",
    )
    .unwrap();

    regex
        .captures(content)
        .map(|caps| (caps["username"].to_string(), caps["hash"].to_string()))
}

```

部分的なマッチパターンの前に`?<hoge>`とやるとあとで同じ文字列をキーにしてマッチした値を取得できる
今回だと`?<username>` `?<hash>`が該当する

## Shuttleのセットアップ

Shuttleでアカウント作って(GitHubで連携すると楽) `NewProject`で出てくるコマンドを打つ

```
cargo install cargo-shuttle
cargo shuttle init   # 対話型のやりとりで設定できる(使用するフレームワークを聞かれたときに今回はSerenityを選択する)
cargo shuttle run    # ローカル実行
cargo shuttle deploy # shuttle.rsのPJにデプロイできる
```

:::note info
新規PJで始めることをおすすめします｡既存のレポのディレクトリを指定するとうまくインストールされなかったりデプロイに失敗します[^1]
:::

### 機密変数について

今回扱うDiscord Botでは`DISCORD_TOKEN`が該当する
これを生成される`Secrets.toml`に`DISCORD_TOKEN="Your secret discrod token"`のように記入する
また､`.gitignore`に`Secrets.toml`を追加すること(ドキュメントにもこう書いてあるんだけど､なぜ最初から追加されていないかは謎)

### Discord Botの作成について

Applicationsでアプリ作って権限設定してBotの欄でTokenをコピーして`Secrets.toml`に貼る｡
詳しくは下記参考にしてください｡

https://qiita.com/raiga0310/items/d5c7b0f852527b82d786

:::note warn
Botの作成方法はほとんど変わりませんが､3年以上前の記事のため***すべてを参考にせず*** Serenityのドキュメントや[Shuttleでのチュートリアル](https://docs.shuttle.rs/examples/serenity)を必ず参照してください｡
:::

## Shuttleのアイドリングについて

デプロイとかもろもろしているとこんなログが出ます

```
PS C:\Users\raiga\dev> cargo shuttle init
What do you want to name your project?
It will be hosted at ${project_name}.shuttleapp.rs, so choose something unique!
✔ Project name · <hoge>

Where should we create this project?
✔ Directory · <huga>

Shuttle works with a range of web frameworks. Which one do you want to use?
✔ Framework · serenity

Creating project "vxbot" in "C:\Users\raiga\dev\<huga>"

Hint: Check the examples repo for a full list of templates:
      https://github.com/shuttle-hq/shuttle-examples
Hint: You can also use `cargo shuttle init --from` to clone templates.
      See https://docs.shuttle.rs/getting-started/shuttle-commands
      or run `cargo shuttle init --help`

✔ Claim the project name "<hoge>" by starting a project container on Shuttle? · yes

Project "<hoge>" is ready
Your project will sleep if it is idle for 30 minutes. ### <- ここ!!!!!!!!!!
To change the idle time refer to the docs: https://docs.shuttle.rs/getting-started/idle-projects

```

`Your project will sleep if it is idle for 30 minutes.`
[ドキュメント](https://docs.shuttle.rs/getting-started/idle-projects)には､｢2 calls/minないとスリープするよ｡スリープ状態の復帰はCLIからでしかできないよ｣みたいなことが書いてある｡Botに関してはこの仕様はつらいので､設定し直す必要がある｡

`cargo shuttle project restart --idle-minutes 0`とするとスリープしないようにできる｡これはドキュメントでも言及されているので問題無し｡
念のため､`status`コマンドでアイドル時間が0なことを確認する｡

```
PS C:\Users\raiga\dev\vxbot_shuttle> cargo shuttle project status
Project "vxbot" is ready
Idle minutes: 0 # きちんと設定されている
```

## 動作例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/606626/7012efbd-ef47-9168-97fc-e84b9cf7b023.png)


## Repo

https://github.com/raiga0310/vxbot_shuttle

## 最後に

Discordで高専生を中心としたメンバーが楽しくやっています｡

https://approvers.dev/

[^1]: そもそも既存のSerenityBotだと`#[shuttle_runtime::main]`とか`#[shuttle_secrets::Secrets] secret_store: SecretStore,`みたいなShuttle特有の書き方を反映するのが少し大変｡今回は小規模なPJだから1時間ほど溶かすにとどまりましたが､大規模なアプリの場合(データベースとかあったりすると)移行がかなり大変かも(そういった移行がし易いように設計しろという話は尤もすぎる)