redd 0.7.10 → 0.8.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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +5 -30
  3. data/.rspec +1 -1
  4. data/.rubocop.yml +16 -3
  5. data/.travis.yml +13 -7
  6. data/Gemfile +3 -1
  7. data/LICENSE.txt +21 -0
  8. data/README.md +40 -126
  9. data/Rakefile +10 -3
  10. data/TODO.md +11 -0
  11. data/bin/console +84 -0
  12. data/bin/setup +8 -0
  13. data/lib/redd.rb +84 -46
  14. data/lib/redd/api_client.rb +109 -0
  15. data/lib/redd/auth_strategies/auth_strategy.rb +60 -0
  16. data/lib/redd/auth_strategies/installed.rb +22 -0
  17. data/lib/redd/auth_strategies/script.rb +23 -0
  18. data/lib/redd/auth_strategies/userless.rb +17 -0
  19. data/lib/redd/auth_strategies/web.rb +29 -0
  20. data/lib/redd/client.rb +88 -0
  21. data/lib/redd/error.rb +19 -142
  22. data/lib/redd/models/access.rb +20 -0
  23. data/lib/redd/models/basic_model.rb +124 -0
  24. data/lib/redd/models/comment.rb +51 -0
  25. data/lib/redd/models/front_page.rb +71 -0
  26. data/lib/redd/models/inboxable.rb +23 -0
  27. data/lib/redd/models/lazy_model.rb +63 -0
  28. data/lib/redd/models/listing.rb +26 -0
  29. data/lib/redd/models/messageable.rb +20 -0
  30. data/lib/redd/models/moderatable.rb +41 -0
  31. data/lib/redd/models/more_comments.rb +10 -0
  32. data/lib/redd/models/multireddit.rb +32 -0
  33. data/lib/redd/models/postable.rb +70 -0
  34. data/lib/redd/models/private_message.rb +29 -0
  35. data/lib/redd/models/replyable.rb +16 -0
  36. data/lib/redd/models/session.rb +86 -0
  37. data/lib/redd/models/submission.rb +40 -0
  38. data/lib/redd/models/subreddit.rb +201 -0
  39. data/lib/redd/models/user.rb +72 -0
  40. data/lib/redd/models/wiki_page.rb +24 -0
  41. data/lib/redd/utilities/error_handler.rb +35 -0
  42. data/lib/redd/utilities/rate_limiter.rb +21 -0
  43. data/lib/redd/utilities/stream.rb +63 -0
  44. data/lib/redd/utilities/unmarshaller.rb +39 -0
  45. data/lib/redd/version.rb +4 -3
  46. data/logo.png +0 -0
  47. data/redd.gemspec +26 -22
  48. metadata +73 -99
  49. data/LICENSE.md +0 -22
  50. data/RedditKit.LICENSE.md +0 -9
  51. data/lib/redd/access.rb +0 -76
  52. data/lib/redd/clients/base.rb +0 -188
  53. data/lib/redd/clients/base/account.rb +0 -20
  54. data/lib/redd/clients/base/identity.rb +0 -22
  55. data/lib/redd/clients/base/none.rb +0 -27
  56. data/lib/redd/clients/base/privatemessages.rb +0 -33
  57. data/lib/redd/clients/base/read.rb +0 -113
  58. data/lib/redd/clients/base/stream.rb +0 -81
  59. data/lib/redd/clients/base/submit.rb +0 -19
  60. data/lib/redd/clients/base/utilities.rb +0 -104
  61. data/lib/redd/clients/base/wikiread.rb +0 -33
  62. data/lib/redd/clients/installed.rb +0 -57
  63. data/lib/redd/clients/script.rb +0 -41
  64. data/lib/redd/clients/userless.rb +0 -32
  65. data/lib/redd/clients/web.rb +0 -58
  66. data/lib/redd/objects/base.rb +0 -39
  67. data/lib/redd/objects/comment.rb +0 -22
  68. data/lib/redd/objects/labeled_multi.rb +0 -13
  69. data/lib/redd/objects/listing.rb +0 -29
  70. data/lib/redd/objects/more_comments.rb +0 -11
  71. data/lib/redd/objects/private_message.rb +0 -28
  72. data/lib/redd/objects/submission.rb +0 -139
  73. data/lib/redd/objects/subreddit.rb +0 -330
  74. data/lib/redd/objects/thing.rb +0 -26
  75. data/lib/redd/objects/thing/editable.rb +0 -22
  76. data/lib/redd/objects/thing/hideable.rb +0 -18
  77. data/lib/redd/objects/thing/inboxable.rb +0 -25
  78. data/lib/redd/objects/thing/messageable.rb +0 -34
  79. data/lib/redd/objects/thing/moderatable.rb +0 -43
  80. data/lib/redd/objects/thing/refreshable.rb +0 -14
  81. data/lib/redd/objects/thing/saveable.rb +0 -21
  82. data/lib/redd/objects/thing/votable.rb +0 -33
  83. data/lib/redd/objects/user.rb +0 -52
  84. data/lib/redd/objects/wiki_page.rb +0 -15
  85. data/lib/redd/rate_limit.rb +0 -88
  86. data/lib/redd/response/parse_json.rb +0 -18
  87. data/lib/redd/response/raise_error.rb +0 -16
  88. data/spec/redd/objects/base_spec.rb +0 -1
  89. data/spec/redd/response/raise_error_spec.rb +0 -11
  90. data/spec/redd_spec.rb +0 -5
  91. data/spec/spec_helper.rb +0 -71
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lazy_model'
4
+
5
+ module Redd
6
+ module Models
7
+ class MoreComments < LazyModel
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lazy_model'
4
+
5
+ module Redd
6
+ module Models
7
+ # A multi.
8
+ class Multireddit < LazyModel
9
+ # Make a Multireddit from its path.
10
+ # @option hash [String] :path the multi's path
11
+ # @return [Multireddit]
12
+ def self.from_response(client, hash)
13
+ path = hash.fetch(:path)
14
+ new(client, hash) { |c| c.get("/api/multi#{path}").body[:data] }
15
+ end
16
+
17
+ # Create a Multireddit from its path.
18
+ # @param client [APIClient] the api client to initialize the object with
19
+ # @param id [String] the multi's path (prepended by a /)
20
+ # @return [Multireddit]
21
+ def self.from_id(client, id)
22
+ from_response(client, path: id)
23
+ end
24
+
25
+ def after_initialize
26
+ @attributes[:subreddits].map! do |subreddit|
27
+ Subreddit.from_response(client, display_name: subreddit[:name])
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redd
4
+ module Models
5
+ # Methods for user-submitted content, i.e. Submissions and Comments.
6
+ module Postable
7
+ # Edit a thing.
8
+ # @param text [String] The new text.
9
+ # @return [self] the edited thing
10
+ def edit(text)
11
+ @client.post('/api/editusertext', thing_id: get_attribute(:name), text: text)
12
+ @attributes[is_a?(Submission) ? :selftext : :body] = text
13
+ self
14
+ end
15
+
16
+ # Delete the thing.
17
+ def delete
18
+ @client.post('/api/del', id: get_attribute(:name))
19
+ end
20
+
21
+ # Save a link or comment to the user's account.
22
+ # @param category [String] a category to save to
23
+ def save(category = nil)
24
+ params = { id: fullname }
25
+ params[:category] = category if category
26
+ @client.post('/api/save', params)
27
+ end
28
+
29
+ # Remove the link or comment from the user's saved links.
30
+ def unsave
31
+ @client.post('/api/unsave', id: get_attribute(:name))
32
+ end
33
+
34
+ # Hide a link from the user.
35
+ def hide
36
+ @client.post('/api/hide', id: get_attribute(:fullname))
37
+ end
38
+
39
+ # Unhide a previously hidden link.
40
+ def unhide
41
+ @client.post('/api/unhide', id: get_attribute(:fullname))
42
+ end
43
+
44
+ # Upvote the model.
45
+ def upvote
46
+ vote(1)
47
+ end
48
+
49
+ # Downvote the model.
50
+ def downvote
51
+ vote(-1)
52
+ end
53
+
54
+ # Clear any upvotes or downvotes on the model.
55
+ def undo_vote
56
+ vote(0)
57
+ end
58
+
59
+ private
60
+
61
+ # Send a vote.
62
+ # @param direction [-1, 0, 1] the direction to vote in
63
+ def vote(direction)
64
+ fullname = get_attribute(:name)
65
+ @client.post('/api/vote', id: fullname, dir: direction)
66
+ @attributes[:ups] += direction
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lazy_model'
4
+ require_relative 'inboxable'
5
+ require_relative 'replyable'
6
+
7
+ module Redd
8
+ module Models
9
+ # A private message
10
+ class PrivateMessage < LazyModel
11
+ include Inboxable
12
+ include Replyable
13
+
14
+ # Make a Message from its id.
15
+ # @option hash [String] :id the post's id (e.g. abc123)
16
+ # @return [Submission]
17
+ def self.from_response(client, hash)
18
+ # FIXME: This returns the entire conversation, not the specific message. Possible to search,
19
+ # because depth of replies is just one.
20
+ super
21
+ end
22
+
23
+ # Delete the message from the user's inbox.
24
+ def delete
25
+ @client.post('/api/del_msg', id: get_attribute(:name))
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redd
4
+ module Models
5
+ # A model that can be commented on or replied to.
6
+ module Replyable
7
+ # Add a comment to a link, reply to a comment or reply to a message.
8
+ # @param text [String] the text to comment
9
+ # @return [Comment, PrivateMessage] The created reply.
10
+ def reply(text)
11
+ fullname = get_attribute(:name)
12
+ @client.model(:post, '/api/comment/', text: text, thing_id: fullname).first
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lazy_model'
4
+
5
+ module Redd
6
+ module Models
7
+ # The starter class.
8
+ class Session < BasicModel
9
+ # @return [FrontPage] the user's front page
10
+ def front_page
11
+ FrontPage.new(@client)
12
+ end
13
+
14
+ # @return [User] the logged-in user
15
+ def me
16
+ User.new(@client) { |client| client.get('/api/v1/me').body }
17
+ end
18
+
19
+ # Get a (lazily loaded) reddit user by their name.
20
+ # @param name [String] the username
21
+ # @return [User]
22
+ def user(name)
23
+ User.from_id(@client, name)
24
+ end
25
+
26
+ # Get a (lazily loaded) subreddit by its name.
27
+ # @param display_name [String] the subreddit's display name
28
+ # @return [Subreddit]
29
+ def subreddit(display_name)
30
+ Subreddit.from_id(@client, display_name)
31
+ end
32
+
33
+ # @return [Array<Multireddit>] array of multireddits belonging to the user
34
+ def my_multis
35
+ @client.get('/api/multi/mine').body.map { |m| @client.unmarshal(m) }
36
+ end
37
+
38
+ # Get a (lazily loaded) multi by its path.
39
+ # @param path [String] the multi's path, prepended by a /
40
+ # @return [Multireddit]
41
+ def multi(path)
42
+ Multireddit.from_id(@client, path)
43
+ end
44
+
45
+ # Get submissions or comments by their fullnames.
46
+ # @param fullnames [String, Array<String>] one or an array of fullnames (e.g. t3_abc1234)
47
+ # @return [Listing<Submission, Comment>]
48
+ def get(fullnames)
49
+ # XXX: Could we use better methods for t1_ and t3_?
50
+ @client.model(:get, '/api/info', id: Array(fullnames).join(','))
51
+ end
52
+
53
+ # Get submissions or comments by their fullnames.
54
+ # @param url [String] the object's url
55
+ # @return [Submission, Comment, nil] the object, or nil if not found
56
+ def from_url(url)
57
+ @client.model(:get, '/api/info', url: url).first
58
+ end
59
+
60
+ # Return a listing of the user's inbox (including comment replies and private messages).
61
+ #
62
+ # @param category ['inbox', 'unread', 'sent'] The category of messages
63
+ # to view.
64
+ # @param mark [Boolean] Whether to remove the orangered from the
65
+ # user's inbox.
66
+ # @param params [Hash] A list of optional params to send with the request.
67
+ # @option params [String] :after Return results after the given
68
+ # fullname.
69
+ # @option params [String] :before Return results before the given
70
+ # fullname.
71
+ # @option params [Integer] :count (0) The number of items already seen
72
+ # in the listing.
73
+ # @option params [1..100] :limit (25) The maximum number of things to
74
+ # return.
75
+ # @return [Listing]
76
+ def my_messages(category: 'inbox', mark: false, **params)
77
+ @client.model(:get, "/message/#{category}.json", params.merge(mark: mark))
78
+ end
79
+
80
+ # Mark all messages as read.
81
+ def read_all_messages
82
+ @client.post('/api/read_all_messages')
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lazy_model'
4
+ require_relative 'moderatable'
5
+ require_relative 'postable'
6
+ require_relative 'replyable'
7
+
8
+ require_relative 'user'
9
+ require_relative 'subreddit'
10
+
11
+ module Redd
12
+ module Models
13
+ # A text or link post.
14
+ class Submission < LazyModel
15
+ include Moderatable
16
+ include Postable
17
+ include Replyable
18
+
19
+ coerce_attribute :author, User
20
+ coerce_attribute :subreddit, Subreddit
21
+
22
+ # Make a Submission from its id.
23
+ # @option hash [String] :id the post's id (e.g. abc123)
24
+ # @return [Submission]
25
+ def self.from_response(client, hash)
26
+ link_id = hash.fetch(:id)
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)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lazy_model'
4
+ require_relative 'messageable'
5
+ require_relative '../utilities/stream'
6
+
7
+ module Redd
8
+ module Models
9
+ # A subreddit.
10
+ class Subreddit < LazyModel
11
+ include Messageable
12
+
13
+ # Make a Subreddit from its name.
14
+ # @option hash [String] :display_name the subreddit's name
15
+ # @return [Subreddit]
16
+ def self.from_response(client, hash)
17
+ name = hash.fetch(:display_name)
18
+ new(client, hash) { |c| c.get("/r/#{name}/about").body[:data] }
19
+ end
20
+
21
+ # Create a Subreddit from its name.
22
+ # @param client [APIClient] the api client to initialize the object with
23
+ # @param id [String] the subreddit name
24
+ # @return [Subreddit]
25
+ def self.from_id(client, id)
26
+ from_response(client, display_name: id)
27
+ end
28
+
29
+ # @return [Array<String>] the subreddit's wiki pages
30
+ def wiki_pages
31
+ @client.get("/r/#{get_attribute(:display_name)}/wiki/pages").body[:data]
32
+ end
33
+
34
+ # Get a wiki page by its title.
35
+ # @param title [String] the page's title
36
+ # @return [WikiPage]
37
+ def wiki_page(title)
38
+ WikiPage.from_response(@client, title: title, subreddit: self)
39
+ end
40
+
41
+ # @!group Listings
42
+
43
+ # Get the appropriate listing.
44
+ # @param sort [:hot, :new, :top, :controversial, :comments, :rising] the type of listing
45
+ # @param params [Hash] a list of params to send with the request
46
+ # @option params [String] :after return results after the given fullname
47
+ # @option params [String] :before return results before the given fullname
48
+ # @option params [Integer] :count the number of items already seen in the listing
49
+ # @option params [1..100] :limit the maximum number of things to return
50
+ # @option params [:hour, :day, :week, :month, :year, :all] :time the time period to consider
51
+ # when sorting
52
+ #
53
+ # @note The option :time only applies to the top and controversial sorts.
54
+ # @return [Listing<Submission, Comment>]
55
+ def listing(sort, **params)
56
+ params[:t] = params.delete(:time) if params.key?(:time)
57
+ @client.model(:get, "/r/#{get_attribute(:display_name)}/#{sort}.json", params)
58
+ end
59
+
60
+ # @!method hot(**params)
61
+ # @!method new(**params)
62
+ # @!method top(**params)
63
+ # @!method controversial(**params)
64
+ # @!method comments(**params)
65
+ # @!method rising(**params)
66
+ #
67
+ # @see #listing
68
+ %i(hot new top controversial comments rising).each do |sort|
69
+ define_method(sort) { |**params| listing(sort, **params) }
70
+ end
71
+
72
+ # @!endgroup
73
+ # @!group Moderator Listings
74
+
75
+ # Get the appropriate moderator listing.
76
+ # @param type [:reports, :spam, :modqueue, :unmoderated, :edited] the type of listing
77
+ # @param params [Hash] a list of params to send with the request
78
+ # @option params [String] :after return results after the given fullname
79
+ # @option params [String] :before return results before the given fullname
80
+ # @option params [Integer] :count the number of items already seen in the listing
81
+ # @option params [1..100] :limit the maximum number of things to return
82
+ # @option params [:links, :comments] :only the type of objects required
83
+ #
84
+ # @return [Listing<Submission, Comment>]
85
+ def moderator_listing(type, **params)
86
+ @client.model(:get, "/r/#{get_attribute(:display_name)}/about/#{type}.json", params)
87
+ end
88
+
89
+ # @!method reports(**params)
90
+ # @!method spam(**params)
91
+ # @!method modqueue(**params)
92
+ # @!method unmoderated(**params)
93
+ # @!method edited(**params)
94
+ #
95
+ # @see #moderator_listing
96
+ %i(reports spam modqueue unmoderated edited).each do |type|
97
+ define_method(type) { |**params| moderator_listing(type, **params) }
98
+ end
99
+
100
+ # @!endgroup
101
+
102
+ # Stream newly submitted posts.
103
+ def post_stream(**params, &block)
104
+ params[:limit] ||= 100
105
+ stream = Utilities::Stream.new do |before|
106
+ listing(:new, params.merge(before: before))
107
+ end
108
+ block_given? ? stream.stream(&block) : stream.enum_for(:stream)
109
+ end
110
+
111
+ # Stream newly submitted comments.
112
+ def comment_stream(**params, &block)
113
+ params[:limit] ||= 100
114
+ stream = Utilities::Stream.new do |before|
115
+ listing(:comments, params.merge(before: before))
116
+ end
117
+ block_given? ? stream.stream(&block) : stream.enum_for(:stream)
118
+ end
119
+
120
+ # Submit a link or a text post to the subreddit.
121
+ # @note If both text and url are provided, url takes precedence.
122
+ #
123
+ # @param title [String] the title of the submission
124
+ # @param text [String] the text of the self-post
125
+ # @param url [String] the URL of the link
126
+ # @param resubmit [Boolean] whether to post a link to the subreddit despite it having been
127
+ # posted there before (you monster)
128
+ # @param sendreplies [Boolean] whether to send the replies to your inbox
129
+ # @return [Submission] The returned object (url, id and name)
130
+ def submit(title, text: nil, url: nil, resubmit: false, sendreplies: true)
131
+ params = {
132
+ title: title, sr: get_attribute(:display_name),
133
+ resubmit: resubmit, sendreplies: sendreplies
134
+ }
135
+ params[:kind] = url ? 'link' : 'self'
136
+ params[:url] = url if url
137
+ params[:text] = text if text
138
+ Submission.from_response(@client, @client.post('/api/submit', params).body[:json][:data])
139
+ end
140
+
141
+ # Compose a message to the moderators of a subreddit.
142
+ #
143
+ # @param subject [String] the subject of the message
144
+ # @param text [String] the message text
145
+ # @param from [Subreddit, nil] the subreddit to send the message on behalf of
146
+ def send_message(subject:, text:, from: nil)
147
+ super(to: "/r/#{get_attribute(:display_name)}", subject: subject, text: text, from: from)
148
+ end
149
+
150
+ # Set the flair for a link or a user for this subreddit.
151
+ # @param thing [User, Submission] the object whose flair to edit
152
+ # @param text [String] a string no longer than 64 characters
153
+ # @param css_class [String] the css class to assign to the flair
154
+ def set_flair(thing, text, css_class: nil)
155
+ key = thing.is_a?(User) ? :name : :link
156
+ params = { :text => text, key => thing.name }
157
+ params[:css_class] = css_class if css_class
158
+ @client.post("/r/#{get_attribute(:display_name)}/api/flair", params)
159
+ end
160
+
161
+ # Get a listing of all user flairs.
162
+ # @param params [Hash] a list of params to send with the request
163
+ # @option params [String] :after return results after the given fullname
164
+ # @option params [String] :before return results before the given fullname
165
+ # @option params [Integer] :count the number of items already seen in the listing
166
+ # @option params [String] :name prefer {#get_flair}
167
+ # @option params [:links, :comments] :only the type of objects required
168
+ #
169
+ # @return [Listing<Hash<Symbol, String>>]
170
+ def flair_listing(**params)
171
+ res = @client.get("/r/#{get_attribute(:display_name)}/api/flairlist", params).body
172
+ Listing.new(@client, children: res[:users], before: res[:prev], after: res[:next])
173
+ end
174
+
175
+ # Get the user's flair data.
176
+ # @param user [User] the user whose flair to fetch
177
+ # @return [Hash, nil]
178
+ def get_flair(user)
179
+ # We have to do this because reddit returns all flairs if given a nonexistent user
180
+ flair = flair_listing(name: user.name).first
181
+ return flair if flair && flair[:user].casecmp(user.name).zero?
182
+ nil
183
+ end
184
+
185
+ # Add the subreddit to the user's subscribed subreddits.
186
+ def subscribe(action: :sub, skip_initial_defaults: false)
187
+ @client.post(
188
+ '/api/subscribe',
189
+ sr_name: get_attribute(:display_name),
190
+ action: action,
191
+ skip_initial_defaults: skip_initial_defaults
192
+ )
193
+ end
194
+
195
+ # Remove the subreddit from the user's subscribed subreddits.
196
+ def unsubscribe
197
+ subscribe(action: :unsub)
198
+ end
199
+ end
200
+ end
201
+ end