ruqqus 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/CHANGELOG.md +24 -1
- data/Gemfile +1 -1
- data/README.md +152 -23
- data/Rakefile +1 -2
- data/TODO.md +27 -0
- data/exe/ruqqus-oauth +98 -0
- data/lib/ruqqus.rb +55 -72
- data/lib/ruqqus/client.rb +474 -0
- data/lib/ruqqus/routes.rb +68 -0
- data/lib/ruqqus/token.rb +148 -0
- data/lib/ruqqus/types.rb +11 -0
- data/lib/ruqqus/{badge.rb → types/badge.rb} +0 -0
- data/lib/ruqqus/types/comment.rb +38 -0
- data/lib/ruqqus/{guild.rb → types/guild.rb} +0 -0
- data/lib/ruqqus/{item_base.rb → types/item_base.rb} +4 -2
- data/lib/ruqqus/{post.rb → types/post.rb} +0 -33
- data/lib/ruqqus/{submission.rb → types/submission.rb} +5 -20
- data/lib/ruqqus/{title.rb → types/title.rb} +0 -0
- data/lib/ruqqus/{user.rb → types/user.rb} +0 -3
- data/lib/ruqqus/version.rb +1 -1
- data/ruqqus.gemspec +4 -2
- metadata +48 -15
- data/lib/ruqqus/comment.rb +0 -61
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb4087c263dae6ef0708a07d756733ea19f95e1e6f02983c658c9cfb40a2f475
|
4
|
+
data.tar.gz: d9d71b14897ec30ea1f5356916d1eb2cc432db02e7c007fb5f8efcea3a3f37fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f5864e192920e53c59921cf9bdb7d2f2acaf261d77ed4c0d3184a7f601cf967cf87f358c4b396705b8366fcda6ed3d54a9a125b0f13e7e139d0ce3ff0f67535e
|
7
|
+
data.tar.gz: 73d7d5b8a4551a7bc83efd54b8be72107a010f4023dc9784396dddce8ceae4808049cc6fed37487b07e259b6efc41a1ff75d8184bf5ce28f18f8c261e2ffd0e4
|
data/.gitignore
CHANGED
@@ -164,4 +164,7 @@ modules.xml
|
|
164
164
|
# Ignore all local history of files
|
165
165
|
.history
|
166
166
|
|
167
|
-
# End of https://www.toptal.com/developers/gitignore/api/ruby,rubymine+all,visualstudiocode
|
167
|
+
# End of https://www.toptal.com/developers/gitignore/api/ruby,rubymine+all,visualstudiocode
|
168
|
+
|
169
|
+
# JSON file containing the token for test user during development
|
170
|
+
/token.json
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,29 @@
|
|
2
2
|
|
3
3
|
Documentation for library API changes.
|
4
4
|
|
5
|
-
## Version 1.0
|
5
|
+
## Version 1.1.0
|
6
|
+
|
7
|
+
* Implemented `Ruqqus::Token` class to handle OAuth2 authentication
|
8
|
+
* Added `Ruqqus::Client` class as the primary object for API usage
|
9
|
+
* Implemented post creation
|
10
|
+
* Implemented comment creation and deletion
|
11
|
+
* Implementing querying for reserved usernames/guilds
|
12
|
+
* Implemented retrieving posts of guilds
|
13
|
+
* Implemented retrieving posts and comments of users
|
14
|
+
* Implemented voting on posts and comments as a user
|
15
|
+
* Implemented enumerating all guilds
|
16
|
+
* Implemented enumerating through all posts
|
17
|
+
* Refactored `Ruqqus.user_info` to `Ruqqus::Client#user`
|
18
|
+
* Refactored `Ruqqus.guild_info` to `Ruqqus::Client#guild`
|
19
|
+
* Refactored `Ruqqus.post_info` to `Ruqqus::Client#post`
|
20
|
+
* Refactored `Ruqqus.comment_info` to `Ruqqus::Client#comment`
|
21
|
+
* Many improvements in code and better adhesion to DRY principles
|
22
|
+
* Added helper method to automate uploading and creating image posts on Ruqqus via Imgur
|
23
|
+
|
24
|
+
## Version 1.0.1
|
25
|
+
|
26
|
+
* Changed validation for `fomr_url` methods in `Post` and `Comment` to also accept relative URIs.
|
27
|
+
|
28
|
+
## Version 1.0.0
|
6
29
|
|
7
30
|
* Initial release, 100% coverage of existing Ruqqus API
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -6,14 +6,16 @@
|
|
6
6
|
|
7
7
|
# Ruqqus
|
8
8
|
|
9
|
-
A Ruby API implementation for [Ruqqus](https://ruqqus.com/), an [open-source](https://github.com/ruqqus/ruqqus) platform for online communities, free of censorship and moderator abuse by design.
|
10
|
-
|
11
|
-
While platform is still in Beta at this time and the public API for it is still quite limited, this gem will be actively updated as it continues to grow and is developed.
|
9
|
+
A Ruby API implementation for [Ruqqus](https://ruqqus.com/), an [open-source](https://github.com/ruqqus/ruqqus) platform for online communities, free of censorship and moderator abuse by design. [Sign up](https://ruqqus.com/signup?ref=foreverzer0
|
10
|
+
) if you haven't yet!
|
12
11
|
|
13
12
|
[![Build Status](https://travis-ci.org/ForeverZer0/ruqqus.svg?branch=master)](https://travis-ci.org/ForeverZer0/ruqqus)
|
14
|
-
[![Gem Version](https://badge.fury.io/rb/ruqqus.svg)](https://
|
13
|
+
[![Gem Version](https://badge.fury.io/rb/ruqqus.svg)](https://rubygems.org/gems/ruqqus)
|
15
14
|
[![Inline docs](http://inch-ci.org/github/ForeverZer0/ruqqus.svg?branch=master)](http://inch-ci.org/github/ForeverZer0/ruqqus)
|
16
15
|
[![Maintainability](https://api.codeclimate.com/v1/badges/c39f44a706302e4cd340/maintainability)](https://codeclimate.com/github/ForeverZer0/ruqqus/maintainability)
|
16
|
+
[![OpenIssues](https://img.shields.io/github/issues/ForeverZer0/ruqqus)](https://github.com/ForeverZer0/ruqqus/issues)
|
17
|
+
[![License](https://img.shields.io/github/license/ForeverZer0/ruqqus)](https://opensource.org/licenses/MIT)
|
18
|
+
[![Ruby](https://img.shields.io/badge/powered%20by-ruby-red)](https://www.ruby-lang.org/en/)
|
17
19
|
|
18
20
|
## Installation
|
19
21
|
|
@@ -31,23 +33,148 @@ Or install it yourself as:
|
|
31
33
|
|
32
34
|
$ gem install ruqqus
|
33
35
|
|
36
|
+
To use the `ruqqus-oauth` helper to generate user tokens for desktop development:
|
37
|
+
|
38
|
+
$ gem install ruqqus --development
|
39
|
+
|
40
|
+
## Authentication
|
41
|
+
|
42
|
+
Ruqqus enables 3rd-party client authorization using the [OAuth2 protocol](https://oauth.net/2/). Before it is possible
|
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
|
45
|
+
assortment of scopes to fit your needs.
|
46
|
+
|
47
|
+
### Desktop Development
|
48
|
+
|
49
|
+
This gem includes a tool to automate obtaining a client code for users, primarily aimed for desktop developers who do
|
50
|
+
not have a server running to receive the redirect URL. The tool requires the `mechanize` and `tty-prompt` gems, which
|
51
|
+
are not included by default.
|
52
|
+
|
53
|
+
To install the tool with its dependencies, install this gem with the following flag.
|
54
|
+
|
55
|
+
$ gem insall ruqqus --development
|
56
|
+
|
57
|
+
Once installed, simply run `ruqqus-oauth`. You will be prompted to input the API key that was issued by Ruqqus to your
|
58
|
+
approved application, and the user credentials for the account you with to authorize, such as that for a bot. Once
|
59
|
+
executed, an authorization code will be displayed on-screen, which you can then use to create a token.
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
require 'ruqqus'
|
63
|
+
|
64
|
+
client_id = 'XXXXXX' # Received after registering application
|
65
|
+
client_secret = 'XXXXXX' # Received after registering application
|
66
|
+
code = 'XXXXXX' # The generated code (or the one you obtained via traditional means)
|
67
|
+
|
68
|
+
# You must implement a responsible way of storing this token for reuse.
|
69
|
+
token = Ruqqus::Token.new(client_id, client_secret, code)
|
70
|
+
client = Ruqqus::Client.new(token)
|
71
|
+
```
|
72
|
+
|
73
|
+
The token will automatically refresh itself as-needed, but you will need to handle storing its new value for repeated
|
74
|
+
uses. To facilitate this and make it easier, there is a callback that can be subscribed to which will be called each
|
75
|
+
time the access key is updated.
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
token = Ruqqus::Token.load_json('./token.json')
|
79
|
+
token.on_refresh do |t|
|
80
|
+
t.save_json('./token.json')
|
81
|
+
end
|
82
|
+
|
83
|
+
client = Ruqqus::Client.new(token)
|
84
|
+
```
|
85
|
+
|
86
|
+
The token obtains sensitive material, and due to the security issues of storing it in plain text, this functionality is
|
87
|
+
left to the user. The token is essentially the equivalent of your user credentials for Ruqqus, so bear that in mind how
|
88
|
+
and where you store this information so that it is not compromised.
|
89
|
+
|
34
90
|
## Usage
|
35
91
|
|
36
|
-
|
92
|
+
See the [documentation](https://www.rubydoc.info/gems/ruqqus) for a complete API reference.
|
93
|
+
|
94
|
+
### Features
|
95
|
+
|
96
|
+
The bulk of the API is obviously related to performing actions as a user, such as posting, commenting, voting, etc.. The
|
97
|
+
following highlights some of these features.
|
98
|
+
|
99
|
+
* Vote on posts and comments
|
100
|
+
* Create posts (text/link/image)
|
101
|
+
* Automated image upload via anonymous upload to Imgur ([API Key](https://imgur.com/account/settings/apps) required)
|
102
|
+
* Create/edit/delete comments
|
103
|
+
* Enumerate all existing guilds
|
104
|
+
* Enumerate all posts in a guild
|
105
|
+
* Enumerate all posts on the "front page", "all", etc., with sorting and filtering
|
106
|
+
* Enumerate all posts/comments of users (excluding private/banned/blocked accounts)
|
107
|
+
|
108
|
+
#### Misc. Examples
|
109
|
+
|
110
|
+
Some random examples displaying the ease in performing various operations.
|
111
|
+
|
112
|
+
##### Let's do some voting!
|
113
|
+
```ruby
|
114
|
+
client.each_user_post('captainmeta4') do |post|
|
115
|
+
# Upvote our fearless leader's posts
|
116
|
+
client.vote_post(post, 1)
|
117
|
+
end
|
118
|
+
|
119
|
+
client.each_user_comment('captainmeta4') do |comment|
|
120
|
+
# ...and his comments.
|
121
|
+
client.vote_comment(comment, 1)
|
122
|
+
end
|
123
|
+
```
|
124
|
+
|
125
|
+
##### Monitor For New Posts
|
126
|
+
```ruby
|
127
|
+
delay = 10
|
128
|
+
puts 'Watching for new posts, press Ctrl+C to quit'
|
129
|
+
loop do
|
130
|
+
time = Time.now
|
131
|
+
sleep(delay)
|
132
|
+
|
133
|
+
puts "Checking the front page for new posts since #{time}"
|
134
|
+
client.each_post(sort: :new, filter: :all) do |post|
|
135
|
+
# Stop checking post once we encounter one older than this iteration
|
136
|
+
break if post.created < time
|
137
|
+
# Do something about this new post showing up in All
|
138
|
+
puts "Found a new post: '#{post.title}'"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
```
|
142
|
+
##### Download all images from a guild
|
143
|
+
```ruby
|
144
|
+
require 'open-uri'
|
145
|
+
domains = %w[i.ruqqus.com i.imgur.com]
|
146
|
+
|
147
|
+
Dir.mkdir('./tree_pics') unless Dir.exist?('./tree_pics')
|
148
|
+
client.each_guild_post('Trees', sort: :new) do |post|
|
149
|
+
next unless domains.include?(post.domain)
|
150
|
+
ext = File.extname(post.url)
|
151
|
+
ext = '.jpg' if ext.empty? # We don't care, just an example
|
152
|
+
path = File.join('./tree_pics', post.id + ext)
|
153
|
+
URI.open(post.url) { |src| File.open(path, 'wb') { |dst| dst.write(src.read) } }
|
154
|
+
end
|
155
|
+
```
|
156
|
+
|
157
|
+
### Types
|
158
|
+
|
159
|
+
The Ruqqus API exposes four primary types:
|
37
160
|
|
38
161
|
* Users
|
39
162
|
* Guilds
|
40
163
|
* Posts
|
41
164
|
* Comments
|
42
165
|
|
43
|
-
|
166
|
+
Nearly all client operations interact with one or more of these entities in some way. Each of these types also has an
|
167
|
+
`#id` property that can be used with other related API functions, such as voting, replying, deleting, etc. The full
|
168
|
+
documentation listing all properties they obtain can be found [here](https://www.rubydoc.info/gems/ruqqus), but the API
|
169
|
+
is rather intuitive. Here are some samples of their basic
|
170
|
+
usage.
|
44
171
|
|
45
|
-
|
172
|
+
#### Users
|
46
173
|
|
47
174
|
Obtain information about users.
|
48
175
|
|
49
176
|
```ruby
|
50
|
-
user =
|
177
|
+
user = client.user_info('foreverzer0')
|
51
178
|
|
52
179
|
# Get user's total rep (as well as separate for comments/posts)
|
53
180
|
user.total_rep
|
@@ -62,12 +189,12 @@ user.created
|
|
62
189
|
#=> 2020-06-16 21:59:04 -0400
|
63
190
|
```
|
64
191
|
|
65
|
-
|
192
|
+
#### Guilds
|
66
193
|
|
67
194
|
Obtain information about guilds.
|
68
195
|
|
69
196
|
```ruby
|
70
|
-
guild =
|
197
|
+
guild = client.guild('Ruby')
|
71
198
|
|
72
199
|
# Query the number of members, description, accent color, etc.
|
73
200
|
guild.member_count
|
@@ -82,14 +209,16 @@ guild.nsfw?
|
|
82
209
|
#=> false
|
83
210
|
```
|
84
211
|
|
85
|
-
|
212
|
+
#### Posts
|
86
213
|
|
87
|
-
Obtain information about posts.
|
214
|
+
Obtain information about posts.
|
88
215
|
|
89
216
|
```ruby
|
90
|
-
|
91
|
-
#
|
92
|
-
|
217
|
+
# Post IDs can be found within any link to a post.
|
218
|
+
# https://ruqqus.com/post/<POST ID>/<POST TITLE>
|
219
|
+
|
220
|
+
post_id = '2e0x'
|
221
|
+
post = client.post(post_id)
|
93
222
|
|
94
223
|
# Obtain relevant information pertaining the guilds on Ruqqus
|
95
224
|
|
@@ -106,27 +235,27 @@ post.score
|
|
106
235
|
#=> 10
|
107
236
|
```
|
108
237
|
|
109
|
-
|
238
|
+
#### Comments
|
110
239
|
|
111
240
|
Obtain information about comments. Comments are very similar to posts, but have a few unique methods for obtaining their nesting level, parent post/comment, etc.
|
112
241
|
|
113
242
|
```ruby
|
114
|
-
|
115
|
-
#
|
116
|
-
comment = Ruqqus::Comment.from_url('https://ruqqus.com/post/1wbo/hi-im-josh-roehl-singer-and/67mt')
|
243
|
+
# Post IDs can be found within any link to a post.
|
244
|
+
# https://ruqqus.com/post/<POST ID>/<POST TITLE>/<COMMENT ID>
|
117
245
|
|
118
|
-
|
246
|
+
comment_id = '67mt'
|
247
|
+
comment = client.comment(comment_id)
|
248
|
+
|
249
|
+
client.post(comment.post).title
|
119
250
|
#=> "Hi. I'm Josh Roehl, singer and songwriter of the hit song \"Endless Summer\". I am hosting an AMA here."
|
120
251
|
|
121
252
|
comment.body
|
122
253
|
#=> "I'm fully aware that I'm not a very good singer. Let's call it half-singing, half-rapping."
|
123
254
|
|
124
|
-
comment.
|
255
|
+
client.user(comment.author_name).ban_reason
|
125
256
|
#=> "Spam"
|
126
257
|
```
|
127
258
|
|
128
|
-
[Documentation](https://www.rubydoc.info/gems/ruqqus)
|
129
|
-
|
130
259
|
## Contributing
|
131
260
|
|
132
261
|
Bug reports and pull requests are welcome on GitHub at https://github.com/ForeverZer0/ruqqus.
|
data/Rakefile
CHANGED
data/TODO.md
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# TODO
|
2
|
+
|
3
|
+
A scratch pad for things to do and ideas to look into
|
4
|
+
|
5
|
+
* Create and centralize all API endpoints into `Routes` module
|
6
|
+
* Check for `json[:error]` in `http_get` and `http_post` for all calls?
|
7
|
+
* Create examples in documentation
|
8
|
+
* Update README with more examples
|
9
|
+
* Create wiki on GitHub
|
10
|
+
* Finish and cleanup and `ruqqus-oauth` app
|
11
|
+
* Groups in documentation
|
12
|
+
* Front page method?
|
13
|
+
* Embed comment/posts API
|
14
|
+
|
15
|
+
# Missing API features
|
16
|
+
|
17
|
+
Some API features that would be beneficial to have implemented
|
18
|
+
|
19
|
+
* Flagging
|
20
|
+
* Post deletion/edit (!)
|
21
|
+
* Notifications (!)
|
22
|
+
* NSFW/NSFW toggling
|
23
|
+
* Kicking/Yanking
|
24
|
+
* User Settings
|
25
|
+
* Guild Settings
|
26
|
+
* Guild join/leave
|
27
|
+
* Follow/Unfollow users
|
data/exe/ruqqus-oauth
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'mechanize'
|
4
|
+
require 'tty-prompt'
|
5
|
+
|
6
|
+
#noinspection RubyResolve
|
7
|
+
class RuqqusOAuth
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
prompt = TTY::Prompt.new
|
11
|
+
agent = Mechanize.new
|
12
|
+
|
13
|
+
prompt.warn('After inputting your client information and credentials of user')
|
14
|
+
prompt.warn('to authorize, and a code for token creation will be generated.')
|
15
|
+
puts
|
16
|
+
url = authorization_url(prompt)
|
17
|
+
login(agent, prompt)
|
18
|
+
prompt.say('Generating code...')
|
19
|
+
puts
|
20
|
+
code = generate_code(url, agent)
|
21
|
+
STDOUT << 'CODE: '
|
22
|
+
prompt.ok(code)
|
23
|
+
end
|
24
|
+
|
25
|
+
def authorization_url(prompt)
|
26
|
+
url = 'https://ruqqus.com/oauth/authorize'
|
27
|
+
url << "?client_id=#{ask_client_id(prompt)}"
|
28
|
+
url << "&redirect_uri=#{ask_client_redirect(prompt)}"
|
29
|
+
url << "&scope=#{ask_scopes(prompt)}"
|
30
|
+
url << "&state=#{SecureRandom.uuid.gsub('-', '')}"
|
31
|
+
url << '&permanent=true'
|
32
|
+
url
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_code(url, agent)
|
36
|
+
agent.get(url) do |page|
|
37
|
+
page.form_with(action: '/oauth/authorize').submit
|
38
|
+
/.*\?code=([a-zA-Z0-9_-]+)&?/.match(agent.history.last.uri.to_s)
|
39
|
+
return $1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def ask_client_id(prompt)
|
44
|
+
prompt.ask('Client ID: ') { |q| q.validate(/^[A-Fa-f0-9]+$/, "Invalid client ID") }
|
45
|
+
end
|
46
|
+
|
47
|
+
def ask_client_secret(prompt)
|
48
|
+
prompt.ask('Client Secret: ') { |q| q.validate(/^[A-Fa-f0-9]+$/, "Invalid client secret") }
|
49
|
+
end
|
50
|
+
|
51
|
+
def ask_client_redirect(prompt)
|
52
|
+
prompt.ask('Redirect URI: ', default: 'https://www.google.com') do |q|
|
53
|
+
proc = Proc.new { |v| URI.regexp =~ v && URI(v).scheme == 'https' }
|
54
|
+
q.validate(proc, 'Invalid HTTPS URI/scheme (must be HTTPS, not localhost)')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def ask_scopes(prompt)
|
59
|
+
msg = 'Select scopes the client is authorized to perform:'
|
60
|
+
choices = %i(identity create read update delete vote guildmaster)
|
61
|
+
scopes = prompt.multi_select(msg, choices, per_page: choices.size) do |q|
|
62
|
+
defaults = (1..choices.size).to_a
|
63
|
+
q.default(*defaults)
|
64
|
+
end
|
65
|
+
scopes.join(',')
|
66
|
+
end
|
67
|
+
|
68
|
+
def login(agent, prompt)
|
69
|
+
agent.get('https://ruqqus.com/login') do |page|
|
70
|
+
page.form_with(action: '/login') do |form|
|
71
|
+
form['username'] = prompt.ask('Username: ') do |q|
|
72
|
+
q.validate(/^[a-zA-Z0-9_]{5,25}$/, 'Invalid username')
|
73
|
+
end
|
74
|
+
form['password'] = prompt.mask("Password: ") do |q|
|
75
|
+
q.validate(/^.{8,100}$/, 'Invalid password')
|
76
|
+
end
|
77
|
+
puts 'Logging into Ruqqus...'
|
78
|
+
end.submit
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
begin
|
84
|
+
RuqqusOAuth.new
|
85
|
+
rescue Interrupt
|
86
|
+
puts
|
87
|
+
# Ignored
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
data/lib/ruqqus.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'rest-client'
|
2
|
+
require 'json'
|
2
3
|
|
3
|
-
require_relative 'ruqqus/
|
4
|
-
require_relative 'ruqqus/
|
5
|
-
require_relative 'ruqqus/
|
6
|
-
require_relative 'ruqqus/
|
7
|
-
require_relative 'ruqqus/user'
|
4
|
+
require_relative 'ruqqus/token'
|
5
|
+
require_relative 'ruqqus/routes'
|
6
|
+
require_relative 'ruqqus/client'
|
7
|
+
require_relative 'ruqqus/types'
|
8
8
|
require_relative 'ruqqus/version'
|
9
9
|
|
10
10
|
##
|
@@ -15,10 +15,6 @@ module Ruqqus
|
|
15
15
|
# The base Ruqqus URL.
|
16
16
|
HOME = 'https://ruqqus.com'.freeze
|
17
17
|
|
18
|
-
##
|
19
|
-
# The Ruqqus API version.
|
20
|
-
API_VERSION = 1
|
21
|
-
|
22
18
|
##
|
23
19
|
# A regular expression used for username validation.
|
24
20
|
VALID_USERNAME = /^[a-zA-Z0-9_]{5,25}$/.freeze
|
@@ -35,91 +31,78 @@ module Ruqqus
|
|
35
31
|
# A regular expression used for post/comment ID validation.
|
36
32
|
VALID_POST = /[A-Za-z0-9]+/.freeze
|
37
33
|
|
34
|
+
##
|
35
|
+
# Captures the ID of a post from a Ruqqus URL
|
36
|
+
POST_REGEX = /\/post\/([A-Za-z0-9]+)\/?.*/.freeze
|
37
|
+
|
38
|
+
##
|
39
|
+
# Captures the ID of a comment from a Ruqqus URL
|
40
|
+
COMMENT_REGEX = /\/post\/.+\/.+\/([A-Za-z0-9]+)\/?/.freeze
|
41
|
+
|
38
42
|
##
|
39
43
|
# Generic error class for exceptions specific to the Ruqqus API.
|
40
44
|
class Error < StandardError
|
41
45
|
end
|
42
46
|
|
43
47
|
##
|
44
|
-
#
|
45
|
-
#
|
46
|
-
# @param username [String] the username of the Ruqqus account to retrieve.
|
48
|
+
# Helper function to automate uploading images to Imgur anonymously and returning the direct image link.
|
47
49
|
#
|
48
|
-
# @
|
50
|
+
# @param client_id [String] an Imgur client ID
|
51
|
+
# @param image_path [String] the path to an image file.
|
52
|
+
# @params opts [Hash] the options hash.
|
53
|
+
# @option opts [String] :title a title to set on the Imgur post
|
54
|
+
# @option opts [String] :description a description to set on the Imgur post
|
49
55
|
#
|
50
|
-
# @
|
51
|
-
# @
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
# @return [String] the direct image link from Imgur.
|
57
|
+
# @note To obtain a free Imgur client ID, visit https://api.imgur.com/oauth2/addclient
|
58
|
+
# @note No authentication is required for anonymous image upload, though rate limiting (very generous) applies.
|
59
|
+
# @see Client.post_create
|
60
|
+
def self.imgur_upload(client_id, image_path, **opts)
|
61
|
+
#noinspection RubyResolve
|
62
|
+
raise(Errno::ENOENT, image_path) unless File.exist?(image_path)
|
63
|
+
raise(ArgumentError, 'client_id cannot be nil or empty') if client_id.nil? || client_id.empty?
|
57
64
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
#
|
65
|
-
# @raise [ArgumentError] when `guild_name` is `nil` or value does match the {VALID_GUILD} regular expression.
|
66
|
-
# @raise [Error] thrown when guild does not exist.
|
67
|
-
def self.guild(guild_name)
|
68
|
-
raise(ArgumentError, 'guild_name cannot be nil') unless guild_name
|
69
|
-
raise(ArgumentError, 'invalid guild name') unless VALID_POST.match?(guild_name)
|
70
|
-
api_get("#{HOME}/api/v1/guild/#{guild_name}", Guild)
|
65
|
+
header = { 'Content-Type': 'application/json', 'Authorization': "Client-ID #{client_id}" }
|
66
|
+
params = { image: File.new(image_path), type: 'file', title: opts[:title], description: opts[:description] }
|
67
|
+
|
68
|
+
response = RestClient.post('https://api.imgur.com/3/upload', params, header)
|
69
|
+
json = JSON.parse(response.body, symbolize_names: true)
|
70
|
+
json[:data][:link]
|
71
71
|
end
|
72
72
|
|
73
73
|
##
|
74
|
-
#
|
75
|
-
#
|
76
|
-
# @param post_id [String] the ID of the post to retrieve.
|
74
|
+
# Checks if the specified guild name is available to be created.
|
77
75
|
#
|
78
|
-
# @
|
76
|
+
# @param guild_name [String] the name of a guild to query.
|
79
77
|
#
|
80
|
-
# @
|
81
|
-
|
82
|
-
|
83
|
-
raise(ArgumentError, 'post_id cannot be nil') unless post_id
|
84
|
-
raise(ArgumentError, 'invalid post ID') unless VALID_POST.match?(post_id)
|
85
|
-
api_get("#{HOME}/api/v1/post/#{post_id}", Post)
|
78
|
+
# @return [Boolean] `true` is name is available, otherwise `false` if it has been reserved or is in use.
|
79
|
+
def self.guild_available?(guild_name)
|
80
|
+
available?(guild_name, VALID_GUILD, "#{Routes::GUILD_AVAILABLE}#{name}")
|
86
81
|
end
|
87
82
|
|
88
83
|
##
|
89
|
-
#
|
84
|
+
# Checks if the specified username is available to be created.
|
90
85
|
#
|
91
|
-
# @param
|
86
|
+
# @param username [String] the name of a user to query.
|
92
87
|
#
|
93
|
-
# @return [
|
94
|
-
|
95
|
-
|
96
|
-
# @raise [Error] when a comment with the specified ID does not exist.
|
97
|
-
def self.comment(comment_id)
|
98
|
-
raise(ArgumentError, 'comment_id cannot be nil') unless comment_id
|
99
|
-
raise(ArgumentError, 'invalid comment ID') unless VALID_POST.match?(comment_id)
|
100
|
-
api_get("#{HOME}/api/v1/comment/#{comment_id}", Comment)
|
88
|
+
# @return [Boolean] `true` is name is available, otherwise `false` if it has been reserved or is in use.
|
89
|
+
def self.username_available?(username)
|
90
|
+
available?(username, VALID_USERNAME, "#{Routes::USERNAME_AVAILABLE}#{name}")
|
101
91
|
end
|
102
92
|
|
93
|
+
private
|
94
|
+
|
103
95
|
##
|
104
|
-
#
|
105
|
-
# Calls the GET method at the specified API route, and returns the deserializes JSON response as an object.
|
106
|
-
#
|
107
|
-
# @param route [String] the full API route to the GET method.
|
108
|
-
# @param klass [Class] a Class instance that is inherited from {ItemBase}.
|
96
|
+
# Checks if the specified guild or user name is available to be created.
|
109
97
|
#
|
110
|
-
# @
|
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.
|
111
101
|
#
|
112
|
-
# @
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
begin
|
118
|
-
response = RestClient.get(route)
|
119
|
-
klass.from_json(response.body)
|
120
|
-
rescue RestClient::BadRequest
|
121
|
-
raise(Error, 'invalid search parameters, object with specified criteria does not exist')
|
122
|
-
end
|
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]
|
123
107
|
end
|
124
|
-
end
|
125
|
-
|
108
|
+
end
|