ruqqus 1.1.2 → 1.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c14af41785a17ac1c28bc3e89f778a1ce64571a2943e9ca44d4f5370c502de8
4
- data.tar.gz: b7e33784b89a2df20e85ead8e7097ab53b48d3bf95311f9ef1555a04bc91a975
3
+ metadata.gz: 1274eade9799203bd720c94da255a81a0e0295da9e438519a687626c1db7f0b7
4
+ data.tar.gz: 96fe1b73d77d38004981f476ddca3acde81577755de6939f77c6c4ea65fdc693
5
5
  SHA512:
6
- metadata.gz: a8557e744ce587f4d30c01b3ba954cec4eba4e5b3cd51ab408c202b4b3d6038f4db5e92f02d4667a7b289738d0efc5e578528535105877fb847c735eac162eb2
7
- data.tar.gz: f421f465e5690496b21050950c6a177b01c7724b67752ae896aaf7ef500c972c0643bb88b82c0562285ed7bb4b9f82f8461e1677f2f212dbf25847e474e7649a
6
+ metadata.gz: da793dbcee6ae0cd28b6f5160f85a752804a44ceb00f295291209e2529a3ba39ed26300f43b8b9df95f4ce41b7c3c10b97f60550eed9b9ce5c27c50b4252a638
7
+ data.tar.gz: 92b77622dcaf2fa8129e013465a91186d1de731eb927bcd219fac8160c7afccceb2beecf4f509f7e3b9fe95c8b9431955d301566eeaa5608302a2331ce87743c
@@ -2,6 +2,20 @@
2
2
 
3
3
  Documentation for library API changes.
4
4
 
5
+ Versioning system:
6
+
7
+ `MAJOR.MINOR.REVISION`
8
+
9
+ * `MAJOR` Corresponds to the native Ruqqus API major version
10
+ * `MINOR` Indicates possible breaking API changes for existing code
11
+ * `REVISION` Added functionality, bug-fixes, and other non-breaking alterations
12
+
13
+ ## Version 1.1.3
14
+
15
+ * Implemented browser-based confirmation process
16
+ * Implemented capturing confirmation code from `localhost` OAuth redirects
17
+ * Fixed bug in querying guild/username availability
18
+
5
19
  ## Version 1.1.2
6
20
 
7
21
  * Implemented enumerating comments of guilds and posts
@@ -1,5 +1,9 @@
1
- require 'rest-client'
1
+ require 'base64'
2
2
  require 'json'
3
+ require 'rbconfig'
4
+ require 'rest-client'
5
+ require 'securerandom'
6
+ require 'socket'
3
7
 
4
8
  require_relative 'ruqqus/token'
5
9
  require_relative 'ruqqus/routes'
@@ -49,12 +53,14 @@ module Ruqqus
49
53
  # @return [URI?] the URI of the proxy server in use, or `nil` if none has been set.
50
54
 
51
55
  ##
52
- # Obtains a list of URIs of proxy servers that can be used to route network traffic through.
56
+ # Obtains a list of URIs of free proxy servers that can be used to route network traffic through.
53
57
  #
54
58
  # @param anon [Symbol] anonymity filter for the servers to return, either `:transparent`, `:anonymous`, or `:elite`.
55
59
  # @param country [String,Symbol] country filter for servers to return, an ISO-3166 two digit county code.
56
60
  #
57
61
  # @return [Array<URI>] an array of proxy URIs that match the input filters.
62
+ # @note These proxies are free, keep that in mind. They are refreshed frequently, can go down unexpectedly, be slow,
63
+ # and other manners of inconvenience that can be expected with free services.
58
64
  # @see https://www.nationsonline.org/oneworld/country_code_list.htm
59
65
  def self.proxy_list(anon: :elite, country: nil)
60
66
  raise(ArgumentError, 'invalid anonymity value') unless %i(transparent anonymous elite).include?(anon.to_sym)
@@ -83,7 +89,7 @@ module Ruqqus
83
89
  #
84
90
  # @param client_id [String] an Imgur client ID
85
91
  # @param image_path [String] the path to an image file.
86
- # @params opts [Hash] the options hash.
92
+ # @param opts [Hash] the options hash.
87
93
  # @option opts [String] :title a title to set on the Imgur post
88
94
  # @option opts [String] :description a description to set on the Imgur post
89
95
  #
@@ -111,7 +117,14 @@ module Ruqqus
111
117
  #
112
118
  # @return [Boolean] `true` is name is available, otherwise `false` if it has been reserved or is in use.
113
119
  def self.guild_available?(guild_name)
114
- available?(guild_name, VALID_GUILD, "#{Routes::GUILD_AVAILABLE}#{name}")
120
+ begin
121
+ response = RestClient.get("#{Routes::GUILD_AVAILABLE}#{guild_name}")
122
+ json = JSON.parse(response.body, symbolize_names: true)
123
+ return json[:available]
124
+ rescue
125
+ puts 'err'
126
+ return false
127
+ end
115
128
  end
116
129
 
117
130
  ##
@@ -121,22 +134,145 @@ module Ruqqus
121
134
  #
122
135
  # @return [Boolean] `true` is name is available, otherwise `false` if it has been reserved or is in use.
123
136
  def self.username_available?(username)
124
- available?(username, VALID_USERNAME, "#{Routes::USERNAME_AVAILABLE}#{name}")
137
+ begin
138
+ response = RestClient.get("#{Routes::USERNAME_AVAILABLE}#{username}")
139
+ json = JSON.parse(response.body)
140
+ return json[username]
141
+ rescue
142
+ return false
143
+ end
144
+ end
145
+
146
+ ##
147
+ # Generates a URL for the user to navigate to that will allow them to authorize an application.
148
+ #
149
+ # @param client_id [String] the unique ID of the approved client to authorize.
150
+ # @param redirect [String] the redirect URL where the client sends the OAuth authorization code.
151
+ # @param scopes [Array<Symbol>] a collection of values indicating the permissions the application is requesting from
152
+ # the user. See {Ruqqus::Client::SCOPES} for valid values.
153
+ # @param permanent [Boolean] `true` if authorization should persist until user explicitly revokes it,
154
+ # otherwise `false`.
155
+ # @param csrf [String] a token to authenticate and prevent a cross-site request forgery (CSRF) attack, or `nil` if
156
+ # you do not plan to validate the presence of the cookie in the redirection.
157
+ #
158
+ # @see https://ruqqus.com/settings/apps
159
+ # @see https://owasp.org/www-community/attacks/csrf
160
+ def self.authorize_url(client_id, redirect, scopes, permanent = true, csrf = nil)
161
+
162
+ raise(ArgumentError, 'invalid redirect URI') unless URI.regexp =~ redirect
163
+ raise(ArgumentError, 'scopes cannot be empty') unless scopes && !scopes.empty?
164
+
165
+ scopes = scopes.map(&:to_sym)
166
+ raise(ArgumentError, "invalid scopes specified") unless scopes.all? { |s| Client::SCOPES.include?(s) }
167
+ if scopes.any? { |s| [:create, :update, :guildmaster].include?(s) } && !scopes.include?(:identity)
168
+ # Add identity permission if missing, which is obviously required for a few other permissions
169
+ scopes << :identity
170
+ end
171
+
172
+ url = 'https://ruqqus.com/oauth/authorize'
173
+ url << "?client_id=#{client_id || raise(ArgumentError, 'client ID cannot be nil')}"
174
+ url << "&redirect_uri=#{redirect}"
175
+ url << "&scope=#{scopes.join(',')}"
176
+ url << "&state=#{csrf || Base64.encode64(SecureRandom.uuid).chomp}"
177
+ url << "&permanent=#{permanent}"
178
+ url
125
179
  end
126
180
 
127
- private
128
181
 
129
182
  ##
130
- # Checks if the specified guild or user name is available to be created.
183
+ # Opens a URL in the system's default web browser, using the appropriate command for the host platform.
131
184
  #
132
- # @param name [String] the name of a guild or username to query.
133
- # @param regex [Regex] a validation regex for the name.
134
- # @param route [String] the API endpoint to invoke.
185
+ # @param [String] the URL to open.
135
186
  #
136
- # @return [Boolean] `true` is name is available, otherwise `false` if it has been reserved or is in use.
137
- def self.available?(name, regex, route)
138
- return false unless name && regex.match?(name)
139
- json = JSON.parse(RestClient.get(route))
140
- !!json[name]
187
+ # @return [void]
188
+ def self.open_browser(url)
189
+
190
+ cmd = case RbConfig::CONFIG['host_os']
191
+ when /mswin|mingw|cygwin/ then "start \"\"#{url}\"\""
192
+ when /darwin/ then "open '#{url}'"
193
+ when /linux|bsd/ then "xdg-open '#{url}'"
194
+ else raise(Ruqqus::Error, 'unable to determine how to open URL for current platform')
195
+ end
196
+
197
+ system(cmd)
198
+ end
199
+
200
+ ##
201
+ # If using a `localhost` address for your application's OAuth redirect, this method can be used to open a socket and
202
+ # listen for a request, returning the authorization code once it arrives.
203
+ #
204
+ # @param port [Integer] the port to listen on.
205
+ # @param timeout [Numeric] sets the number of seconds to wait before cancelling and returning `nil`.
206
+ #
207
+ # @return [String?] the authorization code, `nil` if an error occurred.
208
+ # @note This method is blocking, and will *not* return until a connection is made and data is received on the
209
+ # specified port, or the timeout is reached.
210
+ def self.wait_for_code(port, timeout = 30)
211
+
212
+ thread = Thread.new do
213
+ sleep(timeout)
214
+ TCPSocket.open('localhost', port) { |s| s.puts }
215
+ end
216
+
217
+ params = {}
218
+ TCPServer.open('localhost', port) do |server|
219
+
220
+ session = server.accept
221
+ request = session.gets
222
+ match = /^GET [\/?]+(.*) HTTP.*/.match(request)
223
+
224
+ Thread.kill(thread)
225
+ return nil unless match
226
+
227
+ $1.split('&').each do |str|
228
+ key, value = str.split('=')
229
+ next unless key && value
230
+ params[key.to_sym] = value
231
+ end
232
+
233
+ session.puts "HTTP/1.1 200\r\n"
234
+ session.puts "Content-Type: text/html\r\n"
235
+ session.puts "\r\n"
236
+ session.puts create_response(!!params[:code])
237
+
238
+ session.close
239
+ end
240
+
241
+ params[:code]
242
+ end
243
+
244
+ private
245
+
246
+ ##
247
+ # @return [String] a generic confirmation page to display in the user's browser after confirming application access.
248
+ def self.create_response(success)
249
+ args = success ? ['#339966', 'Authorization Confirmed'] : ['#ff0000', 'Authorization Failed']
250
+ format ='<h1 style="text-align: center;"><span style="color: %s;"><strong>%s</strong></span></h1>'
251
+ message = sprintf(format, *args)
252
+ <<-EOS
253
+ <html>
254
+ <head>
255
+ <style>
256
+ .center {
257
+ margin: 0;
258
+ position: absolute;
259
+ top: 50%;
260
+ left: 50%;
261
+ -ms-transform: translate(-50%, -50%);
262
+ transform: translate(-50%, -50%);
263
+ }
264
+ </style>
265
+ </head>
266
+ <body>
267
+ <div class="center">
268
+ <div><img src="https://raw.githubusercontent.com/ruqqus/ruqqus/master/ruqqus/assets/images/logo/ruqqus_text_logo.png" alt="" width="365" height="92" /></div>
269
+ <p style="text-align: center;">&nbsp;</p>
270
+ #{message}
271
+ <p style="text-align: center;">&nbsp;&nbsp;</p>
272
+ <p style="text-align: center;"><span style="color: #808080;">You can safely close the tab/browser and return to the application.</span></p>
273
+ </div>
274
+ </body>
275
+ </html>
276
+ EOS
141
277
  end
142
278
  end
@@ -62,7 +62,7 @@ module Ruqqus
62
62
  #
63
63
  # @return [User] the requested {User}.
64
64
  #
65
- # @raise [ArgumentError] when `username` is `nil` or value does match the {VALID_USERNAME} regular expression.
65
+ # @raise [ArgumentError] when `username` is `nil` or value does match the {Ruqqus::VALID_USERNAME} regular expression.
66
66
  # @raise [Error] thrown when user account does not exist.
67
67
  def user(username)
68
68
  raise(ArgumentError, 'username cannot be nil') unless username
@@ -77,7 +77,7 @@ module Ruqqus
77
77
  #
78
78
  # @return [Guild] the requested {Guild}.
79
79
  #
80
- # @raise [ArgumentError] when `guild_name` is `nil` or value does match the {VALID_GUILD} regular expression.
80
+ # @raise [ArgumentError] when `guild_name` is `nil` or value does match the {Ruqqus::VALID_GUILD} regular expression.
81
81
  # @raise [Error] thrown when guild does not exist.
82
82
  def guild(guild_name)
83
83
  raise(ArgumentError, 'guild_name cannot be nil') unless guild_name
@@ -92,7 +92,7 @@ module Ruqqus
92
92
  #
93
93
  # @return [Post] the requested {Post}.
94
94
  #
95
- # @raise [ArgumentError] when `post_id` is `nil` or value does match the {VALID_POST} regular expression.
95
+ # @raise [ArgumentError] when `post_id` is `nil` or value does match the {Ruqqus::VALID_POST} regular expression.
96
96
  # @raise [Error] thrown when a post with the specified ID does not exist.
97
97
  def post(post_id)
98
98
  raise(ArgumentError, 'post_id cannot be nil') unless post_id
@@ -107,7 +107,7 @@ module Ruqqus
107
107
  #
108
108
  # @return [Comment] the requested {Comment}.
109
109
  #
110
- # @raise [ArgumentError] when `comment_id` is `nil` or value does match the {VALID_POST} regular expression.
110
+ # @raise [ArgumentError] when `comment_id` is `nil` or value does match the {Ruqqus::VALID_POST} regular expression.
111
111
  # @raise [Error] when a comment with the specified ID does not exist.
112
112
  def comment(comment_id)
113
113
  raise(ArgumentError, 'comment_id cannot be nil') unless comment_id
@@ -1,11 +1,15 @@
1
1
  module Ruqqus
2
2
 
3
3
  ##
4
- # The Ruqqus gem version.
5
- VERSION = '1.1.2'.freeze
4
+ # The Ruqqus gem version. Version changes implement the following versioning system:
5
+ #
6
+ # * `MAJOR` Corresponds to the native Ruqqus API major version
7
+ # * `MINOR` Indicates possible breaking API changes for existing code
8
+ # * `REVISION` Added functionality, bug-fixes, and other non-breaking alterations
9
+ VERSION = '1.1.3'.freeze
6
10
 
7
11
  ##
8
- # Lulz
12
+ # Please listen to this song I wrote. The song is called "Endless Summer".
9
13
  ENDLESS_SUMMER = 'https://youtu.be/o_LskiXQ73c'.freeze
10
14
 
11
15
  private_constant(:ENDLESS_SUMMER)
@@ -36,7 +36,8 @@ Gem::Specification.new do |spec|
36
36
  spec.add_runtime_dependency('rest-client', '~> 2.1')
37
37
 
38
38
  spec.add_development_dependency('mechanize', '~> 2.7')
39
- spec.add_development_dependency('rake', '~> 13.0')
40
39
  spec.add_development_dependency('tty-prompt', '~> 0.22')
41
40
  spec.add_development_dependency('yard', '~> 0.9')
41
+
42
+ spec.post_install_message = 'Please listen to this song I wrote. The song is called "Endless Summer".'
42
43
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruqqus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - ForeverZer0
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-09-07 00:00:00.000000000 Z
11
+ date: 2020-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.7'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '13.0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '13.0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: tty-prompt
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -126,7 +112,8 @@ metadata:
126
112
  changelog_uri: https://github.com/ForeverZer0/ruqqus/CHANGELOG.md
127
113
  documentation_uri: https://www.rubydoc.info/gems/ruqqus
128
114
  bug_tracker_uri: https://github.com/ForeverZer0/ruqqus/issues
129
- post_install_message:
115
+ post_install_message: Please listen to this song I wrote. The song is called "Endless
116
+ Summer".
130
117
  rdoc_options: []
131
118
  require_paths:
132
119
  - lib