defmodule LSG.IRC.CoronavirusPlugin do require Logger @moduledoc """ # Corona Virus Données de [Johns Hopkins University](https://github.com/CSSEGISandData/COVID-19) et mises à jour a peu près tous les jours. * `!coronavirus [France | Country]`: :-) * `!coronavirus`: top 10 confirmés et non guéris * `!coronavirus confirmés`: top 10 confirmés * `!coronavirus morts`: top 10 morts * `!coronavirus soignés`: top 10 soignés """ def irc_doc, do: @moduledoc def start_link(), do: GenServer.start_link(__MODULE__, []) def init(_) do {:ok, _} = Registry.register(IRC.PubSub, "trigger:coronavirus", []) {data, next} = fetch_data(%{}) :timer.send_after(next, :update) {:ok, %{data: data}} end def handle_info(:update, state) do {data, next} = fetch_data(state.data) :timer.send_after(next, :update) {:noreply, %{data: data}} end def handle_info({:irc, :trigger, "coronavirus", m = %IRC.Message{trigger: %{type: :bang, args: args}}}, state) when args in [[], ["morts"], ["confirmés"], ["soignés"], ["malades"]] do {field, name} = case args do ["confirmés"] -> {:confirmed, "confirmés"} ["morts"] -> {:deaths, "morts"} ["soignés"] -> {:recovered, "soignés"} _ -> {:current, "malades"} end sorted = state.data |> Enum.filter(fn({_, %{region: region}}) -> region == true end) |> Enum.map(fn({location, data}) -> {location, Map.get(data, field, 0)} end) |> Enum.sort_by(fn({_,count}) -> count end, &>=/2) |> Enum.take(10) |> Enum.with_index() |> Enum.map(fn({{location, count}, index}) -> "##{index+1}: #{location} #{count}" end) |> Enum.intersperse(" - ") |> Enum.join() m.replyfun.("Corona virus top 10 #{name}: " <> sorted) {:noreply, state} end def handle_info({:irc, :trigger, "coronavirus", m = %IRC.Message{trigger: %{type: :bang, args: location}}}, state) do location = Enum.join(location, " ") |> String.downcase() if data = Map.get(state.data, location) do m.replyfun.("Corona virus: #{location}: #{data.current} malades, #{data.confirmed} confirmés, #{data.deaths} morts, #{data.recovered} soignés") end {:noreply, state} end def handle_info({:irc, :trigger, "coronavirus", m = %IRC.Message{trigger: %{type: :query, args: location}}}, state) do m.replyfun.("https://github.com/CSSEGISandData/COVID-19") {:noreply, state} end # 1. Try to fetch data for today # 2. Fetch yesterday if no results defp fetch_data(current_data, date \\ nil) do now = Date.utc_today() url = fn(date) -> "https://github.com/CSSEGISandData/COVID-19/raw/master/csse_covid_19_data/csse_covid_19_daily_reports/#{date}.csv" end request_date = date || now Logger.debug("Coronavirus check date: #{inspect request_date}") {:ok, date_s} = Timex.format({request_date.year, request_date.month, request_date.day}, "%m-%d-%Y", :strftime) cur_url = url.(date_s) Logger.debug "Fetching URL #{cur_url}" case HTTPoison.get(cur_url, [], follow_redirect: true) do {:ok, %HTTPoison.Response{status_code: 200, body: csv}} -> # Parse CSV update data data = csv |> String.strip() |> String.split("\n") |> Enum.drop(1) |> Enum.reduce(%{}, fn(line, acc) -> [state, region, update, confirmed, deaths, recovered,_lat, _lng] = line |> String.strip() |> String.split(",") state = String.downcase(state) region = String.downcase(region) confirmed = String.to_integer(confirmed) deaths = String.to_integer(deaths) recovered = String.to_integer(recovered) current = (confirmed - recovered) - deaths entry = %{update: update, confirmed: confirmed, deaths: deaths, recovered: recovered, current: current, region: region} acc = if state && state != "" do Map.put(acc, state, entry) else acc end region_entry = Map.get(acc, region, %{update: nil, confirmed: 0, deaths: 0, recovered: 0, current: 0}) region_entry = %{ update: region_entry.update || update, confirmed: region_entry.confirmed + confirmed, deaths: region_entry.deaths + deaths, current: region_entry.current + current, recovered: region_entry.recovered + recovered, region: true } Map.put(acc, region, region_entry) end) Logger.info "Updated coronavirus database" {data, :timer.minutes(60)} {:ok, %HTTPoison.Response{status_code: 404}} -> Logger.debug "Corona 404 #{cur_url}" date = Date.add(date || now, -1) fetch_data(current_data, date) other -> Logger.error "Coronavirus: Update failed #{inspect other}" {current_data, :timer.minutes(5)} end end end