NeonRAW 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +4 -0
- data/CHANGELOG.md +7 -0
- data/CONTRIBUTING.md +11 -0
- data/Gemfile +4 -0
- data/LICENSE.md +373 -0
- data/NeonRAW.gemspec +26 -0
- data/README.md +62 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/examples/crossposter.rb +78 -0
- data/examples/flairbot.rb +60 -0
- data/examples/publicmodlogger.rb +79 -0
- data/examples/settings.yaml +4 -0
- data/examples/userhistoryscraper.rb +108 -0
- data/examples/userhistorywiper.rb +10 -0
- data/lib/NeonRAW/clients/base/listing.rb +55 -0
- data/lib/NeonRAW/clients/base/objectbuilder.rb +173 -0
- data/lib/NeonRAW/clients/base/utilities.rb +46 -0
- data/lib/NeonRAW/clients/base.rb +109 -0
- data/lib/NeonRAW/clients/installed.rb +49 -0
- data/lib/NeonRAW/clients/script.rb +34 -0
- data/lib/NeonRAW/clients/web.rb +52 -0
- data/lib/NeonRAW/errors.rb +518 -0
- data/lib/NeonRAW/objects/access.rb +44 -0
- data/lib/NeonRAW/objects/all.rb +36 -0
- data/lib/NeonRAW/objects/comment.rb +128 -0
- data/lib/NeonRAW/objects/inboxcomment.rb +54 -0
- data/lib/NeonRAW/objects/listing.rb +12 -0
- data/lib/NeonRAW/objects/me.rb +268 -0
- data/lib/NeonRAW/objects/modlogaction.rb +59 -0
- data/lib/NeonRAW/objects/modloguser.rb +35 -0
- data/lib/NeonRAW/objects/morecomments.rb +33 -0
- data/lib/NeonRAW/objects/multireddit.rb +134 -0
- data/lib/NeonRAW/objects/privatemessage.rb +90 -0
- data/lib/NeonRAW/objects/rule.rb +41 -0
- data/lib/NeonRAW/objects/submission.rb +221 -0
- data/lib/NeonRAW/objects/subreddit/flair.rb +169 -0
- data/lib/NeonRAW/objects/subreddit/moderation.rb +200 -0
- data/lib/NeonRAW/objects/subreddit/utilities.rb +73 -0
- data/lib/NeonRAW/objects/subreddit/wiki.rb +31 -0
- data/lib/NeonRAW/objects/subreddit.rb +223 -0
- data/lib/NeonRAW/objects/thing/createable.rb +22 -0
- data/lib/NeonRAW/objects/thing/editable.rb +46 -0
- data/lib/NeonRAW/objects/thing/gildable.rb +29 -0
- data/lib/NeonRAW/objects/thing/inboxable.rb +26 -0
- data/lib/NeonRAW/objects/thing/moderateable.rb +98 -0
- data/lib/NeonRAW/objects/thing/refreshable.rb +21 -0
- data/lib/NeonRAW/objects/thing/repliable.rb +23 -0
- data/lib/NeonRAW/objects/thing/saveable.rb +26 -0
- data/lib/NeonRAW/objects/thing/votable.rb +69 -0
- data/lib/NeonRAW/objects/thing.rb +24 -0
- data/lib/NeonRAW/objects/trophy.rb +25 -0
- data/lib/NeonRAW/objects/user.rb +147 -0
- data/lib/NeonRAW/objects/wikipage.rb +176 -0
- data/lib/NeonRAW/objects/wikipagerevision.rb +45 -0
- data/lib/NeonRAW/version.rb +3 -0
- data/lib/NeonRAW.rb +43 -0
- metadata +161 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require_relative 'base'
|
3
|
+
|
4
|
+
module NeonRAW
|
5
|
+
module Clients
|
6
|
+
# The installed app client.
|
7
|
+
class Installed < Base
|
8
|
+
def initialize(client_id, redirect_uri, opts = {})
|
9
|
+
@client_id = client_id
|
10
|
+
@redirect_uri = redirect_uri
|
11
|
+
@user_agent = opts[:user_agent] ||
|
12
|
+
"Powered by NeonRAW v#{NeonRAW::VERSION}"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Generates the authorization URL.
|
16
|
+
# @!method auth_url(state, scope = ['identity'], duration = 'temporary')
|
17
|
+
# @param state [String] A random string to check later.
|
18
|
+
# @param scope [Array<String>] The scope the app uses.
|
19
|
+
# @return [String] Returns the URL.
|
20
|
+
def auth_url(state, scope = ['identity'], duration = 'temporary')
|
21
|
+
query = {
|
22
|
+
response_type: 'token',
|
23
|
+
client_id: @client_id,
|
24
|
+
redirect_uri: @redirect_uri,
|
25
|
+
state: state,
|
26
|
+
scope: scope.join(','),
|
27
|
+
duration: duration
|
28
|
+
}
|
29
|
+
url = URI.join('https://www.reddit.com', '/api/v1/authorize')
|
30
|
+
url.query = URI.encode_www_form(query)
|
31
|
+
url.to_s
|
32
|
+
end
|
33
|
+
|
34
|
+
# Authorizes the client.
|
35
|
+
# @!method authorize!(fragment)
|
36
|
+
# @param fragment [String] The part of the URL after the #.
|
37
|
+
def authorize!(fragment)
|
38
|
+
data = CGI.parse(fragment)
|
39
|
+
access_data = {
|
40
|
+
access_token: data[:access_token].first,
|
41
|
+
token_type: data[:token_type].first,
|
42
|
+
expires_in: data[:expires_in].first,
|
43
|
+
scope: data[:scope].first
|
44
|
+
}
|
45
|
+
@access = Objects::Access.new(access_data)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module NeonRAW
|
4
|
+
module Clients
|
5
|
+
# The script app client.
|
6
|
+
class Script < Base
|
7
|
+
def initialize(username, password, client_id, secret, opts = {})
|
8
|
+
@username = username
|
9
|
+
@password = password
|
10
|
+
@client_id = client_id
|
11
|
+
@secret = secret
|
12
|
+
@redirect_uri = opts[:redirect_uri] || 'http://127.0.0.1:'
|
13
|
+
@user_agent = opts[:user_agent] ||
|
14
|
+
"Powered by NeonRAW v#{NeonRAW::VERSION}"
|
15
|
+
authorize!
|
16
|
+
end
|
17
|
+
|
18
|
+
# Authorizes the client for oAuth2 requests.
|
19
|
+
# @!method authorize!
|
20
|
+
# @!method refresh_access!
|
21
|
+
def authorize!
|
22
|
+
response = auth_connection(
|
23
|
+
'/api/v1/access_token', :post,
|
24
|
+
grant_type: 'password',
|
25
|
+
username: @username,
|
26
|
+
password: @password
|
27
|
+
)
|
28
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
29
|
+
@access = Objects::Access.new(data)
|
30
|
+
end
|
31
|
+
alias refresh_access! authorize!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require_relative 'base'
|
3
|
+
|
4
|
+
module NeonRAW
|
5
|
+
module Clients
|
6
|
+
# The Web app client.
|
7
|
+
class Web < Base
|
8
|
+
def initialize(client_id, secret, redirect_uri, opts = {})
|
9
|
+
@client_id = client_id
|
10
|
+
@secret = secret
|
11
|
+
@redirect_uri = redirect_uri
|
12
|
+
@user_agent = opts[:user_agent] ||
|
13
|
+
"Powered by NeonRAW v#{NeonRAW::VERSION}"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Generates the authorization URL.
|
17
|
+
# @!method auth_url(state, scope = ['identity'], duration = 'temporary')
|
18
|
+
# @param state [String] A random string to check later.
|
19
|
+
# @param scope [Array<String>] The scopes your app uses.
|
20
|
+
# @param duration [String] The duration of the access token [temporary,
|
21
|
+
# permanent].
|
22
|
+
# @return [String] Returns the URL.
|
23
|
+
def auth_url(state, scope = ['identity'], duration = 'temporary')
|
24
|
+
query = {
|
25
|
+
response_type: 'code',
|
26
|
+
client_id: @client_id,
|
27
|
+
redirect_uri: @redirect_uri,
|
28
|
+
state: state,
|
29
|
+
scope: scope.join(','),
|
30
|
+
duration: duration
|
31
|
+
}
|
32
|
+
url = URI.join('https://www.reddit.com', '/api/v1/authorize')
|
33
|
+
url.query = URI.encode_www_form(query)
|
34
|
+
url.to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
# Authorizes the client.
|
38
|
+
# @!method authorize!(code)
|
39
|
+
# @param code [String] The authorization code.
|
40
|
+
def authorize!(code)
|
41
|
+
response = auth_connection(
|
42
|
+
'/api/v1/access_token', :post,
|
43
|
+
grant_type: 'authorization_code',
|
44
|
+
code: code,
|
45
|
+
redirect_uri: @redirect_uri
|
46
|
+
)
|
47
|
+
data = JSON.parse(response.body, symbolize_names: true)
|
48
|
+
@access = Objects::Access.new(data)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,518 @@
|
|
1
|
+
# rubocop:disable all
|
2
|
+
|
3
|
+
module NeonRAW
|
4
|
+
# Methods and classes for handling errors.
|
5
|
+
module Errors
|
6
|
+
# Reads the HTTP status of the Typhoeus response and gives an exception to
|
7
|
+
# raise.
|
8
|
+
# @!method assign_errors(response)
|
9
|
+
# @param response [Typhoeus::Response] The response object.
|
10
|
+
# @return [StandardError, nil] Returns either the exception or nil if there
|
11
|
+
# is none.
|
12
|
+
def assign_errors(response)
|
13
|
+
code = response.code
|
14
|
+
body = response.body
|
15
|
+
case code
|
16
|
+
when 200
|
17
|
+
case body
|
18
|
+
when /access_denied/i then OAuth2AccessDenied
|
19
|
+
when /unsupported_response_type/i then InvalidResponseType
|
20
|
+
when /unsupported_grant_type/i then InvalidGrantType
|
21
|
+
when /invalid_scope/i then InvalidScope
|
22
|
+
when /invalid_request/i then InvalidRequest
|
23
|
+
when /invalid_grant/i then ExpiredCode
|
24
|
+
when /wrong_password/i then InvalidCredentials
|
25
|
+
when /bad_captcha/i then InvalidCaptcha
|
26
|
+
when /ratelimit/i then RateLimited
|
27
|
+
when /quota_filled/i then QuotaFilled
|
28
|
+
when /bad_css_name/i then InvalidClassName
|
29
|
+
when /too_old/i then Archived
|
30
|
+
when /too_much_flair_css/i then TooManyClassNames
|
31
|
+
when /user_required/i then AuthenticationRequired
|
32
|
+
when /bad_flair_target/i then BadFlairTarget
|
33
|
+
end
|
34
|
+
when 302 then UnexpectedRedirect
|
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 413 then RequestTooLarge
|
46
|
+
when 429 then RateLimited
|
47
|
+
when 500 then InternalServerError
|
48
|
+
when 502 then BadGateway
|
49
|
+
when 503 then ServiceUnavailable
|
50
|
+
when 504 then TimedOut
|
51
|
+
when 520 then CouldntReachServer
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Parses Reddit data for errors.
|
56
|
+
# @!method parse_errors(data)
|
57
|
+
# @param data [Array, Hash] The data.
|
58
|
+
def parse_errors(data)
|
59
|
+
# handles returns from toggleable methods
|
60
|
+
assign_data_errors([]) if data.empty?
|
61
|
+
if data.is_a?(Array) # handles returns from some flair methods
|
62
|
+
# handles multireddits
|
63
|
+
assign_data_errors([]) unless data[0].key?(:errors)
|
64
|
+
messages = []
|
65
|
+
errors = data[0][:errors]
|
66
|
+
errors.each { |_key, error| messages << error } unless errors.empty?
|
67
|
+
assign_data_errors(messages)
|
68
|
+
elsif data.key?(:json) # handles pretty much everything else
|
69
|
+
assign_data_errors([]) unless data[:json].key?(:errors)
|
70
|
+
if data[:json][:errors].is_a?(Array)
|
71
|
+
errors = data[:json][:errors][0] || []
|
72
|
+
assign_data_errors(errors)
|
73
|
+
else
|
74
|
+
errors = data[:json][:errors] || []
|
75
|
+
assign_data_errors(errors)
|
76
|
+
end
|
77
|
+
elsif data.key?(:errors) # handles image uploading
|
78
|
+
errors = data[:errors] || []
|
79
|
+
assign_data_errors(errors)
|
80
|
+
elsif data.key?(:jquery) # handles submitting submissions
|
81
|
+
errors = data[:jquery][-7][3]
|
82
|
+
assign_data_errors(errors)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Checks data for any errors that wouldn't have otherwise thrown an
|
87
|
+
# exception.
|
88
|
+
# @!method assign_data_errors(errors)
|
89
|
+
# @param errors [Array<String>] The errors.
|
90
|
+
def assign_data_errors(errors)
|
91
|
+
return nil if errors.empty?
|
92
|
+
error = errors.first
|
93
|
+
case error
|
94
|
+
when /improperly formatted row/i then BadFlairRowFormat
|
95
|
+
when /no_subject/i then NoSubject
|
96
|
+
when /too_long/i then TooLong
|
97
|
+
when /no_text/i then NoText
|
98
|
+
when /subreddit_noexist/i then InvalidSubreddit
|
99
|
+
when /user_muted/i then UserMuted
|
100
|
+
when /no_sr_to_sr_message/i then InvalidSubreddit
|
101
|
+
when /user_blocked/i then UserBlocked
|
102
|
+
when /muted_from_subreddit/i then MutedFromSubreddit
|
103
|
+
when /subreddit_notallowed/i then PermissionDenied
|
104
|
+
when /no_selfs/i then NoSelfPosts
|
105
|
+
when /no_links/i then NoLinkPosts
|
106
|
+
when /url is required/i then NoUrl
|
107
|
+
when /already been submitted/i then AlreadySubmitted
|
108
|
+
when /no_invite_found/i then NoInviteFound
|
109
|
+
when /deleted_comment/i then DeletedComment
|
110
|
+
when /thread_locked/i then PermissionDenied
|
111
|
+
when /image_error/i then ImageError
|
112
|
+
when /subreddit_exists/i then SubredditExists
|
113
|
+
when /cant_create_sr/i then CantCreateSubreddit
|
114
|
+
when /invalid_option/i then InvalidOption
|
115
|
+
when /gold_required/i then GoldRequired
|
116
|
+
when /gold_only_sr_required/i then GoldOnlySrRequired
|
117
|
+
when /admin_required/i then PermissionDenied
|
118
|
+
when /bad_number/i then BadNumber
|
119
|
+
when /bad_sr_name/i then BadSubredditName
|
120
|
+
when /rows per call reached/i then TooManyFlairRows
|
121
|
+
when /unable to resolve user/i then CouldntResolveUser
|
122
|
+
when /sr_rule_exists/i then RuleExists
|
123
|
+
when /sr_rule_too_many/i then TooManyRules
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Manages the API ratelimit for requesting stuff from Reddit.
|
128
|
+
# @!method handle_ratelimit(headers)
|
129
|
+
# @param headers [Hash] The Typhoeus response headers.
|
130
|
+
def handle_ratelimit(headers)
|
131
|
+
requests_remaining = headers['X-Ratelimit-Remaining'].to_i
|
132
|
+
ratelimit_reset = headers['X-Ratelimit-Reset'].to_i
|
133
|
+
sleep(ratelimit_reset) unless requests_remaining > 0
|
134
|
+
end
|
135
|
+
|
136
|
+
# That URL has already been submitted.
|
137
|
+
class AlreadySubmitted < StandardError
|
138
|
+
def initialize(msg = 'That URL has already been submitted.')
|
139
|
+
super(msg)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Thing is archived and can't be edited/replied to.
|
144
|
+
class Archived < StandardError
|
145
|
+
def initialize(msg = 'This thing is too old to edit/reply to.')
|
146
|
+
super(msg)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Client needs to be authorized.
|
151
|
+
class AuthenticationRequired < StandardError
|
152
|
+
def initialize(msg = 'The client must be authorized to do that.')
|
153
|
+
super(msg)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# The flair row you sent was invalid.
|
158
|
+
# Should be: "Username,flairtext,CSSclass\nUsername,flairtext,CSSclass..."
|
159
|
+
class BadFlairRowFormat < StandardError
|
160
|
+
def initialize(msg = 'Improperly formatted row.')
|
161
|
+
super(msg)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# The thing you tried to flair was a bad target.
|
166
|
+
class BadFlairTarget < StandardError
|
167
|
+
def initialize(msg = 'Bad flair target.')
|
168
|
+
super(msg)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Reddit's servers are shitting themselves.
|
173
|
+
class BadGateway < StandardError
|
174
|
+
def initialize(msg = "Reddit's server's are experiencing technical difficulties. Try again later.")
|
175
|
+
super(msg)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# The number value for a request parameter was incorrect.
|
180
|
+
class BadNumber < StandardError
|
181
|
+
def initialize(msg = 'The number passed to a request parameter was incorrect.')
|
182
|
+
super(msg)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# The request you sent to the API endpoint was bad.
|
187
|
+
class BadRequest < StandardError
|
188
|
+
def initialize(msg = 'The request you sent was incorrect. Please fix it.')
|
189
|
+
super(msg)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# The subreddit name was bad.
|
194
|
+
class BadSubredditName < StandardError
|
195
|
+
def initialize(msg = 'Bad subreddit name. Only [a-zA-Z0-9_] allowed.')
|
196
|
+
super(msg)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Couldn't create the subreddit.
|
201
|
+
class CantCreateSubreddit < StandardError
|
202
|
+
def initialize(msg = "Couldn't create subreddit.")
|
203
|
+
super(msg)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# The multireddit you're trying to create already exists.
|
208
|
+
class Conflict < StandardError
|
209
|
+
def initialize(msg = "The multireddit you're trying to create already exists.")
|
210
|
+
super(msg)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Reddit's servers are shitting themselves.
|
215
|
+
class CouldntReachServer < StandardError
|
216
|
+
def initialize(msg = "Reddit's servers are experiencing technical difficulties. Try again later.")
|
217
|
+
super(msg)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# Couldn't resolve the user provided.
|
222
|
+
class CouldntResolveUser < StandardError
|
223
|
+
def initialize(msg = "Couldn't resolve the user provided.")
|
224
|
+
super(msg)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# The comment you tried to reply to has been deleted.
|
229
|
+
class DeletedComment < StandardError
|
230
|
+
def initialize(msg = 'The comment you tried to reply to has been deleted.')
|
231
|
+
super(msg)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# You already received an access token using this code. They're only good
|
236
|
+
# for one use.
|
237
|
+
class ExpiredCode < StandardError
|
238
|
+
def initialize(msg = 'The code used to get the access token has expired.')
|
239
|
+
super(msg)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Only gold-only subreddits can do that.
|
244
|
+
class GoldOnlySrRequired < StandardError
|
245
|
+
def initialize(msg = 'Only gold-only subreddits can do that.')
|
246
|
+
super(msg)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# You need gold to do that.
|
251
|
+
class GoldRequired < StandardError
|
252
|
+
def initialize(msg = 'You need gold to do that.')
|
253
|
+
super(msg)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# The image you tried to upload wasn't valid.
|
258
|
+
class ImageError < StandardError
|
259
|
+
def initialize(msg = "The image you tried to upload wasn't valid.")
|
260
|
+
super(msg)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Reddit's servers are shitting themselves.
|
265
|
+
class InternalServerError < StandardError
|
266
|
+
def initialize(msg = "Reddit's servers are experiencing technical difficulties. Try again later.")
|
267
|
+
super(msg)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# You got the captcha wrong.
|
272
|
+
class InvalidCaptcha < StandardError
|
273
|
+
def initialize(msg = 'Invalid captcha.')
|
274
|
+
super(msg)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# You got the requested CSS class name wrong.
|
279
|
+
class InvalidClassName < StandardError
|
280
|
+
def initialize(msg = 'Invalid CSS class name.')
|
281
|
+
super(msg)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Your username/password is wrong.
|
286
|
+
class InvalidCredentials < StandardError
|
287
|
+
def initialize(msg = 'Invalid username/password')
|
288
|
+
super(msg)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Your grant_type is wrong.
|
293
|
+
class InvalidGrantType < StandardError
|
294
|
+
def initialize(msg = 'Invalid grant_type.')
|
295
|
+
super(msg)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
# Your client_id/secret is wrong or your access token expired.
|
300
|
+
class InvalidOAuth2Credentials < StandardError
|
301
|
+
def initialize(msg = 'Invalid client_id/secret/access token.')
|
302
|
+
super(msg)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Invalid option specified.
|
307
|
+
class InvalidOption < StandardError
|
308
|
+
def initialize(msg = 'One of the specified options is invalid.')
|
309
|
+
super(msg)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# The response_type parameter you sent was wrong. It should be the
|
314
|
+
# string "code".
|
315
|
+
class InvalidResponseType < StandardError
|
316
|
+
def initialize(msg = 'Invalid response_type. Should be "code".')
|
317
|
+
super(msg)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
# The parameters sent to /api/v1/authorize were wrong.
|
322
|
+
class InvalidRequest < StandardError
|
323
|
+
def initialize(msg = 'Invalid /api/v1/authorize parameters.')
|
324
|
+
super(msg)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
# You don't have the right scope to perform the request.
|
329
|
+
class InvalidScope < StandardError
|
330
|
+
def initialize(msg = "You don't have the right scope to do that.")
|
331
|
+
super(msg)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
# Your from_subreddit parameter was wrong.
|
336
|
+
class InvalidSubreddit < StandardError
|
337
|
+
def initialize(msg = "The subreddit you specified is invalid.")
|
338
|
+
super(msg)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# You are muted from the subreddit.
|
343
|
+
class MutedFromSubreddit < StandardError
|
344
|
+
def initialize(msg = 'User is muted from the subreddit.')
|
345
|
+
super(msg)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# No moderator invite was found.
|
350
|
+
class NoInviteFound < StandardError
|
351
|
+
def initialize(msg = 'No moderator invite found.')
|
352
|
+
super(msg)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# The subreddit doesn't allow link posts.
|
357
|
+
class NoLinkPosts < StandardError
|
358
|
+
def initialize(msg = "The subreddit doesn't allow link posts.")
|
359
|
+
super(msg)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# The subreddit doesn't allow selfposts.
|
364
|
+
class NoSelfPosts < StandardError
|
365
|
+
def initialize(msg = "The subreddit doesn't allow selfposts.")
|
366
|
+
super(msg)
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
# You tried to send a private message with no subject.
|
371
|
+
class NoSubject < StandardError
|
372
|
+
def initialize(msg = 'No message subject. Please add a message subject.')
|
373
|
+
super(msg)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# You tried to send a message with no text.
|
378
|
+
class NoText < StandardError
|
379
|
+
def initialize(msg = 'No message text. Please add message text.')
|
380
|
+
super(msg)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
# The thing you requested wasn't found. Could also mean that a user has
|
385
|
+
# been shadowbanned or a subreddit has been banned.
|
386
|
+
class NotFound < StandardError
|
387
|
+
def initialize(msg = "The thing you requested couldn't be found.")
|
388
|
+
super(msg)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# You didn't include an URL when submitting the submission.
|
393
|
+
class NoUrl < StandardError
|
394
|
+
def initialize(msg = 'No URL. Please add an URL.')
|
395
|
+
super(msg)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
# The user chose not to grant your app access.
|
400
|
+
class OAuth2AccessDenied < StandardError
|
401
|
+
def initialize(msg = 'The user chose not to grant your app access.')
|
402
|
+
super(msg)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
# You don't have adequate privileges to do that.
|
407
|
+
class PermissionDenied < StandardError
|
408
|
+
def initialize(msg = "You don't have permission to do that.")
|
409
|
+
super(msg)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
# This is like being RateLimited only more oAuth2-focused I think.
|
414
|
+
class QuotaFilled < StandardError
|
415
|
+
def initialize(msg = 'Your quota is filled. Try again later.')
|
416
|
+
super(msg)
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
# Gotta wait before making another request.
|
421
|
+
class RateLimited < StandardError
|
422
|
+
def initialize(msg = "You're rate limited. Try again later.")
|
423
|
+
super(msg)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
# There was an error with your request.
|
428
|
+
class RequestError < StandardError
|
429
|
+
def initialize(msg = 'There was an error with your request.')
|
430
|
+
super(msg)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
# The request you sent was too large.
|
435
|
+
class RequestTooLarge < StandardError
|
436
|
+
def initialize(msg = 'The request you sent was too large.')
|
437
|
+
super(msg)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
# This rule already exists.
|
442
|
+
class RuleExists < StandardError
|
443
|
+
def initialize(msg = 'This rule already exists.')
|
444
|
+
super(msg)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# Reddit's servers are shitting themselves/down for maintenance.
|
449
|
+
class ServiceUnavailable < StandardError
|
450
|
+
def initialize(msg = "Reddit's servers are currently unavailable. Try again later.")
|
451
|
+
super(msg)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
# The subreddit you tried to create already exists.
|
456
|
+
class SubredditExists < StandardError
|
457
|
+
def initialize(msg = 'The subreddit you tried to create already exists.')
|
458
|
+
super(msg)
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
# The connection timed out.
|
463
|
+
class TimedOut < StandardError
|
464
|
+
def initialize(msg = 'Your connection timed out.')
|
465
|
+
super(msg)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
# The text you tried to submit was too long.
|
470
|
+
class TooLong < StandardError
|
471
|
+
def initialize(msg = 'The text you tried to send was too long. 10,000 characters maximum.')
|
472
|
+
super(msg)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# You have too many flair classes already.
|
477
|
+
class TooManyClassNames < StandardError
|
478
|
+
def initialize(msg = 'Maxiumum number of flair classes reached.')
|
479
|
+
super(msg)
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# You sent too many flair rows.
|
484
|
+
class TooManyFlairRows < StandardError
|
485
|
+
def initialize(msg = 'Too many flair rows. 100 maximum.')
|
486
|
+
super(msg)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
# You already have the maximum amount of rules.
|
491
|
+
class TooManyRules < StandardError
|
492
|
+
def initialize(msg = 'You already have the maximum amount of rules.')
|
493
|
+
super(msg)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
# Usually happens when the subreddit you requested doesn't exist.
|
498
|
+
class UnexpectedRedirect < StandardError
|
499
|
+
def initialize(msg = 'The subreddit you requested does not exist.')
|
500
|
+
super(msg)
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
# The user you tried to message is blocked.
|
505
|
+
class UserBlocked < StandardError
|
506
|
+
def initialize(msg = "Can't message blocked users.")
|
507
|
+
super(msg)
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
# The user you tried to message is muted from the subreddit.
|
512
|
+
class UserMuted < StandardError
|
513
|
+
def initialize(msg = 'User is muted.')
|
514
|
+
super(msg)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module NeonRAW
|
2
|
+
module Objects
|
3
|
+
# The access object
|
4
|
+
# @!attribute [r] access_token
|
5
|
+
# @return [String] Returns the access token used for oAuth2.
|
6
|
+
# @!attribute [r] token_type
|
7
|
+
# @return [String] Returns the type of the token (bearer)
|
8
|
+
# @!attribute [r] refresh_token
|
9
|
+
# @return [String, nil] Returns the refresh token or nil if there is none.
|
10
|
+
# @!attribute [r] scope
|
11
|
+
# @return [String] Returns the scope where the token is valid.
|
12
|
+
# @!attribute [r] expires_in
|
13
|
+
# @return [Time] Returns how long until the token expires.
|
14
|
+
# @!attribute [r] expires_at
|
15
|
+
# @return [Time] Returns when the token expires.
|
16
|
+
class Access
|
17
|
+
attr_reader :refresh_token
|
18
|
+
def initialize(data)
|
19
|
+
@refresh_token = nil
|
20
|
+
data.each do |key, value|
|
21
|
+
instance_variable_set(:"@#{key}", value)
|
22
|
+
next if key == :refresh_token
|
23
|
+
self.class.send(:attr_reader, key)
|
24
|
+
end
|
25
|
+
# I have it expire 10 seconds early to give a small buffer
|
26
|
+
# for requests to avoid getting those icky 401 errors.
|
27
|
+
@expires_at = Time.now + 3590
|
28
|
+
end
|
29
|
+
|
30
|
+
# @!method expired?
|
31
|
+
# @return [Boolean] Returns whether or not the token is expired.
|
32
|
+
def expired?
|
33
|
+
Time.now > @expires_at
|
34
|
+
end
|
35
|
+
|
36
|
+
# Refresh the access token.
|
37
|
+
# @!method refresh!(data)
|
38
|
+
# @param data [Hash] The new data.
|
39
|
+
def refresh!(data)
|
40
|
+
data.each { |key, value| instance_variable_set(:"@#{key}", value) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|