redd 0.6.5 → 0.7.0

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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +34 -33
  3. data/.rspec +3 -4
  4. data/.rubocop.yml +5 -5
  5. data/.travis.yml +9 -7
  6. data/{LICENSE.md → LICENSE.txt} +22 -22
  7. data/README.md +126 -241
  8. data/Rakefile +5 -6
  9. data/{RedditKit.LICENSE.md → RedditKit.LICENSE.txt} +13 -13
  10. data/lib/redd.rb +50 -4
  11. data/lib/redd/access.rb +76 -0
  12. data/lib/redd/clients/base.rb +178 -0
  13. data/lib/redd/clients/base/account.rb +20 -0
  14. data/lib/redd/clients/base/identity.rb +22 -0
  15. data/lib/redd/clients/base/none.rb +27 -0
  16. data/lib/redd/clients/base/privatemessages.rb +28 -0
  17. data/lib/redd/clients/base/read.rb +66 -0
  18. data/lib/redd/clients/base/stream.rb +74 -0
  19. data/lib/redd/clients/base/submit.rb +54 -0
  20. data/lib/redd/clients/base/utilities.rb +80 -0
  21. data/lib/redd/clients/base/wikiread.rb +33 -0
  22. data/lib/redd/clients/installed.rb +55 -0
  23. data/lib/redd/clients/script.rb +37 -0
  24. data/lib/redd/clients/userless.rb +31 -0
  25. data/lib/redd/clients/web.rb +57 -0
  26. data/lib/redd/error.rb +138 -153
  27. data/lib/redd/objects/base.rb +36 -0
  28. data/lib/redd/objects/comment.rb +22 -0
  29. data/lib/redd/objects/listing.rb +29 -0
  30. data/lib/redd/objects/more_comments.rb +10 -0
  31. data/lib/redd/objects/private_message.rb +18 -0
  32. data/lib/redd/objects/submission.rb +72 -0
  33. data/lib/redd/objects/subreddit.rb +152 -0
  34. data/lib/redd/objects/thing.rb +33 -0
  35. data/lib/redd/objects/thing/editable.rb +22 -0
  36. data/lib/redd/objects/thing/hideable.rb +18 -0
  37. data/lib/redd/objects/thing/inboxable.rb +25 -0
  38. data/lib/redd/objects/thing/messageable.rb +34 -0
  39. data/lib/redd/objects/thing/moderatable.rb +43 -0
  40. data/lib/redd/objects/thing/refreshable.rb +14 -0
  41. data/lib/redd/objects/thing/saveable.rb +21 -0
  42. data/lib/redd/objects/thing/votable.rb +33 -0
  43. data/lib/redd/objects/user.rb +52 -0
  44. data/lib/redd/objects/wiki_page.rb +15 -0
  45. data/lib/redd/rate_limit.rb +50 -49
  46. data/lib/redd/response/parse_json.rb +17 -33
  47. data/lib/redd/response/raise_error.rb +16 -25
  48. data/lib/redd/version.rb +4 -5
  49. data/redd.gemspec +30 -31
  50. data/spec/redd/objects/base_spec.rb +1 -0
  51. data/spec/redd/rate_limit_spec.rb +29 -29
  52. data/spec/redd/response/parse_json_spec.rb +12 -0
  53. data/spec/redd/response/raise_error_spec.rb +11 -0
  54. data/spec/redd_spec.rb +7 -5
  55. data/spec/spec_helper.rb +69 -50
  56. metadata +73 -136
  57. data/.yardopts +0 -1
  58. data/lib/redd/base.rb +0 -56
  59. data/lib/redd/client/authenticated.rb +0 -83
  60. data/lib/redd/client/authenticated/account.rb +0 -13
  61. data/lib/redd/client/authenticated/apps.rb +0 -13
  62. data/lib/redd/client/authenticated/flair.rb +0 -71
  63. data/lib/redd/client/authenticated/gold.rb +0 -13
  64. data/lib/redd/client/authenticated/links_comments.rb +0 -189
  65. data/lib/redd/client/authenticated/live.rb +0 -13
  66. data/lib/redd/client/authenticated/moderation.rb +0 -126
  67. data/lib/redd/client/authenticated/multis.rb +0 -9
  68. data/lib/redd/client/authenticated/private_messages.rb +0 -73
  69. data/lib/redd/client/authenticated/subreddits.rb +0 -172
  70. data/lib/redd/client/authenticated/users.rb +0 -9
  71. data/lib/redd/client/authenticated/wiki.rb +0 -9
  72. data/lib/redd/client/oauth2.rb +0 -71
  73. data/lib/redd/client/oauth2/authorization.rb +0 -108
  74. data/lib/redd/client/oauth2/identity.rb +0 -16
  75. data/lib/redd/client/oauth2_script.rb +0 -24
  76. data/lib/redd/client/oauth2_script/authorization.rb +0 -21
  77. data/lib/redd/client/unauthenticated.rb +0 -118
  78. data/lib/redd/client/unauthenticated/account.rb +0 -30
  79. data/lib/redd/client/unauthenticated/captcha.rb +0 -27
  80. data/lib/redd/client/unauthenticated/links_comments.rb +0 -60
  81. data/lib/redd/client/unauthenticated/listing.rb +0 -65
  82. data/lib/redd/client/unauthenticated/live.rb +0 -9
  83. data/lib/redd/client/unauthenticated/moderation.rb +0 -26
  84. data/lib/redd/client/unauthenticated/subreddits.rb +0 -49
  85. data/lib/redd/client/unauthenticated/users.rb +0 -67
  86. data/lib/redd/client/unauthenticated/utilities.rb +0 -109
  87. data/lib/redd/client/unauthenticated/wiki.rb +0 -33
  88. data/lib/redd/oauth2_access.rb +0 -70
  89. data/lib/redd/object/comment.rb +0 -74
  90. data/lib/redd/object/listing.rb +0 -29
  91. data/lib/redd/object/more_comments.rb +0 -14
  92. data/lib/redd/object/private_message.rb +0 -35
  93. data/lib/redd/object/submission.rb +0 -94
  94. data/lib/redd/object/subreddit.rb +0 -74
  95. data/lib/redd/object/user.rb +0 -34
  96. data/lib/redd/object/wiki_page.rb +0 -27
  97. data/lib/redd/thing.rb +0 -27
  98. data/lib/redd/thing/commentable.rb +0 -27
  99. data/lib/redd/thing/editable.rb +0 -16
  100. data/lib/redd/thing/hideable.rb +0 -16
  101. data/lib/redd/thing/inboxable.rb +0 -20
  102. data/lib/redd/thing/messageable.rb +0 -12
  103. data/lib/redd/thing/moderatable.rb +0 -32
  104. data/lib/redd/thing/reportable.rb +0 -12
  105. data/lib/redd/thing/saveable.rb +0 -16
  106. data/lib/redd/thing/voteable.rb +0 -22
  107. data/spec/README.md +0 -18
  108. data/spec/redd/base_spec.rb +0 -36
  109. data/spec/redd/client/authenticated/account_spec.rb +0 -5
  110. data/spec/redd/client/authenticated/apps_spec.rb +0 -2
  111. data/spec/redd/client/authenticated/flair_spec.rb +0 -26
  112. data/spec/redd/client/authenticated/gold_spec.rb +0 -2
  113. data/spec/redd/client/authenticated/links_comments_spec.rb +0 -231
  114. data/spec/redd/client/authenticated/live_spec.rb +0 -2
  115. data/spec/redd/client/unauthenticated/account_spec.rb +0 -15
  116. data/spec/redd/client/unauthenticated/captcha_spec.rb +0 -23
  117. data/spec/redd/client/unauthenticated/links_comments_spec.rb +0 -28
  118. data/spec/redd/client/unauthenticated/listing_spec.rb +0 -23
  119. data/spec/redd/client/unauthenticated/live_spec.rb +0 -2
  120. data/spec/redd/client/unauthenticated/moderation_spec.rb +0 -14
  121. data/spec/redd/client/unauthenticated/subreddits_spec.rb +0 -35
  122. data/spec/redd/client/unauthenticated/users_spec.rb +0 -34
  123. data/spec/redd/client/unauthenticated/wiki_spec.rb +0 -18
  124. data/spec/redd/oauth2_access_spec.rb +0 -83
  125. data/spec/redd/thing_spec.rb +0 -22
data/lib/redd/error.rb CHANGED
@@ -1,153 +1,138 @@
1
- module Redd
2
- # An error from reddit
3
- class Error < StandardError
4
- attr_reader :code
5
- attr_reader :headers
6
- attr_reader :body
7
-
8
- def initialize(env)
9
- @code = env.status
10
- @headers = env.response_headers
11
- @body = env.body
12
- end
13
-
14
- class << self
15
- # rubocop:disable Style/CyclomaticComplexity, Style/MethodLength
16
- def from_response(response)
17
- status = response[:status]
18
- body = parse_error(response[:body]).to_s
19
- case status
20
- when 200
21
- case body
22
- when /ACCESS_DENIED/i
23
- Redd::Error::OAuth2AccessDenied
24
- when /UNSUPPORTED_RESPONSE_TYPE/i
25
- Redd::Error::InvalidResponseType
26
- when /UNSUPPORTED_GRANT_TYPE/i
27
- Redd::Error::InvalidGrantType
28
- when /INVALID_SCOPE/i
29
- Redd::Error::InvalidScope
30
- when /INVALID_REQUEST/i
31
- Redd::Error::InvalidRequest
32
- when /NO_TEXT/i
33
- Redd::Error::NoTokenGiven
34
- when /INVALID_GRANT/i
35
- Redd::Error::ExpiredCode
36
- when /WRONG_PASSWORD/i
37
- Redd::Error::InvalidCredentials
38
- when /BAD_CAPTCHA/i
39
- Redd::Error::InvalidCaptcha
40
- when /RATELIMIT/i
41
- Redd::Error::RateLimited
42
- when /BAD_CSS_NAME/i
43
- Redd::Error::InvalidClassName
44
- when /TOO_OLD/i
45
- Redd::Error::Archived
46
- when /TOO_MUCH_FLAIR_CSS/i
47
- Redd::Error::TooManyClassNames
48
- when /USER_REQUIRED/i
49
- Redd::Error::AuthenticationRequired
50
- end
51
- when 400
52
- Redd::Error::BadRequest
53
- when 401
54
- Redd::Error::InvalidOAuth2Credentials
55
- when 403
56
- case body
57
- when /USER_REQUIRED/i
58
- Redd::Error::AuthenticationRequired
59
- else
60
- Redd::Error::PermissionDenied
61
- end
62
- when 404
63
- Redd::Error::NotFound
64
- when 409
65
- Redd::Error::Conflict
66
- when 500
67
- Redd::Error::InternalServerError
68
- when 502
69
- Redd::Error::BadGateway
70
- when 503
71
- Redd::Error::ServiceUnavailable
72
- when 504
73
- Redd::Error::TimedOut
74
- end
75
- end
76
- # rubocop:enable Style/CyclomaticComplexity, Style/MethodLength
77
-
78
- def parse_error(body)
79
- return nil unless body.is_a?(Hash)
80
-
81
- if body.key?(:json) && body[:json].key?(:errors)
82
- body[:json][:errors].first
83
- elsif body.key?(:jquery)
84
- body[:jquery]
85
- elsif body.key?(:error)
86
- body[:error]
87
- elsif body.key?(:code) && body[:code] == "NO_TEXT"
88
- "NO_TEXT"
89
- end
90
- end
91
- end
92
-
93
- class NoTokenGiven < Error; end
94
-
95
- class ExpiredCode < Error; end
96
-
97
- class InvalidGrantType < Error; end
98
-
99
- class InvalidOAuth2Credentials < Error; end
100
-
101
- class OAuth2AccessDenied < Error; end
102
-
103
- class InvalidResponseType < Error; end
104
-
105
- class InvalidScope < Error; end
106
-
107
- class InvalidRequest < Error; end
108
-
109
- class AuthenticationRequired < Error; end
110
-
111
- class InvalidCaptcha < Error; end
112
-
113
- class BadGateway < Error; end
114
-
115
- class BadRequest < Error; end
116
-
117
- class InvalidMultiredditName < Error; end
118
-
119
- class Conflict < Error; end
120
-
121
- class InternalServerError < Error; end
122
-
123
- class InvalidClassName < Error; end
124
-
125
- class InvalidCredentials < Error; end
126
-
127
- class NotFound < Error; end
128
-
129
- class PermissionDenied < Error; end
130
-
131
- # Raised when the client needs to wait before making another request
132
- class RateLimited < Error
133
- attr_reader :time
134
-
135
- def initialize(env, time)
136
- @code = env.status
137
- @headers = env.response_headers
138
- @body = env.body
139
- @time = time
140
- end
141
- end
142
-
143
- class RequestError < Error; end
144
-
145
- class ServiceUnavailable < Error; end
146
-
147
- class TooManyClassNames < Error; end
148
-
149
- class Archived < Error; end
150
-
151
- class TimedOut < Error; end
152
- end
153
- end
1
+ module Redd
2
+ # An error from reddit
3
+ class Error < StandardError
4
+ attr_reader :code
5
+ attr_reader :headers
6
+ attr_reader :body
7
+
8
+ def initialize(env)
9
+ @code = env[:status]
10
+ @headers = env[:response_headers]
11
+ @body = env[:body]
12
+ end
13
+
14
+ def self.from_response(env) # rubocop:disable all
15
+ status = env[:status]
16
+ body = parse_error(env[:body]).to_s
17
+ case status
18
+ when 200
19
+ case body
20
+ when /access_denied/i then OAuth2AccessDenied
21
+ when /unsupported_response_type/i then InvalidResponseType
22
+ when /unsupported_grant_type/i then InvalidGrantType
23
+ when /invalid_scope/i then InvalidScope
24
+ when /invalid_request/i then InvalidRequest
25
+ when /no_text/i then NoTokenGiven
26
+ when /invalid_grant/i then ExpiredCode
27
+ when /wrong_password/i then InvalidCredentials
28
+ when /bad_captcha/i then InvalidCaptcha
29
+ when /ratelimit/i then RateLimited
30
+ when /bad_css_name/i then InvalidClassName
31
+ when /too_old/i then Archived
32
+ when /too_much_flair_css/i then TooManyClassNames
33
+ when /user_required/i then AuthenticationRequired
34
+ end
35
+ when 400 then BadRequest
36
+ when 401 then InvalidOAuth2Credentials
37
+ when 403
38
+ if /user_required/i =~ body
39
+ AuthenticationRequired
40
+ else
41
+ PermissionDenied
42
+ end
43
+ when 404 then NotFound
44
+ when 409 then Conflict
45
+ when 500 then InternalServerError
46
+ when 502 then BadGateway
47
+ when 503 then ServiceUnavailable
48
+ when 504 then TimedOut
49
+ end
50
+ end
51
+
52
+ def self.parse_error(body) # rubocop:disable all
53
+ return body unless body.is_a?(Hash)
54
+
55
+ if body.key?(:json) && body[:json].key?(:errors)
56
+ body[:json][:errors].first
57
+ elsif body.key?(:jquery)
58
+ body[:jquery]
59
+ elsif body.key?(:error)
60
+ body[:error]
61
+ elsif body.key?(:code) && body[:code] == "NO_TEXT"
62
+ "NO_TEXT"
63
+ end
64
+ end
65
+
66
+ # This item has been archived and can no longer be edited.
67
+ Archived = Class.new(Error)
68
+
69
+ AuthenticationRequired = Class.new(Error)
70
+
71
+ # Bad Gateway. Either a network or a reddit error. Either way, try again.
72
+ BadGateway = Class.new(Error)
73
+
74
+ BadRequest = Class.new(Error)
75
+
76
+ Conflict = Class.new(Error)
77
+
78
+ # You already received an access token using this code. The user should
79
+ # grant you access again to get a new code.
80
+ ExpiredCode = Class.new(Error)
81
+
82
+ # There is an issue on reddit's end. Try again.
83
+ InternalServerError = Class.new(Error)
84
+
85
+ InvalidCaptcha = Class.new(Error)
86
+
87
+ InvalidClassName = Class.new(Error)
88
+
89
+ # Either your username or your password is wrong.
90
+ InvalidCredentials = Class.new(Error)
91
+
92
+ InvalidGrantType = Class.new(Error)
93
+
94
+ InvalidMultiredditName = Class.new(Error)
95
+
96
+ # Your client id or your secret is wrong.
97
+ InvalidOAuth2Credentials = Class.new(Error)
98
+
99
+ InvalidResponseType = Class.new(Error)
100
+
101
+ InvalidRequest = Class.new(Error)
102
+
103
+ # You don't have the proper scope to perform this request.
104
+ InvalidScope = Class.new(Error)
105
+
106
+ # Four, oh four! The thing you're looking for wasn't found.
107
+ NotFound = Class.new(Error)
108
+
109
+ # No access token was given.
110
+ NoTokenGiven = Class.new(Error)
111
+
112
+ OAuth2AccessDenied = Class.new(Error)
113
+
114
+ PermissionDenied = Class.new(Error)
115
+
116
+ # Raised when the client needs to wait before making another request
117
+ class RateLimited < Error
118
+ # @!attribute [r] time
119
+ # @return [Integer] the seconds to wait before making another request.
120
+ attr_reader :time
121
+
122
+ def initialize(env)
123
+ @time = env[:body][:json][:ratelimit]
124
+ super
125
+ end
126
+ end
127
+
128
+ RequestError = Class.new(Error)
129
+
130
+ # Issue on reddit's end. Try again.
131
+ ServiceUnavailable = Class.new(Error)
132
+
133
+ # The connection timed out. Try again.
134
+ TimedOut = Class.new(Error)
135
+
136
+ TooManyClassNames = Class.new(Error)
137
+ end
138
+ end
@@ -0,0 +1,36 @@
1
+ require "hashie"
2
+ require "forwardable"
3
+
4
+ module Redd
5
+ # A bunch of objects that can hold properties.
6
+ module Objects
7
+ # A base for all objects to inherit from.
8
+ class Base < Hashie::Hash
9
+ include Hashie::Extensions::MergeInitializer
10
+ include Hashie::Extensions::MethodReader
11
+ include Hashie::Extensions::MethodQuery
12
+ include Hashie::Extensions::DeepMerge
13
+
14
+ extend Forwardable
15
+ def_delegators :@client, :get, :post, :put, :delete
16
+
17
+ # @!attribute [r] client
18
+ # @return [Clients::Base] The client that used to make requests.
19
+ attr_reader :client
20
+
21
+ # @param [Clients::Base] client The client instance.
22
+ # @param [Hash] attributes A hash of attributes.
23
+ def initialize(client, attributes = {})
24
+ @client = client
25
+ super(attributes)
26
+ end
27
+
28
+ # Define an alias for a property.
29
+ # @param [Symbol] new_name The alias.
30
+ # @param [Symbol] old_name The existing property.
31
+ def self.alias_property(new_name, old_name)
32
+ define_method(new_name) { send(old_name) }
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ require_relative "thing"
2
+
3
+ module Redd
4
+ module Objects
5
+ # A comment that can be made on a link.
6
+ class Comment < Thing
7
+ include Thing::Editable
8
+ include Thing::Inboxable
9
+ include Thing::Moderatable
10
+ include Thing::Refreshable
11
+ include Thing::Saveable
12
+ include Thing::Votable
13
+
14
+ alias_property :reports_count, :num_reports
15
+
16
+ # @return [Listing] The comment's replies.
17
+ def replies
18
+ @replies ||= client.object_from_body(self[:replies])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ module Redd
2
+ module Objects
3
+ # A collection of reddit things.
4
+ # @see https://www.reddit.com/dev/api#listings
5
+ class Listing < Array
6
+ # @!attribute [r] before
7
+ # @return [String] The id of the object before the listing.
8
+ attr_reader :before
9
+
10
+ # @!attribute [r] after
11
+ # @return [String] The id of the object after the listing.
12
+ attr_reader :after
13
+
14
+ # @param [Clients::Base] client The client to expand the comments with.
15
+ # @param [{:before => String, :after => String,
16
+ # :children => Array<Hash>}] attributes The data to initialize the
17
+ # class with.
18
+ # @todo Only call Clients::Base#object_from_body when item is being
19
+ # accessed.
20
+ def initialize(client, attributes)
21
+ @before = attributes[:before]
22
+ @after = attributes[:after]
23
+ attributes[:children].each do |child|
24
+ self << client.object_from_body(child)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ module Redd
2
+ module Objects
3
+ # The model for a morecomments object
4
+ class MoreComments < Array
5
+ def initialize(_, attributes)
6
+ super(attributes[:children])
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,18 @@
1
+ require_relative "thing"
2
+
3
+ module Redd
4
+ module Objects
5
+ # The model for private messages
6
+ class PrivateMessage < Thing
7
+ include Thing::Inboxable
8
+
9
+ alias_property :from, :author
10
+ alias_property :to, :dest
11
+
12
+ # Block the sender of the message from sending any more.
13
+ def block_sender!
14
+ post("/api/block", id: fullname)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,72 @@
1
+ require_relative "thing"
2
+
3
+ module Redd
4
+ module Objects
5
+ # A submission made in a subreddit.
6
+ class Submission < Thing
7
+ include Thing::Editable
8
+ include Thing::Hideable
9
+ include Thing::Moderatable
10
+ include Thing::Refreshable
11
+ include Thing::Saveable
12
+ include Thing::Votable
13
+
14
+ alias_property :nsfw?, :over_18
15
+ alias_property :self?, :is_self
16
+ alias_property :comments_count, :num_comments
17
+
18
+ # The shorter url for sharing.
19
+ def short_url
20
+ "http://redd.it/#{self[:id]}"
21
+ end
22
+
23
+ # Whether the comment was gilded.
24
+ def gilded?
25
+ self[:gilded] > 0
26
+ end
27
+
28
+ # @return [Listing] The submission's comments.
29
+ # @todo Allow for various depths and contexts and what not. Maybe a
30
+ # get_comment method?
31
+ def comments
32
+ refresh! unless @comments
33
+ @comments
34
+ end
35
+
36
+ # @return [Array<Comment, MoreComments>] A linear array of the
37
+ # submission's comments.
38
+ # @todo Somehow implement. Maybe a recursive method belonging to a
39
+ # CommentListing<Comment, MoreComments>?
40
+ def flat_comments
41
+ fail NotImplementedError
42
+ end
43
+
44
+ # Refresh the submission AND its comments.
45
+ # @return [Submission] The updated submission.
46
+ def refresh!
47
+ body = get("/comments/#{id}.json").body
48
+ @comments = client.object_from_body(body[1])
49
+ deep_merge!(body[0])
50
+ end
51
+
52
+ # Take a MoreComments and return a listing of comments.
53
+ # @param [MoreComments] more The object to expand.
54
+ # @return [Listing] A listing of the expanded comments.
55
+ def expand_more(more)
56
+ response = client.get(
57
+ "/api/morechildren",
58
+ children: more.join(","),
59
+ link_id: fullname
60
+ )
61
+
62
+ client.object_from_body(
63
+ kind: "Listing",
64
+ data: {
65
+ before: "", after: "",
66
+ children: response.body[:json][:data][:things]
67
+ }
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end