Slack botで通知したい投稿日のものを通知する with Qiita API

（この記事は、[「fukuoka.ex(その2) Elixir Advent Calendar 2017」](https://adventar.org/calendars/2871)の20日目、[Slack Advent Calendar 2017](https://qiita.com/advent-calendar/2017/slack)の22日目です）

昨日は@koga1020さんの[「PhoenixでMicrosoft Translator テキスト APIを利用してみる」](https://qiita.com/koga1020/items/738f8332af8e3348a1bd)でした！

# はじめに

|> [ElixirでSlack Botを作った with Qiita API](https://qiita.com/kobatako/items/586f82b2845f7d1583ce)
|> [定期的にSlack Botで記事を通知する with Qiita API](https://qiita.com/kobatako/items/d7b404ba53f9a25ac5aa)
前回に引き続きSlack Botの作成を行います。前回ではSlackへの定期通知を実装を進めていきましたが、今回は取得したい日にちを指定して通知するようにします。

# どうやるの？

Qiita APIには日にちを指定しての投稿の取得はありません、、ですので今考えつく方法としては取得したい日にちを指定し、Qiita APIをコール。指定した日にちが来るまでpageをスライドさせていく方法です。（なにか他にいい方法があれば教えて欲しいです）

# 概要

時間の操作ですとTimexが便利ですので早速使います。


# 実装
## 1. 日にちを指定して取得範囲をきめる

```Elixir
def daily_tag_items() do
  now = Timex.now("Asia/Tokyo")
  Storage.first(:tags)
  [token: token] = Application.get_all_env(:qiita)
  headers = [{"Authorization", "Bearer #{token}"}]
  channels = GenServer.call(ExAviso.Slack, {:channels})
  message = fetch_daily_tag_items(Storage.all(:tags), Timex.shift(now, days: -1), headers, channels)
end
```

`Timex.now`でタイムゾーンを指定し、現在の日にちを取得できます。
`Timex.shift(now, days: -1)` で1日前の日にちをとります。`Time.shift`関数はアトムで指定した項目に対して、足したり引いたりをすることができます。さらに日にちのみではなく、年、月や時間も対象とできるので便利です。

## 2. 一番最初を取得
次にpageを一つずつスライドさせていき、指定した日にちのものが来るまでスライドをしていきます。

```Elixir
defp fetch_start_daily_tag_items({id, tag, _, per_page} = head, day, headers, page) do
  url = "https://qiita.com/api/v2/tags/#{tag}/items?page=#{page}&per_page=#{per_page}"
  body =
    case HTTPoison.get!(url, headers) do
      %{status_code: 200, body: body} ->
        Poison.Parser.parse!(body, keys: :atoms)

      %{error: "account_inactive"} = error ->
        IO.inspect(error)
    end
  days = Enum.filter(body, fn %{created_at: create_at} -> get_create_between_day(create_at, day, 1) end)
  if Enum.count(days) == 0 do
    fetch_start_daily_tag_items(head, day, headers, page + 1)
  else
    {page, days}    
  end
end
```

毎度のことながら`HTTPoison.get!`でリクエストを投げます。
返ってきた値で`created_at`が設定した範囲内かどうかを`Enum.filter`でチェックをしていきます。
ここで返り値に値が入っている（リストの要素数が０以外）場合は指定した日にちがあると判断できるので次の処理を行います。ちなみに`Enum.filter`で実行される関数をしたに記述します。

```Elixir
defp get_create_between_day(create_at, day, range) when is_integer(range) do
  after_day = Timex.shift(day, days: range - 1)
  Timex.between?(Timex.parse!(create_at, "{ISO:Extended}"), Timex.beginning_of_day(day), Timex.end_of_day(after_day))
end
```

`Timex.between?`関数で第一引数のものが第二引数、第三引数の間の日付なのかをboolで返します。
`Timex.parse!`では第一引数のものを第二引数でパースします。第二引数に`{ISO:Extended}`を指定していますが、これはISOのフォーマットとしてパースを行います。
`Timex.beginning_of_day`はその日の最初の時間を（2018/6/8 22:15:00 -> 2018/6/8 00:00:00 ）
`Timex.end_of_day`はその日の最後の時間を（2018/6/8 22:15:00 -> 2018/6/8 23:59:59）
取得します。
この関数を`Enum.filter`で使い、指定した日付のものだけを抽出できるようにします。

## 3. 終了の日にち

次に指定した日付の最初のアイテムが取れたので最後のアイテムが取れるまでpageをスライドさせていきます。

```Elixir
defp fetch_end_daily_tag_items({id, tag, _, per_page} = head, day, headers, page, items) do
  url = "https://qiita.com/api/v2/tags/#{tag}/items?page=#{page}&per_page=#{per_page}"

  body =
    case HTTPoison.get!(url, headers) do
      %{status_code: 200, body: body} ->
        Poison.Parser.parse!(body, keys: :atoms)

      %{error: "account_inactive"} = error ->
        IO.inspect(error)
    end

  days = Enum.filter(body, fn %{created_at: create_at} -> get_create_between_day(create_at, day, 1) end)
  if Enum.count(days) == 0 do
    items
  else
    fetch_end_daily_tag_items(head, day, headers, page + 1, items ++ days)
  end
end
```

ここでさっきの`fetch_start_daily_tag_items`と違うのは`Enum.filter`でフィルタリングした値が0件の場合は指定した日付のものを取得し終えたと判定し、最後に連結したものを返します。
それまではレスポンスの値を次のデータに引き渡していきます。

## 結果

<img width="628" alt="スクリーンショット 2018-06-09 1.02.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/215989/a5fe6b2e-defe-1a04-b85f-5b9015be77c2.png">

お、無事に取れましたー

## まだやってないこと

* 今回は特定の日にちのみだったんですが、今度は週と月での範囲指定での取得を行いたいです。
* あとはAPIコールをなんどもしてしまうので、、、ある程度制限する、もしくは別の方法で算出できるロジックを組みたい
* その指定した日にちがない場合のことが考慮されてないので実装する

あしたは、@takasehidekiさんの「[Erlang/OTPのソースビルドでHiPEが入らないときの対処法](https://qiita.com/takasehideki/items/4a619d869643f8d0e272)」
です
# 最後に

* Timexだと時間の整形や計算が楽だったのである程度使えるようになりたい。
* Qiita APIの仕様とリクエスト数を考慮しながら作るのが難しい（今も悩み中）


:stars::stars::stars::stars::stars:　満員御礼！Elixir MeetUpを6月末に開催します　:stars::stars::stars::stars::stars:
<font color="red" bold>**※応募多数により、増枠しました!!**</font>

[「fukuoka.ex#11：DB／データサイエンスにコネクトするElixir」](https://fukuokaex.connpass.com/event/87241/)を6/22（金）19時に開催します。
自分もLTで発表させていただきますので、ぜひ興味ある方はご参加ください！

![fukuokaex.png](https://qiita-image-store.s3.amazonaws.com/0/215989/16ba72c0-05ac-ac73-79bd-efc39216f436.png)