diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..a6930ee --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8f93a02 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +name: CI +on: [pull_request, push] +jobs: + mix_test: + name: mix test (Elixir ${{ matrix.elixir }} OTP ${{ matrix.otp }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - elixir: 1.10.4 + otp: 21.3.8.16 + - elixir: 1.10.4 + otp: 23.0.3 + steps: + - uses: actions/checkout@v2.3.2 + - uses: actions/setup-elixir@v1.5.0 + with: + otp-version: ${{ matrix.otp }} + elixir-version: ${{ matrix.elixir }} + - name: Install Dependencies + run: | + mix local.hex --force + mix local.rebar --force + mix deps.get --only test + - name: Check formatting + run: mix format --check-formatted + - name: Run tests + run: mix test --exclude reddit_api diff --git a/config/config.exs b/config/config.exs index 1729029..a7e6804 100644 --- a/config/config.exs +++ b/config/config.exs @@ -11,4 +11,4 @@ config :exreddit, # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. -import_config "#{Mix.env}.exs" +import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 2e0b8e5..347aeb3 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -5,4 +5,4 @@ config :exreddit, password: System.get_env("REDDIT_PASS"), client_id: System.get_env("REDDIT_CLIENT_ID"), secret: System.get_env("REDDIT_SECRET"), - api_rate_limit_delay: 10 + api_rate_limit_delay: 10 \ No newline at end of file diff --git a/config/prod.exs b/config/prod.exs index fd37407..ec421e9 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -5,4 +5,4 @@ config :exreddit, password: System.get_env("REDDIT_PASS"), client_id: System.get_env("REDDIT_CLIENT_ID"), secret: System.get_env("REDDIT_SECRET"), - api_rate_limit_delay: 1000 + api_rate_limit_delay: 1000 \ No newline at end of file diff --git a/lib/.DS_Store b/lib/.DS_Store new file mode 100644 index 0000000..217911e Binary files /dev/null and b/lib/.DS_Store differ diff --git a/lib/exreddit/.DS_Store b/lib/exreddit/.DS_Store new file mode 100644 index 0000000..461fd94 Binary files /dev/null and b/lib/exreddit/.DS_Store differ diff --git a/lib/exreddit/api.ex b/lib/exreddit/api.ex index 41eca9a..3323959 100644 --- a/lib/exreddit/api.ex +++ b/lib/exreddit/api.ex @@ -2,31 +2,42 @@ defmodule ExReddit.Api do require Poison alias ExReddit.Requests.Server, as: RequestServer - alias HTTPotion.{Response, Headers, ErrorResponse} + alias HTTPoison.{Response, Error} def get(params, token, opts \\ []) do + IO.inspect(params) response = RequestServer.get_with_token(params, token, opts) respond(token, response) end - defp respond(token, %Response{status_code: 302, headers: %Headers{hdrs: %{"location" => location}}}) do + defp respond(token,{:ok, %Response{ + status_code: 302, + headers: [{"location", location}] + }}) do get({:url, location}, token) end - defp respond(_, %Response{body: %{"error" => error_message}, status_code: 200}) do + + defp respond(_, {:ok, %Response{body: %{"error" => error_message}, status_code: 200}}) do {:error, error_message} end - defp respond(_, %Response{body: body, status_code: 200}) do + + defp respond(_, {:ok, %Response{body: body, status_code: 200}}) do Poison.decode(body) end - defp respond(_, %Response{body: body, status_code: status_code}) when status_code not in 200..299 do + + defp respond(_, {:ok, %Response{body: body, status_code: status_code}}) + when status_code not in 200..299 do case Poison.decode(body) do {:ok, decoded_body} -> error_message = Map.get(decoded_body, "message") {:error, error_message} - other -> other + + other -> + other end end - defp respond(_, %ErrorResponse{message: error_message}) do + + defp respond(_, {:ok, %Error{reason: error_message}}) do {:error, error_message} end @@ -34,6 +45,7 @@ defmodule ExReddit.Api do response_data = response |> Map.get("data") {:ok, response_data} end + def get_request_data(unknown) do {:error, unknown} end diff --git a/lib/exreddit/api/subreddit.ex b/lib/exreddit/api/subreddit.ex index 82fca80..44da3ca 100644 --- a/lib/exreddit/api/subreddit.ex +++ b/lib/exreddit/api/subreddit.ex @@ -2,13 +2,14 @@ defmodule ExReddit.Api.Subreddit do import ExReddit.Api require Poison - require HTTPotion + require HTTPoison def get_sticky(token, subreddit, num \\ 1) do - get({:uri, "/r/#{subreddit}/about/sticky"}, token, [num: num]) + get({:uri, "/r/#{subreddit}/about/sticky"}, token, num: num) end + def get_sticky!(token, subreddit, num \\ 1) do - case get({:uri, "/r/#{subreddit}/about/sticky"}, token, [num: num]) do + case get({:uri, "/r/#{subreddit}/about/sticky"}, token, num: num) do {:ok, response} -> response other -> other end @@ -16,10 +17,25 @@ defmodule ExReddit.Api.Subreddit do def get_new_threads(token, subreddit, opts \\ []) do get({:uri, "/r/#{subreddit}/new"}, token, opts) + |> IO.inspect() |> get_request_data end def get_comments(token, subreddit, thread_id, opts \\ []) do get({:uri, "/r/#{subreddit}/comments/#{thread_id}"}, token, opts) end + + def get_hot_threads(token, subreddit, opts \\ []) do + get({:uri, "/r/#{subreddit}/hot"}, token, opts) + |> get_request_data + end + + def get_top_threads(token, subreddit, opts \\ []) do + get({:uri, "/r/#{subreddit}/top"}, token, opts) + |> get_request_data + end + + def get_more_children(token, id, opts \\ []) do + get({:uri, "/api/morechildren"}, token, opts) + end end diff --git a/lib/exreddit/listing.ex b/lib/exreddit/listing.ex index a41ed85..d143cba 100644 --- a/lib/exreddit/listing.ex +++ b/lib/exreddit/listing.ex @@ -1,5 +1,5 @@ defmodule ExReddit.Listing do - def parse_api_response([submission_listing, comments_listing] ) do + def parse_api_response([submission_listing, comments_listing]) do submission = get_submission(submission_listing) comments = get_comments(comments_listing) diff --git a/lib/exreddit/oauth.ex b/lib/exreddit/oauth.ex index 1d1dbf5..7b99955 100644 --- a/lib/exreddit/oauth.ex +++ b/lib/exreddit/oauth.ex @@ -1,23 +1,28 @@ defmodule ExReddit.OAuth do - alias HTTPotion.{Response, ErrorResponse} + alias HTTPoison.{Response, Error} require Poison def get_token, do: request_token() |> parse() - defp parse(%Response{body: body, status_code: code}), + defp parse({:ok, %HTTPoison.Response{body: body, status_code: code}}), do: Poison.decode(body) |> parse_body(code) - defp parse(%ErrorResponse{message: error}), + + defp parse(%Error{reason: error}), do: {:error, error} + defp parse(unknown), do: {:unknown, unknown} defp parse_body({:ok, %{"access_token" => token}}, 200), do: {:ok, token} - defp parse_body({:ok, %{"error" => error}}, _), - do: {:error, error} + + defp parse_body({:ok, %{"error" => code, "message" => mesage} = resp}, _), + do: {:error, resp} + defp parse_body({:ok, %{"message" => message}}, _), do: {:error, message} + defp parse_body({_, unknown}, _), do: {:unknown, unknown} @@ -28,25 +33,32 @@ defmodule ExReddit.OAuth do end defp request_token do - headers = get_auth_headers() - HTTPotion.post("https://www.reddit.com/api/v1/access_token", headers) - end + config = get_config() - defp request_token! do - headers = get_auth_headers() - HTTPotion.post!("https://www.reddit.com/api/v1/access_token", headers) + HTTPoison.post( + "https://www.reddit.com/api/v1/access_token", + "grant_type=password&username=#{config[:username]}&password=#{config[:password]}", + [ + {"User-Agent", "exreddit-api-wrapper"}, + {"Content-Type", "application/x-www-form-urlencoded"} + ], + hackney: [basic_auth: {config[:client_id], config[:secret]}] + #basic_auth: {config[:client_id], config[:secret]} + ) |> IO.inspect() end - defp get_auth_headers do + defp request_token! do config = get_config() - [ - body: "grant_type=password&username=#{config[:username]}&password=#{config[:password]}", - headers: [ - "User-Agent": "exreddit-api-wrapper", - "Content-Type": "application/x-www-form-urlencoded" + + HTTPoison.post!( + "https://www.reddit.com/api/v1/access_token", + "grant_type=password&username=#{config[:username]}&password=#{config[:password]}", + [ + {"User-Agent", "exreddit-api-wrapper"}, + {"Content-Type", "application/x-www-form-urlencoded"} ], - basic_auth: {config[:client_id], config[:secret]} - ] + hackney: [basic_auth: {config[:client_id], config[:secret]}] + ) end defp get_config do diff --git a/lib/exreddit/request.ex b/lib/exreddit/request.ex index b65c445..a40d4f9 100644 --- a/lib/exreddit/request.ex +++ b/lib/exreddit/request.ex @@ -4,13 +4,14 @@ defmodule ExReddit.Request do def get(uri, options \\ []) do url = get_url(uri, options) - HTTPotion.get(url) + HTTPoison.get(url, timeout: 30_000) end def get_with_token(uri, token, options \\ []) do url = get_token_url(uri, options) headers = get_headers(token) - HTTPotion.get(url, headers) + opts = [timeout: 30_000] + HTTPoison.get(url, headers, opts) end defp get_url({:url, url}, options) do @@ -44,18 +45,19 @@ defmodule ExReddit.Request do end defp get_query(options) do - query = options - |> Enum.map(fn {key, value} -> "#{key}=#{value}" end) # TODO: Can this be cleaned up / shortened? - |> Enum.join("&") + query = + options + # TODO: Can this be cleaned up / shortened? + |> Enum.map(fn {key, value} -> "#{key}=#{value}" end) + |> Enum.join("&") + "?#{query}" end defp get_headers(token) do [ - headers: [ - "User-Agent": "exreddit-api-wrapper/0.1 by yeamanz", - "Authorization": "bearer #{token}" - ] + {"User-Agent", "exreddit-api-wrapper/0.1 by yeamanz"}, + {"Authorization", "bearer #{token}"} ] end end diff --git a/lib/exreddit/requests/server.ex b/lib/exreddit/requests/server.ex index 35b22cb..7556853 100644 --- a/lib/exreddit/requests/server.ex +++ b/lib/exreddit/requests/server.ex @@ -12,7 +12,8 @@ defmodule ExReddit.Requests.Server do req = fn -> Request.get_with_token(uri, token, opts) end - GenServer.call(__MODULE__, {:request, req}) + + GenServer.call(__MODULE__, {:request, req}, :infinity) end ## Server API @@ -22,7 +23,7 @@ defmodule ExReddit.Requests.Server do def init(_) do :timer.send_interval(get_rate_limit_delay(), :tick) - {:ok, :queue.new} + {:ok, :queue.new()} end def handle_info(:tick, queue) do diff --git a/mix.exs b/mix.exs index 87822db..6fb64c7 100644 --- a/mix.exs +++ b/mix.exs @@ -6,7 +6,7 @@ defmodule ExReddit.Mixfile do app: :exreddit, version: "0.1.0", elixir: "~> 1.5", - start_permanent: Mix.env == :prod, + start_permanent: Mix.env() == :prod, deps: deps() ] end @@ -14,15 +14,15 @@ defmodule ExReddit.Mixfile do def application do [ mod: {ExReddit, []}, - applications: [:httpotion], - extra_applications: [:logger] + applications: [:httpoison], + extra_applications: [:logger, :poison] ] end defp deps do [ - {:httpotion, "~> 3.0.2"}, - {:poison, "~> 3.1"} + {:httpoison, ">= 1.8.0"}, + {:poison, "~> 4.0"} ] end end diff --git a/mix.lock b/mix.lock index e142979..7ef94c3 100644 --- a/mix.lock +++ b/mix.lock @@ -1,3 +1,14 @@ -%{"httpotion": {:hex, :httpotion, "3.0.3", "17096ea1a7c0b2df74509e9c15a82b670d66fc4d66e6ef584189f63a9759428d", [], [{:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: false]}], "hexpm"}, - "ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [], [], "hexpm"}, - "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [], [], "hexpm"}} +%{ + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, + "httpoison": {:hex, :httpoison, "2.2.1", "87b7ed6d95db0389f7df02779644171d7319d319178f6680438167d7b69b1f3d", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "51364e6d2f429d80e14fe4b5f8e39719cacd03eb3f9a9286e61e216feac2d2df"}, + "httpotion": {:hex, :httpotion, "3.2.0", "007c81c3a15b4860c893dea858eab2ce859a260b47071e85dcf9611a4226324e", [:mix], [{:ibrowse, "== 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "726b3fdfc47d7f15302dac5f6a4152a5002fe8230dee8bdd65b8d154b573580b"}, + "ibrowse": {:hex, :ibrowse, "4.4.0", "2d923325efe0d2cb09b9c6a047b2835a5eda69d8a47ed6ff8bc03628b764e991", [:rebar3], [], "hexpm", "6a8e5988872086f0506bef68311493551ac5beae7c06ba2a00d5e9f97a60f1c2"}, + "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, +} diff --git a/test/exreddit/api/subreddit_test.exs b/test/exreddit/api/subreddit_test.exs index 27f5442..eec5c52 100644 --- a/test/exreddit/api/subreddit_test.exs +++ b/test/exreddit/api/subreddit_test.exs @@ -29,7 +29,7 @@ defmodule ExReddit.Api.SubredditTest do end test "get_new_threads/3 with [limit: 1] test", state do - {:ok, response} = Subreddit.get_new_threads(state[:token], "learnprogramming", [limit: 1]) + {:ok, response} = Subreddit.get_new_threads(state[:token], "learnprogramming", limit: 1) threads = response |> Map.get("children") assert length(threads) == 1 end diff --git a/test/exreddit/listing_test.exs b/test/exreddit/listing_test.exs index 32f688d..53a4dd0 100644 --- a/test/exreddit/listing_test.exs +++ b/test/exreddit/listing_test.exs @@ -6,7 +6,7 @@ defmodule ExReddit.ListingTest do setup do json = - Path.expand('./test/examples/submission.json' ) + Path.expand('./test/examples/submission.json') |> File.read!() |> Poison.decode!() diff --git a/test/exreddit/oauth_test.exs b/test/exreddit/oauth_test.exs index 30ad484..5ac79db 100644 --- a/test/exreddit/oauth_test.exs +++ b/test/exreddit/oauth_test.exs @@ -13,25 +13,27 @@ defmodule ExReddit.OAuthTest do end test "get_token/0 should return {:ok, token}" do - {:ok, token} = OAuth.get_token + {:ok, token} = OAuth.get_token() assert byte_size(token) > 0 end test "get_token/0 should return {:error, message} on bad auth data" do Application.put_env(:exreddit, :username, "") - {:error, _} = OAuth.get_token + {:error, _} = OAuth.get_token() Application.put_env(:exreddit, :client_id, "") - {:error, _} = OAuth.get_token + {:error, _} = OAuth.get_token() end test "get_token!/0 should return a token" do - size = OAuth.get_token! + size = + OAuth.get_token!() |> byte_size + assert size > 0 end test "get_token!/0 should throw on bad auth data" do Application.put_env(:exreddit, :username, "") - assert OAuth.get_token! == nil + assert OAuth.get_token!() == nil end end diff --git a/test/exreddit/request_test.exs b/test/exreddit/request_test.exs index 9982415..1630103 100644 --- a/test/exreddit/request_test.exs +++ b/test/exreddit/request_test.exs @@ -3,14 +3,13 @@ defmodule ExReddit.Api.RequestTest do doctest ExReddit.Request alias ExReddit.Request - alias HTTPotion.Response + alias HTTPoison.Response @moduletag :reddit_api @moduletag :request test "get with uri" do - %Response{body: body, status_code: 200} = - Request.get({:uri, "/r/learnprogramming/new" }) + %Response{body: body, status_code: 200} = Request.get({:uri, "/r/learnprogramming/new"}) {:ok, response} = Poison.decode(body) assert length(Map.keys(response)) > 0 diff --git a/test/test_helper.exs b/test/test_helper.exs index ec99421..40b5980 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1,4 +1,4 @@ ExUnit.start() -token = ExReddit.OAuth.get_token! +token = ExReddit.OAuth.get_token!() Application.put_env(:exreddit, :token, token)