(この記事は、「fukuoka.ex(その2) Elixir Advent Calendar 2017」の20日目、Slack Advent Calendar 2017の22日目です)
昨日は@koga1020さんの「PhoenixでMicrosoft Translator テキスト APIを利用してみる」でした!
はじめに
|> ElixirでSlack Botを作った with Qiita API
|> 定期的にSlack Botで記事を通知する with Qiita API
前回に引き続きSlack Botの作成を行います。前回ではSlackへの定期通知を実装を進めていきましたが、今回は取得したい日にちを指定して通知するようにします。
どうやるの?
Qiita APIには日にちを指定しての投稿の取得はありません、、ですので今考えつく方法としては取得したい日にちを指定し、Qiita APIをコール。指定した日にちが来るまでpageをスライドさせていく方法です。(なにか他にいい方法があれば教えて欲しいです)
概要
時間の操作ですとTimexが便利ですので早速使います。
実装
1. 日にちを指定して取得範囲をきめる
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を一つずつスライドさせていき、指定した日にちのものが来るまでスライドをしていきます。
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
でチェックをしていきます。
ここで返り値に値が入っている(リストの要素数が0以外)場合は指定した日にちがあると判断できるので次の処理を行います。ちなみにEnum.filter
で実行される関数をしたに記述します。
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をスライドさせていきます。
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件の場合は指定した日付のものを取得し終えたと判定し、最後に連結したものを返します。
それまではレスポンスの値を次のデータに引き渡していきます。
結果
お、無事に取れましたー
まだやってないこと
- 今回は特定の日にちのみだったんですが、今度は週と月での範囲指定での取得を行いたいです。
- あとはAPIコールをなんどもしてしまうので、、、ある程度制限する、もしくは別の方法で算出できるロジックを組みたい
- その指定した日にちがない場合のことが考慮されてないので実装する
あしたは、@takasehidekiさんの「Erlang/OTPのソースビルドでHiPEが入らないときの対処法」
です
最後に
- Timexだと時間の整形や計算が楽だったのである程度使えるようになりたい。
- Qiita APIの仕様とリクエスト数を考慮しながら作るのが難しい(今も悩み中)
満員御礼!Elixir MeetUpを6月末に開催します
※応募多数により、増枠しました!!
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します。
自分もLTで発表させていただきますので、ぜひ興味ある方はご参加ください!