├── test
├── test_helper.exs
└── elixirplusreddit_test.exs
├── .gitignore
├── mix.lock
├── lib
├── elixirplusreddit.ex
└── elixirplusreddit
│ ├── pqueue.ex
│ ├── requestbuilder.ex
│ ├── api
│ ├── authentication.ex
│ ├── comment.ex
│ ├── identity.ex
│ ├── post.ex
│ ├── inbox.ex
│ ├── subreddit.ex
│ └── user.ex
│ ├── requestqueue.ex
│ ├── scheduler.ex
│ ├── config.ex
│ ├── request.ex
│ ├── parser.ex
│ ├── tokenserver.ex
│ ├── requestserver.ex
│ └── paginator.ex
├── mix.exs
└── README.md
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /_build
2 | /cover
3 | /deps
4 | /config/config.exs
5 | erl_crash.dump
6 | *.ez
7 |
--------------------------------------------------------------------------------
/test/elixirplusreddit_test.exs:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusRedditTest do
2 | use ExUnit.Case
3 | doctest ElixirPlusReddit
4 |
5 | test "the truth" do
6 | assert 1 + 1 == 2
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{"earmark": {:hex, :earmark, "0.2.0"},
2 | "ex_doc": {:hex, :ex_doc, "0.11.3"},
3 | "httpotion": {:hex, :httpotion, "2.1.0"},
4 | "ibrowse": {:git, "https://github.com/cmullaparthi/ibrowse.git", "ea3305d21f37eced4fac290f64b068e56df7de80", [tag: "v4.1.2"]},
5 | "poison": {:hex, :poison, "1.5.0"},
6 | "pqueue": {:git, "https://github.com/okeuday/pqueue.git", "3e8be6969eaf7d5d327716ad2175a2341dd004f3", []}}
7 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit do
2 | use Application
3 |
4 | alias ElixirPlusReddit.RequestQueue
5 | alias ElixirPlusReddit.RequestServer
6 | alias ElixirPlusReddit.TokenServer
7 |
8 | def start(_type, _args) do
9 | import Supervisor.Spec, warn: false
10 |
11 | children = [
12 | worker(RequestQueue, []),
13 | worker(RequestServer, []),
14 | worker(TokenServer, [])
15 | ]
16 |
17 | opts = [strategy: :one_for_one, name: ElixirPlusReddit.Supervisor]
18 | Supervisor.start_link(children, opts)
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [app: :elixirplusreddit,
6 | version: "0.0.1",
7 | elixir: "~> 1.2",
8 | build_embedded: Mix.env == :prod,
9 | start_permanent: Mix.env == :prod,
10 | deps: deps]
11 | end
12 |
13 | def application do
14 | [applications: [:logger, :httpotion],
15 | mod: {ElixirPlusReddit, []}]
16 | end
17 |
18 | defp deps do
19 | [
20 | {:pqueue, github: "okeuday/pqueue"},
21 | {:ibrowse, github: "cmullaparthi/ibrowse", tag: "v4.1.2"},
22 | {:httpotion, "~> 2.1.0"},
23 | {:poison, "~> 1.5"},
24 | {:earmark, "~> 0.1", only: :dev},
25 | {:ex_doc, "~> 0.11", only: :dev}
26 | ]
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/pqueue.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.PQueue do
2 |
3 | @moduledoc """
4 | A tiny wrapper around okeuday's pqueue library.
5 | """
6 |
7 | @doc """
8 | Create a new pqueue.
9 | """
10 |
11 | def new do
12 | :pqueue.new
13 | end
14 |
15 | @doc """
16 | Insert a request into a pqueue.
17 | """
18 |
19 | def enqueue(pqueue, request, priority) do
20 | :pqueue.in(request, priority, pqueue)
21 | end
22 |
23 | @doc """
24 | Return the next request in the pqueue and the new pqueue.
25 | """
26 |
27 | def dequeue(pqueue) do
28 | :pqueue.out(pqueue)
29 | end
30 |
31 | @doc """
32 | Return if the pqueue is empty.
33 | """
34 |
35 | def is_empty?(pqueue) do
36 | :pqueue.is_empty(pqueue)
37 | end
38 |
39 | end
40 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/requestbuilder.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.RequestBuilder do
2 |
3 | @moduledoc """
4 | A utility module for formatting request data.
5 | """
6 |
7 | def format_get(from, tag, url, parse_strategy, priority) do
8 | %{
9 | method: :get,
10 | from: from,
11 | tag: tag,
12 | url: url,
13 | parse_strategy: parse_strategy,
14 | priority: priority
15 | }
16 | end
17 |
18 |
19 | def format_get(from, tag, url, query, parse_strategy, priority) do
20 | %{
21 | method: :get,
22 | from: from,
23 | tag: tag,
24 | url: url,
25 | query: query,
26 | parse_strategy: parse_strategy,
27 | priority: priority
28 | }
29 | end
30 |
31 | def format_post(from, tag, url, query, parse_strategy, priority) do
32 | %{
33 | method: :post,
34 | from: from,
35 | tag: tag,
36 | url: url,
37 | query: query,
38 | parse_strategy: parse_strategy,
39 | priority: priority
40 | }
41 | end
42 |
43 | end
44 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/api/authentication.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.API.Authentication do
2 |
3 | @moduledoc """
4 | Provides an interface for acquiring access tokens. Requesting a token
5 | is considered a special case and is NOT subject to rate limiting through
6 | the priority queue.
7 | """
8 |
9 | alias ElixirPlusReddit.Config
10 | alias ElixirPlusReddit.Request
11 | alias ElixirPlusReddit.Parser
12 |
13 | @token_endpoint "https://www.reddit.com/api/v1/access_token"
14 |
15 |
16 | @doc """
17 | Grants the client an access token.
18 | """
19 |
20 | def request_token do
21 | creds = Config.credentials
22 | body = [grant_type: "password", username: creds[:username], password: creds[:password]]
23 | basic_auth = [basic_auth: {creds[:client_id], creds[:client_secret]}]
24 | retry_until_success(@token_endpoint, body, basic_auth)
25 | end
26 |
27 | defp retry_until_success(endpoint, body, basic_auth) do
28 | {resp, strategy} = Request.request_token(@token_endpoint, body, basic_auth)
29 | case resp.status_code do
30 | c when c in 500..599 -> retry_until_success(endpoint, body, basic_auth)
31 | _ -> Parser.parse(resp, strategy)
32 | end
33 | end
34 |
35 |
36 | end
37 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/requestqueue.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.RequestQueue do
2 |
3 | @moduledoc """
4 | A request queue built on top of an Agent. Handles distributing
5 | requests to the request server. The queue is externalized from the server
6 | to avoid loss of important state and for handling timeouts.
7 | """
8 |
9 | @request_queue __MODULE__
10 |
11 | alias ElixirPlusReddit.PQueue
12 |
13 | def start_link do
14 | Agent.start_link(fn -> PQueue.new end, name: @request_queue)
15 | end
16 |
17 | def enqueue_request(%{priority: p} = request_data) do
18 | Agent.update(@request_queue, fn(request_queue) ->
19 | PQueue.enqueue(request_queue, request_data, p)
20 | end)
21 | end
22 |
23 | def peek_request do
24 | Agent.get(@request_queue, fn(request_queue) ->
25 | {{:value, request}, _request_queue} = PQueue.dequeue(request_queue)
26 | request
27 | end)
28 | end
29 |
30 | def dequeue_request do
31 | Agent.update(@request_queue, fn(request_queue) ->
32 | {{:value, _request}, request_queue} = PQueue.dequeue(request_queue)
33 | request_queue
34 | end)
35 | end
36 |
37 | def is_empty? do
38 | Agent.get(@request_queue, fn(request_queue) ->
39 | PQueue.is_empty?(request_queue)
40 | end)
41 | end
42 |
43 | end
44 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/scheduler.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.Scheduler do
2 | use GenServer
3 |
4 | @moduledoc """
5 | A generic implementation for scheduling API calls on an interval.
6 | """
7 |
8 | @scheduler __MODULE__
9 |
10 | def schedule({module, function}, arguments, interval) do
11 | start_link({module, function}, arguments, interval)
12 | end
13 |
14 | def schedule({module, function}, interval) do
15 | start_link({module, function}, [], interval)
16 | end
17 |
18 | def start_link({module, function}, arguments, interval) do
19 | config = [
20 | module: module,
21 | function: function,
22 | arguments: arguments,
23 | interval: interval
24 | ]
25 |
26 | GenServer.start_link(@scheduler, config, [])
27 | end
28 |
29 | def init(config) do
30 | process_request(config)
31 | schedule_request(config[:interval])
32 | {:ok, config}
33 | end
34 |
35 | def handle_info(:next_request, config) do
36 | process_request(config)
37 | schedule_request(config[:interval])
38 | {:noreply, config}
39 | end
40 |
41 | defp process_request([module: m, function: x, arguments: a, interval: _]) do
42 | apply(m, x, a)
43 | end
44 |
45 | defp schedule_request(interval) do
46 | Process.send_after(self, :next_request, interval)
47 | end
48 |
49 | end
50 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/config.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.Config do
2 |
3 | @moduledoc """
4 | An interface for accessing configuration data from config.exs.
5 | """
6 |
7 | @doc """
8 | Return credentials from config.exs. Credentials are stored as a
9 | keyword list:
10 |
11 | [
12 | username: username,
13 | password: password,
14 | client_id: client_id,
15 | client_secret: client_secret
16 | ]
17 | """
18 |
19 | def credentials do
20 | Application.get_env(:elixirplusreddit, :creds)
21 | end
22 |
23 | @doc """
24 | Return the client's user agent.
25 | """
26 |
27 | def user_agent do
28 | Application.get_env(:elixirplusreddit, :user_agent)
29 | end
30 |
31 | @doc """
32 | Manually configure credentials.
33 | """
34 |
35 | def set_credentials(username, password, client_id, client_secret, user_agent) do
36 | creds = [
37 | username: username,
38 | password: password,
39 | client_id: client_id,
40 | client_secret: client_secret
41 | ]
42 |
43 | Application.put_env(:elixirplusreddit, :creds, creds)
44 | Application.put_env(:elixirplusreddit, :user_agent, user_agent)
45 | end
46 |
47 | @doc """
48 | Check if credentials are configured.
49 | """
50 |
51 | def is_configured? do
52 | case Application.get_env(:elixirplusreddit, :creds) do
53 | nil -> false
54 | _ -> true
55 | end
56 | end
57 |
58 | end
59 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/request.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.Request do
2 |
3 | alias ElixirPlusReddit.Parser
4 | alias ElixirPlusReddit.TokenServer
5 | alias ElixirPlusReddit.Config
6 |
7 | @moduledoc """
8 | A thin wrapper around HTTPotion's HTTP request function. All requests are processed inside
9 | of this module after being received from the request server.
10 | """
11 |
12 | def request_token(url, query, options) do
13 | options = [body: encode_post_query(query), headers: headers] |> Keyword.merge(options)
14 | {HTTPotion.request(:post, url, options), :token}
15 | end
16 |
17 | def request(:get, url, parse_strategy) do
18 | options = [headers: headers(:token_required)]
19 | {HTTPotion.request(:get, url, options), parse_strategy}
20 | end
21 |
22 | def request(:get, url, query, parse_strategy) do
23 | url = encode_get_query(url, query)
24 | options = [headers: headers(:token_required)]
25 | {HTTPotion.request(:get, url, options), parse_strategy}
26 | end
27 |
28 | def request(:post, url, query, parse_strategy) do
29 | options = [body: encode_post_query(query), headers: headers(:token_required)]
30 | {HTTPotion.request(:post, url, options), parse_strategy}
31 | end
32 |
33 | defp headers(:token_required) do
34 | ["Authorization": TokenServer.token] |> Keyword.merge(headers)
35 | end
36 |
37 | defp headers do
38 | ["Content-Type": "application/x-www-form-urlencoded",
39 | "User-Agent": Config.user_agent]
40 | end
41 |
42 | defp encode_get_query(url, query) do
43 | "#{url}?#{URI.encode_query(query)}"
44 | end
45 |
46 | defp encode_post_query(query) do
47 | URI.encode_query(query)
48 | end
49 |
50 | end
51 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/parser.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.Parser do
2 |
3 | @moduledoc """
4 | A parser built on top of Poison for decoding Reddit's various return
5 | structures.
6 | """
7 |
8 | def parse(%HTTPotion.Response{body: body, status_code: code}, strategy) do
9 | case code do
10 | _ ->
11 | body = Poison.decode!(body, keys: :atoms)
12 | parse(body, strategy)
13 | end
14 | end
15 |
16 | def parse(resp, :token) do
17 | "bearer #{resp.access_token}"
18 | end
19 |
20 | def parse(resp, :trophies) do
21 | Enum.map(resp.data.trophies, fn(trophy) -> trophy.data end)
22 | end
23 |
24 | def parse(resp, :listing) when is_list(resp) do
25 | for r <- resp, do: parse(r, :listing)
26 | end
27 |
28 | def parse(resp, :listing) do
29 | parse_data(resp)
30 | end
31 |
32 | defp parse_data(resp) do
33 | # Recursively flatten the data member of any children
34 | if Map.has_key?(resp, :data) && Map.has_key?(resp.data, :children) do
35 | Map.update!(resp.data, :children, fn(children) ->
36 | Enum.map(children, fn(child) ->
37 | parse_data(child.data)
38 | end)
39 | end)
40 | else
41 | resp
42 | end
43 | end
44 |
45 | def parse(resp, :reply) do
46 | errors = resp.json.errors
47 | things = resp.json.data.things |> List.first |> Map.get(:data)
48 | Map.put(things, :errors, errors)
49 | end
50 |
51 | def parse(resp, :submission) do
52 | resp.json.data
53 | end
54 |
55 | def parse(resp, :compose) do
56 | resp.json
57 | end
58 |
59 | def parse(resp, :no_data) do
60 | resp
61 | end
62 |
63 | def parse(resp, _) do
64 | resp
65 | end
66 |
67 | end
68 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/tokenserver.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.TokenServer do
2 | use GenServer
3 |
4 | @moduledoc """
5 | Handles distrubuting and acquiring access tokens.
6 |
7 | ### Details
8 |
9 | A new token is automatically acquired every 58 minutes. It is possible to manually
10 | acquire tokens but should only be used when you manually configure credentials or
11 | a request returns with the status code 401. (Expired token)
12 | """
13 |
14 | alias ElixirPlusReddit.API.Authentication
15 | alias ElixirPlusReddit.Config
16 |
17 | @tokenserver __MODULE__
18 | @token_interval 1000 * 60 * 58
19 |
20 | @doc """
21 | Start the token server. A token is acquired inside of init/1.
22 | """
23 |
24 | def start_link do
25 | GenServer.start_link(@tokenserver, [], name: @tokenserver)
26 | end
27 |
28 | @doc """
29 | Returns the current access token.
30 | """
31 |
32 | def token do
33 | GenServer.call(@tokenserver, :token)
34 | end
35 |
36 | @doc """
37 | Issues a new token manually.
38 | """
39 |
40 | # NOTE: The request is sent to the server with send so that it is handled by
41 | # the handle_info callback.
42 |
43 | def acquire_token do
44 | send(@tokenserver, :new_token)
45 | end
46 |
47 | def init(_) do
48 | case Config.is_configured? do
49 | true ->
50 | token_state = Authentication.request_token
51 | schedule_token_request
52 | {:ok, token_state}
53 | false ->
54 | {:ok, :no_token}
55 | end
56 | end
57 |
58 | def handle_call(:token, _from, token_state) do
59 | {:reply, token_state, token_state}
60 | end
61 |
62 | def handle_info(:new_token, _token_state) do
63 | token_state = Authentication.request_token
64 | schedule_token_request
65 | {:noreply, token_state}
66 | end
67 |
68 | defp schedule_token_request do
69 | Process.send_after(@tokenserver, :new_token, @token_interval)
70 | end
71 |
72 | end
73 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/requestserver.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.RequestServer do
2 | use GenServer
3 |
4 | @moduledoc """
5 | Rate limits requests and distributes responses.
6 | """
7 |
8 | alias ElixirPlusReddit.Request
9 | alias ElixirPlusReddit.Parser
10 | alias ElixirPlusReddit.RequestQueue
11 |
12 | @requestserver __MODULE__
13 | @request_interval 1000
14 | @no_request_interval 500
15 |
16 | def start_link do
17 | GenServer.start_link(@requestserver, [], name: @requestserver)
18 | end
19 |
20 | def init(_) do
21 | schedule_request(@no_request_interval)
22 | {:ok, :no_state}
23 | end
24 |
25 | def handle_info(:next_request, :no_state) do
26 | case RequestQueue.is_empty? do
27 | true ->
28 | schedule_request(@no_request_interval)
29 | {:noreply, :no_state}
30 | false ->
31 | %{from: from, tag: tag} = request_data = RequestQueue.peek_request
32 | resp = retry_until_success(request_data)
33 | RequestQueue.dequeue_request
34 | send_response(from, {tag, resp})
35 | schedule_request(@request_interval)
36 | {:noreply, :no_state}
37 | end
38 | end
39 |
40 | defp send_response(from, {tag, resp}) do
41 | send(from, {tag, resp})
42 | end
43 |
44 | defp issue_request(%{method: :post} = request_data) do
45 | %{url: url, query: query, parse_strategy: strategy} = request_data
46 | Request.request(:post, url, query, strategy)
47 | end
48 |
49 | defp issue_request(%{method: :get, query: query} = request_data) do
50 | %{url: url, parse_strategy: strategy} = request_data
51 | Request.request(:get, url, query, strategy)
52 | end
53 |
54 | defp issue_request(%{method: :get} = request_data) do
55 | %{url: url, parse_strategy: strategy} = request_data
56 | Request.request(:get, url, strategy)
57 | end
58 |
59 | defp retry_until_success(request_data) do
60 | {resp, parse_strategy} = issue_request(request_data)
61 | case resp.status_code do
62 | 503 -> retry_until_success(request_data)
63 | _ -> resp |> Parser.parse(parse_strategy)
64 | end
65 | end
66 |
67 | defp schedule_request(interval) do
68 | Process.send_after(@requestserver, :next_request, interval)
69 | end
70 |
71 | end
72 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/paginator.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.Paginator do
2 | use GenServer
3 |
4 | @moduledoc """
5 | A generic interface for paginating through listings.
6 | """
7 |
8 | @paginator __MODULE__
9 |
10 | def paginate({module, function}, arguments) do
11 | start_link({module, function}, arguments)
12 | end
13 |
14 | def start_link({module, function}, [from|arguments]) do
15 | config = [
16 | from: from,
17 | module: module,
18 | function: function,
19 | arguments: arguments
20 | ]
21 |
22 | GenServer.start_link(@paginator, config, [])
23 | end
24 |
25 | def init(config) do
26 | process_request(config, self)
27 | {:ok, config}
28 | end
29 |
30 | def handle_info({tag, resp}, config) do
31 | send(config[:from], {tag, resp.children})
32 | case resp.after do
33 | nil ->
34 | send(config[:from], {tag, :complete})
35 | send(self(), :stop)
36 | {:noreply, config}
37 | id ->
38 | new_config = update_limit(config)
39 | cond do
40 | get_limit(new_config) <= 0 ->
41 | send(config[:from], {tag, :complete})
42 | send(self(), :stop)
43 | {:noreply, new_config}
44 | true ->
45 | new_config = update_id(new_config, id)
46 | process_request(new_config, self)
47 | {:noreply, new_config}
48 | end
49 | end
50 | end
51 |
52 | def handle_info(:stop, config) do
53 | {:stop, :normal, config}
54 | end
55 |
56 | def terminate(:normal, _config) do
57 | :ok
58 | end
59 |
60 | defp process_request([from: _from, module: m, function: x, arguments: a], server) do
61 | apply(m, x, [server|a])
62 | end
63 |
64 | defp get_limit(config) do
65 | Enum.reduce(config[:arguments], nil, fn(arg, acc) ->
66 | case is_list(arg) do
67 | true -> arg[:limit]
68 | false -> acc
69 | end
70 | end)
71 | end
72 |
73 | defp update_limit(config) do
74 | Keyword.update!(config, :arguments, fn(arguments) ->
75 | Enum.map(arguments, fn(arg) ->
76 | case is_list(arg) do
77 | true -> Keyword.update!(arg, :limit, fn(limit) -> limit - 100 end)
78 | false -> arg
79 | end
80 | end)
81 | end)
82 | end
83 |
84 | defp update_id(config, after_id) do
85 | Keyword.update!(config, :arguments, fn(arguments) ->
86 | Enum.map(arguments, fn(arg) ->
87 | case is_list(arg) do
88 | true -> Keyword.put(arg, :after, after_id)
89 | false -> arg
90 | end
91 | end)
92 | end)
93 | end
94 |
95 | end
96 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/api/comment.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.API.Comment do
2 | @moduledoc """
3 | An interface for getting comment information.
4 | """
5 |
6 | alias ElixirPlusReddit.RequestBuilder
7 | alias ElixirPlusReddit.RequestQueue
8 | alias ElixirPlusReddit.Paginator
9 | alias ElixirPlusReddit.Scheduler
10 |
11 | @comment __MODULE__
12 | @comment_base "https://oauth.reddit.com/comments"
13 | @default_priority 0
14 |
15 | @doc """
16 | Get an article's `new` comments.
17 |
18 | ### Parameters
19 |
20 | * `from`: The pid or name of the requester.
21 | * `tag`: Anything.
22 | * `article_id:` An article id.
23 | * `options`: The query. (Optional)
24 | * `priority`: The request's priority. (Optional)
25 |
26 | ### Options
27 |
28 | * `limit`: a value between 1-100
29 | * `after`: a fullname of a thing
30 | * `before`: a fullname of a thing
31 |
32 | ### Fields
33 |
34 | after
35 | before
36 | modhash
37 | children:
38 | author_flair_css_class
39 | gilded
40 | body_html
41 | quarantine
42 | report_reasons
43 | stickied
44 | created
45 | score
46 | user_reports
47 | over_18
48 | controversiality
49 | link_id
50 | edited
51 | downs
52 | mod_reports
53 | author_flair_text
54 | created_utc
55 | parent_id
56 | body
57 | likes
58 | ups
59 | distinguished
60 | link_author
61 | removal_reason
62 | replies
63 | name
64 | archived
65 | link_title
66 | subreddit
67 | author
68 | subreddit_id
69 | saved
70 | num_reports
71 | id
72 | score_hidden
73 | approved_by
74 | link_url
75 | banned_by
76 | """
77 |
78 | def new_comments(from, tag, article_id) do
79 | listing(from, tag, article_id, [sort: :new], @default_priority)
80 | end
81 |
82 | def new_comments(from, tag, article_id, options, priority) do
83 | options = Keyword.put(options, :sort, :new)
84 | listing(from, tag, article_id, options, priority)
85 | end
86 |
87 | def new_comments(from, tag, article_id, options) when is_list(options) do
88 | options = Keyword.put(options, :sort, :new)
89 | listing(from, tag, article_id, options, @default_priority)
90 | end
91 |
92 | def new_comments(from, tag, article_id, priority) do
93 | listing(from, tag, article_id, [sort: :new], priority)
94 | end
95 |
96 | defp listing(from, tag, article_id, options, priority) do
97 | url = "#{@comment_base}/#{article_id}"
98 | request_data = RequestBuilder.format_get(from, tag, url, options, :listing, priority)
99 | RequestQueue.enqueue_request(request_data)
100 | end
101 | end
102 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/api/identity.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.API.Identity do
2 |
3 | @moduledoc """
4 | An interface for getting information about the authenticated user.
5 | """
6 |
7 | alias ElixirPlusReddit.RequestQueue
8 | alias ElixirPlusReddit.RequestBuilder
9 | alias ElixirPlusReddit.Scheduler
10 |
11 | @identity __MODULE__
12 | @identity_base "https://oauth.reddit.com/api/v1/me"
13 | @default_priority 0
14 |
15 | @doc """
16 | Get information about the authenticated user.
17 |
18 | ### Parameters
19 |
20 | * `from`: The pid or name of the requester.
21 | * `tag`: Anything.
22 | * `priority`: The request's priority. (Optional)
23 |
24 | ### Fields
25 |
26 | comment_karma
27 | created
28 | created_utc
29 | gold_creddits
30 | gold_expiration
31 | has_mail
32 | has_mod_mail
33 | has_verified_email
34 | hide_from_robots
35 | id
36 | inbox_count
37 | is_gold
38 | is_mod
39 | is_suspended
40 | link_karma
41 | name
42 | over_18
43 | suspension_expiration_utc
44 | """
45 |
46 | def self_data(from, tag, priority \\ @default_priority) do
47 | request_data = RequestBuilder.format_get(from, tag, @identity_base, :no_data, priority)
48 | RequestQueue.enqueue_request(request_data)
49 | end
50 |
51 | @doc """
52 | Get the authenticated user's preferences.
53 |
54 | ### Parameters
55 |
56 | * `from`: The pid or name of the requester.
57 | * `tag`: Anything.
58 | * `priority`: The request's priority. (Optional)
59 |
60 | ### Fields
61 |
62 | highlight_controversial
63 | over_18
64 | enable_default_themes
65 | show_promote
66 | numsites
67 | private_feeds
68 | min_comment_score
69 | public_votes
70 | label_nsfw
71 | domain_details
72 | media
73 | hide_locationbar
74 | show_snoovatar
75 | show_flair
76 | monitor_mentions
77 | num_comments
78 | threaded_messages
79 | organic
80 | show_link_flair
81 | show_trending
82 | highlight_new_comments
83 | default_comment_sort
84 | compress
85 | min_link_score
86 | newwindow
87 | creddit_autorenew
88 | show_gold_expiration
89 | collapse_left_bar
90 | lang
91 | force_https
92 | media_preview
93 | store_visits
94 | no_profanity
95 | use_global_defaults
96 | content_langs
97 | hide_downs
98 | ignore_suggested_sort
99 | collapse_read_messages
100 | mark_messages_read
101 | research
102 | default_theme_sr
103 | threaded_modmail
104 | hide_ups
105 | clickgadget
106 | email_messages
107 | beta
108 | hide_ads
109 | show_stylesheets
110 | legacy_search
111 | public_server_seconds
112 | hide_from_robots
113 | """
114 |
115 | def prefs(from, tag, priority \\ @default_priority) do
116 | url = "#{@identity_base}/prefs"
117 | request_data = RequestBuilder.format_get(from, tag, url, :no_data, priority)
118 | RequestQueue.enqueue_request(request_data)
119 | end
120 |
121 | @doc """
122 | Get a list of the authenticated user's trophies.
123 |
124 | ### Parameters
125 |
126 | * `from`: The pid or name of the requester.
127 | * `tag`: Anything.
128 | * `priority`: The request's priority. (Optional)
129 |
130 | ### Fields
131 |
132 | award_id
133 | description
134 | icon_40
135 | icon_70
136 | id
137 | name
138 | url
139 | """
140 |
141 | def trophies(from, tag, priority \\ @default_priority) do
142 | url = "#{@identity_base}/trophies"
143 | request_data = RequestBuilder.format_get(from, tag, url, :trophies, priority)
144 | RequestQueue.enqueue_request(request_data)
145 | end
146 |
147 | @doc """
148 | Get information about the authenticated user on an interval. This is commonly used
149 | to check for periodically checking for new messages.
150 |
151 | ### Parameters
152 |
153 | * `interval`: A value in milliseconds.
154 |
155 | ### Other
156 |
157 | Refer to `Identity.self_data` documentation for other parameter and field information.
158 | """
159 |
160 | def stream_self_data(from, tag, interval) do
161 | Scheduler.schedule({@identity, :self_data}, [from, tag, @default_priority], interval)
162 | end
163 |
164 | def stream_self_data(from, tag, priority, interval) do
165 | Scheduler.schedule({@identity, :self_data}, [from, tag, priority], interval)
166 | end
167 |
168 | end
169 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/api/post.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.API.Post do
2 |
3 | @moduledoc """
4 | An interface for various post actions. These functions are generally
5 | delegated to.
6 | """
7 |
8 | alias ElixirPlusReddit.RequestQueue
9 | alias ElixirPlusReddit.RequestBuilder
10 |
11 | @comment_endpoint "https://oauth.reddit.com/api/comment"
12 | @submit_endpoint "https://oauth.reddit.com/api/submit"
13 | @compose_endpoint "https://oauth.reddit.com/api/compose"
14 | @default_priority 0
15 |
16 | @doc """
17 | Reply to a comment, submission or private message.
18 |
19 | ### Parameters
20 |
21 | * `from`: The pid or name of the requester.
22 | * `tag`: Anything.
23 | * `id`: A comma separated list of message ids.
24 | * `text`: The reply body.
25 | * `priority`: The request's priority. (Optional)
26 |
27 | ### Fields
28 |
29 | distinguished
30 | saved
31 | replies
32 | link_id
33 | report_reasons
34 | likes
35 | banned_by
36 | subreddit_id
37 | body_html
38 | archived?
39 | gilded
40 | score_hidden
41 | body
42 | created_utc
43 | ups
44 | stickied
45 | edited
46 | num_reports
47 | mod_reports
48 | approved_by
49 | errors
50 | user_reports
51 | name
52 | controversiality
53 | parent_id
54 | removal_reason
55 | author_flair_text
56 | created
57 | id
58 | downs
59 | author
60 | subreddit
61 | author_flair_css_class
62 | score
63 | """
64 |
65 | def reply(from, tag, id, text, priority \\ @default_priority) do
66 | query = [api_type: :json, text: text, thing_id: id]
67 | request_data = RequestBuilder.format_post(from, tag, @comment_endpoint, query, :reply, priority)
68 | RequestQueue.enqueue_request(request_data)
69 | end
70 |
71 |
72 | @doc """
73 | Submit a url submission.
74 |
75 | ### Parameters
76 |
77 | * `from`: The pid or name of the requester.
78 | * `tag`: Anything.
79 | * `subreddit`: The name of a subreddit.
80 | * `title`: The submission's title.
81 | * `url`: The submission's url.
82 | * `send_replies?`: Send submission replies to inbox. (true, false)
83 | * `priority`: The request's priority. (Optional)
84 |
85 | ### Fields
86 |
87 | id
88 | name
89 | url
90 | errors
91 | """
92 |
93 | def submit_url(from, tag, subreddit, title, url, send_replies?, priority \\ @default_priority) do
94 | query = [
95 | api_type: :json,
96 | sr: subreddit,
97 | resubmit: true,
98 | kind: :link,
99 | sendreplies: send_replies?,
100 | title: title,
101 | url: url
102 | ]
103 | request_data = RequestBuilder.format_post(from, tag, @submit_endpoint, query, :submission, priority)
104 | RequestQueue.enqueue_request(request_data)
105 | end
106 |
107 | @doc """
108 | Submit a text submission.
109 |
110 | ### Parameters
111 |
112 | * `from`: The pid or name of the requester.
113 | * `tag`: Anything.
114 | * `subreddit`: The name of a subreddit.
115 | * `title`: The submission's title.
116 | * `text`: The submission's text.
117 | * `send_replies?`: Send submission replies to inbox. (true, false)
118 | * `priority`: The request's priority. (Optional)
119 |
120 | ### Fields
121 |
122 | id
123 | name
124 | url
125 | errors
126 | """
127 |
128 | def submit_text(from, tag, subreddit, title, text, send_replies?, priority \\ @default_priority) do
129 | query = [
130 | api_type: :json,
131 | sr: subreddit,
132 | resubmit: true,
133 | kind: :self,
134 | sendreplies: send_replies?,
135 | title: title,
136 | text: text
137 | ]
138 | request_data = RequestBuilder.format_post(from, tag, @submit_endpoint, query, :submission, priority)
139 | RequestQueue.enqueue_request(request_data)
140 | end
141 |
142 | @doc """
143 | Start a new private conversation.
144 |
145 | ### Parameters
146 |
147 | * `from`: The pid or name of the requester.
148 | * `tag`: Anything.
149 | * `to`: The user receiving the message.
150 | * `subject`: The message's subject.
151 | * `text`: The message's text.
152 | * `priority`: The request's priority. (Optional)
153 |
154 | ### Fields
155 | errors
156 | """
157 |
158 | def compose(from, tag, to, subject, text, priority \\ @default_priority) do
159 | query = [api_type: :json, to: to, subject: subject, text: text]
160 | request_data = RequestBuilder.format_post(from, tag, @compose_endpoint, query, :compose, priority)
161 | RequestQueue.enqueue_request(request_data)
162 | end
163 |
164 | end
165 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/api/inbox.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.API.Inbox do
2 |
3 | @moduledoc """
4 | An interface for getting inbox related information and performing
5 | inbox related actions.
6 | """
7 |
8 | alias ElixirPlusReddit.RequestQueue
9 | alias ElixirPlusReddit.RequestBuilder
10 | alias ElixirPlusReddit.Paginator
11 |
12 | @message_base "https://oauth.reddit.com/message"
13 | @mark_read_endpoint "https://oauth.reddit.com/api/read_message"
14 | @default_priority 0
15 |
16 | defdelegate compose(from,
17 | tag,
18 | to,
19 | subject,
20 | text), to: ElixirPlusReddit.API.Post
21 |
22 | defdelegate compose(from,
23 | tag,
24 | to,
25 | subject,
26 | text,
27 | priority), to: ElixirPlusReddit.API.Post
28 |
29 | defdelegate reply(from,
30 | tag,
31 | id,
32 | text), to: ElixirPlusReddit.API.Post
33 |
34 | defdelegate reply(from,
35 | tag,
36 | id,
37 | text,
38 | priority), to: ElixirPlusReddit.API.Post
39 |
40 | @doc """
41 | Mark a message or list a messages as read.
42 |
43 | ### Parameters
44 |
45 | * `from`: The pid or name of the requester.
46 | * `tag`: Anything.
47 | * `id`: A comma separated list of message ids.
48 | * `priority`: The request's priority. (Optional)
49 |
50 | ### Fields
51 | empty map (%{})
52 | """
53 |
54 | def mark_read(from, tag, id, priority \\ @default_priority) do
55 | request_data = RequestBuilder.format_post(from, tag, @mark_read_endpoint, [id: id], :ok, priority)
56 | RequestQueue.enqueue_request(request_data)
57 | end
58 |
59 | @doc """
60 | Get the authenticated user's inbox.
61 |
62 | ### Parameters
63 |
64 | * `from`: The pid or name of the requester.
65 | * `tag`: Anything.
66 | * `options`: The query. (Optional)
67 | * `priority`: The request's priority. (Optional)
68 |
69 | ### Options
70 |
71 | * `mark`: true or false
72 | * `limit`: a value between 1-100
73 | * `after`: a fullname of a thing
74 | * `before`: a fullname of a thing
75 |
76 | ### Fields
77 |
78 | after
79 | before
80 | modhash
81 | children:
82 | author
83 | body
84 | body_html
85 | context
86 | created
87 | created_utc
88 | dest
89 | distinguished
90 | first_message
91 | first_message_name
92 | id
93 | likes
94 | link_title
95 | name
96 | new
97 | parent_id
98 | replies
99 | subject
100 | subreddit
101 | """
102 |
103 | def inbox(from, tag) do
104 | listing(from, tag, [], :inbox, @default_priority)
105 | end
106 |
107 | def inbox(from, tag, options) when is_list(options) do
108 | listing(from, tag, options, :inbox, @default_priority)
109 | end
110 |
111 | def inbox(from, tag, priority) do
112 | listing(from, tag, [], :inbox, priority)
113 | end
114 |
115 | @doc """
116 | Get the authenticated user's unread inbox.
117 |
118 | ### Parameters
119 |
120 | * `from`: The pid or name of the requester.
121 | * `tag`: Anything.
122 | * `options`: The query. (Optional)
123 | * `priority`: The request's priority. (Optional)
124 |
125 | ### Options
126 |
127 | * `mark`: true or false
128 | * `limit`: a value between 1-100
129 | * `after`: a fullname of a thing
130 | * `before`: a fullname of a thing
131 |
132 | ### Fields
133 |
134 | after
135 | before
136 | modhash
137 | children:
138 | author
139 | body
140 | body_html
141 | context
142 | created
143 | created_utc
144 | dest
145 | distinguished
146 | first_message
147 | first_message_name
148 | id
149 | likes
150 | link_title
151 | name
152 | new
153 | parent_id
154 | replies
155 | subject
156 | subreddit
157 | """
158 |
159 | def unread(from, tag) do
160 | listing(from, tag, [], :unread, @default_priority)
161 | end
162 |
163 | def unread(from, tag, options) when is_list(options) do
164 | listing(from, tag, options, :unread, @default_priority)
165 | end
166 |
167 | def unread(from, tag, priority) do
168 | listing(from, tag, [], :unread, priority)
169 | end
170 |
171 | def unread(from, tag, options, priority) do
172 | listing(from, tag, options, :unread, priority)
173 | end
174 |
175 | @doc """
176 | Get the authenticated user's sent inbox.
177 |
178 | ### Parameters
179 |
180 | * `from`: The pid or name of the requester.
181 | * `tag`: Anything.
182 | * `options`: The query. (Optional)
183 | * `priority`: The request's priority. (Optional)
184 |
185 | ### Options
186 |
187 | * `mark`: true or false
188 | * `limit`: a value between 1-100
189 | * `after`: a fullname of a thing
190 | * `before`: a fullname of a thing
191 |
192 | ### Fields
193 |
194 | after
195 | before
196 | modhash
197 | children:
198 | author
199 | body
200 | body_html
201 | context
202 | created
203 | created_utc
204 | dest
205 | distinguished
206 | first_message
207 | first_message_name
208 | id
209 | likes
210 | link_titleb
211 | name
212 | new
213 | parent_id
214 | replies
215 | subject
216 | subreddit
217 | """
218 |
219 | def sent(from, tag) do
220 | listing(from, tag, [], :sent, @default_priority)
221 | end
222 |
223 | def sent(from, tag, options) when is_list(options) do
224 | listing(from, tag, options, :sent, @default_priority)
225 | end
226 |
227 | def sent(from, tag, priority) do
228 | listing(from, tag, [], :sent, priority)
229 | end
230 |
231 | defp listing(from, tag, options, endpoint, priority) do
232 | url = "#{@message_base}/#{endpoint}"
233 | request_data = RequestBuilder.format_get(from, tag, url, options, :listing, priority)
234 | RequestQueue.enqueue_request(request_data)
235 | end
236 |
237 | end
238 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EPR - ElixirPlusReddit
2 |
3 | EPR is a wrapper for the Reddit API in Elixir. EPR strives to provide simple access to Reddit's API and a flexible
4 | interface.
5 |
6 | ## Installation
7 |
8 | Soon!
9 |
10 | ## Setting up your first script
11 |
12 | *Setting up your script on Reddit*
13 |
14 |
15 |
16 | *Configuring your credentials*
17 |
18 | Credentials can either be set inside of `config.exs` or be set manually after EPR is started.
19 |
20 | ```elixir
21 | config :elixirplusreddit, :creds, [
22 | username: "username",
23 | password: "password",
24 | client_id: "[client_id]",
25 | client_secret: "[secret]"
26 | ]
27 |
28 | config :elixirplusreddit, user_agent: "something meaningful"
29 | ```
30 |
31 | or
32 |
33 | ```elixir
34 | Config.set_credentials("username",
35 | "password",
36 | "[client_id]",
37 | "[secret]",
38 | "something meaningful here")
39 | `````
40 |
41 |
42 | ## A gentle introduction
43 |
44 | Now that you've got your script set up, let's talk a little bit about how EPR works so we can start talking to
45 | Reddit! Everytime you make a request it is sent to EPR's request server and stuck inside of a queue. It is issued
46 | and processed at a later time. This is how rate limiting is implemented, requests are issued on an interval that
47 | complies with Reddit's API terms. (*Currently static, hoping to support dymamic ratelimiting in the future*)
48 | All requests, no matter what data you are requesting, expect a pid or name `from` and a tag `tag`. All `from` is,
49 | is who is asking for the data so the request server knows who to send it back to. A tag can be anything and is simply
50 | a way to pattern match on responses when they are received. This may seem cumbersome now but it starts to become
51 | much more intuitive in practice. If you followed the installation instructions and your script is set up,
52 | jump to your project's directory and run `iex -S mix`, if not go read the installation instructions and set up your script!
53 |
54 | Before we do anything, I suggest defining a couple of aliases, this is solely for the sake of your fingers and is not mandatory.
55 |
56 | ```elixir
57 | iex(1)> alias ElixirPlusReddit.TokenServer
58 | nil
59 | iex(2)> alias ElixirPlusReddit.API.Identity
60 | nil
61 | ```
62 |
63 | When EPR is started a token is automatically acquired for us and a new one is acquired automatically when necessary,
64 | this can easily be verified. *NOTE: This assumes that credentials were set inside of config.exs, if they were set manually
65 | call* `TokenServer.acquire_token` *first. All subsequent tokens will be acquired automatically.*
66 |
67 | ```elixir
68 | iex(4)> TokenServer.token
69 | "bearer xxxxxxxx-xxxxxxxxxxxxxxxxxxx_xxxxxxx"
70 | ```
71 |
72 | Okay, good. Now that we know we're authenticated and ready to make requests, let's ask Reddit about ourselves!
73 |
74 | ```elixir
75 | iex(5)> Identity.self_data(self(), :me)
76 | :ok
77 | ```
78 |
79 | I'm sure most people who are reading this already know that `self` is just the current process's pid, `:me` is
80 | the value that is going to accompany our response. Hey, these are actually the `from` and `tag` parameters that
81 | I mentioned earlier. So if you're wondering where the hell our response is, assuming that the message didn't
82 | take a wrong turn and an ample amount of time has passed, it's in our mailbox. Let's flush it out!
83 |
84 | ```elixir
85 | iex(6)> flush
86 | {:me,
87 | %{comment_karma: 8,
88 | created: 1453238732.0,
89 | created_utc: 1453209932.0,
90 | gold_creddits: 0,
91 | gold_expiration: nil,
92 | has_mail: false,
93 | has_mod_mail: false
94 | has_verified_email: true,
95 | hide_from_robots: false,
96 | id: "txy38",
97 | inbox_count: 0,
98 | is_gold: false,
99 | is_mod: false,
100 | is_suspended: false,
101 | link_karma: 14,
102 | name: "elixirplusreddit",
103 | over_18: false,
104 | suspension_expiration_utc: nil}}
105 | ```
106 |
107 | Oh look, that's me. As you can see, all keys are stored as atoms, this allows them to be accessed with the dot syntax. Once again,
108 | you probably already knew that but I'll show you that later anyways. Right now we have a bigger problem! This isn't particularly
109 | useful if there's no way to grab the information out of our mailbox. You know... So we can do stuff with it? Let's write a
110 | little utility function to do exactly that and try again.
111 |
112 | ```elixir
113 | iex(7)> capture = fn(tag) ->
114 | ...(7)> receive do
115 | ...(7)> {^tag, response} -> response
116 | ...(7)> after
117 | ...(7)> 5000 -> "Nope, nothing, nothing at all."
118 | ...(7)> end
119 | ...(7)> end
120 | #Function
121 | iex(8)> Identity.self_data(self(), :me)
122 | :ok
123 | iex(9)> response = capture.(:me)
124 | %{comment_karma: 9,
125 | created: 1453238732.0,
126 | created_utc: 1453209932.0,
127 | gold_creddits: 0,
128 | gold_expiration: nil,
129 | has_mail: false,
130 | has_mod_mail: false,
131 | has_verified_email: true,
132 | hide_from_robots: false,
133 | id: "txy38",
134 | inbox_count: 0,
135 | is_gold: false,
136 | is_mod: false,
137 | is_suspended: false,
138 | link_karma: 14,
139 | name: "elixirplusreddit",
140 | over_18: false,
141 | suspension_expiration_utc: nil}
142 | iex(10)> IO.puts("Hey, look at me, I am #{response.name}!")
143 | Hey, look at me, I am elixirplusreddit!
144 | :ok
145 | iex(11)> response = capture.(:me)
146 | "Nope, nothing, nothing at all." # After five very dramatic seconds.
147 | ```
148 |
149 | The anonymous function `capture` takes a tag and matches it against our mailbox, if it finds something within five seconds
150 | it grabs it, otherwise we're left with a disappointing message. That's pretty much the gist of it, honestly. If you understand this
151 | you already have the proper foundation for making neat stuff. Next we'll take a brief tour of EPR's most important bits
152 | and then finally put it to practice and write a (useless) bot!
153 |
154 | ## A non-comprehensive tour of EPR
155 |
156 | Here we'll take a look at some of EPR's different functionality and discuss certain implementation details. If you're following
157 | along I would suggest reconfiguring your iex session before hopping into the next part.
158 |
159 | ```elixir
160 | iex(12)> IEx.configure(inspect: [limit: 5])
161 | ```
162 |
163 | We're going to dealing with larger data and this will make it truncate earlier (default is 25) instead of flooding your terminal.
164 |
165 | #### Comments and submissions
166 |
167 | I think this is the most natural place to begin. No matter what you're writing, you're probably going to have to deal with
168 | gathering comments or submissions from a user or a subreddit. EPR offers a variety of functions for achieving this, so let's
169 | just jump right in and give it a try. Our goal is to gather the last 100 comments posted to /r/Elixir.
170 |
171 | ```elixir
172 | iex(13)> alias ElixirPlusReddit.API.Subreddit
173 | nil
174 | iex(14)> Subreddit.new_comments(self(), :elixir_comments, :elixir, [limit: 100])
175 | :ok
176 | ```
177 |
178 | As before, `self()` is where the response is going to be sent, `:elixir_comments` is the tag that'll come with the response.
179 | `:elixir` and `[limit: 100]` are what we're interested. `:elixir` is the name of the subreddit, which can either be an atom or
180 | a string. `[limit: 100]` is our query options. Options differ based on what data you're requesting. The `limit` option specifies
181 | how many items we want from the listing. It's important to note that 25 is the default and is used when the limit you specify is
182 | greater than 100 or less than 0. Options are entirely optional (*ha*) and can be omitted, in this case, as you would expect, the
183 | default values that Reddit's API specifies will be used. Now, let's grab our response and see what's up.
184 |
185 | ```elixir
186 | iex(15)> response = capture.(:elixir_comments)
187 | %{after: "t1_xxxxxxx",
188 | before: nil,
189 | children: [%{author_flair_css_class: nil, ...}, %{...}, ...],
190 | modhash: nil}
191 | ```
192 |
193 | Inside of the `children` field is where all of the comments and their data reside. Each child has many fields but we're interested in
194 | the author of the comment and the comment itself. They are stored in the fields `author` and `body` respectively. Let's check out
195 | who's talking about what!
196 |
197 | ```elixir
198 | iex(16)> Enum.each(response.children, fn(child) ->
199 | ...(16)> IO.puts("#{child.author}: #{child.body}\n")
200 | ...(16)> end)
201 | ```
202 |
203 | I'm not going to show you the output because it's going to be very large but it's that simple. Now what if we had to gather the `next`
204 | 100 comments? One way would be to take our last response's after id from the `after` field and include it in our next request
205 | as an option with the key `after`. Let's do that.
206 |
207 | ```elixir
208 | iex(17)> Subreddit.new_comments(self(), :elixir_comments, :elixir, [limit: 100, after: response.after])
209 | :ok
210 | ```
211 |
212 | What we have acheived here is pagination. In practice, you would never do it this way because EPR has built in pagination
213 | and streaming. I saw no reason to preclude manual pagination and it's nice to know what's happening behind the scenes. Okay, let's
214 | talk about pagination and streaming now. Actually, you should experiment a bit first! Try to get a user's top submissions and print
215 | the upvote count and submission title. Type `h ElixirPlusReddit.API.User.top_submissions` in your shell to get started. Seriously,
216 | moving on now.
217 |
218 | #### Pagination and streaming
219 |
220 |
221 | ##### Pagination
222 |
223 | Often times you'll want to get more than 100 items from a listing. As demonstrated in the previous section, it isn't difficult to
224 | manually pass around the after id but it is not necessary. Let's try paginating through a user's top comments and then talk about
225 | what's happening.
226 |
227 | ```elixir
228 | iex(18)> alias ElixirPlusReddit.API.User
229 | nil
230 | iex(19)> {:ok, pid} = User.paginate_top_comments(self(), :comments, :hutsboR) # I'm hutsboR!
231 | {:ok, #PID}
232 | ```
233 |
234 | The first thing you probably noticed is that when we invoke a paginator function a pid is returned with the `:ok` atom. This is
235 | because paginators are implemented on top of genservers, they chug away at your request in a separate process and send you data as it
236 | becomes available. When there's no more data left to be acquired the genserver will gracefully shutdown and clean itself up. To
237 | see this in action repeatedly call `Process.is_alive?(pid)`.
238 |
239 | Another thing you probably noticed is that I didn't specify any options, most notably a limit. Paginators specify a default limit
240 | of 1000, which is also the most items that you can fetch from a listing. Understand that this is not a limit imposed by EPR but
241 | by Reddit. If the resource you're fetching from has less items than the limit, it will give you everything that it has to offer.
242 | Generous right? Anyways, let's see what's in our mailbox.
243 |
244 | ```elixir
245 | iex(20)> response = capture.(:comments)
246 | [%{user_reports: [], banned_by: nil, link_id: "t3_xxxxxx", ...},
247 | %{user_reports: [], banned_by: nil, ...}, %{user_reports: [], ...}, %{...},
248 | ...]
249 | ```
250 |
251 | That's a list of the top 100 comments. An important difference in return structure relative to our example in the last section
252 | is that paginators don't bother to return the before and after ids. (and a couple other fields we don't need) It simply returns
253 | what we referred to as `children` before. That's only the first 100 comments though, right? More comments have probably been
254 | delivered to our mailbox by now. Let's just flush them out.
255 |
256 | ```elixir
257 | iex(21)> flush
258 | {:comments,
259 | [%{user_reports: [], banned_by: nil, link_id: "t3_xxxxxx", ...},
260 | %{user_reports: [], banned_by: nil, ...}, %{user_reports: [], ...}, %{...},
261 | ...]}
262 | {:comments,
263 | [%{user_reports: [], banned_by: nil, link_id: "t3_xxxxxx", ...},
264 | %{user_reports: [], banned_by: nil, ...}, %{user_reports: [], ...}, %{...},
265 | ...]}
266 | {:comments,
267 | [%{user_reports: [], banned_by: nil, link_id: "t3_xxxxxx", ...},
268 | %{user_reports: [], banned_by: nil, ...}, %{user_reports: [], ...}, %{...},
269 | ...]}
270 | {:comments,
271 | [%{user_reports: [], banned_by: nil, link_id: "t3_xxxxxx", ...},
272 | %{user_reports: [], banned_by: nil, ...}, %{user_reports: [], ...}, %{...},
273 | ...]}
274 | {:comments,
275 | [%{user_reports: [], banned_by: nil, link_id: "t3_xxxxxx", ...},
276 | %{user_reports: [], banned_by: nil, ...}, %{user_reports: [], ...}, %{...},
277 | ...]}
278 | {:comments,
279 | [%{user_reports: [], banned_by: nil, link_id: "t3_xxxxxx", ...},
280 | %{user_reports: [], banned_by: nil, ...}, %{user_reports: [], ...}, %{...},
281 | ...]}
282 | {:comments, :complete}
283 | :ok
284 | ```
285 |
286 | There's the rest of the comments. Wait, what's that at the bottom? That's actually the interesting bit. When a paginator
287 | is done paginating it sends one last dying message to let us know that is it has completed it's sole duty. This is important
288 | because otherwise there is no convenient, reliable way to tell if we've received all the data that we've asked for. Now let's talk
289 | about streaming, a simple but useful feature.
290 |
291 | ##### Streaming
292 |
293 | Unlike pagination, streaming is simply requesting the *same* data indefinitely on an interval. Remember the first example we did in there
294 | introduction section where we got collected a little bit of data about ourselves by calling `Identity.self_data`? The response
295 | structure has a field called `has_mail` which lets us know if we have some form of mail. This includes username mentions, comment and submission replies, messages and so forth. Now what if we write a bot where it's critical to respond to mail and we need to
296 | periodically check for mail? Well let's just take care of that, luckily that's built in.
297 |
298 | ```elixir
299 | iex(22)> Identity.stream_self_data(self(), :me, 30000)
300 | {:ok, #PID}
301 | ```
302 |
303 | Notice the function name `stream_self_data`, some functions have stream implementations built in and their names
304 | are generally preceded by `stream`. The argument `30000` is how often in milliseconds that the request
305 | should be made. Do we have mail?
306 |
307 | ```elixir
308 | iex(23)> capture.(:me).has_mail
309 | false
310 | ```
311 |
312 | Nope, not this time. That's okay, we'll check again (*and again and again and again*) later. Another and probably the most common
313 | use case of streams is checking a subreddit for new comments that need attention. The `Subreddit` and `User`
314 | modules only have two functions for streaming comments and submissions and they fetch data with the `new` sort option.
315 | In other words, there's no built in support for streaming `hot`, `top` or other sortings. I might add
316 | built in support in the future but I figured that they have very limited uses unless you're streaming them on a large interval.
317 | As an example, let's say we need to check for new submissions to /r/Elixir.
318 |
319 | ```elixir
320 | iex(24)> Subreddit.stream_submissions(self(), :elixir_submissions, :elixir, [limit: 10], 1000 * 60 * 10)
321 | {:ok, #PID}
322 | ```
323 |
324 | Now, every 10 minutes we'll get the 10 `newest` submissions. It's important to understand that this doesn't know which submissions
325 | we've seen and as a consequence there is a chance we will see the same submissions. It is the programmer's responsibility to provide
326 | a way to handle duplicate submissions. As a rule of thumb, use a smaller limit and larger interval for relatively inactive subreddits and
327 | users. Pretty simple right? Next we'll take a quick peek at writing custom paginators and streams.
328 |
329 | ##### Implementing custom paginators and streams
330 |
331 | Implementing your own paginators and streams isn't difficult. Let's pretend that we lived in a world where there was no way to
332 | automatically paginate a user's newest comments. We know that `User.new_comments` exists to get a single chunk of a user's comments.
333 | This alone is all we need to expand it into a paginator. Let's do that.
334 |
335 | ```elixir
336 | iex(25)> Paginator.paginate({User, :new_comments}, [self(), :my_comments, :hutsboR, [limit: 1000], 0])
337 | {:ok, #PID}
338 | ```
339 |
340 | Let's break down the arguments. The tuple contains two elements, the first being the module name and the second being the function name.
341 | So it reads "The function `new_comments` from the module `User`". This is the resource that we will be paginating from. The next argument
342 | is well, `new_comments`'s arguments. We already know that `self` and `:my_comments` are the pid and the tag.
343 | `:hutsboR` is the username, `[limit: 1000]` is the list of options and `0` is.. Well, pretend you didn't
344 | see that. We'll discuss that later. Your paginator will only work if the function you're trying to turn into a paginator returns a `listing`.
345 | The function's return structure must have the `after` and `before` fields. You can always type `h ElixirPlusReddit.API. ...` in your shell
346 | to quickly and conveniently find out. I'm sure I don't need to tell you this but typically this would be wrapped in a function opposed to
347 | manually providing the username and other arguments. In fact, this is exactly how `User.paginate_new_comments` is implemented. Makes sense.
348 |
349 | Okay, okay, let's write a custom stream now. Let's imagine (*seriously, imagine, this is contrived*) that for some reason you need to receive a
350 | list of a user's trophies every so often. As we did with our paginator, we can build this on top of the existing API function `Identity.trophies`. This will look quite similar to our paginator, take a look.
351 |
352 | ```elixir
353 | iex(26)> Scheduler.schedule({Identity, :trophies}, [self(), :my_comments, 0], 10000)
354 | {:ok, #PID}
355 | ```
356 |
357 | Not too different, no? Again, `Identity` is the module and `:trophies` is the function. We know `self` is our pid and `:my_comments` is the tag
358 | we chose. Again, that weird `0` which we will ignore. Like the other streams we wrote we need to provide a millisecond interval that the function should be invoked on, that's our `10000` argument, as expected. Notice the module name, `Scheduler`. Streams are built on top of a generic
359 | scheduling implementation that simply issues API requests on an interval, no magic, no internal state, nothing. Moving on, let's talk about
360 | the `0` argument.
361 |
362 |
363 | #### The *priority* queue
364 |
365 | I mentioned the request queue way back in the introduction but what I *forgot* to mention is that it's actually a priority queue. What does
366 | that mean, though? It means that you can force important requests to be served immediately while other requests scoff at you for allowing them
367 | to be put on the back burner. So, how do we specify a priority? The `0`s that I buttered you up with in the previous section is the requests' priorities and the default priority that EPR uses for all requests. The priority is not required, hence why we have never specified it outside of last section. (Like I said, I had to butter you up... and I needed a smooth seque.) The last argument of all API requests is an optionial priority. Requests with the same priority are taken care of in the order that they are placed in the queue.
368 | Valid priorities are the values `-20` through `20`, where the **lower the value the higher priority**. ... No, you're reading that correctly. This
369 | convention is inherited from okeuday's [pqueue implementation](https://github.com/okeuday/pqueue) and is used in many other pqueue
370 | implementations. Why? I don't really know. This concept a little more difficult to illustrate than others but is possible with paginators.
371 | Time to get to work, I hope you're fast.
372 |
373 | ```elixir
374 | iex(27)> higher_priority = -1
375 | -1
376 | iex(28)> Subreddit.paginate_comments(self(), :pri_demo_one, :learnprogramming)
377 | {:ok, #PID}
378 | iex(29)> flush
379 | {:pri_demo_one,
380 | [%{stickied: false, from_id: nil, permalink: "...", ...}, ...]}
381 | :ok
382 | iex(30)> Subreddit.paginate_comments(self(), :pri_demo_two, :programming, higher_priority)
383 | {:ok, #PID}
384 | iex(31)> flush # After a little while...
385 | {:pri_demo_one,
386 | [%{stickied: false, from_id: nil, permalink: "...", ...}, ...]}
387 | {:pri_demo_two,
388 | [%{stickied: false, from_id: nil, permalink: "...", ...}, ...]}
389 | {:pri_demo_two,
390 | [%{stickied: false, from_id: nil, permalink: "...", ...}, ...]}
391 | .
392 | .
393 | .
394 | {:pri_demo_two, :complete}
395 | {:pri_demo_one,
396 | [%{stickied: false, from_id: nil, permalink: "...", ...}, ...]}
397 | .
398 | .
399 | .
400 | {:pri_demo_one, :complete}
401 | :ok
402 | ```
403 |
404 | Like I said, a little difficult to illustrate and the output is a little messy but as you can see that we queued up `:pri_demo_one` first but
405 | because `:pri_demo_two` has a higher priority it finished first. Often times including a priority isn't important but one example of when it's
406 | useful is streaming on a short interval and you need to reply to a comment or submission immediately. When it becomes necessary you will probably
407 | recognize it quickly, so know don't forget that it exists! We're getting close to the end, let's continue!
408 |
409 | #### Replies, submissions and messages
410 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/api/subreddit.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.API.Subreddit do
2 |
3 | @moduledoc """
4 | An interface for getting subreddit information.
5 | """
6 |
7 | alias ElixirPlusReddit.RequestQueue
8 | alias ElixirPlusReddit.RequestBuilder
9 | alias ElixirPlusReddit.Paginator
10 | alias ElixirPlusReddit.Scheduler
11 |
12 | @subreddit __MODULE__
13 | @subreddit_base "https://oauth.reddit.com/r"
14 | @default_priority 0
15 |
16 | defdelegate submit_url(from,
17 | tag,
18 | subreddit,
19 | title,
20 | url,
21 | send_replies?), to: ElixirPlusReddit.API.Post
22 |
23 | defdelegate submit_url(from,
24 | tag,
25 | subreddit,
26 | title,
27 | url,
28 | send_replies?,
29 | priority), to: ElixirPlusReddit.API.Post
30 |
31 | defdelegate submit_text(from,
32 | tag,
33 | subreddit,
34 | title,
35 | text,
36 | send_replies?), to: ElixirPlusReddit.API.Post
37 |
38 | defdelegate submit_text(from,
39 | tag,
40 | subreddit,
41 | title,
42 | text,
43 | send_replies?,
44 | priority), to: ElixirPlusReddit.API.Post
45 |
46 | @doc """
47 | Get a subreddit's `hot` submissions.
48 |
49 | ### Parameters
50 |
51 | * `from`: The pid or name of the requester.
52 | * `tag`: Anything.
53 | * `subreddit`: A subreddit name or id.
54 | * `options`: The query. (Optional)
55 | * `priority`: The request's priority. (Optional)
56 |
57 | ### Options
58 |
59 | * `limit`: a value between 1-100
60 | * `after`: a fullname of a thing
61 | * `before`: a fullname of a thing
62 |
63 | ### Fields
64 |
65 | after
66 | before
67 | modhash
68 | children:
69 | link_flair_css_class
70 | author_flair_css_class
71 | thumbnail
72 | gilded
73 | quarantine
74 | report_reasons
75 | selftext_html
76 | stickied
77 | domain
78 | created
79 | score
80 | num_comments
81 | secure_media_embed
82 | user_reports
83 | over_18
84 | suggested_sort
85 | url
86 | edited
87 | downs
88 | mod_reports
89 | author_flair_text
90 | hide_score
91 | created_utc
92 | from_kind
93 | likes
94 | is_self
95 | ups
96 | distinguished
97 | media
98 | selftext
99 | removal_reason
100 | name
101 | link_flair_text
102 | from
103 | archived
104 | subreddit
105 | hidden
106 | locked
107 | author
108 | subreddit_id
109 | visited
110 | saved
111 | media_embed
112 | from_id
113 | num_reports
114 | id
115 | secure_media
116 | permalink
117 | approved_by
118 | title
119 | clicked
120 | banned_by
121 | """
122 |
123 | def hot(from, tag, subreddit) do
124 | listing(from, tag, subreddit, [], :hot, @default_priority)
125 | end
126 |
127 | def hot(from, tag, subreddit, options, priority) do
128 | listing(from, tag, subreddit, options, :hot, priority)
129 | end
130 |
131 | def hot(from, tag, subreddit, options) when is_list(options) do
132 | listing(from, tag, subreddit, options, :hot, @default_priority)
133 | end
134 |
135 | def hot(from, tag, subreddit, priority) do
136 | listing(from, tag, subreddit, [], :hot, priority)
137 | end
138 |
139 | @doc """
140 | Get a subreddit's `new` submissions.
141 |
142 | ### Parameters
143 |
144 | * `from`: The pid or name of the requester.
145 | * `tag`: Anything.
146 | * `subreddit`: A subreddit name or id.
147 | * `options`: The query. (Optional)
148 | * `priority`: The request's priority. (Optional)
149 |
150 | ### Options
151 |
152 | * `limit`: a value between 1-100
153 | * `after`: a fullname of a thing
154 | * `before`: a fullname of a thing
155 |
156 | ### Fields
157 |
158 | after
159 | before
160 | modhash
161 | children:
162 | link_flair_css_class
163 | author_flair_css_class
164 | thumbnail
165 | gilded
166 | quarantine
167 | report_reasons
168 | selftext_html
169 | stickied
170 | domain
171 | created
172 | score
173 | num_comments
174 | secure_media_embed
175 | user_reports
176 | over_18
177 | suggested_sort
178 | url
179 | edited
180 | downs
181 | mod_reports
182 | author_flair_text
183 | hide_score
184 | created_utc
185 | from_kind
186 | likes
187 | is_self
188 | ups
189 | distinguished
190 | media
191 | selftext
192 | removal_reason
193 | name
194 | link_flair_text
195 | from
196 | archived
197 | subreddit
198 | hidden
199 | locked
200 | author
201 | subreddit_id
202 | visited
203 | saved
204 | media_embed
205 | from_id
206 | num_reports
207 | id
208 | secure_media
209 | permalink
210 | approved_by
211 | title
212 | clicked
213 | banned_by
214 | """
215 |
216 | def new(from, tag, subreddit) do
217 | listing(from, tag, subreddit, [], :new, @default_priority)
218 | end
219 |
220 | def new(from, tag, subreddit, options, priority) do
221 | listing(from, tag, subreddit, options, :new, priority)
222 | end
223 |
224 | def new(from, tag, subreddit, options) when is_list(options) do
225 | listing(from, tag, subreddit, options, :new, @default_priority)
226 | end
227 |
228 | def new(from, tag, subreddit, priority) do
229 | listing(from, tag, subreddit, [], :new, priority)
230 | end
231 |
232 | @doc """
233 | Get a subreddit's `rising` submissions.
234 |
235 | ### Parameters
236 |
237 | * `from`: The pid or name of the requester.
238 | * `tag`: Anything.
239 | * `subreddit`: A subreddit name or id.
240 | * `options`: The query. (Optional)
241 | * `priority`: The request's priority. (Optional)
242 |
243 | ### Options
244 |
245 | * `limit`: a value between 1-100
246 | * `after`: a fullname of a thing
247 | * `before`: a fullname of a thing
248 |
249 | ### Fields
250 |
251 | after
252 | before
253 | modhash
254 | children:
255 | link_flair_css_class
256 | author_flair_css_class
257 | thumbnail
258 | gilded
259 | quarantine
260 | report_reasons
261 | selftext_html
262 | stickied
263 | domain
264 | created
265 | score
266 | num_comments
267 | secure_media_embed
268 | user_reports
269 | over_18
270 | suggested_sort
271 | url
272 | edited
273 | downs
274 | mod_reports
275 | author_flair_text
276 | hide_score
277 | created_utc
278 | from_kind
279 | likes
280 | is_self
281 | ups
282 | distinguished
283 | media
284 | selftext
285 | removal_reason
286 | name
287 | link_flair_text
288 | from
289 | archived
290 | subreddit
291 | hidden
292 | locked
293 | author
294 | subreddit_id
295 | visited
296 | saved
297 | media_embed
298 | from_id
299 | num_reports
300 | id
301 | secure_media
302 | permalink
303 | approved_by
304 | title
305 | clicked
306 | banned_by
307 | """
308 |
309 | def rising(from, tag, subreddit) do
310 | listing(from, tag, subreddit, [], :rising, @default_priority)
311 | end
312 |
313 | def rising(from, tag, subreddit, options, priority) do
314 | listing(from, tag, subreddit, options, :rising, priority)
315 | end
316 |
317 | def rising(from, tag, subreddit, options) when is_list(options) do
318 | listing(from, tag, subreddit, options, :rising, @default_priority)
319 | end
320 |
321 | def rising(from, tag, subreddit, priority) do
322 | listing(from, tag, subreddit, [], :rising, priority)
323 | end
324 |
325 | @doc """
326 | Get a subreddit's `controversial` submissions.
327 |
328 | ### Parameters
329 |
330 | * `from`: The pid or name of the requester.
331 | * `tag`: Anything.
332 | * `subreddit`: A subreddit name or id.
333 | * `options`: The query. (Optional)
334 | * `priority`: The request's priority. (Optional)
335 |
336 | ### Options
337 |
338 | * `limit`: a value between 1-100
339 | * `t`: hour, day, week, month, year, all
340 | * `after`: a fullname of a thing
341 | * `before`: a fullname of a thing
342 |
343 | ### Fields
344 |
345 | after
346 | before
347 | modhash
348 | children:
349 | link_flair_css_class
350 | author_flair_css_class
351 | thumbnail
352 | gilded
353 | quarantine
354 | report_reasons
355 | selftext_html
356 | stickied
357 | domain
358 | created
359 | score
360 | num_comments
361 | secure_media_embed
362 | user_reports
363 | over_18
364 | suggested_sort
365 | url
366 | edited
367 | downs
368 | mod_reports
369 | author_flair_text
370 | hide_score
371 | created_utc
372 | from_kind
373 | likes
374 | is_self
375 | ups
376 | distinguished
377 | media
378 | selftext
379 | removal_reason
380 | name
381 | link_flair_text
382 | from
383 | archived
384 | subreddit
385 | hidden
386 | locked
387 | author
388 | subreddit_id
389 | visited
390 | saved
391 | media_embed
392 | from_id
393 | num_reports
394 | id
395 | secure_media
396 | permalink
397 | approved_by
398 | title
399 | clicked
400 | banned_by
401 | """
402 |
403 | def controversial(from, tag, subreddit) do
404 | listing(from, tag, subreddit, [], :controversial, @default_priority)
405 | end
406 |
407 | def controversial(from, tag, subreddit, options, priority) do
408 | listing(from, tag, subreddit, options, :controversial, priority)
409 | end
410 |
411 | def controversial(from, tag, subreddit, options) when is_list(options) do
412 | listing(from, tag, subreddit, options, :controversial, @default_priority)
413 | end
414 |
415 | def controversial(from, tag, subreddit, priority) do
416 | listing(from, tag, subreddit, [], :controversial, priority)
417 | end
418 |
419 | @doc """
420 | Get a subreddit's `top` submissions.
421 |
422 | ### Parameters
423 |
424 | * `from`: The pid or name of the requester.
425 | * `tag`: Anything.
426 | * `subreddit`: A subreddit name or id.
427 | * `options`: The query. (Optional)
428 | * `priority`: The request's priority. (Optional)
429 |
430 | ### Options
431 |
432 | * `limit`: a value between 1-100
433 | * `t`: hour, day, week, month, year, all
434 | * `after`: a fullname of a thing
435 | * `before`: a fullname of a thing
436 |
437 | ### Fields
438 |
439 | after
440 | before
441 | modhash
442 | children:
443 | link_flair_css_class
444 | author_flair_css_class
445 | thumbnail
446 | gilded
447 | quarantine
448 | report_reasons
449 | selftext_html
450 | stickied
451 | domain
452 | created
453 | score
454 | num_comments
455 | secure_media_embed
456 | user_reports
457 | over_18
458 | suggested_sort
459 | url
460 | edited
461 | downs
462 | mod_reports
463 | author_flair_text
464 | hide_score
465 | created_utc
466 | from_kind
467 | likes
468 | is_self
469 | ups
470 | distinguished
471 | media
472 | selftext
473 | removal_reason
474 | name
475 | link_flair_text
476 | from
477 | archived
478 | subreddit
479 | hidden
480 | locked
481 | author
482 | subreddit_id
483 | visited
484 | saved
485 | media_embed
486 | from_id
487 | num_reports
488 | id
489 | secure_media
490 | permalink
491 | approved_by
492 | title
493 | clicked
494 | banned_by
495 | """
496 |
497 | def top(from, tag, subreddit) do
498 | listing(from, tag, subreddit, [], :top, @default_priority)
499 | end
500 |
501 | def top(from, tag, subreddit, options, priority) do
502 | listing(from, tag, subreddit, options, :top, priority)
503 | end
504 |
505 | def top(from, tag, subreddit, options) when is_list(options) do
506 | listing(from, tag, subreddit, options, :top, @default_priority)
507 | end
508 |
509 | def top(from, tag, subreddit, priority) do
510 | listing(from, tag, subreddit, [], :top, priority)
511 | end
512 |
513 | @doc """
514 | Get a subreddit's `gilded` submissions.
515 |
516 | ### Parameters
517 |
518 | * `from`: The pid or name of the requester.
519 | * `tag`: Anything.
520 | * `subreddit`: A subreddit name or id.
521 | * `options`: The query. (Optional)
522 | * `priority`: The request's priority. (Optional)
523 |
524 | ### Options
525 |
526 | * `limit`: a value between 1-100
527 | * `after`: a fullname of a thing
528 | * `before`: a fullname of a thing
529 |
530 | ### Fields
531 |
532 | after
533 | before
534 | modhash
535 | children:
536 | link_flair_css_class
537 | author_flair_css_class
538 | thumbnail
539 | gilded
540 | quarantine
541 | report_reasons
542 | selftext_html
543 | stickied
544 | domain
545 | created
546 | score
547 | num_comments
548 | secure_media_embed
549 | user_reports
550 | over_18
551 | suggested_sort
552 | url
553 | edited
554 | downs
555 | mod_reports
556 | author_flair_text
557 | hide_score
558 | created_utc
559 | from_kind
560 | likes
561 | is_self
562 | ups
563 | distinguished
564 | media
565 | selftext
566 | removal_reason
567 | name
568 | link_flair_text
569 | from
570 | archived
571 | subreddit
572 | hidden
573 | locked
574 | author
575 | subreddit_id
576 | visited
577 | saved
578 | media_embed
579 | from_id
580 | num_reports
581 | id
582 | secure_media
583 | permalink
584 | approved_by
585 | title
586 | clicked
587 | banned_by
588 | """
589 |
590 | def gilded(from, tag, subreddit) do
591 | listing(from, tag, subreddit, [], :gilded, @default_priority)
592 | end
593 |
594 | def gilded(from, tag, subreddit, options, priority) do
595 | listing(from, tag, subreddit, options, :gilded, priority)
596 | end
597 |
598 | def gilded(from, tag, subreddit, options) when is_list(options) do
599 | listing(from, tag, subreddit, options, :gilded, @default_priority)
600 | end
601 |
602 | def gilded(from, tag, subreddit, priority) do
603 | listing(from, tag, subreddit, [], :gilded, priority)
604 | end
605 |
606 | @doc """
607 | Get a subreddit's most recent comments.
608 |
609 | ### Parameters
610 |
611 | * `from`: The pid or name of the requester.
612 | * `tag`: Anything.
613 | * `subreddit:` A subreddit name or id.
614 | * `options`: The query. (Optional)
615 | * `priority`: The request's priority. (Optional)
616 |
617 | ### Options
618 |
619 | * `limit`: a value between 1-100
620 | * `after`: a fullname of a thing
621 | * `before`: a fullname of a thing
622 |
623 | ### Fields
624 |
625 | after
626 | before
627 | modhash
628 | children:
629 | author_flair_css_class
630 | gilded
631 | body_html
632 | quarantine
633 | report_reasons
634 | stickied
635 | created
636 | score
637 | user_reports
638 | over_18
639 | controversiality
640 | link_id
641 | edited
642 | downs
643 | mod_reports
644 | author_flair_text
645 | created_utc
646 | parent_id
647 | body
648 | likes
649 | ups
650 | distinguished
651 | link_author
652 | removal_reason
653 | replies
654 | name
655 | archived
656 | link_title
657 | subreddit
658 | author
659 | subreddit_id
660 | saved
661 | num_reports
662 | id
663 | score_hidden
664 | approved_by
665 | link_url
666 | banned_by
667 | """
668 |
669 | def comments(from, tag, subreddit) do
670 | listing(from, tag, subreddit, [], :comments, @default_priority)
671 | end
672 |
673 | def comments(from, tag, subreddit, options, priority) do
674 | listing(from, tag, subreddit, options, :comments, priority)
675 | end
676 |
677 | def comments(from, tag, subreddit, options) when is_list(options) do
678 | listing(from, tag, subreddit, options, :comments, @default_priority)
679 | end
680 |
681 | def comments(from, tag, subreddit, priority) do
682 | listing(from, tag, subreddit, [], :comments, priority)
683 | end
684 |
685 | @doc """
686 | Get a subreddit's new submissions on an interval.
687 |
688 | ### Parameters
689 |
690 | * `interval`: A value in milliseconds.
691 |
692 | ### Other
693 |
694 | Refer to `Subreddit.new_submissions` documentation for other parameter, option and field information.
695 | """
696 |
697 | def stream_submissions(from, tag, subreddit, interval) do
698 | Scheduler.schedule({@subreddit, :new}, [from, tag, subreddit, [], @default_priority], interval)
699 | end
700 |
701 | def stream_submissions(from, tag, subreddit, options, interval) when is_list(options) do
702 | Scheduler.schedule({@subreddit, :new}, [from, tag, subreddit, options, @default_priority], interval)
703 | end
704 |
705 | def stream_submissions(from, tag, subreddit, priority, interval) do
706 | Scheduler.schedule({@subreddit, :new}, [from, tag, subreddit, [] , priority], interval)
707 | end
708 |
709 | def stream_submissions(from, tag, subreddit, options, priority, interval) do
710 | Scheduler.schedule({@subreddit, :new}, [from, tag, subreddit, options, priority], interval)
711 | end
712 |
713 | @doc """
714 | Paginate a subreddit's `hot` submissions. Pagination does NOT return the `before` and `after` fields, only the
715 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
716 | and the pagination process is gracefully terminated.
717 |
718 | ### Options
719 |
720 | * `limit`: a value between 1-1000
721 |
722 | ### Other
723 |
724 | Refer to `Subreddit.hot_submissions` documentation for other parameter, option and field information.
725 | """
726 |
727 | def paginate_hot(from, tag, subreddit) do
728 | Paginator.paginate({@subreddit, :hot}, [from, tag, subreddit, [limit: 1000], @default_priority])
729 | end
730 |
731 | def paginate_hot(from, tag, subreddit, options) when is_list(options) do
732 | Paginator.paginate({@subreddit, :hot}, [from, tag, subreddit, put_limit(options), @default_priority])
733 | end
734 |
735 | def paginate_hot(from, tag, subreddit, priority) do
736 | Paginator.paginate({@subreddit, :hot}, [from, tag, subreddit, [limit: 1000], priority])
737 | end
738 |
739 | def paginate_hot(from, tag, subreddit, options, priority) do
740 | Paginator.paginate({@subreddit, :hot}, [from, tag, subreddit, put_limit(options), priority])
741 | end
742 |
743 | @doc """
744 | Paginate a subreddit's `new` submissions. Pagination does NOT return the `before` and `after` fields, only the
745 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
746 | and the pagination process is gracefully terminated.
747 |
748 | ### Options
749 |
750 | * `limit`: a value between 1-1000
751 |
752 | ### Other
753 |
754 | Refer to `Subreddit.new_submissions` documentation for other parameter, option and field information.
755 | """
756 |
757 | def paginate_new(from, tag, subreddit) do
758 | Paginator.paginate({@subreddit, :new}, [subreddit, [from, tag, limit: 1000], @default_priority])
759 | end
760 |
761 | def paginate_new(from, tag, subreddit, options) when is_list(options) do
762 | Paginator.paginate({@subreddit, :new}, [from, tag, subreddit, put_limit(options), @default_priority])
763 | end
764 |
765 | def paginate_new(from, tag, subreddit, priority) do
766 | Paginator.paginate({@subreddit, :new}, [from, tag, subreddit, [limit: 1000], priority])
767 | end
768 |
769 | def paginate_new(from, tag, subreddit, options, priority) do
770 | Paginator.paginate({@subreddit, :new}, [from, tag, subreddit, put_limit(options), priority])
771 | end
772 |
773 | @doc """
774 | Paginate a subreddit's `rising` submissions. Pagination does NOT return the `before` and `after` fields, only the
775 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
776 | and the pagination process is gracefully terminated.
777 |
778 | ### Options
779 |
780 | * `limit`: a value between 1-1000
781 |
782 | ### Other
783 |
784 | Refer to `Subreddit.rising_submissions` documentation for other parameter, option and field information.
785 | """
786 |
787 | def paginate_rising(from, tag, subreddit) do
788 | Paginator.paginate({@subreddit, :rising}, [from, tag, subreddit, [limit: 1000], @default_priority])
789 | end
790 |
791 | def paginate_rising(from, tag, subreddit, options) when is_list(options) do
792 | Paginator.paginate({@subreddit, :rising}, [from, tag, subreddit, put_limit(options), @default_priority])
793 | end
794 |
795 | def paginate_rising(from, tag, subreddit, priority) do
796 | Paginator.paginate({@subreddit, :rising}, [from, tag, subreddit, [limit: 1000], priority])
797 | end
798 |
799 | def paginate_rising(from, tag, subreddit, options, priority) do
800 | Paginator.paginate({@subreddit, :rising}, [from, tag, subreddit, put_limit(options), priority])
801 | end
802 |
803 | @doc """
804 | Paginate a subreddit's `controversial` submissions. Pagination does NOT return the `before` and `after` fields, only the
805 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
806 | and the pagination process is gracefully terminated.
807 |
808 | ### Options
809 |
810 | * `limit`: a value between 1-1000
811 |
812 | ### Other
813 |
814 | Refer to `Subreddit.controversial_submissions` documentation for other parameter, option and field information.
815 | """
816 |
817 | def paginate_controversial(from, tag, subreddit) do
818 | Paginator.paginate({@subreddit, :controversial}, [from, tag, subreddit, [limit: 1000], @default_priority])
819 | end
820 |
821 | def paginate_controversial(from, tag, subreddit, options) when is_list(options) do
822 | Paginator.paginate({@subreddit, :controversial}, [from, tag, subreddit, put_limit(options), @default_priority])
823 | end
824 |
825 | def paginate_controversial(from, tag, subreddit, priority) do
826 | Paginator.paginate({@subreddit, :controversial}, [from, tag, subreddit, [limit: 1000], priority])
827 | end
828 |
829 | def paginate_controversial(from, tag, subreddit, options, priority) do
830 | Paginator.paginate({@subreddit, :controversial}, [from, tag, subreddit, put_limit(options), priority])
831 | end
832 |
833 | @doc """
834 | Paginate a subreddit's `top` submissions. Pagination does NOT return the `before` and `after` fields, only the
835 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
836 | and the pagination process is gracefully terminated.
837 |
838 | ### Options
839 |
840 | * `limit`: a value between 1-1000
841 |
842 | ### Other
843 |
844 | Refer to `Subreddit.top_submissions` documentation for other parameter, option and field information.
845 | """
846 |
847 | def paginate_top(from, tag, subreddit) do
848 | Paginator.paginate({@subreddit, :top}, [from, tag, subreddit, [limit: 1000], @default_priority])
849 | end
850 |
851 | def paginate_top(from, tag, subreddit, options) when is_list(options) do
852 | Paginator.paginate({@subreddit, :top}, [from, tag, subreddit, put_limit(options), @default_priority])
853 | end
854 |
855 | def paginate_top(from, tag, subreddit, priority) do
856 | Paginator.paginate({@subreddit, :top}, [from, tag, subreddit, [limit: 1000], priority])
857 | end
858 |
859 | def paginate_top(from, tag, subreddit, options, priority) do
860 | Paginator.paginate({@subreddit, :top}, [from, tag, subreddit, put_limit(options), priority])
861 | end
862 |
863 | @doc """
864 | Paginate a subreddit's `gilded` submissions. Pagination does NOT return the `before` and `after` fields, only the
865 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
866 | and the pagination process is gracefully terminated.
867 |
868 | ### Options
869 |
870 | * `limit`: a value between 1-1000
871 |
872 | ### Other
873 |
874 | Refer to `Subreddit.gilded_submissions` documentation for other parameter, option and field information.
875 | """
876 |
877 | def paginate_gilded(from, tag, subreddit) do
878 | Paginator.paginate({@subreddit, :gilded}, [from, tag, subreddit, [limit: 1000], @default_priority])
879 | end
880 |
881 | def paginate_gilded(from, tag, subreddit, options) when is_list(options) do
882 | Paginator.paginate({@subreddit, :gilded}, [from, tag, subreddit, put_limit(options), @default_priority])
883 | end
884 |
885 | def paginate_gilded(from, tag, subreddit, priority) do
886 | Paginator.paginate({@subreddit, :gilded}, [from, tag, subreddit, [limit: 1000], priority])
887 | end
888 |
889 | def paginate_gilded(from, tag, subreddit, options, priority) do
890 | Paginator.paginate({@subreddit, :gilded}, [from, tag, subreddit, put_limit(options), priority])
891 | end
892 |
893 | @doc """
894 | Get a subreddit's new comments on an interval.
895 |
896 | ### Parameters
897 |
898 | * `interval`: A value in milliseconds.
899 |
900 | ### Other
901 |
902 | Refer to `Subreddit.new_submissions` documentation for other parameter, option and field information.
903 | """
904 |
905 |
906 | def stream_comments(from, tag, subreddit, interval) do
907 | Scheduler.schedule({@subreddit, :comments}, [from, tag, subreddit, [], @default_priority], interval)
908 | end
909 |
910 | def stream_comments(from, tag, subreddit, options, interval) when is_list(options) do
911 | Scheduler.schedule({@subreddit, :comments}, [from, tag, subreddit, options, @default_priority], interval)
912 | end
913 |
914 | def stream_comments(from, tag, subreddit, priority, interval) do
915 | Scheduler.schedule({@subreddit, :comments}, [from, tag, subreddit, [] , priority], interval)
916 | end
917 |
918 | def stream_comments(from, tag, subreddit, options, priority, interval) do
919 | Scheduler.schedule({@subreddit, :comments}, [from, tag, subreddit, options, priority], interval)
920 | end
921 |
922 | @doc """
923 | Paginate a subreddit's comments. Pagination does NOT return the `before` and `after` fields, only the
924 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
925 | and the pagination process is gracefully terminated.
926 |
927 | ### Options
928 |
929 | * `limit`: a value between 1-1000
930 |
931 | ### Other
932 |
933 | Refer to `Subreddit.comments` documentation for other parameter, option and field information.
934 | """
935 |
936 | def paginate_comments(from, tag, subreddit) do
937 | Paginator.paginate({@subreddit, :comments}, [from, tag, subreddit, [limit: 1000], @default_priority])
938 | end
939 |
940 | def paginate_comments(from, tag, subreddit, options) when is_list(options) do
941 | Paginator.paginate({@subreddit, :comments}, [from, tag, subreddit, put_limit(options), @default_priority])
942 | end
943 |
944 | def paginate_comments(from, tag, subreddit, priority) do
945 | Paginator.paginate({@subreddit, :comments}, [from, tag, subreddit, [limit: 1000], priority])
946 | end
947 |
948 | def paginate_comments(from, tag, subreddit, options, priority) do
949 | Paginator.paginate({@subreddit, :comments}, [from, tag, subreddit, put_limit(options), priority])
950 | end
951 |
952 | defp put_limit(options) do
953 | case Keyword.has_key?(options, :limit) do
954 | true -> options
955 | false -> Keyword.put(options, :limit, 1000)
956 | end
957 | end
958 |
959 | defp listing(from, tag, subreddit, options, endpoint, priority) do
960 | url = "#{@subreddit_base}/#{subreddit}/#{endpoint}"
961 | request_data = RequestBuilder.format_get(from, tag, url, options, :listing, priority)
962 | RequestQueue.enqueue_request(request_data)
963 | end
964 |
965 | end
966 |
--------------------------------------------------------------------------------
/lib/elixirplusreddit/api/user.ex:
--------------------------------------------------------------------------------
1 | defmodule ElixirPlusReddit.API.User do
2 |
3 | @moduledoc """
4 | An interface for getting user information.
5 | """
6 |
7 | alias ElixirPlusReddit.RequestBuilder
8 | alias ElixirPlusReddit.RequestQueue
9 | alias ElixirPlusReddit.Paginator
10 | alias ElixirPlusReddit.Scheduler
11 |
12 | @user __MODULE__
13 | @user_base "https://oauth.reddit.com/user"
14 | @default_priority 0
15 |
16 | @doc """
17 | Get a user's `hot` comments.
18 |
19 | ### Parameters
20 |
21 | * `from`: The pid or name of the requester.
22 | * `tag`: Anything.
23 | * `username:` A username or id.
24 | * `options`: The query. (Optional)
25 | * `priority`: The request's priority. (Optional)
26 |
27 | ### Options
28 |
29 | * `limit`: a value between 1-100
30 | * `after`: a fullname of a thing
31 | * `before`: a fullname of a thing
32 |
33 | ### Fields
34 |
35 | after
36 | before
37 | modhash
38 | children:
39 | author_flair_css_class
40 | gilded
41 | body_html
42 | quarantine
43 | report_reasons
44 | stickied
45 | created
46 | score
47 | user_reports
48 | over_18
49 | controversiality
50 | link_id
51 | edited
52 | downs
53 | mod_reports
54 | author_flair_text
55 | created_utc
56 | parent_id
57 | body
58 | likes
59 | ups
60 | distinguished
61 | link_author
62 | removal_reason
63 | replies
64 | name
65 | archived
66 | link_title
67 | subreddit
68 | author
69 | subreddit_id
70 | saved
71 | num_reports
72 | id
73 | score_hidden
74 | approved_by
75 | link_url
76 | banned_by
77 | """
78 |
79 | def hot_comments(from, tag, username) do
80 | listing(from, tag, username, [sort: :hot], :comments, @default_priority)
81 | end
82 |
83 | def hot_comments(from, tag, username, options, priority) do
84 | options = Keyword.put(options, :sort, :hot)
85 | listing(from, tag, username, options, :comments, priority)
86 | end
87 |
88 | def hot_comments(from, tag, username, options) when is_list(options) do
89 | options = Keyword.put(options, :sort, :hot)
90 | listing(from, tag, username, options, :comments, @default_priority)
91 | end
92 |
93 | def hot_comments(from, tag, username, priority) do
94 | listing(from, tag, username, [sort: :hot], :comments, priority)
95 | end
96 |
97 | @doc """
98 | Get a user's `new` comments.
99 |
100 | ### Parameters
101 |
102 | * `from`: The pid or name of the requester.
103 | * `tag`: Anything.
104 | * `username:` A username or id.
105 | * `options`: The query. (Optional)
106 | * `priority`: The request's priority. (Optional)
107 |
108 | ### Options
109 |
110 | * `limit`: a value between 1-100
111 | * `after`: a fullname of a thing
112 | * `before`: a fullname of a thing
113 |
114 | ### Fields
115 |
116 | after
117 | before
118 | modhash
119 | children:
120 | author_flair_css_class
121 | gilded
122 | body_html
123 | quarantine
124 | report_reasons
125 | stickied
126 | created
127 | score
128 | user_reports
129 | over_18
130 | controversiality
131 | link_id
132 | edited
133 | downs
134 | mod_reports
135 | author_flair_text
136 | created_utc
137 | parent_id
138 | body
139 | likes
140 | ups
141 | distinguished
142 | link_author
143 | removal_reason
144 | replies
145 | name
146 | archived
147 | link_title
148 | subreddit
149 | author
150 | subreddit_id
151 | saved
152 | num_reports
153 | id
154 | score_hidden
155 | approved_by
156 | link_url
157 | banned_by
158 | """
159 |
160 | def new_comments(from, tag, username) do
161 | listing(from, tag, username, [sort: :new], :comments, @default_priority)
162 | end
163 |
164 | def new_comments(from, tag, username, options, priority) do
165 | options = Keyword.put(options, :sort, :new)
166 | listing(from, tag, username, options, :comments, priority)
167 | end
168 |
169 | def new_comments(from, tag, username, options) when is_list(options) do
170 | options = Keyword.put(options, :sort, :new)
171 | listing(from, tag, username, options, :comments, @default_priority)
172 | end
173 |
174 | def new_comments(from, tag, username, priority) do
175 | listing(from, tag, username, [sort: :new], :comments, priority)
176 | end
177 |
178 | @doc """
179 | Get a user's `top` comments.
180 |
181 | ### Parameters
182 |
183 | * `from`: The pid or name of the requester.
184 | * `tag`: Anything.
185 | * `username:` A username or id.
186 | * `options`: The query. (Optional)
187 | * `priority`: The request's priority. (Optional)
188 |
189 | ### Options
190 |
191 | * `limit`: a value between 1-100
192 | * `t`: hour, day, week, month, year, all
193 | * `after`: a fullname of a thing
194 | * `before`: a fullname of a thing
195 |
196 | ### Fields
197 |
198 | after
199 | before
200 | modhash
201 | children:
202 | author_flair_css_class
203 | gilded
204 | body_html
205 | quarantine
206 | report_reasons
207 | stickied
208 | created
209 | score
210 | user_reports
211 | over_18
212 | controversiality
213 | link_id
214 | edited
215 | downs
216 | mod_reports
217 | author_flair_text
218 | created_utc
219 | parent_id
220 | body
221 | likes
222 | ups
223 | distinguished
224 | link_author
225 | removal_reason
226 | replies
227 | name
228 | archived
229 | link_title
230 | subreddit
231 | author
232 | subreddit_id
233 | saved
234 | num_reports
235 | id
236 | score_hidden
237 | approved_by
238 | link_url
239 | banned_by
240 | """
241 |
242 | def top_comments(from, tag, username) do
243 | listing(from, tag, username, [sort: :top], :comments, @default_priority)
244 | end
245 |
246 | def top_comments(from, tag, username, options, priority) do
247 | options = Keyword.put(options, :sort, :top)
248 | listing(from, tag, username, options, :comments, priority)
249 | end
250 |
251 | def top_comments(from, tag, username, options) when is_list(options) do
252 | options = Keyword.put(options, :sort, :top)
253 | listing(from, tag, username, options, :comments, @default_priority)
254 | end
255 |
256 | def top_comments(from, tag, username, priority) do
257 | listing(from, tag, username, [sort: :top], :comments, priority)
258 | end
259 |
260 | @doc """
261 | Get a user's `controversial` comments.
262 |
263 | ### Parameters
264 |
265 | * `from`: The pid or name of the requester.
266 | * `tag`: Anything.
267 | * `username:` A username or id.
268 | * `options`: The query. (Optional)
269 | * `priority`: The request's priority. (Optional)
270 |
271 | ### Options
272 |
273 | * `limit`: a value between 1-100
274 | * `t`: hour, day, week, month, year, all
275 | * `after`: a fullname of a thing
276 | * `before`: a fullname of a thing
277 |
278 | ### Fields
279 |
280 | after
281 | before
282 | modhash
283 | children:
284 | author_flair_css_class
285 | gilded
286 | body_html
287 | quarantine
288 | report_reasons
289 | stickied
290 | created
291 | score
292 | user_reports
293 | over_18
294 | controversiality
295 | link_id
296 | edited
297 | downs
298 | mod_reports
299 | author_flair_text
300 | created_utc
301 | parent_id
302 | body
303 | likes
304 | ups
305 | distinguished
306 | link_author
307 | removal_reason
308 | replies
309 | name
310 | archived
311 | link_title
312 | subreddit
313 | author
314 | subreddit_id
315 | saved
316 | num_reports
317 | id
318 | score_hidden
319 | approved_by
320 | link_url
321 | banned_by
322 | """
323 |
324 | def controversial_comments(from, tag, username) do
325 | listing(from, tag, username, [sort: :controversial], :comments, @default_priority)
326 | end
327 |
328 | def controversial_comments(from, tag, username, options, priority) do
329 | options = Keyword.put(options, :sort, :controversial)
330 | listing(from, tag, username, options, :comments, priority)
331 | end
332 |
333 | def controversial_comments(from, tag, username, options) when is_list(options) do
334 | options = Keyword.put(options, :sort, :controversial)
335 | listing(from, tag, username, options, :comments, @default_priority)
336 | end
337 |
338 | def controversial_comments(from, tag, username, priority) do
339 | listing(from, tag, username, [sort: :controversial], :comments, priority)
340 | end
341 |
342 | @doc """
343 | Get a user's `hot` submissions.
344 |
345 | ### Parameters
346 |
347 | * `from`: The pid or name of the requester.
348 | * `tag`: Anything.
349 | * `subreddit`: A subreddit name or id.
350 | * `options`: The query. (Optional)
351 | * `priority`: The request's priority. (Optional)
352 |
353 | ### Options
354 |
355 | * `limit`: a value between 1-100
356 | * `after`: a fullname of a thing
357 | * `before`: a fullname of a thing
358 |
359 | ### Fields
360 |
361 | after
362 | before
363 | modhash
364 | children:
365 | link_flair_css_class
366 | author_flair_css_class
367 | thumbnail
368 | gilded
369 | quarantine
370 | report_reasons
371 | selftext_html
372 | stickied
373 | domain
374 | created
375 | score
376 | num_comments
377 | secure_media_embed
378 | user_reports
379 | over_18
380 | suggested_sort
381 | url
382 | edited
383 | downs
384 | mod_reports
385 | author_flair_text
386 | hide_score
387 | created_utc
388 | from_kind
389 | likes
390 | is_self
391 | ups
392 | distinguished
393 | media
394 | selftext
395 | removal_reason
396 | name
397 | link_flair_text
398 | from
399 | archived
400 | subreddit
401 | hidden
402 | locked
403 | author
404 | subreddit_id
405 | visited
406 | saved
407 | media_embed
408 | from_id
409 | num_reports
410 | id
411 | secure_media
412 | permalink
413 | approved_by
414 | title
415 | clicked
416 | banned_by
417 | """
418 |
419 | def hot_submissions(from, tag, username) do
420 | listing(from, tag, username, [sort: :hot], :submitted, @default_priority)
421 | end
422 |
423 | def hot_submissions(from, tag, username, options, priority) do
424 | options = Keyword.put(options, :sort, :hot)
425 | listing(from, tag, username, options, :submitted, priority)
426 | end
427 |
428 | def hot_submissions(from, tag, username, options) when is_list(options) do
429 | options = Keyword.put(options, :sort, :hot)
430 | listing(from, tag, username, options, :submitted, @default_priority)
431 | end
432 |
433 | def hot_submissions(from, tag, username, priority) do
434 | listing(from, tag, username, [sort: :hot], :submitted, priority)
435 | end
436 |
437 | @doc """
438 | Get a user's `new` submissions.
439 |
440 | ### Parameters
441 |
442 | * `from`: The pid or name of the requester.
443 | * `tag`: Anything.
444 | * `subreddit`: A subreddit name or id.
445 | * `options`: The query. (Optional)
446 | * `priority`: The request's priority. (Optional)
447 |
448 | ### Options
449 |
450 | * `limit`: a value between 1-100
451 | * `after`: a fullname of a thing
452 | * `before`: a fullname of a thing
453 |
454 | ### Fields
455 |
456 | after
457 | before
458 | modhash
459 | children:
460 | link_flair_css_class
461 | author_flair_css_class
462 | thumbnail
463 | gilded
464 | quarantine
465 | report_reasons
466 | selftext_html
467 | stickied
468 | domain
469 | created
470 | score
471 | num_comments
472 | secure_media_embed
473 | user_reports
474 | over_18
475 | suggested_sort
476 | url
477 | edited
478 | downs
479 | mod_reports
480 | author_flair_text
481 | hide_score
482 | created_utc
483 | from_kind
484 | likes
485 | is_self
486 | ups
487 | distinguished
488 | media
489 | selftext
490 | removal_reason
491 | name
492 | link_flair_text
493 | from
494 | archived
495 | subreddit
496 | hidden
497 | locked
498 | author
499 | subreddit_id
500 | visited
501 | saved
502 | media_embed
503 | from_id
504 | num_reports
505 | id
506 | secure_media
507 | permalink
508 | approved_by
509 | title
510 | clicked
511 | banned_by
512 | """
513 |
514 | def new_submissions(from, tag, username) do
515 | listing(from, tag, username, [sort: :new], :submitted, @default_priority)
516 | end
517 |
518 | def new_submissions(from, tag, username, options, priority) do
519 | options = Keyword.put(options, :sort, :new)
520 | listing(from, tag, username, options, :submitted, priority)
521 | end
522 |
523 | def new_submissions(from, tag, username, options) when is_list(options) do
524 | options = Keyword.put(options, :sort, :new)
525 | listing(from, tag, username, options, :submitted, @default_priority)
526 | end
527 |
528 | def new_submissions(from, tag, username, priority) do
529 | listing(from, tag, username, [sort: :hot], :submitted, priority)
530 | end
531 |
532 | @doc """
533 | Get a user's `top` submissions.
534 |
535 | ### Parameters
536 |
537 | * `from`: The pid or name of the requester.
538 | * `tag`: Anything.
539 | * `subreddit`: A subreddit name or id.
540 | * `options`: The query. (Optional)
541 | * `priority`: The request's priority. (Optional)
542 |
543 | ### Options
544 |
545 | * `limit`: a value between 1-100
546 | * `t`: hour, day, week, month, year, all
547 | * `after`: a fullname of a thing
548 | * `before`: a fullname of a thing
549 |
550 | ### Fields
551 |
552 | after
553 | before
554 | modhash
555 | children:
556 | link_flair_css_class
557 | author_flair_css_class
558 | thumbnail
559 | gilded
560 | quarantine
561 | report_reasons
562 | selftext_html
563 | stickied
564 | domain
565 | created
566 | score
567 | num_comments
568 | secure_media_embed
569 | user_reports
570 | over_18
571 | suggested_sort
572 | url
573 | edited
574 | downs
575 | mod_reports
576 | author_flair_text
577 | hide_score
578 | created_utc
579 | from_kind
580 | likes
581 | is_self
582 | ups
583 | distinguished
584 | media
585 | selftext
586 | removal_reason
587 | name
588 | link_flair_text
589 | from
590 | archived
591 | subreddit
592 | hidden
593 | locked
594 | author
595 | subreddit_id
596 | visited
597 | saved
598 | media_embed
599 | from_id
600 | num_reports
601 | id
602 | secure_media
603 | permalink
604 | approved_by
605 | title
606 | clicked
607 | banned_by
608 | """
609 |
610 | def top_submissions(from, tag, username) do
611 | listing(from, tag, username, [sort: :top], :submitted, @default_priority)
612 | end
613 |
614 | def top_submissions(from, tag, username, options, priority) do
615 | options = Keyword.put(options, :sort, :top)
616 | listing(from, tag, username, options, :submitted, priority)
617 | end
618 |
619 | def top_submissions(from, tag, username, options) when is_list(options) do
620 | options = Keyword.put(options, :sort, :top)
621 | listing(from, tag, username, options, :submitted, @default_priority)
622 | end
623 |
624 | def top_submissions(from, tag, username, priority) do
625 | listing(from, tag, username, [sort: :top], :submitted, priority)
626 | end
627 |
628 | @doc """
629 | Get a user's `controversial` submissions.
630 |
631 | ### Parameters
632 |
633 | * `from`: The pid or name of the requester.
634 | * `tag`: Anything.
635 | * `subreddit`: A subreddit name or id.
636 | * `options`: The query. (Optional)
637 | * `priority`: The request's priority. (Optional)
638 |
639 | ### Options
640 |
641 | * `limit`: a value between 1-100
642 | * `t`: hour, day, week, month, year, all
643 | * `after`: a fullname of a thing
644 | * `before`: a fullname of a thing
645 |
646 | ### Fields
647 |
648 | after
649 | before
650 | modhash
651 | children:
652 | link_flair_css_class
653 | author_flair_css_class
654 | thumbnail
655 | gilded
656 | quarantine
657 | report_reasons
658 | selftext_html
659 | stickied
660 | domain
661 | created
662 | score
663 | num_comments
664 | secure_media_embed
665 | user_reports
666 | over_18
667 | suggested_sort
668 | url
669 | edited
670 | downs
671 | mod_reports
672 | author_flair_text
673 | hide_score
674 | created_utc
675 | from_kind
676 | likes
677 | is_self
678 | ups
679 | distinguished
680 | media
681 | selftext
682 | removal_reason
683 | name
684 | link_flair_text
685 | from
686 | archived
687 | subreddit
688 | hidden
689 | locked
690 | author
691 | subreddit_id
692 | visited
693 | saved
694 | media_embed
695 | from_id
696 | num_reports
697 | id
698 | secure_media
699 | permalink
700 | approved_by
701 | title
702 | clicked
703 | banned_by
704 | """
705 |
706 | def controversial_submissions(from, tag, username) do
707 | listing(from, tag, username, [sort: :controversial], :submitted, @default_priority)
708 | end
709 |
710 | def controversial_submissions(from, tag, username, options, priority) do
711 | options = Keyword.put(options, :sort, :controversial)
712 | listing(from, tag, username, options, :submitted, priority)
713 | end
714 |
715 | def controversial_submissions(from, tag, username, options) when is_list(options) do
716 | options = Keyword.put(options, :sort, :controversial)
717 | listing(from, tag, username, options, :submitted, @default_priority)
718 | end
719 |
720 | def controversial_submissions(from, tag, username, priority) do
721 | listing(from, tag, username, [sort: :controversial], :submitted, priority)
722 | end
723 |
724 | @doc """
725 | Get a user's `new` comments on an interval.
726 |
727 | ### Parameters
728 |
729 | * `interval`: A value in milliseconds.
730 |
731 | ### Other
732 |
733 | Refer to `User.new_comments` documentation for other parameter, option and field information.
734 | """
735 |
736 | def stream_comments(from, tag, username, interval) do
737 | Scheduler.schedule({@user, :new_comments}, [from, tag, username, [], @default_priority], interval)
738 | end
739 |
740 | def stream_comments(from, tag, username, options, interval) when is_list(options) do
741 | Scheduler.schedule({@user, :new_comments}, [from, tag, username, options, @default_priority], interval)
742 | end
743 |
744 | def stream_comments(from, tag, username, priority, interval) do
745 | Scheduler.schedule({@user, :new_comments}, [from, tag, username, [] , priority], interval)
746 | end
747 |
748 | def stream_comments(from, tag, username, options, priority, interval) do
749 | Scheduler.schedule({@user, :new_comments}, [from, tag, username, options, priority], interval)
750 | end
751 |
752 | @doc """
753 | Get a user's `new` submissions on an interval.
754 |
755 | ### Parameters
756 |
757 | * `interval`: A value in milliseconds.
758 |
759 | ### Other
760 |
761 | Refer to `User.submissions` documentation for other parameter, option and field information.
762 | """
763 |
764 | def stream_submissions(from, tag, username, interval) do
765 | Scheduler.schedule({@user, :submissions}, [from, tag, username, [], @default_priority], interval)
766 | end
767 |
768 | def stream_submissions(from, tag, username, options, interval) when is_list(options) do
769 | Scheduler.schedule({@user, :submissions}, [from, tag, username, options, @default_priority], interval)
770 | end
771 |
772 | def stream_submissions(from, tag, username, priority, interval) do
773 | Scheduler.schedule({@user, :submissions}, [from, tag, username, [] , priority], interval)
774 | end
775 |
776 | def stream_submissions(from, tag, username, options, priority, interval) do
777 | Scheduler.schedule({@user, :submissions}, [from, tag, username, options, priority], interval)
778 | end
779 |
780 | @doc """
781 | Paginate a user's `hot` comments. Pagination does NOT return the `before` and `after` fields, only the
782 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
783 | and the pagination process is gracefully terminated.
784 |
785 | ### Options
786 |
787 | * `limit`: a value between 1-1000
788 |
789 | ### Other
790 |
791 | Refer to `User.hot_comments` documentation for other parameter, option and field information.
792 |
793 | """
794 |
795 | def paginate_hot_comments(from, tag, username) do
796 | Paginator.paginate({@user, :hot_comments}, [from, tag, username, [limit: 1000], @default_priority])
797 | end
798 |
799 | def paginate_hot_comments(from, tag, username, options) when is_list(options) do
800 | Paginator.paginate({@user, :hot_comments}, [from, tag, username, put_limit(options), @default_priority])
801 | end
802 |
803 | def paginate_hot_comments(from, tag, username, priority) do
804 | Paginator.paginate({@user, :hot_comments}, [from, tag, username, [limit: 1000], priority])
805 | end
806 |
807 | def paginate_hot_comments(from, tag, username, options, priority) do
808 | Paginator.paginate({@user, :hot_comments}, [from, tag, username, put_limit(options), priority])
809 | end
810 |
811 | @doc """
812 | Paginate a user's `new` comments. Pagination does NOT return the `before` and `after` fields, only the
813 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
814 | and the pagination process is gracefully terminated.
815 |
816 | ### Options
817 |
818 | * `limit`: a value between 1-1000
819 |
820 | ### Other
821 |
822 | Refer to `User.new_comments` documentation for other parameter, option and field information.
823 | """
824 |
825 | def paginate_new_comments(from, tag, username) do
826 | Paginator.paginate({@user, :new_comments}, [from, tag, username, [limit: 1000], @default_priority])
827 | end
828 |
829 | def paginate_new_comments(from, tag, username, options) when is_list(options) do
830 | Paginator.paginate({@user, :new_comments}, [from, tag, username, put_limit(options), @default_priority])
831 | end
832 |
833 | def paginate_new_comments(from, tag, username, priority) do
834 | Paginator.paginate({@user, :new_comments}, [from, tag, username, [limit: 1000], priority])
835 | end
836 |
837 | def paginate_new_comments(from, tag, username, options, priority) do
838 | Paginator.paginate({@user, :new_comments}, [from, tag, username, put_limit(options), priority])
839 | end
840 |
841 | @doc """
842 | Paginate a user's `top` comments. Pagination does NOT return the `before` and `after` fields, only the
843 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
844 | and the pagination process is gracefully terminated.
845 |
846 | ### Options
847 |
848 | * `limit`: a value between 1-1000
849 |
850 | ### Other
851 |
852 | Refer to `User.top_comments` documentation for other parameter, option and field information.
853 | """
854 |
855 | def paginate_top_comments(from, tag, username) do
856 | Paginator.paginate({@user, :top_comments}, [from, tag, username, [limit: 1000], @default_priority])
857 | end
858 |
859 | def paginate_top_comments(from, tag, username, options) when is_list(options) do
860 | Paginator.paginate({@user, :top_comments}, [from, tag, username, put_limit(options), @default_priority])
861 | end
862 |
863 | def paginate_top_comments(from, tag, username, priority) do
864 | Paginator.paginate({@user, :top_comments}, [from, tag, username, [limit: 1000], priority])
865 | end
866 |
867 | def paginate_top_comments(from, tag, username, options, priority) do
868 | Paginator.paginate({@user, :top_comments}, [from, tag, username, put_limit(options), priority])
869 | end
870 |
871 | @doc """
872 | Paginate a user's `controversial` comments. Pagination does NOT return the `before` and `after` fields, only the
873 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
874 | and the pagination process is gracefully terminated.
875 |
876 | ### Options
877 |
878 | * `limit`: a value between 1-1000
879 |
880 | ### Other
881 |
882 | Refer to `User.controversial_comments` documentation for other parameter, option and field information.
883 | """
884 |
885 | def paginate_controversial_comments(from, tag, username) do
886 | Paginator.paginate({@user, :controversial_comments}, [from, tag, username, [limit: 1000], @default_priority])
887 | end
888 |
889 | def paginate_controversial_comments(from, tag, username, options) when is_list(options) do
890 | Paginator.paginate({@user, :controversial_comments}, [from, tag, username, put_limit(options), @default_priority])
891 | end
892 |
893 | def paginate_controversial_comments(from, tag, username, priority) do
894 | Paginator.paginate({@user, :controversial_comments}, [from, tag, username, [limit: 1000], priority])
895 | end
896 |
897 | def paginate_controversial_comments(from, tag, username, options, priority) do
898 | Paginator.paginate({@user, :controversial_comments}, [from, tag, username, put_limit(options), priority])
899 | end
900 |
901 | @doc """
902 | Paginate a user's `hot` submissions. Pagination does NOT return the `before` and `after` fields, only the
903 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
904 | and the pagination process is gracefully terminated.
905 |
906 | ### Options
907 |
908 | * `limit`: a value between 1-1000
909 |
910 | ### Other
911 |
912 | Refer to `User.hot_submissions` documentation for other parameter, option and field information.
913 | """
914 |
915 | def paginate_hot_submissions(from, tag, username) do
916 | Paginator.paginate({@user, :hot_submissions}, [from, tag, username, [limit: 1000], @default_priority])
917 | end
918 |
919 | def paginate_hot_submissions(from, tag, username, options) when is_list(options) do
920 | Paginator.paginate({@user, :hot_submissions}, [from, tag, username, put_limit(options), @default_priority])
921 | end
922 |
923 | def paginate_hot_submissions(from, tag, username, priority) do
924 | Paginator.paginate({@user, :hot_submissions}, [from, tag, username, [limit: 1000], priority])
925 | end
926 |
927 | def paginate_hot_submissions(from, tag, username, options, priority) do
928 | Paginator.paginate({@user, :hot_submissions}, [from, tag, username, put_limit(options), priority])
929 | end
930 |
931 | @doc """
932 | Paginate a user's `new` submissions. Pagination does NOT return the `before` and `after` fields, only the
933 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
934 | and the pagination process is gracefully terminated.
935 |
936 | ### Options
937 |
938 | * `limit`: a value between 1-1000
939 |
940 | ### Other
941 |
942 | Refer to `User.new_submissions` documentation for other parameter, option and field information.
943 | """
944 |
945 | def paginate_new_submissions(from, tag, username) do
946 | Paginator.paginate({@user, :new_submissions}, [from, tag, username, [limit: 1000], @default_priority])
947 | end
948 |
949 | def paginate_new_submissions(from, tag, username, options) when is_list(options) do
950 | Paginator.paginate({@user, :new_submissions}, [from, tag, username, put_limit(options), @default_priority])
951 | end
952 |
953 | def paginate_new_submissions(from, tag, username, priority) do
954 | Paginator.paginate({@user, :new_submissions}, [from, tag, username, [limit: 1000], priority])
955 | end
956 |
957 | def paginate_new_submissions(from, tag, username, options, priority) do
958 | Paginator.paginate({@user, :new_submissions}, [from, tag, username, put_limit(options), priority])
959 | end
960 |
961 | @doc """
962 | Paginate a user's `top` submissions. Pagination does NOT return the `before` and `after` fields, only the
963 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
964 | and the pagination process is gracefully terminated.
965 |
966 | ### Options
967 |
968 | * `limit`: a value between 1-1000
969 |
970 | ### Other
971 |
972 | Refer to `User.top_submissions` documentation for other parameter, option and field information.
973 | """
974 |
975 | def paginate_top_submissions(from, tag, username) do
976 | Paginator.paginate({@user, :top_submissions}, [from, tag, username, [limit: 1000], @default_priority])
977 | end
978 |
979 | def paginate_top_submissions(from, tag, username, options) when is_list(options) do
980 | Paginator.paginate({@user, :top_submissions}, [from, tag, username, put_limit(options), @default_priority])
981 | end
982 |
983 | def paginate_top_submissions(from, tag, username, priority) do
984 | Paginator.paginate({@user, :top_submissions}, [from, tag, username, [limit: 1000], priority])
985 | end
986 |
987 | def paginate_top_submissions(from, tag, username, options, priority) do
988 | Paginator.paginate({@user, :top_submissions}, [from, tag, username, put_limit(options), priority])
989 | end
990 |
991 | @doc """
992 | Paginate a user's `controversial` submissions. Pagination does NOT return the `before` and `after` fields, only the
993 | `children` field. When pagination is complete a message is sent to `from` in the form of {`tag`, `:complete`}
994 | and the pagination process is gracefully terminated.
995 |
996 | ### Options
997 |
998 | * `limit`: a value between 1-1000
999 |
1000 | ### Other
1001 |
1002 | Refer to `User.contorversial_submissions` documentation for other parameter, option and field information.
1003 | """
1004 |
1005 | def paginate_controversial_submissions(from, tag, username) do
1006 | Paginator.paginate({@user, :top_submissions}, [from, tag, username, [limit: 1000], @default_priority])
1007 | end
1008 |
1009 | def paginate_controversial_submissions(from, tag, username, options) when is_list(options) do
1010 | Paginator.paginate({@user, :top_submissions}, [from, tag, username, put_limit(options), @default_priority])
1011 | end
1012 |
1013 | def paginate_controversial_submissions(from, tag, username, priority) do
1014 | Paginator.paginate({@user, :top_submissions}, [from, tag, username, [limit: 1000], priority])
1015 | end
1016 |
1017 | def paginate_controversial_submissions(from, tag, username, options, priority) do
1018 | Paginator.paginate({@user, :top_submissions}, [from, tag, username, put_limit(options), priority])
1019 | end
1020 |
1021 | defp put_limit(options) do
1022 | case Keyword.has_key?(options, :limit) do
1023 | true -> options
1024 | false -> Keyword.put(options, :limit, 1000)
1025 | end
1026 | end
1027 |
1028 | defp listing(from, tag, username, options, endpoint, priority) do
1029 | url = "#{@user_base}/#{username}/#{endpoint}"
1030 | request_data = RequestBuilder.format_get(from, tag, url, options, :listing, priority)
1031 | RequestQueue.enqueue_request(request_data)
1032 | end
1033 |
1034 | end
1035 |
--------------------------------------------------------------------------------