ruqqus 1.1.0 → 1.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb4087c263dae6ef0708a07d756733ea19f95e1e6f02983c658c9cfb40a2f475
4
- data.tar.gz: d9d71b14897ec30ea1f5356916d1eb2cc432db02e7c007fb5f8efcea3a3f37fa
3
+ metadata.gz: 62b35161bf5278e264c66836f39a80de3492dcd627f7e1e9d701a5439af30649
4
+ data.tar.gz: 0cbc0628bcceebaac0770ed2c2fa5558d632ab3f02f5c7c9deb500f9552c3e4f
5
5
  SHA512:
6
- metadata.gz: f5864e192920e53c59921cf9bdb7d2f2acaf261d77ed4c0d3184a7f601cf967cf87f358c4b396705b8366fcda6ed3d54a9a125b0f13e7e139d0ce3ff0f67535e
7
- data.tar.gz: 73d7d5b8a4551a7bc83efd54b8be72107a010f4023dc9784396dddce8ceae4808049cc6fed37487b07e259b6efc41a1ff75d8184bf5ce28f18f8c261e2ffd0e4
6
+ metadata.gz: da177c6d90e02598bb5438d79b3c0fca5219922a1bda1d20a67e458be8bebf078621c45bdbf557f84c278bfa2d668cee6002911512a98ce6dda8945383c97c72
7
+ data.tar.gz: 46f0839094cfd3e134df3719cc7bf189df295963f34a106c0540063af57d5dac887c7ca5c019e1afb7152c4d4051a025b1dbe12dd7f287ac4c316e9fe265bc18
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: "[BUG] "
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Additional context**
24
+ Add any other context about the problem here.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your feature request related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ **Describe the solution you'd like**
14
+ A clear and concise description of what you want to happen.
15
+
16
+ **Additional context**
17
+ Add any other context or screenshots about the feature request here.
data/.gitignore CHANGED
@@ -168,3 +168,4 @@ modules.xml
168
168
 
169
169
  # JSON file containing the token for test user during development
170
170
  /token.json
171
+ /test.rb
@@ -2,6 +2,39 @@
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.5
14
+
15
+ * Added `Ruqqus::Clien#post_delete` method
16
+ * Added `Ruqqus::Guild#guildmasters` attribute
17
+
18
+ ## Version 1.1.4
19
+
20
+ * Improved the way refreshing works to do so within a certain threshold
21
+
22
+ ## Version 1.1.3
23
+
24
+ * Implemented browser-based confirmation process
25
+ * Implemented capturing confirmation code from `localhost` OAuth redirects
26
+ * Fixed bug in querying guild/username availability
27
+
28
+ ## Version 1.1.2
29
+
30
+ * Implemented enumerating comments of guilds and posts
31
+
32
+ ## Version 1.1.1
33
+
34
+ * BUGFIX: Added acceptance of variable args to `Token#to_json`
35
+ * BUGFIX: Fixed regex validator in `ruqqus-oauth` for the client ID
36
+ * Separated the client ID/secret from the `Token` class, and placed within `Client` to now handle this logic
37
+
5
38
  ## Version 1.1.0
6
39
 
7
40
  * Implemented `Ruqqus::Token` class to handle OAuth2 authentication
@@ -0,0 +1,51 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful. This will always involve a public and
28
+ open discussion of the action beinng taken in full transparency.
29
+
30
+ By adopting this Code of Conduct, project maintainers commit themselves to
31
+ fairly and consistently applying these principles to every aspect of managing
32
+ this project. Project maintainers who do not follow or enforce the Code of
33
+ Conduct may be permanently removed from the project team.
34
+
35
+ This code of conduct applies both within project spaces and in public spaces
36
+ when an individual is representing the project or its community.
37
+
38
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
39
+ reported by contacting a project maintainer at efreed09@gmail.com. All
40
+ complaints will be reviewed and investigated and will result in a response that
41
+ is deemed necessary and appropriate to the circumstances. Maintainers are
42
+ obligated to maintain confidentiality with regard to the reporter of an
43
+ incident.
44
+
45
+
46
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
47
+ version 1.3.0, available at
48
+ [http://contributor-covenant.org/version/1/3/0/][version]
49
+
50
+ [homepage]: http://contributor-covenant.org
51
+ [version]: http://contributor-covenant.org/version/1/3/0/
@@ -0,0 +1,24 @@
1
+ # How to contribute
2
+ First of all, thank you for taking the time to contribute to this project. We've tried to make a stable project and try to fix bugs and add new features continuously. You can help us do more.
3
+
4
+ ## Getting started
5
+
6
+ ### Check out existing issues/pull requests
7
+
8
+ If there is a bug or a feature that is not listed in the **issues** page or there is no one assigned to the issue, feel free to fix/add it! Although it's better to discuss it in the issue or create a new issue for it so there is no confilcting code.
9
+
10
+ ### Writing some code!
11
+
12
+ Contributing to a project on Github is pretty straight forward. If this is you're first time, these are the steps you should take.
13
+
14
+ - Fork this repo.
15
+
16
+ And that's it! Read the code available and change the part you don't like! You're change should not break the existing code and should pass the tests.
17
+
18
+ If you're adding a new functionality, start from the branch **master**. It would be a better practice to create a new branch and work in there.
19
+
20
+ When you're done, submit a pull request and for one of the maintainers to check it out. We would let you know if there is any problem or any changes that should be considered.
21
+
22
+ ### Documentation
23
+
24
+ This project enforces 100% documentation coverage, even on non-public methods that aren't part of the API surface. We use [YARD](https://www.rubydoc.info/gems/yard/file/docs/Tags.md) as our tool of choice for documentation. It is rather intuitive, but feel free to ask if you questions if unsure or are unfamiliar with it. Pull requests will not be merged unless documented, so please don't forget to include it!
data/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/ruqqus/ruqqus/master/ruqqus/assets/images/logo/ruqqus_text_logo.png" width="250"/>
2
+ <img src="https://raw.githubusercontent.com/ForeverZer0/ruqqus/master/assets/ruqqus_text_logo.png" width="360"/>
3
3
  </p>
4
4
 
5
5
  <hr>
@@ -41,7 +41,7 @@ To use the `ruqqus-oauth` helper to generate user tokens for desktop development
41
41
 
42
42
  Ruqqus enables 3rd-party client authorization using the [OAuth2 protocol](https://oauth.net/2/). Before it is possible
43
43
  to interact with the API, you will need to first [register an application](https://ruqqus.com/settings/apps), which can
44
- be supply you with an API key/secret pair. This key will allow you to authorize users and grant privileges with a an
44
+ be supply you with an API key/secret pair. This key will allow you to authorize users and grant privileges with an
45
45
  assortment of scopes to fit your needs.
46
46
 
47
47
  ### Desktop Development
@@ -67,7 +67,11 @@ code = 'XXXXXX' # The generated code (or the one you obtained via tradi
67
67
 
68
68
  # You must implement a responsible way of storing this token for reuse.
69
69
  token = Ruqqus::Token.new(client_id, client_secret, code)
70
- client = Ruqqus::Client.new(token)
70
+ client = Ruqqus::Client.new(client_id, client_secret, token)
71
+
72
+ # Alternatively, you can create a new token and a client with a single call.
73
+ # This will perform the "grant" action of the code while creating it automatically
74
+ client = Ruqqus::Client.new(client_id, client_secret, code)
71
75
  ```
72
76
 
73
77
  The token will automatically refresh itself as-needed, but you will need to handle storing its new value for repeated
@@ -75,12 +79,16 @@ uses. To facilitate this and make it easier, there is a callback that can be sub
75
79
  time the access key is updated.
76
80
 
77
81
  ```ruby
82
+ # Load an existing token that has already been authorized
78
83
  token = Ruqqus::Token.load_json('./token.json')
79
- token.on_refresh do |t|
84
+
85
+ # Create your client
86
+ client = Ruqqus::Client.new(client_id, client_secret, token)
87
+
88
+ # Set the callback block to automatically update the saved token when it refreshes
89
+ client.token_refreshed do |t|
80
90
  t.save_json('./token.json')
81
91
  end
82
-
83
- client = Ruqqus::Client.new(token)
84
92
  ```
85
93
 
86
94
  The token obtains sensitive material, and due to the security issues of storing it in plain text, this functionality is
@@ -89,7 +97,10 @@ and where you store this information so that it is not compromised.
89
97
 
90
98
  ## Usage
91
99
 
92
- See the [documentation](https://www.rubydoc.info/gems/ruqqus) for a complete API reference.
100
+ For in-depth documentation and instructions on how to get started, please see the following resources:
101
+
102
+ * [API Documentation](https://www.rubydoc.info/gems/ruqqus) - Complete documentation of the entire API (100% coverage)
103
+ * [Wiki](https://github.com/ForeverZer0/ruqqus/wiki) - Public wiki with more in-depth explanation, code samples, best practices, etc.
93
104
 
94
105
  ### Features
95
106
 
data/TODO.md CHANGED
@@ -8,8 +8,6 @@ A scratch pad for things to do and ideas to look into
8
8
  * Update README with more examples
9
9
  * Create wiki on GitHub
10
10
  * Finish and cleanup and `ruqqus-oauth` app
11
- * Groups in documentation
12
- * Front page method?
13
11
  * Embed comment/posts API
14
12
 
15
13
  # Missing API features
Binary file
@@ -41,11 +41,11 @@ class RuqqusOAuth
41
41
  end
42
42
 
43
43
  def ask_client_id(prompt)
44
- prompt.ask('Client ID: ') { |q| q.validate(/^[A-Fa-f0-9]+$/, "Invalid client ID") }
44
+ prompt.ask('Client ID: ') { |q| q.validate(/^[A-Za-z0-9_-]+$/, "Invalid client ID") }
45
45
  end
46
46
 
47
47
  def ask_client_secret(prompt)
48
- prompt.ask('Client Secret: ') { |q| q.validate(/^[A-Fa-f0-9]+$/, "Invalid client secret") }
48
+ prompt.ask('Client Secret: ') { |q| q.validate(/^[A-Za-z0-9_-]+$/, "Invalid client secret") }
49
49
  end
50
50
 
51
51
  def ask_client_redirect(prompt)
@@ -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'
@@ -44,12 +48,48 @@ module Ruqqus
44
48
  class Error < StandardError
45
49
  end
46
50
 
51
+ ##
52
+ # @!attribute self.proxy [rw]
53
+ # @return [URI?] the URI of the proxy server in use, or `nil` if none has been set.
54
+
55
+ ##
56
+ # Obtains a list of URIs of free proxy servers that can be used to route network traffic through.
57
+ #
58
+ # @param anon [Symbol] anonymity filter for the servers to return, either `:transparent`, `:anonymous`, or `:elite`.
59
+ # @param country [String,Symbol] country filter for servers to return, an ISO-3166 two digit county code.
60
+ #
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.
64
+ # @see https://www.nationsonline.org/oneworld/country_code_list.htm
65
+ def self.proxy_list(anon: :elite, country: nil)
66
+ raise(ArgumentError, 'invalid anonymity value') unless %i(transparent anonymous elite).include?(anon.to_sym)
67
+
68
+ url = "https://www.proxy-list.download/api/v1/get?type=https&anon=#{anon}"
69
+ url << "&country=#{country}" if country
70
+
71
+ RestClient.get(url) do |resp|
72
+ break if resp.code != 200
73
+ return resp.body.split.map { |proxy| URI.parse("https://#{proxy}") }
74
+ end
75
+ Array.new
76
+ end
77
+
78
+ def self.proxy
79
+ RestClient.proxy
80
+ end
81
+
82
+ def self.proxy=(uri)
83
+ raise(TypeError, "#{uri} is not a URI") if uri && !uri.is_a?(URI)
84
+ RestClient.proxy = uri
85
+ end
86
+
47
87
  ##
48
88
  # Helper function to automate uploading images to Imgur anonymously and returning the direct image link.
49
89
  #
50
90
  # @param client_id [String] an Imgur client ID
51
91
  # @param image_path [String] the path to an image file.
52
- # @params opts [Hash] the options hash.
92
+ # @param opts [Hash] the options hash.
53
93
  # @option opts [String] :title a title to set on the Imgur post
54
94
  # @option opts [String] :description a description to set on the Imgur post
55
95
  #
@@ -77,7 +117,14 @@ module Ruqqus
77
117
  #
78
118
  # @return [Boolean] `true` is name is available, otherwise `false` if it has been reserved or is in use.
79
119
  def self.guild_available?(guild_name)
80
- 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
81
128
  end
82
129
 
83
130
  ##
@@ -87,22 +134,145 @@ module Ruqqus
87
134
  #
88
135
  # @return [Boolean] `true` is name is available, otherwise `false` if it has been reserved or is in use.
89
136
  def self.username_available?(username)
90
- 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
91
179
  end
92
180
 
93
- private
94
181
 
95
182
  ##
96
- # 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.
97
184
  #
98
- # @param name [String] the name of a guild or username to query.
99
- # @param regex [Regex] a validation regex for the name.
100
- # @param route [String] the API endpoint to invoke.
185
+ # @param [String] the URL to open.
101
186
  #
102
- # @return [Boolean] `true` is name is available, otherwise `false` if it has been reserved or is in use.
103
- def self.available?(name, regex, route)
104
- return false unless name && regex.match?(name)
105
- json = JSON.parse(RestClient.get(route))
106
- !!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
107
277
  end
108
278
  end