自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた

消防士時代からQiita投稿を続け、累計投稿数は140記事を超えました。
投稿記事が増えると「あれ？これ前Qiitaに書いた気がするけどなんだっけな..？」となっても、自分の過去投稿を探すのが一苦労です。
そんな手間を解消するため、Qiita APIとAlfredを使って自分のQiita記事をインクリメント検索するAflred Workflowを作ってみました。

結構便利なので、Qiitaヘビーユーザーの方々に使ってもらえると嬉しいです。

# alfred-my-qiita

こちらのGIF画像のように、自分の投稿記事をインクリメンタル検索できます。
初回はAPIリクエストが走るので少し時間がかかりますが、その後はキャッシュが効くのである程度高速に動作します。

https://github.com/kawamataryo/alfred-my-qiita-post

![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/ebc8df07-219f-cddc-bbb1-87bcc9de29ae.gif)


# インストール・環境変数の設定

以下コマンドでnpmからインストールできます（後述するAlfyで実現しています）。
npmインストールするだけでAlfredがWorkflowを認識し、すぐに使えるようになります。


```bash
$ npm i -g alfred-my-qiita
```

また、[GitHubのリリース](https://github.com/kawamataryo/alfred-my-qiita-post/releases) から直接`alfred-my-qiita.workflow`ファイルをダウンロードし、Alfredに取り込むことも可能です。

![スクリーンショット 2020-09-12 13.38.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/56ef8745-83ac-e440-564a-a8914163de47.png)

インストールが完了したら、AlfredのWorkflow設定画面から `alfred-my-qiita` を選択して、環境変数を設定してください。

![スクリーンショット 2020-09-12 13.54.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/e1fc02c2-2f24-b323-e3a8-c4ebfa2da292.png)

設定する環境変数は以下2つです。

|名前|値|
|---|---|
|API_TOKEN | Qiita APIのAPIトークン。[こちら](https://qiita.com/api/v2/docs#%E8%AA%8D%E8%A8%BC%E8%AA%8D%E5%8F%AF)の案内にしたがって取得・設定してください。|
|USER_NAME | Qiitaのユーザー名（@を除く）を設定してください。|

![スクリーンショット 2020-09-12 11.06.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/4ee1bd3c-20cb-304c-f362-a104c6d2e304.png)

これで準備は完了です。
Alfredを開き`.qiita`と打ち、あとは半角スペースを空けて検索したい文字列を入力すればOKです!!

![ezgif.com-gif-maker (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/ebc8df07-219f-cddc-bbb1-87bcc9de29ae.gif)

# 実装

今回も以前[こちらの記事](https://qiita.com/ryo2132/items/358ce9d4baa8a8a27092)で紹介した[Alfy](https://github.com/sindresorhus/alfy) というNode.jsベースでAlfred Workflowを作れるフレームワークを使っています。

Alfyを使う利点は以下の通りです。

- Node.jsでWorkflowを作れる
- npmモジュールが使える
- npmに公開し、手軽にインストールしてもらえるようになる

何より、Bashスクリプト書かなくてよいというのが最高便利です。

https://github.com/sindresorhus/alfy

今回のworkflowは以下2つのファイルで構成されています。

1つはQiita APIから投稿記事を取得する処理です。
最初に自分の投稿総数を取得して、その後Promise.allとmapを使い全投稿データを取得しています。

```lib/fetchAllPosts.js
import alfy from "alfy"

export const fetchAllPosts = async (username, token) => {
  const options = {
    headers: {
      Authorization: `Bearer ${token}`,
    },
    json: true,
    maxAge: 60000
  };

  const user = await alfy.fetch(
    `https://qiita.com/api/v2/users/${username}`,
    options
  );

  const maxPage = Math.ceil(user.items_count / 100);

  let posts = [];
  await Promise.all(
    [...Array(maxPage)].map(async (_, i) => {
      const res = await alfy.fetch(
        `https://qiita.com/api/v2/authenticated_user/items?page=${
          i + 1
        }&per_page=100`,
        options
      );
      posts = [...posts, ...res];
    })
  );

  return posts
};
```

そして、index.jsがメインの処理です。AlfyのinputMatches APIを使い投稿記事をフィルタリングして出力しています。

```index.js
import alfy from "alfy";
import { fetchAllPosts } from "./lib/fetchAllPosts";

const token = process.env.apiToken;
const username = process.env.username;

const createResponse = (title, subtitle, url) => {
  return {
    title,
    subtitle,
    arg: url,
    icon: {
      type: "png",
      path: "icon.png",
    }
  };
}

(async () => {
  const posts = await fetchAllPosts(username, token);

  if(alfy.input.length > 1) {
    const items = alfy.inputMatches(posts, "title").map(
        (p) => createResponse(p.title, p.body, p.url));

    if (!items.length) {
      alfy.output(
          [createResponse("The requested post was not found. ⚠️", "", "")]);
      return;
    }
    alfy.output(items);
  } else {
    alfy.output([createResponse("Loading...", "", "")])
  }
})()
```

# 終わりに

以上「自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた」でした。
完全自分用のツールとして作ってみたものなのですが、Qiita投稿数が多い方には何かと便利だと思います。使ってもらえると嬉しいです。
（もし動かない場合は、[Twitter](https://twitter.com/KawamataRyo)か[GitHub Issue](https://github.com/kawamataryo/alfred-my-qiita-post/issues), この記事のコメントで教えてください :pray: ）