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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bc08060b2e7111adfd18a83c7d23e464f20a92dc
4
- data.tar.gz: 79897c294f7f612e90acee7184b91a4982d54cb6
3
+ metadata.gz: 69d6cf992f71356b1db86a000bdc3be4508e351c
4
+ data.tar.gz: 01d0b098fb7c26a91e34ff001310417c93e3bd74
5
5
  SHA512:
6
- metadata.gz: 0ccfa94a4923e3748931c6ca5e5515f23c10bd23d979c1ef79e5f3f62a87c78ff66ed01f72033d95d3da3e8ca0e47cf9ebd412d758d3b751d3d935545abd787c
7
- data.tar.gz: f2881316388b4b3b1183aebdc867184aa64750c57ac29cfb6b17532ef099f0c4de309f064f3160a3eb670ee4d70d91cee335367b9c43a34c5f70eb3254781743
6
+ metadata.gz: 4019ce12ee1cfac8927642bfdd327e4221835842a230ad73bce35b0ce3e1f0445523bf5a796aeca182b63c7061861ba1a28a587fe8814594d6d89531ece2c3a6
7
+ data.tar.gz: 292775ce9121b4de029ee49e451417508b64524940043569ebe53b807387fac0b1539a7cf5e37a13d494c83267421a1789a75e00ec96cadbaa2cf01128c0f9b7
data/.rubocop.yml CHANGED
@@ -4,6 +4,10 @@ AllCops:
4
4
  Metrics/LineLength:
5
5
  Max: 100
6
6
 
7
+ # Dumb, but also brand new.
8
+ Style/NumericPredicate:
9
+ Enabled: false
10
+
7
11
  # Allows for raising Redd::ResponseErrors with a Response object.
8
12
  Style/RaiseArgs:
9
13
  Enabled: false
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
- - [ ] More comprehensive error checking
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
@@ -64,37 +64,21 @@ module Redd
64
64
  @unmarshaller.unmarshal(object)
65
65
  end
66
66
 
67
- def model(verb, path, params = {})
67
+ def model(verb, path, options = {})
68
68
  # XXX: make unmarshal explicit in methods?
69
- unmarshal(send(verb, path, params).body)
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, params: {}, form: {})
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, form: form) }
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
- attribute?(method_name) || attribute?(depredicate(method_name)) || super
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 attribute?(method_name)
72
- return get_attribute(depredicate(method_name)) if attribute?(depredicate(method_name))
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
@@ -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, hash.fetch(:name).tr('t1_', ''))
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
- ensure_fully_loaded unless @attributes.key?(name)
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
@@ -16,7 +16,7 @@ module Redd
16
16
  new(client, hash)
17
17
  end
18
18
 
19
- %i([] each empty? length size).each do |method_name|
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 'lazy_model'
3
+ require_relative 'basic_model'
4
4
 
5
5
  module Redd
6
6
  module Models
7
- class MoreComments < LazyModel
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
@@ -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 get(fullnames)
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
- # `details` is a pair (2-element array):
29
- # - details[0] is a one-item listing containing the submission
30
- # - details[1] is listing of comments
31
- details = c.get("/comments/#{link_id}").body
32
- comments = details[1][:data][:children].map do |comment_object|
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}.json", params)
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}.json", params)
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
@@ -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
- return HTTP_ERRORS[response.code].new(response) if HTTP_ERRORS.key?(response.code)
26
- if response.code == 403
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
- return klass.new(response) if response.headers['www-authenticate'].include?(key)
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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'enumerator'
4
-
5
3
  module Redd
6
4
  module Utilities
7
5
  # A forward-expading listing of items that can be enumerated forever.
data/lib/redd/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Redd
4
- VERSION = '0.8.0.pre.1'
4
+ VERSION = '0.8.0.pre.2'
5
5
  end
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.1
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-25 00:00:00.000000000 Z
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