rubyに入門する

Rubyってどうなの?ナナオです。 今回はあまり触ってこなかったRubyに入門してみようと思います。 きっかけとしてはある会社と面談したときに面白そうな会社だな~~と思ったのですが、Rubyをメインで使っている会社だったからです。 私のRubyのレベルは若干しかできないくらいのレベルなので、基礎はできるようにしておこうと思い、今回備忘録もかねて入門してみようと思います。 Rubyのセットアップ まずはRubyのインストールを行っていきましょう。 バージョン管理はしっかりやっておきたいので、rbenvを使います。 私の開発環境はWindowsですが、anyenvを使いたかったのでWSL2で作業はしています。 また、すでにanyenvをダウンロード済みの環境ですが、anyenvのインストールは公式リポジトリを参照してください。 ではanyenvでrbenvをセットアップしていきます。 anyenv install rbenv これで問題なくrbenvはインストールできます。 あとは適当に安定版の3.2.2くらいをインストールします。 rbenv install 3.2.2 ただここでエラーが起きてしまいました。かなしいね。 ==> Downloading openssl-3.1.4.tar.gz... -> curl -q -fL -o openssl-3.1.4.tar.gz https://dqw8nmjcqpjn7.cloudfront.net/840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 14.8M 100 14.8M 0 0 10.0M 0 0:00:01 0:00:01 --:--:-- 10.0M ==> Installing openssl-3.1.4... -> ./config "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl" "--openssldir=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl/ssl" zlib-dynamic no-ssl3 shared -> make -j 24 BUILD FAILED (Ubuntu 22.04 on x86_64 using ruby-build 20231225-4-g33168b3) You can inspect the build directory at /tmp/ruby-build.20240115225829.19003.NXfhFA See the full build log at /tmp/ruby-build.20240115225829.19003.log ログを見てみると、zlibがインストールされていないのが原因だったようです。 ...

1月 15, 2024 · にあえん

gasでCheerioを使うとスクレイピングが捗る

あけましておめでとうございます。ナナオです。 年が明けてからだいぶ経ってしまいましたが、最近GAS(Google App Script)でスクレイピングする方法が分かったのでやっていこうと思います。 どうやるか jqueryライクにHTMLを操作できるcheerioを利用します。 GASではnpm installはできないので、こちらを使用します。 GitHub - tani/cheeriogs: Cheerio/ jQuery for Google Apps Script このライブラリをGASでインポートします。 まず新しいGASのプロジェクトを作成します。 作成したらライブラリにスクリプトIDとして1ReeQ6WO8kKNxoaA_O0XEQ589cIrRvEBA9qcWpNqdOP17i47u6N9M5Xh0を貼り付け、検索します。 Cheerioが表示されました! これを追加ボタンから追加すれば、Cheerioが使用可能な状態になります。 以下のような実装を追加してみます。 function myFunction() { const content = Cheerio.load("<div>example</div>") console.log(content) } 実行してみます。 ちゃんとCheerioのオブジェクトとして読み込まれていますね! クエリによる検索が可能かどうかも検証してみましょう。 function myFunction() { const $ = Cheerio.load(` <div class="1">example</div> <div class="2">example</div> `) const div_2 = $("div.2") console.log(div_2.length) } これを実行してみます。 ちゃんと検索できていますね! さらに、UrlFetchAppと組み合わせて検索してみます。 何の意味もないですが、divがHTML内に何個あるか出力するプログラムを作りました。 function myFunction() { const response = UrlFetchApp.fetch("http://www.google.com/") const $ = Cheerio.load(response.getContentText()) const search_area = $("div") console.log(search_area.length) } では実行します。 いくつあるか出力されました!意外と少ないな。。 まとめ ということでCheerioをGASで使用するという記事でした。 応用することで簡易なスクレイピングができると思います。 ではまた。

1月 13, 2024 · にあえん

WSLでzshの導入&zshのプラグイン管理にZnapを導入してみる

こんばんは。ナナオです。 Windowsに切り替えてから半年近く経ちますが、まだwslのセットアップが全然できていませんでした。 普段仕事ではMacを使っているのでこちらもzshを使おうと思っていたんですが、せっかくなら新しいものを使いたいなと思って調べてみたら、プラグイン管理にznapというツールが使用できることを知ったのでzshと一緒に導入してみようと思います。 ZSHの導入 私のWSLはUbuntuを使用しているため、その設定でやります。 まずは以下のコマンドでzshをインストールします。 sudo apt install zsh 次にchshで起動するシェルをZSHに変更します。 chsh -s /bin/zsh はい、これで導入終わりです。 ホームディレクトリに空の.zshrcを配置したら設定はこれで終わりです。 Znapの導入 続けてznapの導入です。 リポジトリをクローンします。 git clone -depth 1 -- https://github.com/marlonrichert/zsh-snap.git ~/.znap そしたらznapを読み込む設定を.zshrcに追加します。 # .zshrc source ~/.znap/znap.zsh はい、これで終わりです。 とりあえずhelpコマンドを表示してみましょう。 % znap --help Usage: znap <command> [ <argument> ... ] Commands: clean remove outdated .zwc binaries clone download repos in parallel compile compile zsh scripts and functions eval cache & eval output of command fpath install command output as a function function create a set of lazy-loading functions help print help text for command ignore add local exclude patterns to repo install install executables & completion functions multi run tasks in parallel prompt instant prompt from repo pull update repos in parallel restart (DEPRECATED) validate dotfiles & safely restart Zsh source load plugins status fetch updates & show git status uninstall remove executables & completion functions For more info on a command, type `znap help <command>`. ちゃんとznapコマンドが使えていますね。 ...

12月 22, 2023 · にあえん

LineのOAuth認証を試してみた

OAuth触るの5年ぶり。ナナオです。 今回はLINEのOAuth認証を、実際にサーバーを実装しながら試していきたいと思います。 LINEログイン v2.1 APIリファレンス | LINE Developers そもそもOAuthとは?といったところは↓ 一番分かりやすい OAuth の説明 #OAuth - Qiita OAuthでログイン画面表示まで まずLINE Developersコンソールからログインチャネルを作成します。 これはMessaging APIなどとは別で作成する必要があります。 以下のように設定しました。 続けて、「LINEログイン設定」からコールバックURLを設定します。 コールバックURLのリクエスト先はまだ実装していなくても大丈夫です。 今回はウェブアプリから使用する設定で実装します。 ここまで出来たらブラウザからログインの様子を確認することができます。 認可リクエストのドキュメントを確認し、URLを作ってブラウザからリクエストしてみましょう。 うまくいけば以下のようなLINEのログイン画面に遷移します。 コールバックURLを実装する 今のままだとリダイレクトURLの先には何もないので、自分で実装しましょう。 Cargoパッケージを作成します。 cargo new rust-line-oauth-playground 必要な依存関係も追加しておきます。 [dependencies] axum = "0.7.1" dotenv = "0.15.0" reqwest = { version = "0.11.22", features = ["json"] } serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.108" tokio = { version = "1.34.0", features = ["macros", "rt", "rt-multi-thread"] } では実際にリダイレクト先の実装をしてあげましょう。 これを実施すると、アクセストークンが取得できるようにになります。 以下のように行いました。 use axum::{extract::{Query, State}, routing::get, Router}; use serde::Deserialize; use reqwest::Client; use serde_json::{Value, json}; #[derive(Debug, Deserialize)] struct LineCallbackQuery { code: String, state: String, friendship_status_changed: Option<bool>, liffClientId: Option<String>, liffRedirectUri: Option<String>, } #[derive(Debug, Deserialize)] struct LineCallbackError { error: String, error_description: Option<String>, state: Option<String>, } #[derive(Debug, Deserialize)] struct AccessTokenResponse { access_token: String, expires_in: i32, id_token: String, refresh_token: String, scope: String, token_type: String, } #[derive(Debug, Clone)] struct BaseSetting { redirect_uri: String, client_id: String, client_secret: String, } #[derive(Debug, Clone)] struct AppState { http_client: Client, setting: BaseSetting, } async fn line_callback( Query(query): Query<Value>, State(state): State<AppState>, ) { // エラーの場合 // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#receiving-an-error-response if query.get("error").is_some() { let query: LineCallbackError = serde_json::from_value(query).unwrap(); eprintln!("リダイレクト時にエラーが発生しました: {:?}", query); return } // 正常な場合 // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#receiving-the-authorization-code let query: LineCallbackQuery = serde_json::from_value(query).unwrap(); let params = json!({ "grant_type": "authorization_code", "code": query.code, "redirect_uri": state.setting.redirect_uri, "client_id": state.setting.client_id, "client_secret": state.setting.client_secret, }); // アクセストークンを取得する // https://developers.line.biz/ja/docs/line-login/integrate-line-login/#get-access-token let res = state.http_client.post("https://api.line.me/oauth2/v2.1/token") .form(&params) .send().await.unwrap(); let res_json: AccessTokenResponse = res.json().await.unwrap(); println!("res: {:?}", res_json); } #[tokio::main] async fn main() { // 環境変数をdotenvで取得する dotenv::dotenv().ok(); let setting = BaseSetting { redirect_uri: std::env::var("REDIRECT_URI") .expect("REDIRECT_URI must be set"), client_id: std::env::var("CLIENT_ID") .expect("CLIENT_ID must be set"), client_secret: std::env::var("CLIENT_SECRET") .expect("CLIENT_SECRET must be set"), }; let http_client = reqwest::Client::new(); let state = AppState { http_client, setting, }; let app = Router::new() .route("/callback", get(line_callback)) .with_state(state); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap(); println!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app).await.unwrap(); } あとはルートパッケージ直下に.envを配置します。 ...

11月 30, 2023 · にあえん

Rustでかんたん動的サイト構築

いろんなライブラリを触りたい。ナナオです。 今回はPythonのHTMLテンプレートエンジンで有名なJinjaに影響を受けたTeraを使って、簡単な動的サイトを作ってみたいと思います。 プロジェクトの作成 とりあえず実験用のプロジェクトを作成します。 cargo new rust-ssr-playground 必要なライブラリをインストールしてあげます。 今回はWebサーバーとしてAxum、HTMLのテンプレートエンジンとしてTeraを使用します。 [dependencies] axum = "0.7.1" serde = { version = "1.0.193", features = ["derive"] } tera = "1.19.1" tokio = { version = "1.34.0", features = ["full"] } じゃあとりあえずaxumを起動できるところまで実装します。 use axum::{Router, routing::get}; #[tokio::main] async fn main() { let app = Router::new().route("/", get(|| async { "hello world" })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } 起動してアクセスしてみましょう。 ちゃんと起動しましたね。 次にプロジェクトにHTMLを追加してみましょう。 templatesというディレクトリを作成して、その中にindex.htmlを作成しましょう。 作成したHTMLは以下の通りです。 (Teraのテンプレート構文についてはこちらを参照してください) hello {{ name }}! nameの部分はTeraで入れるようにしました。 HTMLの準備ができたので、これをTeraで描画できるようにします。 use axum::{Router, response::Html, routing::get}; use tera::{Context, Tera}; use serde::Serialize; #[derive(Serialize)] struct Index { name: String } #[tokio::main] async fn main() { let tera = match Tera::new("templates/**/*.html") { Ok(t) => t, Err(e) => { println!("Parsing error(s): {}", e); ::std::process::exit(1); } }; let index = Index { name: String::from("test") }; let page = tera.render("index.html", &Context::from_serialize(&index).unwrap()).unwrap(); let app = Router::new().route("/", get(|| async move { Html(page.to_owned()) })); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap(); } できました。nameにはtestと描画できるようにします。 ...

11月 27, 2023 · にあえん

調整さんで絶対に回答させるline botを作ってみた【7】

どうも、ナナオです。 結構長いこと書いていますが、今回で完成かな…?(前回も同じこと言ったような気がするけど いや、完成させます! 期日設定によって送られるメッセージ頻度を変化させる 最初の記事で想定していたように、このBotは全員が回答するまでメッセージを送り続けるようにしたいです。 その頻度は以下のように期日からの経過時間によって変わります。 期日まで 未回答者に1日に一回催促のDMが送られる 期日経過~1日 未回答者に1時間に一回催促のDMが送られる 期日経過1日~2日 未回答者に30分に一回催促のDMが送られる 期日経過3日~ 未回答者に15分に一回催促のDMが送られる これを実装していきます。 pub struct MultipleService { router: Router, scheduler: JobScheduler, } impl MultipleService { pub async fn new(pool: PgPool) -> Result<MultipleService, shuttle_runtime::Error> { let state = AxumState { pool: pool.clone() }; let router = Router::new() .route("/webhook", post(webhook)) .route("/healthcheck", get(healthcheck)) .with_state(state); let scheduler = JobScheduler::new().await.unwrap(); // 時間ごとにバッチを追加 // 1日に一回 let pool_clone = pool.clone(); let daily_job = Job::new_async("0 0 0 * * *", move |_, _| { let inner_pool = pool_clone.clone(); Self::send_message_with_deadline(inner_pool, DailyCondition) }).expect("ジョブの作成に失敗しました"); // 1時間に一回 let pool_clone = pool.clone(); let hourly_job = Job::new_async("0 0 * * * *", move |_, _| { let inner_pool = pool_clone.clone(); Self::send_message_with_deadline(inner_pool, HourlyCondition) }).expect("ジョブの作成に失敗しました"); // 30分に一回 let pool_clone = pool.clone(); let every_thirty_minutes_job = Job::new_async("0 */30 * * * *", move |_, _| { let inner_pool = pool_clone.clone(); Self::send_message_with_deadline(inner_pool, EveryThirtyMinutesCondition) }).expect("ジョブの作成に失敗しました"); // 15分に一回 let pool_clone = pool.clone(); let every_fifteen_minutes_job = Job::new_async("0 */15 * * * *", move |_, _| { let inner_pool = pool_clone.clone(); Self::send_message_with_deadline(inner_pool, EveryFifteenMinutesCondition) }).expect("ジョブの作成に失敗しました"); scheduler.add(daily_job).await.expect("スケジューラへジョブを追加するのに失敗しました。"); scheduler.add(hourly_job).await.expect("スケジューラへジョブを追加するのに失敗しました。"); scheduler.add(every_thirty_minutes_job).await.expect("スケジューラへジョブを追加するのに失敗しました。"); scheduler.add(every_fifteen_minutes_job).await.expect("スケジューラへジョブを追加するのに失敗しました。"); Ok(Self { router, scheduler, }) } // バッチ内の処理はほぼ同じことをやっているので共通関数として定義 fn send_message_with_deadline(pool: PgPool, cond: impl JobCondition + Send + 'static) -> Pin<Box<(dyn Future<Output = ()> + Send)>> { let inner_pool = pool.clone(); let http_client = HttpClient::new(); Box::pin(async move { let line_group_repository = LineGroupRepository::new(inner_pool); let all_line_group = line_group_repository.get_all() .await .expect("全てのline_group取得に失敗しました"); for line_group in all_line_group.iter() { if let Some(chousei_id) = line_group.chousei_id.clone() { let group_count = http_client.count_group_members(line_group.id.clone()) .await .expect(format!("line_groupの人数取得に失敗しました。id: {}", line_group.id).as_str()) .try_into() .expect("グループカウントをusizeに変換できませんでした"); let res = http_client.get_chousei_csv(chousei_id) .await .expect("調整さんのCSV取得に失敗しました"); // グループ内の人数より調整さんの回答者数が少ない場合はメッセージをプッシュします if res.member_info_map.len() < group_count { if let Some(deadline_date) = line_group.deadline_date { if cond.check(deadline_date) { // 実行時間ごとに条件が異なるため、JobConditionトレイトを使用して条件を入れ替え可能にしています。 let message = "調整さんに回答してください!".to_string(); http_client.push_message(line_group.id.clone(), vec![message]) .await .expect("lineのメッセージ送信に失敗しました"); } } } } } }) } } 少しだけテクいのは、send_message_with_deadline関数でJobConditionトレイトを使用しているあたりです。 ...

11月 20, 2023 · にあえん

調整さんで絶対に回答させるline botを作ってみた【6】

会社で発表したら緊張しすぎて手汗がすごい、ナナオです。 今回でおそらく完成させたい、LineBotのヤバいさんを早速実装していきます。 line_groupテーブルに調整さんのIDを設定できるようにする まずはマイグレーションを追加します。 sqlx-cliを使用しましょう。 sqlx migrate add add_column_chousei_id 作成されたマイグレーションファイルを実装します。 PostgreSQL: Documentation: 17: ALTER TABLE 調整さんのIDは小文字の英数字32桁からなる文字列なので、そのように定義します。 -- 調整さんのイベントIDを設定できる列を追加 ALTER TABLE "line_group" ADD COLUMN "chousei_id" varchar(32); この定義だと1グループにつき一つのイベントまでしか設定できませんが、そんなに立て続けに同じグループでイベントの出欠を取ることはないと信じています。 一旦この状態でshuttleを起動して、マイグレーションが問題なく適用されるか確認します。 cargo shuttle run DBを確認してみます。 postgres=# select * from line_group; id | deadline_date | chousei_id -----------------------------------+---------------+------------ C480b2f8b56ecaf62c2033867e2ff78b2 | 2023-11-12 | (1 行) 既存データにchousei_idが追加されています! 続けて、LineGroupのモデルにchousei_idプロパティを追加してあげましょう。 #[derive(Debug, FromRow, Serialize, Deserialize)] pub struct LineGroup { pub id: String, pub deadline_date: NaiveDate, pub chousei_id: Option<String>, } 期日設定を行うCommandの実装にLineGroupの初期化処理があるので、ここにchousei_idの初期化も追加しておきます。 impl BotSetDeadlineCommand { async fn new( pool: Arc<PgPool>, group_id: &str, deadline_date: &str, ) -> Self { let repository = LineGroupRepository::new(&pool); let id = group_id.to_string(); let line_group_option = repository.select(id.clone()) .await .expect("期日設定中のline_groupの取得に失敗しました"); let deadline_date = NaiveDate::parse_from_str(deadline_date, "%Y-%m-%d") .expect("日付型への変換に失敗しました"); let line_group = match line_group_option { Some(mut line_group) => { line_group.id = id; line_group.deadline_date = deadline_date; line_group }, _ => LineGroup { id, deadline_date, chousei_id: None, } }; Self { pool, line_group } } } 期日設定によってchousei_idがリセットされないように、line_groupの初期化処理の前に既存のline_groupを取得するようにしました。 ...

11月 17, 2023 · にあえん

調整さんで絶対に回答させるline botを作ってみた【5】

どうも、ナナオです。 今回はCSVをパースして、未回答の人が誰かを取得する処理を実装していきます。 CSVパース処理の仕様 とりあえず、調整さん側の動きについて確認するためにイベントを作ってみます。 調整さん - 簡単スケジュール調整、出欠管理ツール 作ったイベントはこんな感じです。 作成したイベントに関連するURLは以下のようになっています。 イベントページのURL https://chouseisan.com/s?h={イベントID} CSVダウンロードのURL https://chouseisan.com/schedule/List/createCsv?h={イベントID}&charset=utf-8&row=choice イベントを作成してすぐにCSVを取得すると、以下のようなフォーマットになっています。 テストイベント 日程 11/15(水) 19:00〜 11/16(木) 19:00〜 11/17(金) 19:00〜 コメント これに回答すると以下のようになります。 テストイベント 日程,nanao 11/15(水) 19:00〜,◯ 11/16(木) 19:00〜,△ 11/17(金) 19:00〜,× コメント,test 回答者が増えると以下のようになります。 テストイベント 日程,nanao,test_1,test_2 11/15(水) 19:00〜,◯,×,△ 11/16(木) 19:00〜,△,△,△ 11/17(金) 19:00〜,×,◯,△ コメント,test,test_1,test_2 名前からLineのアカウント名を推測するという方法もあるんですが、ここはシンプルに回答者の数とグループ内の人数が合わない場合に通知するという感じにしましょう。 (ここまでやってて思ったんですが、調整さんだとちょっとカスタマイズ性にかけてしまいますね。。今後の改修ポイントかも) パース処理を実装 CSVをパースするためにまずはcsvモジュールを依存関係に追加します。 csv = "1.3.0" bytes = "1.5.0" # reqwestからのレスポンスボディをバイトで取得するため 前回、line_clientモジュールという名前で実装しましたが、調整さんへのリクエストも処理させたいのでhttp_clientという名前に変更して調整さんへのリクエスト処理を実装します。 // src/http_client.rs // ...中略... // 調整さんのCSV取得エンドポイントのレスポンスを表す構造体 pub struct GetChouseiCsv { title: String, csv_text: String, } // LineClient -> HttpClientに変更 pub struct HttpClient { client: Client, } impl HttpClient { // ...中略... // 調整CSVを取得する pub async fn get_chousei_csv(&self, event_id: String) -> Result<GetChouseiCsv, reqwest::Error> { let url = format!("https://chouseisan.com/schedule/List/createCsv?h={}&charset=utf-8&row=choice", event_id); let res_body = self.client.get(url).send().await?.text().await?; let lines: Vec<&str> = res_body.lines().collect(); let title = lines[0]; let csv_text = lines[2..].join("\n"); Ok(GetChouseiCsv{ title: title.to_string(), csv_text }) } } 実装できました。 ...

11月 15, 2023 · にあえん

調整さんで絶対に回答させるline botを作ってみた【4】

もう4回目の記事になるんですね。ナナオです。 今回は設定された期日まで15分おきにメッセージを送れるようにしてみたいと思います。 実装をモジュール化 今までの実装でメインモジュールがだいぶごちゃごちゃしてきたので分離してあげます。 LineGroupの構造体はmodel.rsを作ってそちらに移動します。 use sqlx::FromRow; use chrono::NaiveDate; use serde::{Serialize, Deserialize}; #[derive(FromRow, Serialize, Deserialize)] pub struct LineGroup { pub id: String, pub deadline_date: NaiveDate, } DBのline_groupに対する処理はrepository.rsを作ってそちらに移動します。 use crate::model::LineGroup; use sqlx::PgPool; pub struct LineGroupRepository<'a> { pool: &'a PgPool } impl<'a> LineGroupRepository<'a> { pub fn new(pool: &'a PgPool) -> Self { Self { pool } } pub async fn select(&self, id: String) -> Result<Option<LineGroup>, sqlx::Error> { let line_group = sqlx::query_as(r"SELECT * FROM line_group WHERE id = $1;") .bind(id) .fetch_optional(self.pool) .await?; Ok(line_group) } pub async fn update(&self, data: &LineGroup) -> Result<(), sqlx::Error> { sqlx::query(r"UPDATE line_group SET deadline_date = $2 WHERE id = $1;") .bind(&data.id) .bind(&data.deadline_date) .execute(self.pool) .await?; Ok(()) } pub async fn insert(&self, data: &LineGroup) -> Result<(), sqlx::Error> { sqlx::query(r"INSERT INTO line_group (id, deadline_date) VALUES ($1, $2);") .bind(&data.id) .bind(&data.deadline_date) .execute(self.pool) .await?; Ok(()) } } 署名認証処理はutils.rsを作ってそちらに移動します。 ...

11月 11, 2023 · にあえん

調整さんで絶対に回答させるline botを作ってみた【3】

意外とRustってとっつきやすいなと考えを改めました。ナナオです。 今回はメッセージから期日設定などを行えるように実装を修正していきます。 ヤバいさんのコマンドを呼び出す では具体的にどうやって期日設定を行うかといえば、「グループチャット内」で「矢倍くん宛」にメッセージを送ったときに行うようにしましょう。 また、期日設定以外にも調整さんのURLを設定できる必要があります。あとヘルプコマンドもあると便利ですね。 ということで、コマンドを3つ用意します。 調整さんのURL設定 「ヤバいさん https://chouseisan.com/s?h=[調整さんのイベントID]」と送られた場合に実行される 候補日に対してどのくらい回答しているか確認するため 期日設定 「ヤバいさん 2023-01-01」と送られた場合に実行される ヘルプ 「ヤバいさん」とだけ送られる、もしくは対応していないコマンドを送られた場合に実行される 使い方、コマンドの一覧を教えてくれる コマンドといえばコマンドパターンですね。 こんな感じのtraitを実装します。 trait BotCommand { fn execute(&self, value: Value); } これを使って、3つのコマンドを実装します。 struct BotSetDeadlineCommand { pool: PgPool, line_group: LineGroup, }; impl BotSetDeadlineCommand { /// コマンドのコンストラクタ /// DBの更新に必要な値を渡します fn new( pool: PgPool, group_id: &str, deadline_date: &str, ) -> Self { let line_group = LineGroup { id: group_id.to_string(), deadline_date: NaiveDate::parse_from_str(deadline_date, "%Y-%m-%d") .expect("日付型への変換に失敗しました") }; Self { pool, line_group } } } impl BotCommand for BotSetDeadlineCommand { fn execute(&self, value: Value) {} } // それ以外のコマンドも一応用意しておく struct BotSetUrlCommand; impl BotCommand for BotSetUrlCommand { fn execute(&self, value: Value) {} } struct BotHelpCommand; impl BotCommand for BotHelpCommand { fn execute(&self, value: Value) {} } まずは期日設定を行えるようにしましょう。 ...

11月 9, 2023 · にあえん