redd 0.8.8 → 0.9.0.pre.1

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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CONTRIBUTING.md +63 -0
  4. data/Guardfile +7 -0
  5. data/README.md +6 -5
  6. data/Rakefile +1 -1
  7. data/TODO.md +423 -0
  8. data/bin/console +91 -77
  9. data/bin/guard +2 -0
  10. data/lib/redd.rb +7 -5
  11. data/lib/redd/api_client.rb +2 -3
  12. data/lib/redd/auth_strategies/auth_strategy.rb +7 -2
  13. data/lib/redd/auth_strategies/script.rb +7 -0
  14. data/lib/redd/auth_strategies/userless.rb +7 -0
  15. data/lib/redd/auth_strategies/web.rb +6 -1
  16. data/lib/redd/client.rb +0 -3
  17. data/lib/redd/errors.rb +56 -0
  18. data/lib/redd/middleware.rb +10 -8
  19. data/lib/redd/models/access.rb +30 -18
  20. data/lib/redd/models/comment.rb +185 -27
  21. data/lib/redd/models/front_page.rb +16 -36
  22. data/lib/redd/models/gildable.rb +1 -1
  23. data/lib/redd/models/inboxable.rb +13 -3
  24. data/lib/redd/models/listing.rb +27 -6
  25. data/lib/redd/models/live_thread.rb +76 -23
  26. data/lib/redd/models/live_update.rb +46 -0
  27. data/lib/redd/models/messageable.rb +1 -1
  28. data/lib/redd/models/mod_action.rb +59 -0
  29. data/lib/redd/models/model.rb +23 -0
  30. data/lib/redd/models/moderatable.rb +6 -6
  31. data/lib/redd/models/modmail.rb +61 -0
  32. data/lib/redd/models/modmail_conversation.rb +154 -0
  33. data/lib/redd/models/modmail_message.rb +35 -0
  34. data/lib/redd/models/more_comments.rb +29 -5
  35. data/lib/redd/models/multireddit.rb +63 -20
  36. data/lib/redd/models/paginated_listing.rb +113 -0
  37. data/lib/redd/models/postable.rb +11 -13
  38. data/lib/redd/models/private_message.rb +78 -11
  39. data/lib/redd/models/replyable.rb +2 -2
  40. data/lib/redd/models/reportable.rb +14 -0
  41. data/lib/redd/models/searchable.rb +2 -2
  42. data/lib/redd/models/self.rb +17 -0
  43. data/lib/redd/models/session.rb +75 -31
  44. data/lib/redd/models/submission.rb +309 -56
  45. data/lib/redd/models/subreddit.rb +330 -103
  46. data/lib/redd/models/trophy.rb +34 -0
  47. data/lib/redd/models/user.rb +185 -46
  48. data/lib/redd/models/wiki_page.rb +37 -16
  49. data/lib/redd/utilities/error_handler.rb +13 -13
  50. data/lib/redd/utilities/unmarshaller.rb +7 -5
  51. data/lib/redd/version.rb +1 -1
  52. data/redd.gemspec +18 -15
  53. metadata +82 -16
  54. data/lib/redd/error.rb +0 -53
  55. data/lib/redd/models/basic_model.rb +0 -80
  56. data/lib/redd/models/lazy_model.rb +0 -75
  57. data/lib/redd/models/mod_mail.rb +0 -142
  58. data/lib/redd/utilities/stream.rb +0 -61
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'basic_model'
3
+ require_relative 'model'
4
4
 
5
5
  module Redd
6
6
  module Models
@@ -8,35 +8,47 @@ module Redd
8
8
  # @note This model also supports an additional key, called `:created_at` which is a UNIX time
9
9
  # representing the time the access was created. The default value is the time the object was
10
10
  # initialized.
11
- class Access < BasicModel
11
+ class Access < Model
12
12
  # Create a non-lazily initialized Access.
13
- # @param client [Object] (deprecated) the client to create the object with
14
- # @param attributes [Hash] the Access's attributes
13
+ # @param attributes [Hash] the access's attributes
15
14
  # @example
16
15
  # access = Redd::Models::Access.new(access_token: ...)
17
- def initialize(client = nil, attributes = {})
18
- if client.is_a?(Hash)
19
- super(nil, client)
20
- else
21
- super(client, attributes)
22
- end
16
+ def initialize(attributes = {})
17
+ super(nil, attributes)
18
+ @creation_time = Time.now
23
19
  end
24
20
 
21
+ # Whether the access has expired.
22
+ # @param grace_period [Integer] the grace period where the model expires early
23
+ # @return [Boolean] whether the access has expired
25
24
  def expired?(grace_period = 60)
26
- # We're not sure, so we just assume it hasn't expired.
27
- return false unless @attributes[:expires_in]
28
- Time.now.to_i > @attributes[:created_at] + (@attributes[:expires_in] - grace_period)
25
+ Time.now > read_attribute(:created_at) + read_attribute(:expires_in) - grace_period
29
26
  end
30
27
 
28
+ # @return [Boolean] whether the access can be refreshed
31
29
  def permanent?
32
- !@attributes[:refresh_token].nil?
30
+ read_attribute(:refresh_token).nil?
33
31
  end
34
32
 
35
- private
33
+ # @!attribute [r] access_token
34
+ # @return [String] the access token
35
+ property :access_token
36
36
 
37
- def after_initialize
38
- @attributes[:created_at] ||= Time.now.to_i
39
- end
37
+ # @!attribute [r] refresh_token
38
+ # @return [String] the (optional) refresh token
39
+ property :refresh_token, :nil
40
+
41
+ # @!attribute [r] expires_in
42
+ # @return [Integer] the number of seconds before the access expires
43
+ property :expires_in
44
+
45
+ # @!attribute [r] created_at
46
+ # @return [Time] the time the access was created
47
+ property :created_at, default: ->() { @creation_time }
48
+
49
+ # @!attribute [r] scope
50
+ # @return [Array<String>] the scopes that the user is allowed to access
51
+ property :scope, with: ->(scope) { scope.split(' ') }
40
52
  end
41
53
  end
42
54
  end
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lazy_model'
3
+ require_relative 'model'
4
4
  require_relative 'gildable'
5
5
  require_relative 'inboxable'
6
6
  require_relative 'moderatable'
7
7
  require_relative 'postable'
8
8
  require_relative 'replyable'
9
+ require_relative 'reportable'
9
10
 
10
11
  require_relative 'listing'
11
12
  require_relative 'subreddit'
@@ -14,47 +15,204 @@ require_relative 'user'
14
15
  module Redd
15
16
  module Models
16
17
  # A comment.
17
- class Comment < LazyModel
18
+ class Comment < Model
18
19
  include Gildable
19
20
  include Inboxable
20
21
  include Moderatable
21
22
  include Postable
22
23
  include Replyable
24
+ include Reportable
23
25
 
24
- # Create a Comment from its fullname.
25
- # @param client [APIClient] the api client to initialize the object with
26
- # @param id [String] the fullname
27
- # @return [Comment]
28
- def self.from_id(client, id)
29
- new(client, name: id)
30
- end
26
+ # @!attribute [r] subreddit_id
27
+ # @return [String] the subreddit fullname
28
+ property :subreddit_id
31
29
 
32
- private
30
+ # @!attribute [r] approved_at
31
+ # @return [Time, nil] the time when the comment was approved
32
+ property :approved_at, from: :approved_at_utc, with: ->(t) { Time.at(t) if t }
33
33
 
34
- def after_initialize
35
- @attributes[:replies] =
36
- if @attributes[:replies].is_a?(Hash)
37
- @client.unmarshal(@attributes[:replies])
38
- else
39
- Models::Listing.new(@client, children: [], after: nil, before: nil)
40
- end
41
- @attributes[:author] = User.from_id(@client, @attributes.fetch(:author))
42
- @attributes[:subreddit] = Subreddit.from_id(@client, @attributes.fetch(:subreddit))
43
- end
34
+ # @!attribute [r] banned_by
35
+ # @return [String] the user (?) that banned this comment
36
+ property :banned_by
37
+
38
+ # @!attribute [r] removal_reason
39
+ # @return [String, nil] the reason for comment removal
40
+ property :removal_reason
41
+
42
+ # @!attribute [r] link
43
+ # @return [Submission] the link that the comment was posted to
44
+ property :link, from: :link_id, with: ->(id) { Submission.new(client, name: id) }
45
+
46
+ # @!attribute [r] upvoted?
47
+ # @return [Boolean, nil] whether the user liked/disliked this comment
48
+ property :upvoted?, from: :likes
49
+
50
+ # @!attribute [r] replies
51
+ # @return [Listing<Comment>] the comment replies
52
+ property :replies, with: ->(l) { Listing.new(client, l[:data]) if l.is_a?(Hash) }
53
+
54
+ # @!attribute [r] user_reports
55
+ # @return [Array<String>] user reports
56
+ property :user_reports
57
+
58
+ # @!attribute [r] saved?
59
+ # @return [Boolean] whether the submission was saved by the logged-in user
60
+ property :saved?, from: :saved
61
+
62
+ # @!attribute [r] id
63
+ # @return [String] the comment id
64
+ property :id
65
+
66
+ # @!attribute [r] banned_at
67
+ # @return [Time, nil] the time when the comment was banned
68
+ property :banned_at, from: :banned_at_utc, with: ->(t) { Time.at(t) if t }
69
+
70
+ # @!attribute [r] gilded
71
+ # @return [Integer] the number of times the comment was gilded
72
+ property :gilded
73
+
74
+ # @!attribute [r] archived?
75
+ # @return [Boolean] whether this comment was archived
76
+ property :archived?, from: :archived
77
+
78
+ # @!attribute [r] report_reasons
79
+ # @return [Array<String>] report reasons
80
+ property :report_reasons
81
+
82
+ # @!attribute [r] author
83
+ # @return [User] the comment author
84
+ property :author, with: ->(name) { User.new(client, name: name) }
85
+
86
+ # @!attribute [r] can_mod_post?
87
+ # @return [Boolean] whether the logged-in user can mod post
88
+ property :can_mod_post?, from: :can_mod_post
89
+
90
+ # @!attribute [r] ups
91
+ # @return [Integer] the comment upvotes
92
+ # @deprecated use {#score} instead
93
+ property :ups
94
+
95
+ # @!attribute [r] downs
96
+ # @return [Integer] the comment downvotes
97
+ # @deprecated is always 0; use {#score} instead
98
+ property :downs
99
+
100
+ # @!attribute [r] parent
101
+ # @return [Comment, Submission] the comment parent
102
+ property :parent,
103
+ from: :parent_id,
104
+ with: ->(id) { Session.new(client).from_fullnames(id).first }
105
+
106
+ # @!attribute [r] score
107
+ # @return [Integer] the comment score
108
+ property :score
109
+
110
+ # @!attribute [r] approved_by
111
+ # @return [String] the user that approved the comment
112
+ property :approved_by
113
+
114
+ # @!attribute [r] body
115
+ # @return [String] the markdown comment body
116
+ property :body
117
+
118
+ # @!attribute [r] body_html
119
+ # @return [String] the html-rendered version of the body
120
+ property :body_html
121
+
122
+ # @!attribute [r] edited_at
123
+ # @return [Time, nil] the time when the comment was edited
124
+ property :edited_at, from: :edited, with: ->(t) { Time.at(t) if t }
125
+
126
+ # @!attribute [r] author_flair_css_class
127
+ # @return [String] the author flair css class
128
+ property :author_flair_css_class
129
+
130
+ # @!attribute [r] collapsed?
131
+ # @return [Boolean] whether the comment was collapsed
132
+ property :collapsed?, from: :collapsed
133
+
134
+ # @!attribute [r] submitter?
135
+ # @return [Boolean] whether the comment author is the link submitter
136
+ property :submitter?, from: :is_submitter
137
+
138
+ # @!attribute [r] collapsed_reason
139
+ # @return [String] the reason for collapse (?)
140
+ property :collapsed_reason
141
+
142
+ # @!attribute [r] stickied?
143
+ # @return [Boolean] whether the comment was stickied
144
+ property :stickied?, from: :stickied
145
+
146
+ # @!attribute [r] can_gild?
147
+ # @return [Boolean] whether the comment is gildable
148
+ property :can_gild?, from: :can_gild
149
+
150
+ # @!attribute [r] subreddit
151
+ # @return [Subreddit] the comment's subreddit
152
+ property :subreddit, with: ->(n) { Subreddit.new(client, display_name: n) }
153
+
154
+ # @!attribute [r] score_hidden
155
+ # @return [Boolean] whether the comment score is hidden
156
+ property :score_hidden?, from: :score_hidden
157
+
158
+ # @!attribute [r] subreddit_type
159
+ # @return [String] subreddit type
160
+ property :subreddit_type
161
+
162
+ # @!attribute [r] name
163
+ # @return [String] the comment fullname
164
+ property :name
165
+
166
+ # @!attribute [r] author_flair_text
167
+ # @return [String] the author flair text
168
+ property :author_flair_text
169
+
170
+ # @!attribute [r] created_at
171
+ # @return [String] the time when the model was created
172
+ property :created_at, from: :created_utc, with: ->(t) { Time.at(t) }
173
+
174
+ # @!attribute [r] subreddit_name_prefixed
175
+ # @return [String] the subreddit name, prefixed with "r/"
176
+ property :subreddit_name_prefixed
177
+
178
+ # @!attribute [r] controversiality
179
+ # @return [Integer] the comment controversiality
180
+ property :controversiality
181
+
182
+ # @!attribute [r] depth
183
+ # @return [Integer] the comment depth
184
+ property :depth
185
+
186
+ # @!attribute [r] mod_reports
187
+ # @return [Array<String>] the moderator reports
188
+ property :mod_reports
189
+
190
+ # @!attribute [r] report_count
191
+ # @return [Integer] the report count
192
+ property :report_count, from: :num_reports
193
+
194
+ # @!attribute [r] distinguished?
195
+ # @return [Boolean] whether the comment is distinguished
196
+ property :distinguished?, from: :distinguished
197
+
198
+ private
44
199
 
45
- def default_loader
46
- @attributes.key?(:link_id) ? load_with_comments : load_without_comments
200
+ def lazer_reload
201
+ self[:link] ? load_with_comments : load_without_comments
47
202
  end
48
203
 
49
204
  def load_with_comments
50
- id = @attributes.fetch(:id) { @attributes.fetch(:name).sub('t1_', '') }
51
- link_id = @attributes[:link_id].sub('t3_', '')
52
- @client.get("/comments/#{link_id}/_/#{id}").body[1][:data][:children][0][:data]
205
+ fully_loaded!
206
+ id = self[:id] || read_attribute(:name).sub('t1_', '')
207
+ link_id = read_attribute(:link).name.sub('t3_', '')
208
+ client.get("/comments/#{link_id}/_/#{id}").body[1][:data][:children][0][:data]
53
209
  end
54
210
 
55
211
  def load_without_comments
56
- id = @attributes.fetch(:id) { @attributes.fetch(:name).sub('t1_', '') }
57
- @client.get('/api/info', id: "t1_#{id}").body[:data][:children][0][:data]
212
+ id = self[:id] || read_attribute(:name).sub('t1_', '')
213
+ response = client.get('/api/info', id: "t1_#{id}").body[:data][:children][0][:data]
214
+ response.delete(:replies) # Make sure replies are lazy-loaded later.
215
+ response
58
216
  end
59
217
  end
60
218
  end
@@ -1,41 +1,41 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'basic_model'
4
- require_relative '../utilities/stream'
3
+ require_relative 'model'
5
4
 
6
5
  module Redd
7
6
  module Models
8
7
  # The front page.
9
8
  # FIXME: deal with serious code duplication from Subreddit
10
- class FrontPage < BasicModel
9
+ class FrontPage < Model
11
10
  # @return [Array<String>] reddit's base wiki pages
12
11
  def wiki_pages
13
- @client.get('/wiki/pages').body[:data]
12
+ client.get('/wiki/pages').body[:data]
14
13
  end
15
14
 
16
15
  # Get a wiki page by its title.
17
16
  # @param title [String] the page's title
18
17
  # @return [WikiPage]
19
18
  def wiki_page(title)
20
- WikiPage.new(@client, title: title)
19
+ WikiPage.new(client, title: title)
21
20
  end
22
21
 
23
22
  # Get the appropriate listing.
24
23
  # @param sort [:hot, :new, :top, :controversial, :comments, :rising, :gilded] the type of
25
24
  # listing
26
- # @param params [Hash] a list of params to send with the request
27
- # @option params [String] :after return results after the given fullname
28
- # @option params [String] :before return results before the given fullname
29
- # @option params [Integer] :count the number of items already seen in the listing
30
- # @option params [1..100] :limit the maximum number of things to return
31
- # @option params [:hour, :day, :week, :month, :year, :all] :time the time period to consider
25
+ # @param options [Hash] a list of options to send with the request
26
+ # @option options [String] :after return results after the given fullname
27
+ # @option options [String] :before return results before the given fullname
28
+ # @option options [Integer, nil] :limit maximum number of items to return (nil for no limit)
29
+ # @option options [:hour, :day, :week, :month, :year, :all] :time the time period to consider
32
30
  # when sorting.
33
31
  #
34
32
  # @note The option :time only applies to the top and controversial sorts.
35
- # @return [Listing<Submission>]
36
- def listing(sort, **params)
37
- params[:t] = params.delete(:time) if params.key?(:time)
38
- @client.model(:get, "/#{sort}", params)
33
+ # @return [PaginatedListing<Submission>]
34
+ def listing(sort, **options)
35
+ options[:t] = options.delete(:time) if options.key?(:time)
36
+ PaginatedListing.new(client, options) do |**req_options|
37
+ client.model(:get, "/#{sort}", options.merge(req_options))
38
+ end
39
39
  end
40
40
 
41
41
  # @!method hot(**params)
@@ -47,29 +47,9 @@ module Redd
47
47
  # @!method gilded(**params)
48
48
  #
49
49
  # @see #listing
50
- %i(hot new top controversial comments rising gilded).each do |sort|
50
+ %i[hot new top controversial comments rising gilded].each do |sort|
51
51
  define_method(sort) { |**params| listing(sort, **params) }
52
52
  end
53
-
54
- # Stream newly submitted posts.
55
- def post_stream(**params, &block)
56
- params[:limit] ||= 100
57
- stream = Utilities::Stream.new do |previous|
58
- before = previous ? previous.first.name : nil
59
- listing(:new, params.merge(before: before))
60
- end
61
- block_given? ? stream.stream(&block) : stream.enum_for(:stream)
62
- end
63
-
64
- # Stream newly submitted comments.
65
- def comment_stream(**params, &block)
66
- params[:limit] ||= 100
67
- stream = Utilities::Stream.new do |previous|
68
- before = previous ? previous.first.name : nil
69
- listing(:comments, params.merge(before: before))
70
- end
71
- block_given? ? stream.stream(&block) : stream.enum_for(:stream)
72
- end
73
53
  end
74
54
  end
75
55
  end
@@ -6,7 +6,7 @@ module Redd
6
6
  module Gildable
7
7
  # Gift a user one month of reddit gold for their link or comment.
8
8
  def gild
9
- @client.post("/api/v1/gold/gild/#{get_attribute(:name)}")
9
+ client.post("/api/v1/gold/gild/#{read_attribute(:name)}")
10
10
  end
11
11
  end
12
12
  end
@@ -6,17 +6,27 @@ module Redd
6
6
  module Inboxable
7
7
  # Block the user that sent this item.
8
8
  def block
9
- @client.post('/api/block', id: get_attribute(:name))
9
+ client.post('/api/block', id: read_attribute(:name))
10
+ end
11
+
12
+ # Collapse the item.
13
+ def collapse
14
+ client.post('/api/collapse_message', id: read_attribute(:name))
15
+ end
16
+
17
+ # Uncollapse the item.
18
+ def uncollapse
19
+ client.post('/api/uncollapse_message', id: read_attribute(:name))
10
20
  end
11
21
 
12
22
  # Mark this thing as read.
13
23
  def mark_as_read
14
- @client.post('/api/read_message', id: get_attribute(:name))
24
+ client.post('/api/read_message', id: read_attribute(:name))
15
25
  end
16
26
 
17
27
  # Mark one or more messages as unread.
18
28
  def mark_as_unread
19
- @client.post('/api/unread_message', id: get_attribute(:name))
29
+ client.post('/api/unread_message', id: read_attribute(:name))
20
30
  end
21
31
  end
22
32
  end
@@ -1,24 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'basic_model'
3
+ require_relative 'model'
4
4
 
5
5
  module Redd
6
6
  module Models
7
7
  # A backward-expading listing of items.
8
8
  # @see Stream
9
- class Listing < BasicModel
9
+ class Listing < Model
10
10
  include Enumerable
11
11
 
12
+ # Create a fully initialized listing.
13
+ # @param client [APIClient] the api client
14
+ # @param attributes [Hash] the attribute hash
15
+ def initialize(client, attributes = {})
16
+ super
17
+ fully_loaded!
18
+ end
19
+
12
20
  # @return [Array<Comment, Submission, PrivateMessage>] an array representation of self
13
- def to_ary
14
- get_attribute(:children)
21
+ def to_a
22
+ read_attribute(:children)
15
23
  end
24
+ alias to_ary to_a
16
25
 
17
- %i([] each empty? first last).each do |method_name|
26
+ %i[[] each empty? first last].each do |method_name|
18
27
  define_method(method_name) do |*args, &block|
19
- get_attribute(:children).public_send(method_name, *args, &block)
28
+ read_attribute(:children).public_send(method_name, *args, &block)
20
29
  end
21
30
  end
31
+
32
+ # @!attribute [r] before
33
+ # @return [String] the fullname of the item before this listing
34
+ property :before, :nil
35
+
36
+ # @!attribute [r] after
37
+ # @return [String] the fullname of the item that the next listing will start from
38
+ property :after, :nil
39
+
40
+ # @!attribute [r] children
41
+ # @return [Array<Model>] the listing's children
42
+ property :children, :required, with: ->(a) { a.map { |m| client.unmarshal(m) } }
22
43
  end
23
44
  end
24
45
  end