Alfred Workflows + Rust で Qiita の記事を検索する

Mac の人気なランチャーアプリ Alfred のワークフローを Rust を使って作成する方法についてご紹介します。

今回は Qiita の記事を検索するワークフローを作成します。以下の記事を参考にさせていただきました。

- [RustでAlfredのWorkflowを作って業務効率化](https://qiita.com/ryosukeeeee/items/18f0cf99b1f4b0090963)
- [Node.js で Alfred Workflows を作る](https://blog.chick-p.work/nodejs-aflred-workflow/)

![alfred-qiita-search-4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/205daf98-51c2-911f-60cf-6ee4ca6ddb6b.gif)

## プログラムを作成する

[alfred](https://docs.rs/alfred/4.0.2/alfred/index.html) というクレートを使用します。これは Alfred が読み込める形の JSON を簡単に作成できるクレートです。キーワードをコマンドライン引数で受け取って、Alfred が読み込める形の JSON を標準出力に出力するコードを作成します。

```rust
use chrono::{DateTime, FixedOffset};
use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct QiitaItem {
    title: String,
    user: QiitaUser,
    url: String,
    created_at: String,
}

#[derive(Deserialize, Debug)]
struct QiitaUser {
    id: String,
}

// Qiita から返ってきた日時（文字列）を日本時間（文字列）にフォーマット
fn format_date(date_string: &str) -> String {
    DateTime::parse_from_rfc3339(date_string)
        .ok()
        .map(|date| {
            date.with_timezone(&FixedOffset::east(9 * 3600)) // +9時間
                .format("%Y/%m/%d %H:%M")
                .to_string()
        })
        .unwrap_or("".to_owned())
}

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();
    if args.len() == 0 {
        return;
    }

    let search_word = args.join(" ");
    let access_token = match std::env::var("QIITA_ACCESS_TOKEN") {
        Ok(x) => x,
        _ => return,
    };

    // Qiita の検索 API を実行
    let qiita_items: Vec<QiitaItem> = ureq::get(&format!(
        "https://qiita.com/api/v2/items?query=body:{}",
        search_word
    ))
    .set("Authorization", &format!("Bearer {}", access_token))
    .call()
    .ok()
    .and_then(|res| res.into_json().ok())
    .unwrap_or(vec![]);

    // 取得したデータを Alfred が読み込めるように変換
    let alfred_items: Vec<_> = qiita_items
        .into_iter()
        .map(|qiita_item| {
            alfred::ItemBuilder::new(qiita_item.title)
                .subtitle(format!(
                    "@{} {}",
                    qiita_item.user.id,
                    format_date(&qiita_item.created_at)
                ))
                .arg(qiita_item.url)
                .icon_path("./icon.png") // ワークフローに設定されているアイコンを使用する
                .into_item()
        })
        .collect();

    // JSON データを標準出力へ出力
    alfred::json::write_items(std::io::stdout(), &alfred_items).ok();
}
```

- 軽量な HTTP クライアント [ureq](https://docs.rs/ureq/2.1.1/ureq/index.html) を使用しています。
- ワークフローには環境変数を設定することができます。アクセストークンは環境変数から受け取るようにしています。

```toml
[package]
name = "alfred-qiita-search"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
alfred = "4.0.2"
ureq = { version = "2.1.1", features = ["json"] }
serde = { version = "1.0.126", features = ["derive"] }
chrono = "0.4"
```

## Alfred でワークフローを作成する

空ワークフローを新規作成します。

![スクリーンショット 2021-08-27 18.09.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/0f75f5c0-ba27-6826-457b-13ab783c0b26.png)

ワークフローの情報を記述します（ここでアイコンも設定しておきます）。

![スクリーンショット 2021-08-27 18.10.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/3bcdaf34-a435-9240-735d-8dc1d55a8ba5.png)

ワークフローに `Inputs > Script Filter` を追加します。

![スクリーンショット 2021-08-27 18.10.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/c0b6f51c-3c1f-f5f3-9cfb-5cd7ca631068.png)

キーワードなどと一緒に以下のスクリプトを入力します。

![スクリーンショット 2021-08-27 18.11.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/18a7ab71-26c6-551f-ca4f-b2feb9c2c18b.png)

```sh
query=$1

./alfred-qiita-search $query
```

ワークフローに `Actions > Open URL` を追加します（何も設定せずそのまま保存します）。

![スクリーンショット 2021-08-27 18.11.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/7218f5b8-c1e6-783f-64dd-a95a333e8573.png)

`Script Filter` と `Open URL` を接続します。

![スクリーンショット 2021-08-27 18.12.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/02adb471-693e-93ca-8ea4-73d8f886bb5b.png)

環境変数を設定します（右上の `[Χ]` のようなアイコンから設定できます）。

![スクリーンショット 2021-08-27 18.14.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/fdf1534b-a716-ad5e-cbe4-788fbed2e81f.png)

## バイナリを配置する

リリースビルドしてバイナリをワークフローのディレクトリ下に配置します。

```sh
$ cargo build --release
$ cp ./target/release/alfred-qiita-search ~/Library/Application\ Support/Alfred/Alfred.alfredpreferences/workflows/user.workflow.{上で作成したワークフローのUUID}/
```

※ワークフローのディレクトリは以下からターミナルで開くことができます。
![スクリーンショット 2021-08-27 18.15.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/d9fba504-75c2-f2aa-2e26-fb048a5f804c.png)

以上でワークフローの作成は完了です。`qiita` のプレフィックスの後にキーワードを入力するとその内容で検索をしてくれます。

![alfred-qiita-search-4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/676183/205daf98-51c2-911f-60cf-6ee4ca6ddb6b.gif)