From 1355d171806274597565094ff456918c3c85c8b5 Mon Sep 17 00:00:00 2001 From: Sasha Voynow Date: Thu, 12 Jan 2017 10:51:01 -0800 Subject: [PATCH 1/5] Add websocket connection to listen to mopidy events --- README.md | 20 +++++++++++++ lib/mopidy.ex | 70 +++++++++++++++++++++++++++++++++++++++++++- lib/mopidy/events.ex | 12 ++++++++ mix.exs | 3 +- 4 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 lib/mopidy/events.ex diff --git a/README.md b/README.md index a3caf23..fd9b357 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ config :mopidy, api_url: System.get_env("MOPIDY_API_URL") ``` +If you want to be able to receive events via websockets you will also need to set `websocket_api_url` + ## Usage The online [documentation][doc] for the Mopidy HTTP API will give you a general @@ -46,6 +48,24 @@ iex> search_results.artists uri: "spotify:artist:4Z8W4fKeB5YxbusRsdQVPb"}] ``` +To receive events: +```elixir +iex> Mopidy.Events.create_stream |> Enum.each(&IO.inspect/1) +{:ok, + %{"event" => "playback_state_changed", "new_state" => "playing", + "old_state" => "paused"}} +{:ok, + %{event: "track_playback_resumed", time_position: 1819, + tl_track: %Mopidy.TlTrack{__model__: "TlTrack", tlid: 658, + track: %Mopidy.Track{__model__: "Track", + album: %Mopidy.Album{__model__: "Album", + name: "It'll End In Tears (Remastered)", + uri: "spotify:album:6D6C7jGsJdzJpcEaMcxswR"}, + artists: [%Mopidy.Artist{__model__: "Artist", name: "This Mortal Coil", + uri: "spotify:artist:5OK8j1JnhoBlivN32G7yOO"}], + name: "Song To The Siren (Remastered)", + uri: "spotify:track:0BPTTsnnfz44XmZn3EE0oo"}}}} +``` ## License MIT License, see [LICENSE](LICENSE) for details. diff --git a/lib/mopidy.ex b/lib/mopidy.ex index 2413c5f..6d2efb7 100644 --- a/lib/mopidy.ex +++ b/lib/mopidy.ex @@ -83,6 +83,46 @@ defmodule Mopidy.Playlist do } end + +defmodule Mopidy.Websocket do + defstruct connection: nil + + @type t :: %__MODULE__{ + connection: Socket.Web.t + } + + def new do + %Mopidy.Websocket{connection: connect} + end + + def receive_next_event(%Mopidy.Websocket{connection: connection} = socket) when not is_nil(connection) do + case Socket.Web.recv(connection) do + {:ok, {:text, data}} -> {[parse_event(data)], socket} + _ -> {:halt, socket} + end + end + + def receive_next_event(%Mopidy.Websocket{connection: nil} = socket) do + {:halt, socket} + end + + def parse_event(data) do + Mopidy.parse_data(:event, Poison.decode!(data)) + end + + defp connect do + %URI{host: host, path: path, port: port} = URI.parse(mopidy_websocket_api_url) + case Socket.Web.connect {host, port || 80}, path: path || "" do + {:ok, conn} -> conn + _ -> nil + end + end + + defp mopidy_websocket_api_url do + Application.get_env(:mopidy, :websocket_api_url) + end +end + defmodule Mopidy do @moduledoc """ An HTTP client for Mopidy @@ -167,12 +207,40 @@ defmodule Mopidy do end # Entry points + def parse_data(:event, %{"event" => event} = body), do: {:ok, parse_data(event, body)} def parse_data(_data_type, %{"error" => _error} = body), do: {:error, body} def parse_data(:success, _body), do: {:ok, :success} def parse_data(:value, body), do: {:ok, body["value"]} def parse_data(:result, body), do: {:ok, body["result"]} def parse_data(:uri, body), do: {:ok, parse_data(:uri, body["result"], %{})} - def parse_data(data_type, body), do: {:ok, parse_data(data_type, body["result"], [])} + def parse_data(data_type, %{result: result}), do: {:ok, parse_data(data_type, result, [])} + + # Event parsing + def parse_data(event, datum_data) when event in ~w(track_playback_resumed track_playback_paused track_playback_ended) do + %{ + event: event, + time_position: datum_data["time_position"], + tl_track: parse_data(%TlTrack{}, datum_data["tl_track"], %{}) + } + end + +def parse_data("track_playback_started" = event, datum_data) do + %{ + event: event, + tl_track: parse_data(%TlTrack{}, datum_data["tl_track"], %{}) + } + end + + def parse_data("playlist_changed" = event, datum_data) do + %{ + event: event, + playlist: parse_data(%Playlist{}, datum_data["playlist"], %{}) + } + end + + def parse_data(_, %{"event" => event} = datum_data) do + datum_data + end # List parsing def parse_data(_data_type, nil, _accumulator), do: nil diff --git a/lib/mopidy/events.ex b/lib/mopidy/events.ex new file mode 100644 index 0000000..5d55204 --- /dev/null +++ b/lib/mopidy/events.ex @@ -0,0 +1,12 @@ +defmodule Mopidy.Events do + + def create_stream do + Stream.resource( + fn -> Mopidy.Websocket.new end, + fn (socket) -> + Mopidy.Websocket.receive_next_event(socket) + end, + fn _ -> IO.puts("stream ended") end + ) + end +end \ No newline at end of file diff --git a/mix.exs b/mix.exs index 52db6f4..338a347 100644 --- a/mix.exs +++ b/mix.exs @@ -40,7 +40,8 @@ defmodule Mopidy.Mixfile do {:httpotion, "~> 3.0.0"}, {:inch_ex, "~> 0.4", only: :docs}, {:mix_test_watch, "~> 0.2", only: :test}, - {:poison, "~> 2.1"} + {:poison, "~> 3.0"}, + {:socket, "~> 0.3"} ] end From 1ebd6e66e877ee29a93a0bf60a7b972ff454601e Mon Sep 17 00:00:00 2001 From: Sasha Voynow Date: Thu, 12 Jan 2017 10:58:06 -0800 Subject: [PATCH 2/5] add moduledocs --- lib/mopidy.ex | 3 +++ lib/mopidy/events.ex | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/mopidy.ex b/lib/mopidy.ex index 6d2efb7..5c22b35 100644 --- a/lib/mopidy.ex +++ b/lib/mopidy.ex @@ -85,6 +85,9 @@ end defmodule Mopidy.Websocket do + @moduledoc """ + A Websocket connection to Mopidy + """ defstruct connection: nil @type t :: %__MODULE__{ diff --git a/lib/mopidy/events.ex b/lib/mopidy/events.ex index 5d55204..94cd937 100644 --- a/lib/mopidy/events.ex +++ b/lib/mopidy/events.ex @@ -1,5 +1,7 @@ -defmodule Mopidy.Events do - +defmodule Mopidy.Events do` + @moduledoc """ + Get a stream of Mopidy events + """ def create_stream do Stream.resource( fn -> Mopidy.Websocket.new end, From aa8facf0ff8cb0394cd2da027fb2904b2e38c2da Mon Sep 17 00:00:00 2001 From: Sasha Voynow Date: Thu, 12 Jan 2017 12:56:45 -0800 Subject: [PATCH 3/5] move event parsing into events.ex --- lib/mopidy.ex | 32 ++------------------------------ lib/mopidy/events.ex | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/lib/mopidy.ex b/lib/mopidy.ex index 5c22b35..3e54071 100644 --- a/lib/mopidy.ex +++ b/lib/mopidy.ex @@ -110,7 +110,7 @@ defmodule Mopidy.Websocket do end def parse_event(data) do - Mopidy.parse_data(:event, Poison.decode!(data)) + Mopidy.Events.parse_event(Poison.decode!(data)) end defp connect do @@ -210,40 +210,12 @@ defmodule Mopidy do end # Entry points - def parse_data(:event, %{"event" => event} = body), do: {:ok, parse_data(event, body)} def parse_data(_data_type, %{"error" => _error} = body), do: {:error, body} def parse_data(:success, _body), do: {:ok, :success} def parse_data(:value, body), do: {:ok, body["value"]} def parse_data(:result, body), do: {:ok, body["result"]} def parse_data(:uri, body), do: {:ok, parse_data(:uri, body["result"], %{})} - def parse_data(data_type, %{result: result}), do: {:ok, parse_data(data_type, result, [])} - - # Event parsing - def parse_data(event, datum_data) when event in ~w(track_playback_resumed track_playback_paused track_playback_ended) do - %{ - event: event, - time_position: datum_data["time_position"], - tl_track: parse_data(%TlTrack{}, datum_data["tl_track"], %{}) - } - end - -def parse_data("track_playback_started" = event, datum_data) do - %{ - event: event, - tl_track: parse_data(%TlTrack{}, datum_data["tl_track"], %{}) - } - end - - def parse_data("playlist_changed" = event, datum_data) do - %{ - event: event, - playlist: parse_data(%Playlist{}, datum_data["playlist"], %{}) - } - end - - def parse_data(_, %{"event" => event} = datum_data) do - datum_data - end + def parse_data(data_type, body), do: {:ok, parse_data(data_type, body["result"], [])} # List parsing def parse_data(_data_type, nil, _accumulator), do: nil diff --git a/lib/mopidy/events.ex b/lib/mopidy/events.ex index 94cd937..a377124 100644 --- a/lib/mopidy/events.ex +++ b/lib/mopidy/events.ex @@ -1,7 +1,9 @@ -defmodule Mopidy.Events do` +defmodule Mopidy.Events do @moduledoc """ Get a stream of Mopidy events """ + alias Mopidy.{TlTrack, Playlist} + def create_stream do Stream.resource( fn -> Mopidy.Websocket.new end, @@ -11,4 +13,35 @@ defmodule Mopidy.Events do` fn _ -> IO.puts("stream ended") end ) end + + # Entry point + def parse_event(%{"event" => event} = body), do: {:ok, parse_event(event, body)} + + # Event parsing + def parse_event(event, datum_data) when event in ~w(track_playback_resumed track_playback_paused track_playback_ended) do + %{ + event: event, + time_position: datum_data["time_position"], + tl_track: Mopidy.parse_data(%TlTrack{}, datum_data["tl_track"], %{}) + } + end + + def parse_event("track_playback_started" = event, datum_data) do + %{ + event: event, + tl_track: Mopidy.parse_data(%TlTrack{}, datum_data["tl_track"], %{}) + } + end + + def parse_event("playlist_changed" = event, datum_data) do + %{ + event: event, + playlist: Mopidy.parse_data(%Playlist{}, datum_data["playlist"], %{}) + } + end + + def parse_event(_, datum_data) do + datum_data + end + end \ No newline at end of file From 2bc5f13dd40a437358c740ac38752be7e94285b9 Mon Sep 17 00:00:00 2001 From: Chris Bucchere Date: Fri, 20 Jan 2017 10:53:58 -0800 Subject: [PATCH 4/5] remove dependency on inch --- mix.exs | 1 - mix.lock | 9 +++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/mix.exs b/mix.exs index 338a347..2b0cef7 100644 --- a/mix.exs +++ b/mix.exs @@ -38,7 +38,6 @@ defmodule Mopidy.Mixfile do {:ex_doc, "~> 0.11", only: :dev}, {:excoveralls, "~> 0.4", only: :test}, {:httpotion, "~> 3.0.0"}, - {:inch_ex, "~> 0.4", only: :docs}, {:mix_test_watch, "~> 0.2", only: :test}, {:poison, "~> 3.0"}, {:socket, "~> 0.3"} diff --git a/mix.lock b/mix.lock index e7b02d9..4bc0849 100644 --- a/mix.lock +++ b/mix.lock @@ -4,10 +4,10 @@ "dogma": {:hex, :dogma, "0.1.7", "927f76a89a809db96e0983b922fc899f601352690aefa123529b8aa0c45123b2", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, optional: false]}]}, "earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], []}, "ex_doc": {:hex, :ex_doc, "0.12.0", "b774aabfede4af31c0301aece12371cbd25995a21bb3d71d66f5c2fe074c603f", [:mix], [{:earmark, "~> 0.2", [hex: :earmark, optional: false]}]}, - "excoveralls": {:hex, :excoveralls, "0.5.5", "d97b6fc7aa59c5f04f2fa7ec40fc0b7555ceea2a5f7e7c442aad98ddd7f79002", [:mix], [{:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}, {:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}]}, + "excoveralls": {:hex, :excoveralls, "0.5.5", "d97b6fc7aa59c5f04f2fa7ec40fc0b7555ceea2a5f7e7c442aad98ddd7f79002", [:mix], [{:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]}, "exjsx": {:hex, :exjsx, "3.2.0", "7136cc739ace295fc74c378f33699e5145bead4fdc1b4799822d0287489136fb", [:mix], [{:jsx, "~> 2.6.2", [hex: :jsx, optional: false]}]}, "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []}, - "hackney": {:hex, :hackney, "1.6.0", "8d1e9440c9edf23bf5e5e2fe0c71de03eb265103b72901337394c840eec679ac", [:rebar3], [{:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:certifi, "0.4.0", [hex: :certifi, optional: false]}]}, + "hackney": {:hex, :hackney, "1.6.0", "8d1e9440c9edf23bf5e5e2fe0c71de03eb265103b72901337394c840eec679ac", [:rebar3], [{:certifi, "0.4.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}]}, "httpoison": {:hex, :httpoison, "0.8.3", "b675a3fdc839a0b8d7a285c6b3747d6d596ae70b6ccb762233a990d7289ccae4", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, optional: false]}]}, "httpotion": {:hex, :httpotion, "3.0.0", "4ce0af28f4254bdd0457d0fe065a83787751e0a5b5f5d27030948253be38df5c", [:mix], [{:ibrowse, "~> 4.2", [hex: :ibrowse, optional: false]}]}, "ibrowse": {:hex, :ibrowse, "4.2.2", "b32b5bafcc77b7277eff030ed32e1acc3f610c64e9f6aea19822abcadf681b4b", [:rebar3], []}, @@ -17,5 +17,6 @@ "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, "mix_test_watch": {:hex, :mix_test_watch, "0.2.6", "9fcc2b1b89d1594c4a8300959c19d50da2f0ff13642c8f681692a6e507f92cab", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}]}, - "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], []}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:rebar, :make], []}} + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, + "socket": {:hex, :socket, "0.3.7", "68c029a8449fc6efc5f4c3bdf6d8b19ca94d3304e419f364c5cfc8b716f7c219", [:mix], []}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:make, :rebar], []}} From 19663e71d71236c663fd35102f53f0cb9796d9e2 Mon Sep 17 00:00:00 2001 From: Brien Wankel Date: Tue, 23 Jan 2018 10:07:19 -0700 Subject: [PATCH 5/5] Update dependencies and fix deprecation warnings --- lib/mopidy.ex | 20 ++++++++++---------- mix.exs | 12 ++++++------ mix.lock | 45 +++++++++++++++++++++++---------------------- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/lib/mopidy.ex b/lib/mopidy.ex index 3e54071..d96d1cd 100644 --- a/lib/mopidy.ex +++ b/lib/mopidy.ex @@ -95,7 +95,7 @@ defmodule Mopidy.Websocket do } def new do - %Mopidy.Websocket{connection: connect} + %Mopidy.Websocket{connection: connect()} end def receive_next_event(%Mopidy.Websocket{connection: connection} = socket) when not is_nil(connection) do @@ -111,13 +111,13 @@ defmodule Mopidy.Websocket do def parse_event(data) do Mopidy.Events.parse_event(Poison.decode!(data)) - end + end defp connect do - %URI{host: host, path: path, port: port} = URI.parse(mopidy_websocket_api_url) + %URI{host: host, path: path, port: port} = URI.parse(mopidy_websocket_api_url()) case Socket.Web.connect {host, port || 80}, path: path || "" do {:ok, conn} -> conn - _ -> nil + _ -> nil end end @@ -139,7 +139,7 @@ defmodule Mopidy do @request_timeout 5_000 def start(_type, _args) do - start + start() import Supervisor.Spec, warn: false @@ -154,7 +154,7 @@ defmodule Mopidy do Returns string """ def process_url(endpoint) do - mopidy_api_url <> endpoint + mopidy_api_url() <> endpoint end def process_request_body(body) do @@ -169,8 +169,8 @@ defmodule Mopidy do Set our request headers for every request. """ def process_request_headers(headers) do - Dict.put headers, :"User-Agent", "Mopidy/v1 mopidy-elixir/0.2.0" - Dict.put headers, :"Content-Type", "application/json" + Keyword.put headers, :"User-Agent", "Mopidy/v1 mopidy-elixir/0.2.0" + Keyword.put headers, :"Content-Type", "application/json" end @doc """ @@ -184,7 +184,7 @@ defmodule Mopidy do def api_request(data \\ %{}) do body = Map.merge(%{id: "1", jsonrpc: "2.0"}, data) - with %HTTPotion.Response{body: body} <- Mopidy.post(nil, [body: body, timeout: mopidy_request_timeout]) do + with %HTTPotion.Response{body: body} <- Mopidy.post(nil, [body: body, timeout: mopidy_request_timeout()]) do {:ok, body} else %HTTPotion.ErrorResponse{message: message} -> {:error, message} @@ -215,7 +215,7 @@ defmodule Mopidy do def parse_data(:value, body), do: {:ok, body["value"]} def parse_data(:result, body), do: {:ok, body["result"]} def parse_data(:uri, body), do: {:ok, parse_data(:uri, body["result"], %{})} - def parse_data(data_type, body), do: {:ok, parse_data(data_type, body["result"], [])} + def parse_data(data_type, body), do: {:ok, parse_data(data_type, body["result"], [])} # List parsing def parse_data(_data_type, nil, _accumulator), do: nil diff --git a/mix.exs b/mix.exs index 2b0cef7..2f7098e 100644 --- a/mix.exs +++ b/mix.exs @@ -6,9 +6,9 @@ defmodule Mopidy.Mixfile do app: :mopidy, version: "0.3.0", elixir: ">= 1.3.0", - deps: deps, - description: description, - package: package, + deps: deps(), + description: description(), + package: package(), build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, test_coverage: [tool: ExCoveralls], @@ -34,12 +34,12 @@ defmodule Mopidy.Mixfile do [ {:credo, "~> 0.4", only: [:dev, :test]}, {:dogma, "~> 0.1", only: :dev}, - {:earmark, "~> 0.1", only: :dev}, + {:earmark, "~> 1.2", only: :dev}, {:ex_doc, "~> 0.11", only: :dev}, {:excoveralls, "~> 0.4", only: :test}, - {:httpotion, "~> 3.0.0"}, + {:httpotion, "~> 3.0"}, {:mix_test_watch, "~> 0.2", only: :test}, - {:poison, "~> 3.0"}, + {:poison, "~> 3.1"}, {:socket, "~> 0.3"} ] end diff --git a/mix.lock b/mix.lock index 4bc0849..9abf891 100644 --- a/mix.lock +++ b/mix.lock @@ -1,22 +1,23 @@ -%{"bunt": {:hex, :bunt, "0.1.6", "5d95a6882f73f3b9969fdfd1953798046664e6f77ec4e486e6fafc7caad97c6f", [:mix], []}, - "certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], []}, - "credo": {:hex, :credo, "0.4.5", "5c5daaf50a2a96068c0f21b6fbd382d206702efa8836a946eeab0b8ac25f5f22", [:mix], [{:bunt, "~> 0.1.6", [hex: :bunt, optional: false]}]}, - "dogma": {:hex, :dogma, "0.1.7", "927f76a89a809db96e0983b922fc899f601352690aefa123529b8aa0c45123b2", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, optional: false]}]}, - "earmark": {:hex, :earmark, "0.2.1", "ba6d26ceb16106d069b289df66751734802777a3cbb6787026dd800ffeb850f3", [:mix], []}, - "ex_doc": {:hex, :ex_doc, "0.12.0", "b774aabfede4af31c0301aece12371cbd25995a21bb3d71d66f5c2fe074c603f", [:mix], [{:earmark, "~> 0.2", [hex: :earmark, optional: false]}]}, - "excoveralls": {:hex, :excoveralls, "0.5.5", "d97b6fc7aa59c5f04f2fa7ec40fc0b7555ceea2a5f7e7c442aad98ddd7f79002", [:mix], [{:exjsx, "~> 3.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]}, - "exjsx": {:hex, :exjsx, "3.2.0", "7136cc739ace295fc74c378f33699e5145bead4fdc1b4799822d0287489136fb", [:mix], [{:jsx, "~> 2.6.2", [hex: :jsx, optional: false]}]}, - "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], []}, - "hackney": {:hex, :hackney, "1.6.0", "8d1e9440c9edf23bf5e5e2fe0c71de03eb265103b72901337394c840eec679ac", [:rebar3], [{:certifi, "0.4.0", [hex: :certifi, optional: false]}, {:idna, "1.2.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, optional: false]}]}, - "httpoison": {:hex, :httpoison, "0.8.3", "b675a3fdc839a0b8d7a285c6b3747d6d596ae70b6ccb762233a990d7289ccae4", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, optional: false]}]}, - "httpotion": {:hex, :httpotion, "3.0.0", "4ce0af28f4254bdd0457d0fe065a83787751e0a5b5f5d27030948253be38df5c", [:mix], [{:ibrowse, "~> 4.2", [hex: :ibrowse, optional: false]}]}, - "ibrowse": {:hex, :ibrowse, "4.2.2", "b32b5bafcc77b7277eff030ed32e1acc3f610c64e9f6aea19822abcadf681b4b", [:rebar3], []}, - "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], []}, - "inch_ex": {:hex, :inch_ex, "0.5.3", "39f11e96181ab7edc9c508a836b33b5d9a8ec0859f56886852db3d5708889ae7", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, optional: false]}]}, - "jsx": {:hex, :jsx, "2.6.2", "213721e058da0587a4bce3cc8a00ff6684ced229c8f9223245c6ff2c88fbaa5a", [:mix, :rebar], []}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []}, - "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []}, - "mix_test_watch": {:hex, :mix_test_watch, "0.2.6", "9fcc2b1b89d1594c4a8300959c19d50da2f0ff13642c8f681692a6e507f92cab", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, optional: false]}]}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []}, - "socket": {:hex, :socket, "0.3.7", "68c029a8449fc6efc5f4c3bdf6d8b19ca94d3304e419f364c5cfc8b716f7c219", [:mix], []}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:make, :rebar], []}} +%{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, + "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"}, + "credo": {:hex, :credo, "0.8.10", "261862bb7363247762e1063713bb85df2bbd84af8d8610d1272cd9c1943bba63", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, + "dogma": {:hex, :dogma, "0.1.16", "3c1532e2f63ece4813fe900a16704b8e33264da35fdb0d8a1d05090a3022eef9", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, + "excoveralls": {:hex, :excoveralls, "0.8.0", "99d2691d3edf8612f128be3f9869c4d44b91c67cec92186ce49470ae7a7404cf", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"}, + "fs": {:hex, :fs, "0.9.2", "ed17036c26c3f70ac49781ed9220a50c36775c6ca2cf8182d123b6566e49ec59", [:rebar], [], "hexpm"}, + "hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, + "httpotion": {:hex, :httpotion, "3.0.3", "17096ea1a7c0b2df74509e9c15a82b670d66fc4d66e6ef584189f63a9759428d", [:mix], [{:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: false]}], "hexpm"}, + "ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [:rebar3], [], "hexpm"}, + "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, + "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, + "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "mix_test_watch": {:hex, :mix_test_watch, "0.5.0", "2c322d119a4795c3431380fca2bca5afa4dc07324bd3c0b9f6b2efbdd99f5ed3", [:mix], [{:fs, "~> 0.9.1", [hex: :fs, repo: "hexpm", optional: false]}], "hexpm"}, + "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "socket": {:hex, :socket, "0.3.12", "4a6543815136503fee67eff0932da1742fad83f84c49130c854114153cc549a6", [:mix], [], "hexpm"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, +}