redd 0.8.0.pre.1 → 0.8.0.pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/TODO.md +1 -5
- data/bin/console +1 -1
- data/lib/redd/api_client.rb +21 -21
- data/lib/redd/client.rb +13 -11
- data/lib/redd/error.rb +14 -0
- data/lib/redd/models/basic_model.rb +3 -9
- data/lib/redd/models/comment.rb +9 -1
- data/lib/redd/models/lazy_model.rb +18 -6
- data/lib/redd/models/listing.rb +1 -1
- data/lib/redd/models/more_comments.rb +23 -2
- data/lib/redd/models/searchable.rb +35 -0
- data/lib/redd/models/session.rb +4 -1
- data/lib/redd/models/submission.rb +60 -8
- data/lib/redd/models/subreddit.rb +106 -2
- data/lib/redd/models/user.rb +14 -0
- data/lib/redd/utilities/error_handler.rb +11 -6
- data/lib/redd/utilities/stream.rb +0 -2
- data/lib/redd/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 69d6cf992f71356b1db86a000bdc3be4508e351c
|
4
|
+
data.tar.gz: 01d0b098fb7c26a91e34ff001310417c93e3bd74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4019ce12ee1cfac8927642bfdd327e4221835842a230ad73bce35b0ce3e1f0445523bf5a796aeca182b63c7061861ba1a28a587fe8814594d6d89531ece2c3a6
|
7
|
+
data.tar.gz: 292775ce9121b4de029ee49e451417508b64524940043569ebe53b807387fac0b1539a7cf5e37a13d494c83267421a1789a75e00ec96cadbaa2cf01128c0f9b7
|
data/.rubocop.yml
CHANGED
data/TODO.md
CHANGED
@@ -1,11 +1,7 @@
|
|
1
1
|
# v0.8.0 checklist
|
2
2
|
|
3
|
-
- [ ] Make v0.8.0 feature-complete with v0.7.x
|
4
|
-
- [ ] `Messageable`
|
5
|
-
- [ ] `MoreComments` and expansion
|
6
3
|
- [ ] make rubocop fail on error
|
7
4
|
- [ ] unduplicate duplicated code
|
8
5
|
- [ ] Write tests (oh boy)
|
9
|
-
- [ ]
|
10
|
-
- [ ] A `FrontPage` object
|
6
|
+
- [ ] abstract out listing methods?
|
11
7
|
- [ ] Add `#==` and `#to_s` methods to models
|
data/bin/console
CHANGED
@@ -37,7 +37,7 @@ server.mount_proc '/authenticate' do |_, res|
|
|
37
37
|
state: '0',
|
38
38
|
redirect_uri: 'http://localhost:8000/redirect',
|
39
39
|
'scope': %w(identity read subscribe privatemessages wikiread submit vote edit modposts history
|
40
|
-
modflair)
|
40
|
+
modflair modconfig)
|
41
41
|
)
|
42
42
|
)
|
43
43
|
end
|
data/lib/redd/api_client.rb
CHANGED
@@ -64,37 +64,21 @@ module Redd
|
|
64
64
|
@unmarshaller.unmarshal(object)
|
65
65
|
end
|
66
66
|
|
67
|
-
def model(verb, path,
|
67
|
+
def model(verb, path, options = {})
|
68
68
|
# XXX: make unmarshal explicit in methods?
|
69
|
-
unmarshal(
|
70
|
-
end
|
71
|
-
|
72
|
-
private
|
73
|
-
|
74
|
-
# Makes sure a valid access is present, raising an error if nil
|
75
|
-
def ensure_access_is_valid
|
76
|
-
# Authenticate first if auto_login is enabled
|
77
|
-
authenticate if @access.nil? && @auto_login
|
78
|
-
# Refresh access if auto_refresh is enabled
|
79
|
-
refresh if @access.expired? && @auto_refresh
|
80
|
-
# Fuck it, panic
|
81
|
-
raise 'client access is nil, try calling #authenticate' if @access.nil?
|
82
|
-
end
|
83
|
-
|
84
|
-
def connection
|
85
|
-
super.auth("Bearer #{@access.access_token}")
|
69
|
+
unmarshal(request(verb, path, options).body)
|
86
70
|
end
|
87
71
|
|
88
72
|
# Makes a request, ensuring not to break the rate limit by sleeping.
|
89
73
|
# @see Client#request
|
90
|
-
def request(verb, path,
|
74
|
+
def request(verb, path, raw: false, params: {}, **options)
|
91
75
|
# Make sure @access is populated by a valid access
|
92
76
|
ensure_access_is_valid
|
93
77
|
# Setup base API params and make request
|
94
78
|
api_params = { api_type: 'json', raw_json: 1 }.merge(params)
|
95
|
-
response = @rate_limiter.after_limit { super(verb, path, params: api_params,
|
79
|
+
response = @rate_limiter.after_limit { super(verb, path, params: api_params, **options) }
|
96
80
|
# Check for errors in the returned response
|
97
|
-
response_error = @error_handler.check_error(response)
|
81
|
+
response_error = @error_handler.check_error(response, raw: raw)
|
98
82
|
raise response_error unless response_error.nil?
|
99
83
|
# All done, return the response
|
100
84
|
@failures = 0
|
@@ -105,5 +89,21 @@ module Redd
|
|
105
89
|
warn "Redd got a #{e.class.name} error (#{e.message}), retrying..."
|
106
90
|
retry
|
107
91
|
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Makes sure a valid access is present, raising an error if nil
|
96
|
+
def ensure_access_is_valid
|
97
|
+
# Authenticate first if auto_login is enabled
|
98
|
+
authenticate if @access.nil? && @auto_login
|
99
|
+
# Refresh access if auto_refresh is enabled
|
100
|
+
refresh if @access.expired? && @auto_refresh
|
101
|
+
# Fuck it, panic
|
102
|
+
raise 'client access is nil, try calling #authenticate' if @access.nil?
|
103
|
+
end
|
104
|
+
|
105
|
+
def connection
|
106
|
+
super.auth("Bearer #{@access.access_token}")
|
107
|
+
end
|
108
108
|
end
|
109
109
|
end
|
data/lib/redd/client.rb
CHANGED
@@ -24,6 +24,19 @@ module Redd
|
|
24
24
|
@user_agent = user_agent
|
25
25
|
end
|
26
26
|
|
27
|
+
# Make an HTTP request.
|
28
|
+
# @param verb [:get, :post, :put, :patch, :delete] the HTTP verb to use
|
29
|
+
# @param path [String] the path relative to the endpoint
|
30
|
+
# @param options [Hash] the request parameters
|
31
|
+
# @option options [Hash] :params the parameters to supply with the url
|
32
|
+
# @option options [Hash] :form the parameters to supply in the body
|
33
|
+
# @option options [Hash] :body the direct body contents
|
34
|
+
# @return [Response] the response
|
35
|
+
def request(verb, path, options = {})
|
36
|
+
response = connection.request(verb, path, **options)
|
37
|
+
Response.new(response.status.code, response.headers, response.body.to_s)
|
38
|
+
end
|
39
|
+
|
27
40
|
# Make a GET request.
|
28
41
|
# @param path [String] the path relative to the endpoint
|
29
42
|
# @param options [Hash] the parameters to supply
|
@@ -73,16 +86,5 @@ module Redd
|
|
73
86
|
.headers('User-Agent' => @user_agent)
|
74
87
|
.timeout(:per_operation, write: 5, connect: 5, read: 5)
|
75
88
|
end
|
76
|
-
|
77
|
-
# Make an HTTP request.
|
78
|
-
# @param verb [:get, :post, :put, :patch, :delete] the HTTP verb to use
|
79
|
-
# @param path [String] the path relative to the endpoint
|
80
|
-
# @param params [Hash] the parameters to supply with the url
|
81
|
-
# @param form [Hash] the parameters to supply in the body
|
82
|
-
# @return [Response] the response
|
83
|
-
def request(verb, path, params: {}, form: {})
|
84
|
-
response = connection.request(verb, path, params: params, form: form)
|
85
|
-
Response.new(response.status.code, response.headers, response.body.to_s)
|
86
|
-
end
|
87
89
|
end
|
88
90
|
end
|
data/lib/redd/error.rb
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Redd
|
4
|
+
# An error with the API.
|
5
|
+
class APIError < StandardError
|
6
|
+
attr_reader :response, :name
|
7
|
+
|
8
|
+
def initialize(response)
|
9
|
+
@response = response
|
10
|
+
@name, message = response.body[:json][:errors][0]
|
11
|
+
super(message)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
4
15
|
# Represents an error from reddit returned in a response.
|
5
16
|
class ResponseError < StandardError
|
6
17
|
attr_accessor :response
|
@@ -14,6 +25,9 @@ module Redd
|
|
14
25
|
# An error with Redd, probably (let me know!)
|
15
26
|
class BadRequest < ResponseError; end
|
16
27
|
|
28
|
+
# Whatever it is, you're not allowed to do it.
|
29
|
+
class Forbidden < ResponseError; end
|
30
|
+
|
17
31
|
# You don't have the correct scope to do this.
|
18
32
|
class InsufficientScope < ResponseError; end
|
19
33
|
|
@@ -61,15 +61,15 @@ module Redd
|
|
61
61
|
# @param include_private [Boolean] whether to also include private methods
|
62
62
|
# @return [Boolean] whether the method is handled by method_missing
|
63
63
|
def respond_to_missing?(method_name, include_private = false)
|
64
|
-
|
64
|
+
@attributes.key?(method_name) || @attributes.key?(depredicate(method_name)) || super
|
65
65
|
end
|
66
66
|
|
67
67
|
# Return an attribute or raise a NoMethodError if it doesn't exist.
|
68
68
|
# @param method_name [Symbol] the name of the attribute
|
69
69
|
# @return [Object] the result of the attribute check
|
70
70
|
def method_missing(method_name, *args, &block)
|
71
|
-
return get_attribute(method_name) if
|
72
|
-
return get_attribute(depredicate(method_name)) if
|
71
|
+
return get_attribute(method_name) if @attributes.key?(method_name)
|
72
|
+
return get_attribute(depredicate(method_name)) if @attributes.key?(depredicate(method_name))
|
73
73
|
super
|
74
74
|
end
|
75
75
|
|
@@ -113,12 +113,6 @@ module Redd
|
|
113
113
|
# Fetch the attribute, raising a KeyError if it doesn't exist.
|
114
114
|
@attributes.fetch(name)
|
115
115
|
end
|
116
|
-
|
117
|
-
# @param name [Symbol] the name of the attribute to check
|
118
|
-
# @return [Boolean] whether the attribute exists
|
119
|
-
def attribute?(name)
|
120
|
-
@attributes.key?(name)
|
121
|
-
end
|
122
116
|
end
|
123
117
|
end
|
124
118
|
end
|
data/lib/redd/models/comment.rb
CHANGED
@@ -28,8 +28,10 @@ module Redd
|
|
28
28
|
# @option hash [String] :id the comment's id (e.g. abc123)
|
29
29
|
# @return [Comment]
|
30
30
|
def self.from_response(client, hash)
|
31
|
+
# FIXME: listings can be empty... (for some reason)
|
32
|
+
|
31
33
|
# Ensure we have the comment's id.
|
32
|
-
id = hash.fetch(:id
|
34
|
+
id = hash.fetch(:id) { hash.fetch(:name).tr('t1_', '') }
|
33
35
|
|
34
36
|
# If we have the link_id, we can load the listing with replies.
|
35
37
|
if hash.key?(:link_id)
|
@@ -46,6 +48,12 @@ module Redd
|
|
46
48
|
c.get('/api/info', id: "t1_#{id}").body[:data][:children][0][:data]
|
47
49
|
end
|
48
50
|
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def after_initialize
|
55
|
+
@attributes[:replies] = [] if !@attributes.key?(:replies) || @attributes[:replies] == ''
|
56
|
+
end
|
49
57
|
end
|
50
58
|
end
|
51
59
|
end
|
@@ -40,6 +40,23 @@ module Redd
|
|
40
40
|
super
|
41
41
|
end
|
42
42
|
|
43
|
+
# Checks whether an attribute is supported by method_missing. Since we don't know whether an
|
44
|
+
# attribute exists until we load it, we have to respond true until we load it.
|
45
|
+
# @param method_name [Symbol] the method name or attribute to check
|
46
|
+
# @param include_private [Boolean] whether to also include private methods
|
47
|
+
# @return [Boolean] whether the method is handled by method_missing
|
48
|
+
def respond_to_missing?(method_name, include_private = false)
|
49
|
+
@definitely_fully_loaded ? super : true
|
50
|
+
end
|
51
|
+
|
52
|
+
# Return an attribute or raise a NoMethodError if it doesn't exist.
|
53
|
+
# @param method_name [Symbol] the name of the attribute
|
54
|
+
# @return [Object] the result of the attribute check
|
55
|
+
def method_missing(method_name, *args, &block)
|
56
|
+
ensure_fully_loaded unless @attributes.key?(method_name)
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
43
60
|
private
|
44
61
|
|
45
62
|
# Make sure the model is loaded at least once.
|
@@ -49,12 +66,7 @@ module Redd
|
|
49
66
|
|
50
67
|
# Gets the attribute and loads it if it may be available from the response.
|
51
68
|
def get_attribute(name)
|
52
|
-
|
53
|
-
super
|
54
|
-
end
|
55
|
-
|
56
|
-
# Checks whether an attribute exists, loading it first if necessary.
|
57
|
-
def attribute?(name)
|
69
|
+
# XXX: Replace get_attribute calls with simple method calls?
|
58
70
|
ensure_fully_loaded unless @attributes.key?(name)
|
59
71
|
super
|
60
72
|
end
|
data/lib/redd/models/listing.rb
CHANGED
@@ -16,7 +16,7 @@ module Redd
|
|
16
16
|
new(client, hash)
|
17
17
|
end
|
18
18
|
|
19
|
-
%i([] each empty?
|
19
|
+
%i([] each empty? first last).each do |method_name|
|
20
20
|
define_method(method_name) do |*args, &block|
|
21
21
|
get_attribute(:children).public_send(method_name, *args, &block)
|
22
22
|
end
|
@@ -1,10 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '
|
3
|
+
require_relative 'basic_model'
|
4
4
|
|
5
5
|
module Redd
|
6
6
|
module Models
|
7
|
-
|
7
|
+
# An object that represents a bunch of comments that need to be expanded.
|
8
|
+
class MoreComments < BasicModel
|
9
|
+
# Expand the object's children into a listing of Comments and MoreComments.
|
10
|
+
# @param link [String] the fullname of the submission the object belongs to
|
11
|
+
# @param sort [String] the sort order of the submission
|
12
|
+
# @return [Listing<Comment, MoreComments>] the expanded children
|
13
|
+
def expand(link:, sort: 'best')
|
14
|
+
@client.model(
|
15
|
+
:get, '/api/morechildren',
|
16
|
+
link_id: link,
|
17
|
+
children: get_attribute(:children).join(','),
|
18
|
+
sort: sort
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Keep expanding until all top-level MoreComments are converted to comments
|
23
|
+
def recursive_expand(link:, sort: 'best')
|
24
|
+
# FIXME: this returns a flattened listing of comments and doesn't preserve the structure
|
25
|
+
expand(link: link, sort: sort).flat_map do |o|
|
26
|
+
o.is_a?(MoreComments) && o.count > 0 ? o.recursive_expand(link: link, sort: sort) : [o]
|
27
|
+
end
|
28
|
+
end
|
8
29
|
end
|
9
30
|
end
|
10
31
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Redd
|
4
|
+
module Models
|
5
|
+
# Applied to {Session} for site-wide and {Subreddit} for subreddit-specific search.
|
6
|
+
module Searchable
|
7
|
+
# Search reddit.
|
8
|
+
# @see https://www.reddit.com/wiki/search
|
9
|
+
#
|
10
|
+
# @param query [String] the search query
|
11
|
+
# @param params [Hash] the search params
|
12
|
+
# @option params [:cloudsearch, :lucene, :plain] :syntax the query's syntax
|
13
|
+
# @option params [String] :after return results after the given fullname
|
14
|
+
# @option params [String] :before return results before the given fullname
|
15
|
+
# @option params [Integer] :count the number of items already seen in the listing
|
16
|
+
# @option params [1..100] :limit the maximum number of things to return
|
17
|
+
# @option params [:hour, :day, :week, :month, :year, :all] :time the time period to restrict
|
18
|
+
# search results by
|
19
|
+
# @option params [:relevance, :hot, :top, :new, :comments] :sort the sort order of results
|
20
|
+
# @option params [String] :restrict_to restrict by subreddit (prefer {Subreddit#search})
|
21
|
+
# @return [Listing<Comment, Submission>] the search results
|
22
|
+
def search(query, **params)
|
23
|
+
params[:q] = query if query
|
24
|
+
params[:t] = params.delete(:time) if params.key?(:time)
|
25
|
+
if params[:restrict_to]
|
26
|
+
subreddit = params.delete(:restrict_to)
|
27
|
+
params[:restrict_sr] = true
|
28
|
+
@client.model(:get, "/r/#{subreddit}/search", params)
|
29
|
+
else
|
30
|
+
@client.model(:get, '/search', params)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/redd/models/session.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'lazy_model'
|
4
|
+
require_relative 'searchable'
|
4
5
|
|
5
6
|
module Redd
|
6
7
|
module Models
|
7
8
|
# The starter class.
|
8
9
|
class Session < BasicModel
|
10
|
+
include Searchable
|
11
|
+
|
9
12
|
# @return [FrontPage] the user's front page
|
10
13
|
def front_page
|
11
14
|
FrontPage.new(@client)
|
@@ -45,7 +48,7 @@ module Redd
|
|
45
48
|
# Get submissions or comments by their fullnames.
|
46
49
|
# @param fullnames [String, Array<String>] one or an array of fullnames (e.g. t3_abc1234)
|
47
50
|
# @return [Listing<Submission, Comment>]
|
48
|
-
def
|
51
|
+
def from_ids(fullnames)
|
49
52
|
# XXX: Could we use better methods for t1_ and t3_?
|
50
53
|
@client.model(:get, '/api/info', id: Array(fullnames).join(','))
|
51
54
|
end
|
@@ -25,16 +25,68 @@ module Redd
|
|
25
25
|
def self.from_response(client, hash)
|
26
26
|
link_id = hash.fetch(:id)
|
27
27
|
new(client, hash) do |c|
|
28
|
-
# `
|
29
|
-
# -
|
30
|
-
# -
|
31
|
-
|
32
|
-
|
33
|
-
Comment.from_response(c, comment_object[:data])
|
34
|
-
end
|
35
|
-
details[0][:data][:children][0][:data].merge(comments: comments)
|
28
|
+
# `data` is a pair (2-element array):
|
29
|
+
# - data[0] is a one-item listing containing the submission
|
30
|
+
# - data[1] is listing of comments
|
31
|
+
data = c.get("/comments/#{link_id}").body
|
32
|
+
data[0][:data][:children][0][:data].merge(comments: c.unmarshal(data[1]))
|
36
33
|
end
|
37
34
|
end
|
35
|
+
|
36
|
+
# Mark the link as "Not Suitable For Work".
|
37
|
+
def mark_as_nsfw
|
38
|
+
@client.get('/api/marknsfw', id: get_attribute(:name))
|
39
|
+
@attributes[:over_18] = true
|
40
|
+
end
|
41
|
+
|
42
|
+
# No longer mark the link as "Not Suitable For Work".
|
43
|
+
def unmark_as_nsfw
|
44
|
+
@client.get('/api/unmarknsfw', id: get_attribute(:name))
|
45
|
+
@attributes[:over_18] = false
|
46
|
+
end
|
47
|
+
|
48
|
+
# Mark the link as a spoiler.
|
49
|
+
def mark_as_spoiler
|
50
|
+
@client.get('/api/spoiler', id: get_attribute(:name))
|
51
|
+
@attributes[:spoiler] = true
|
52
|
+
end
|
53
|
+
|
54
|
+
# No longer mark the link as a spoiler.
|
55
|
+
def unmark_as_spoiler
|
56
|
+
@client.get('/api/unspoiler', id: get_attribute(:name))
|
57
|
+
@attributes[:spoiler] = false
|
58
|
+
end
|
59
|
+
|
60
|
+
# Set the submission to "contest mode" (comments are randomly sorted)
|
61
|
+
def enable_contest_mode
|
62
|
+
@client.post('/api/set_contest_mode', id: get_attribute(:name), state: true)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Disable the "contest mode".
|
66
|
+
def disable_contest_mode
|
67
|
+
@client.post('/api/set_contest_mode', id: get_attribute(:name), state: false)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set the submission as the sticky post of the subreddit.
|
71
|
+
# @param slot [1, 2] which "slot" to place the sticky on
|
72
|
+
def make_sticky(slot: nil)
|
73
|
+
@client.post('/api/set_subreddit_sticky', id: get_attribute(:name), num: slot, state: true)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Unsticky the post from the subreddit.
|
77
|
+
def remove_sticky
|
78
|
+
@client.post('/api/set_subreddit_sticky', id: get_attribute(:name), state: false)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Prevent users from commenting on the link (and hide it as well).
|
82
|
+
def lock
|
83
|
+
@client.post('/api/lock', id: get_attribute(:name))
|
84
|
+
end
|
85
|
+
|
86
|
+
# Allow users to comment on the link again.
|
87
|
+
def unlock
|
88
|
+
@client.post('/api/unlock', id: get_attribute(:name))
|
89
|
+
end
|
38
90
|
end
|
39
91
|
end
|
40
92
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative 'lazy_model'
|
4
4
|
require_relative 'messageable'
|
5
|
+
require_relative 'searchable'
|
5
6
|
require_relative '../utilities/stream'
|
6
7
|
|
7
8
|
module Redd
|
@@ -9,6 +10,24 @@ module Redd
|
|
9
10
|
# A subreddit.
|
10
11
|
class Subreddit < LazyModel
|
11
12
|
include Messageable
|
13
|
+
include Searchable
|
14
|
+
|
15
|
+
# A list of keys that are required by #modify_settings.
|
16
|
+
SETTINGS_KEYS = %i(allow_images allow_top collapse_deleted_comments comment_score_hide_mins
|
17
|
+
description exclude_banned_modqueue header-title hide_ads lang link_type
|
18
|
+
over_18 public_description public_traffic show_media show_media_preview
|
19
|
+
spam_comments spam_links spam_selfposts spoilers_enabled submit_link_label
|
20
|
+
submit_text submit_text_label suggested_comment_sort theme_sr
|
21
|
+
theme_sr_update title type wiki_edit_age wiki_edit_karma wikimode).freeze
|
22
|
+
|
23
|
+
# A mapping from keys returned by #settings to keys required by #modify_settings
|
24
|
+
SETTINGS_MAP = {
|
25
|
+
subreddit_type: :type,
|
26
|
+
language: :lang,
|
27
|
+
content_options: :link_type,
|
28
|
+
default_set: :allow_top,
|
29
|
+
header_hover_text: :'header-title'
|
30
|
+
}.freeze
|
12
31
|
|
13
32
|
# Make a Subreddit from its name.
|
14
33
|
# @option hash [String] :display_name the subreddit's name
|
@@ -38,6 +57,15 @@ module Redd
|
|
38
57
|
WikiPage.from_response(@client, title: title, subreddit: self)
|
39
58
|
end
|
40
59
|
|
60
|
+
# Search a subreddit.
|
61
|
+
# @param query [String] the search query
|
62
|
+
# @param params [Hash] refer to {Searchable} to see search parameters
|
63
|
+
# @see Searchable#search
|
64
|
+
def search(query, **params)
|
65
|
+
restricted_params = { restrict_to: get_attribute(:display_name) }.merge(params)
|
66
|
+
super(query, restricted_params)
|
67
|
+
end
|
68
|
+
|
41
69
|
# @!group Listings
|
42
70
|
|
43
71
|
# Get the appropriate listing.
|
@@ -54,7 +82,7 @@ module Redd
|
|
54
82
|
# @return [Listing<Submission, Comment>]
|
55
83
|
def listing(sort, **params)
|
56
84
|
params[:t] = params.delete(:time) if params.key?(:time)
|
57
|
-
@client.model(:get, "/r/#{get_attribute(:display_name)}/#{sort}
|
85
|
+
@client.model(:get, "/r/#{get_attribute(:display_name)}/#{sort}", params)
|
58
86
|
end
|
59
87
|
|
60
88
|
# @!method hot(**params)
|
@@ -83,7 +111,7 @@ module Redd
|
|
83
111
|
#
|
84
112
|
# @return [Listing<Submission, Comment>]
|
85
113
|
def moderator_listing(type, **params)
|
86
|
-
@client.model(:get, "/r/#{get_attribute(:display_name)}/about/#{type}
|
114
|
+
@client.model(:get, "/r/#{get_attribute(:display_name)}/about/#{type}", params)
|
87
115
|
end
|
88
116
|
|
89
117
|
# @!method reports(**params)
|
@@ -97,6 +125,38 @@ module Redd
|
|
97
125
|
define_method(type) { |**params| moderator_listing(type, **params) }
|
98
126
|
end
|
99
127
|
|
128
|
+
# @!endgroup
|
129
|
+
# @!group Relationship Listings
|
130
|
+
|
131
|
+
# Get the appropriate relationship listing.
|
132
|
+
# @param type [:banned, :muted, :wikibanned, :contributors, :wikicontributors, :moderators]
|
133
|
+
# the type of listing
|
134
|
+
# @param params [Hash] a list of params to send with the request
|
135
|
+
# @option params [String] :after return results after the given fullname
|
136
|
+
# @option params [String] :before return results before the given fullname
|
137
|
+
# @option params [Integer] :count the number of items already seen in the listing
|
138
|
+
# @option params [1..100] :limit the maximum number of things to return
|
139
|
+
# @option params [String] :user find a specific user
|
140
|
+
#
|
141
|
+
# @return [Array<Hash>]
|
142
|
+
def relationship_listing(type, **params)
|
143
|
+
# TODO: add methods to determine if a certain user was banned/muted/etc
|
144
|
+
user_list = @client.get("/r/#{get_attribute(:display_name)}/about/#{type}", params).body
|
145
|
+
user_list[:data][:children]
|
146
|
+
end
|
147
|
+
|
148
|
+
# @!method banned(**params)
|
149
|
+
# @!method muted(**params)
|
150
|
+
# @!method wikibanned(**params)
|
151
|
+
# @!method contributors(**params)
|
152
|
+
# @!method wikicontributors(**params)
|
153
|
+
# @!method moderators(**params)
|
154
|
+
#
|
155
|
+
# @see #relationship_listing
|
156
|
+
%i(banned muted wikibanned contributors wikicontributors moderators).each do |type|
|
157
|
+
define_method(type) { |**params| relationship_listing(type, **params) }
|
158
|
+
end
|
159
|
+
|
100
160
|
# @!endgroup
|
101
161
|
|
102
162
|
# Stream newly submitted posts.
|
@@ -196,6 +256,50 @@ module Redd
|
|
196
256
|
def unsubscribe
|
197
257
|
subscribe(action: :unsub)
|
198
258
|
end
|
259
|
+
|
260
|
+
# Get the subreddit's CSS.
|
261
|
+
# @return [String, nil] the stylesheet or nil if no stylesheet exists
|
262
|
+
def stylesheet
|
263
|
+
url = @client.get("/r/#{get_attribute(:display_name)}/stylesheet").headers['location']
|
264
|
+
HTTP.get(url).body.to_s
|
265
|
+
rescue Redd::NotFound
|
266
|
+
nil
|
267
|
+
end
|
268
|
+
|
269
|
+
# Edit the subreddit's stylesheet.
|
270
|
+
# @param text [String] the updated CSS
|
271
|
+
# @param reason [String] the reason for modifying the stylesheet
|
272
|
+
def update_stylesheet(text, reason: nil)
|
273
|
+
params = { op: 'save', stylesheet_contents: text }
|
274
|
+
params[:reason] = reason if reason
|
275
|
+
@client.post("/r/#{get_attribute(:display_name)}/api/subreddit_stylesheet", params)
|
276
|
+
end
|
277
|
+
|
278
|
+
# @return [Hash] the subreddit's settings
|
279
|
+
def settings
|
280
|
+
@client.get("/r/#{get_attribute(:display_name)}/about/edit").body[:data]
|
281
|
+
end
|
282
|
+
|
283
|
+
# Modify the subreddit's settings.
|
284
|
+
# @param params [Hash] the settings to change
|
285
|
+
# @see https://www.reddit.com/dev/api#POST_api_site_admin
|
286
|
+
def modify_settings(**params)
|
287
|
+
full_params = settings.merge(params)
|
288
|
+
full_params[:sr] = get_attribute(:name)
|
289
|
+
SETTINGS_MAP.each { |src, dest| full_params[dest] = full_params.delete(src) }
|
290
|
+
@client.post('/api/site_admin', full_params)
|
291
|
+
end
|
292
|
+
|
293
|
+
private
|
294
|
+
|
295
|
+
def add_relationship(**params)
|
296
|
+
# FIXME: add public methods
|
297
|
+
@client.post("/r/#{get_attribute(:display_name)}/api/friend", params)
|
298
|
+
end
|
299
|
+
|
300
|
+
def remove_relationship(**params)
|
301
|
+
@client.post("/r/#{get_attribute(:display_name)}/api/unfriend", params)
|
302
|
+
end
|
199
303
|
end
|
200
304
|
end
|
201
305
|
end
|
data/lib/redd/models/user.rb
CHANGED
@@ -34,6 +34,20 @@ module Redd
|
|
34
34
|
super(to: get_attribute(:name), subject: subject, text: text, from: from)
|
35
35
|
end
|
36
36
|
|
37
|
+
# Add the user as a friend.
|
38
|
+
# @param note [String] a note for the friend
|
39
|
+
def friend(note = nil)
|
40
|
+
name = get_attribute(:name)
|
41
|
+
body = JSON.generate(note ? { name: name, note: note } : { name: name })
|
42
|
+
@client.request(:put, "/api/v1/me/friends/#{name}", body: body)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Unfriend the user.
|
46
|
+
def unfriend
|
47
|
+
name = get_attribute(:name)
|
48
|
+
@client.request(:delete, "/api/v1/me/friends/#{name}", raw: true, form: { id: name })
|
49
|
+
end
|
50
|
+
|
37
51
|
# Get the appropriate listing.
|
38
52
|
# @param type [:overview, :submitted, :comments, :liked, :disliked, :hidden, :saved, :gilded]
|
39
53
|
# the type of listing to request
|
@@ -5,10 +5,10 @@ require_relative '../error'
|
|
5
5
|
module Redd
|
6
6
|
module Utilities
|
7
7
|
# Handles response errors in API responses.
|
8
|
-
# TODO: handle [:json][:errors] array
|
9
8
|
class ErrorHandler
|
10
9
|
HTTP_ERRORS = {
|
11
10
|
400 => Redd::BadRequest,
|
11
|
+
403 => Redd::Forbidden,
|
12
12
|
404 => Redd::NotFound,
|
13
13
|
500 => Redd::ServerError,
|
14
14
|
502 => Redd::ServerError,
|
@@ -21,14 +21,19 @@ module Redd
|
|
21
21
|
'invalid_token' => Redd::InvalidAccess
|
22
22
|
}.freeze
|
23
23
|
|
24
|
-
def check_error(response)
|
25
|
-
|
26
|
-
if response.
|
24
|
+
def check_error(response, raw:)
|
25
|
+
# TODO: deal with errors of type { fields:, explanation:, message:, reason: }
|
26
|
+
if !raw && response.body[:json] && response.body[:json][:errors] &&
|
27
|
+
!response.body[:json][:errors].empty?
|
28
|
+
Redd::APIError.new(response)
|
29
|
+
elsif HTTP_ERRORS.key?(response.code)
|
30
|
+
HTTP_ERRORS[response.code].new(response)
|
31
|
+
elsif response.code == 401
|
27
32
|
AUTHORIZATION_ERRORS.each do |key, klass|
|
28
|
-
|
33
|
+
auth_header = response.headers['www-authenticate']
|
34
|
+
return klass.new(response) if auth_header && auth_header.include?(key)
|
29
35
|
end
|
30
36
|
end
|
31
|
-
nil
|
32
37
|
end
|
33
38
|
end
|
34
39
|
end
|
data/lib/redd/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.0.pre.
|
4
|
+
version: 0.8.0.pre.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Avinash Dwarapu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-02-
|
11
|
+
date: 2017-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -163,6 +163,7 @@ files:
|
|
163
163
|
- lib/redd/models/postable.rb
|
164
164
|
- lib/redd/models/private_message.rb
|
165
165
|
- lib/redd/models/replyable.rb
|
166
|
+
- lib/redd/models/searchable.rb
|
166
167
|
- lib/redd/models/session.rb
|
167
168
|
- lib/redd/models/submission.rb
|
168
169
|
- lib/redd/models/subreddit.rb
|