defmodule OsuAPI.HTTP do @moduledoc "The underlying osu! API HTTP wrapper." use HTTPoison.Base @base_url "https://osu.ppy.sh/api" def process_url(url), do: "#{@base_url}/get_#{url}" def process_request_options(options) do case Keyword.fetch(options, :params) do {:ok, params} -> # Add the API key. api_key = Application.get_env(:osu_api, :api_key, System.get_env("OSU_API_KEY")) params = Map.put_new(params, :k, api_key) # If the request has a user parameter, infer the type. params = case Map.fetch(params, :u) do {:ok, u} -> Map.put_new(params, :type, if(is_integer(u), do: "id", else: "string")) :error -> params end Keyword.put(options, :params, params) :error -> options end end def process_response_body(body) do case Jason.decode(body) do {:ok, data} -> process(data) {:error, _} -> body end end # Response body JSON parsing. # String lists. defp process({"tags", v}), do: {:tags, String.split(v)} # Booleans. defp process({"perfect", v}), do: {:perfect, v == "1"} defp process({"replay_available", v}), do: {:replay_available, v == "1"} # These fields should always be strings but can appear as numbers. defp process({"artist", v}), do: {:artist, v} defp process({"creator", v}), do: {:creator, v} defp process({"file_md5", v}), do: {:file_md5, v} defp process({"title", v}), do: {:title, v} defp process({"username", v}), do: {:username, v} defp process({"version", v}), do: {:version, v} # Special cased types. defp process({k, nil}), do: {String.to_atom(k), nil} defp process({k, v}) when is_integer(v), do: {String.to_atom(k), v} # Enums. defp process({"approved", v}) do {:approved, case v do "-2" -> :graveyard "-1" -> :wip "0" -> :pending "1" -> :ranked "2" -> :approved "3" -> :qualified "4" -> :loved _ -> process(v) end} end defp process({"genre_id", v}) do # Note that we rename the key to just :genre here. {:genre, case v do "0" -> :any "1" -> :unspecified "2" -> :video_game "3" -> :anime "4" -> :rock "5" -> :pop "6" -> :other "7" -> :novelty "9" -> :hip_hop "10" -> :electronic _ -> v end} end defp process({"language_id", v}) do # Note that we rename the key to just :language here. {:language, case v do "0" -> :any "1" -> :other "2" -> :english "3" -> :japanese "4" -> :chinese "5" -> :instrumental "6" -> :korean "7" -> :french "8" -> :german "9" -> :swedish "10" -> :spanish "11" -> :italian _ -> process(v) end} end defp process({"mode", v}) do {:mode, case v do "0" -> :standard "1" -> :taiko "2" -> :catch "3" -> :mania _ -> process(v) end} end defp process({"play_mode", v}) do {:play_mode, case v do "0" -> :standard "1" -> :taiko "2" -> :catch "3" -> :mania _ -> process(v) end} end defp process({"scoring_type", v}) do {:scoring_type, case v do "0" -> :score "1" -> :accuracy "2" -> :combo "3" -> :score_v2 _ -> process(v) end} end defp process({"team_type", v}) do {:team_type, case v do "0" -> :head_to_head "1" -> :tag_coop "2" -> :team_vs "3" -> :tag_team_vs _ -> process(v) end} end defp process({"team", v}) do {:team, case v do "1" -> :blue "2" -> :red _ -> process(v) end} end # Process collections recursively. defp process(list) when is_list(list), do: Enum.map(list, &process/1) defp process(map) when is_map(map), do: map |> Enum.map(&process/1) |> Map.new() # General cases. defp process({k, v}), do: {String.to_atom(k), process(v)} defp process(scalar) do case Integer.parse(scalar) do {i, ""} -> i _ -> case Float.parse(scalar) do {f, ""} -> f _ -> case DateTime.from_iso8601(scalar <> "Z") do {:ok, d, _} -> d _ -> scalar end end end end end