redd 0.6.5 → 0.7.0

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