AtCoder用Discord Botを作ってみた
この記事は 朝活部 Advent Calendar 2024 2日目の記事です
ユーザーを登録しておくと毎日0時にACした問題を報告してくれるBotです。
もともとPythonで書かれていたのですが、Rustで書き直してみました。
Rustにしてよかったところ
Embedの生成周りをイテレータを使ってきれいにできたのがうれしかったです。
仕様としては、
- Embedのタイトルは「(ユーザー名)さんが昨日ACした問題」にし、ユーザーへのリンクにする
- Embedのフィールドに解いた問題のタイトル、Difficulty、色、言語、提出リンクなどを列挙する
- Embed左側の色は、解いた問題の中で最も高いDifficultyの色にし、解いた問題のDifficultyがすべて不明だった場合は黒色にする(APG4bなど一部の問題はDifficultyが
null
になることがある) - フィールドが25個以上になった場合はEmbedを分割する(Discordの仕様で、25個以上のフィールドを送信することはできないため)
という感じです。
(Inlay Hintsを表示したかったのでスクリーンショットを貼っています)
Rustでは.chunks(25)
を使って配列を25個区切りのイテレータにできるので、indexを持って剰余を計算して区切っていく必要がなくなりました。
また、問題をフィールドに変換する部分も問題情報のstructのimpl
に逃し、こちらではp.to_field()
とすることで実装をスッキリさせることができました。
impl側の実装
impl ProblemDetail {
fn to_field(&self) -> (String, String, bool) {
(
self.title.clone(),
format!(
"{} | {} | [提出]({})",
self.difficulty
.map(|d| {
let diff = difficulty::normalize(d);
format!("{}({})", diff, difficulty::Color::from(diff))
})
.unwrap_or("不明".into()),
self.language,
self.submission_url
),
false,
)
}
}
さらに、.map
を使うことで、None
の可能性があるDifficulty値に対してNone
はNone
を保ったまま計算を適応していくことができてかなりうれしかったです。Color
(色のenum)にOrd
トレイトをderiveすることでimpl Iterator<Item = Color>
に対してmax
を取れるのも良いです。(こうすることで、Color::Black
は最下位なので、すべての問題のDifficultyが不明でないと選択されなくなります)
Rustにして大変だったところ
APIレスポンスの型定義がめんどくさい
APIレスポンスの結果をstructにするために型を定義する必要があり、少しでも間違えているとパースに失敗するので辛かったです。AtCoder ProblemsのAPIにはレスポンスの型がわかるようなドキュメントが(おそらく)ないので、nullableかどうか調べるのが大変でした。
後で知ったのですが、serde_json::Value
ならすべてのjsonを表現できるらしいので、それを使えばもう少し楽だったかもしれません。
ただ、この型定義を書くことで実行時間の情報がnullableであることに気づけたので、意味がないこともないのかなとは思います。
おわり
おわり
Discussion