redd 0.7.10 → 0.8.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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