defmodule OsuEx.API.Error do defexception reason: nil, value: nil @type t :: {:status_code | integer} | {:httpoison | HTTPoison.Error.t()} def message(e), do: inspect(e) end defmodule OsuEx.API do @moduledoc """ A wrapper around the osu! API. ## Usage iex> OsuEx.API.get_user("cookiezi", event_days: 5) {:ok, %{user_id: 124493, username: "Cookiezi"}} The `get_*` function names mirror the API itself as do the parameter names, which can be passed as a trailing keyword list. The returned data is mostly identical to the osu! API documentation, except for the following: * The return value of functions which return at most one result (`get_user/2` for example) is a map instead of a list containing one map. If no result is found, then the value is `nil`, instead of an empty list. * Numbers, booleans, dates, and lists are parsed to their native types. To parse enum values like `approved: 3` into more human-readable atoms, or encode/decode mods, see the `OsuEx.API.Utils` module. ## Configuration To access the osu! API, you need to provide an API key. You can pass the `k` parameter around if you want, but otherwise you can configure its value in `config.exs`: config :osu_api, api_key: "" You can also set the `OSU_API_KEY` environment variable. """ alias OsuEx.API.Error alias OsuEx.API.HTTP @type user :: binary | integer @spec get(binary, keyword) :: {:ok, [map]} | {:error, Error.t()} defp get(endpoint, params) when is_binary(endpoint) and is_list(params) do case HTTP.get(endpoint, [], params: Map.new(params)) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> {:ok, body} {:ok, %HTTPoison.Response{status_code: code}} -> {:error, %Error{reason: :status_code, value: code}} {:error, err} -> {:error, %Error{reason: :httpoison, value: err}} end end @spec get!(binary, keyword) :: [map] defp get!(endpoint, params) when is_binary(endpoint) and is_list(params) do case get(endpoint, params) do {:ok, body} -> body {:error, err} -> raise err end end @spec get_first(binary, keyword) :: {:ok, map | nil} | {:error, Error.t()} defp get_first(endpoint, params) when is_binary(endpoint) and is_list(params) do case get(endpoint, params) do {:ok, [h | _t]} -> {:ok, h} {:ok, []} -> {:ok, nil} {:error, err} -> {:error, err} end end @spec get_first!(binary, keyword) :: map | nil defp get_first!(endpoint, params) when is_binary(endpoint) and is_list(params) do case get_first(endpoint, params) do {:ok, body} -> body {:error, err} -> raise err end end @doc "Gets beatmaps." @spec get_beatmaps(keyword) :: {:ok, [map]} | {:error, Error.t()} def get_beatmaps(opts \\ []) when is_list(opts) do get("beatmaps", opts) end @doc "Same as `get_beatmaps/2` but throws exceptions." @spec get_beatmaps!(keyword) :: [map] def get_beatmaps!(opts \\ []) when is_list(opts) do get!("beatmaps", opts) end @doc "Gets a beatmap by ID (beatmap ID, not beatmapset ID)." @spec get_beatmap(integer, keyword) :: {:ok, map | nil} | {:error, Error.t()} def get_beatmap(id, opts \\ []) when is_integer(id) and is_list(opts) do get_first("beatmaps", Keyword.put(opts, :b, id)) end @doc "Same as `get_beatmap/2` but throws exceptions." @spec get_beatmap!(integer, keyword) :: map | nil def get_beatmap!(id, opts \\ []) when is_integer(id) and is_list(opts) do get_first!("beatmaps", Keyword.put(opts, :b, id)) end @doc "Gets a beatmapset by ID (beatmapset ID, not beatmap ID)." @spec get_beatmapset(integer, keyword) :: {:ok, [map]} | {:error, Error.t()} def get_beatmapset(id, opts \\ []) when is_integer(id) and is_list(opts) do get("beatmaps", Keyword.put(opts, :s, id)) end @doc "Same as `get_beatmapset/2` but throws exceptions." @spec get_beatmapset!(integer, keyword) :: [map] def get_beatmapset!(id, opts \\ []) when is_integer(id) and is_list(opts) do get!("beatmaps", Keyword.put(opts, :s, id)) end @doc "Gets a user by username or user ID." @spec get_user(user, keyword) :: {:ok, map | nil} | {:error, Error.t()} def get_user(user, opts \\ []) when (is_binary(user) or is_integer(user)) and is_list(opts) do get_first("user", Keyword.put(opts, :u, user)) end @doc "Same as `get_user/2` but throws exceptions." @spec get_user!(user, keyword) :: map | nil def get_user!(user, opts \\ []) when (is_binary(user) or is_integer(user)) and is_list(opts) do get_first!("user", Keyword.put(opts, :u, user)) end @doc "Gets a beatmap's top scores." @spec get_scores(integer, keyword) :: {:ok, [map]} | {:error, Error.t()} def get_scores(map_id, opts \\ []) when is_integer(map_id) and is_list(opts) do get("scores", Keyword.put(opts, :b, map_id)) end @doc "Same as `get_scores/2` but throws exceptions." @spec get_scores!(integer, keyword) :: [map] def get_scores!(map_id, opts \\ []) when is_integer(map_id) and is_list(opts) do get!("scores", Keyword.put(opts, :b, map_id)) end @doc "Gets a user's top scores." @spec get_user_best(user, keyword) :: {:ok, [map]} | {:error, Error.t()} def get_user_best(user, opts \\ []) when (is_binary(user) or is_integer(user)) and is_list(opts) do get("user_best", Keyword.put(opts, :u, user)) end @doc "Same as `get_user_best/2` but throws exceptions." @spec get_user_best!(user, keyword) :: [map] def get_user_best!(user, opts \\ []) when (is_binary(user) or is_integer(user)) and is_list(opts) do get!("user_best", Keyword.put(opts, :u, user)) end @doc "Gets a user's recent scores." @spec get_user_recent(user, keyword) :: {:ok, [map]} | {:error, Error.t()} def get_user_recent(user, opts \\ []) when (is_binary(user) or is_integer(user)) and is_list(opts) do get("user_recent", Keyword.put(opts, :u, user)) end @doc "Same as `get_user_recent/2` but throws exceptions." @spec get_user_recent!(user, keyword) :: [map] def get_user_recent!(user, opts \\ []) when (is_binary(user) or is_integer(user)) and is_list(opts) do get!("user_recent", Keyword.put(opts, :u, user)) end @doc "Gets a multiplayer match by ID." @spec get_match(integer, keyword) :: {:ok, map | nil} | {:error, Error.t()} def get_match(id, opts \\ []) when is_integer(id) and is_list(opts) do get_first("match", Keyword.put(opts, :mp, id)) end @doc "Same as `get_match/2` but throws exceptions." @spec get_match!(integer, keyword) :: map | nil def get_match!(id, opts \\ []) when is_integer(id) and is_list(opts) do get_first!("match", Keyword.put(opts, :mp, id)) end @doc "Gets replay data for a score." @spec get_replay(integer, user, integer, keyword) :: {:ok, map | nil} | {:error, Error.t()} def get_replay(map_id, user, mode, opts \\ []) when is_integer(map_id) and (is_binary(user) or is_integer(user)) and is_integer(mode) and is_list(opts) do get("replay", Keyword.merge(opts, b: map_id, m: mode, u: user)) end @doc "Same as `get_replay/4` but throws exceptions." @spec get_replay!(integer, user, integer, keyword) :: map | nil def get_replay!(map_id, user, mode, opts \\ []) when is_integer(map_id) and (is_binary(user) or is_integer(user)) and is_integer(mode) and is_list(opts) do get!("replay", Keyword.merge(opts, b: map_id, m: mode, u: user)) end end