ruqqus 1.0.0 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -1
- data/CHANGELOG.md +48 -1
- data/Gemfile +1 -1
- data/README.md +160 -23
- data/Rakefile +1 -2
- data/TODO.md +25 -0
- data/exe/ruqqus-oauth +98 -0
- data/lib/ruqqus.rb +217 -64
- data/lib/ruqqus/client.rb +571 -0
- data/lib/ruqqus/routes.rb +68 -0
- data/lib/ruqqus/token.rb +124 -0
- data/lib/ruqqus/types.rb +11 -0
- data/lib/ruqqus/{badge.rb → types/badge.rb} +0 -0
- data/lib/ruqqus/types/comment.rb +44 -0
- data/lib/ruqqus/{guild.rb → types/guild.rb} +51 -33
- data/lib/ruqqus/{item_base.rb → types/item_base.rb} +25 -15
- data/lib/ruqqus/types/post.rb +70 -0
- data/lib/ruqqus/{submission.rb → types/submission.rb} +66 -59
- data/lib/ruqqus/{title.rb → types/title.rb} +0 -0
- data/lib/ruqqus/types/user.rb +118 -0
- data/lib/ruqqus/version.rb +7 -3
- data/ruqqus.gemspec +6 -2
- metadata +50 -16
- data/lib/ruqqus/comment.rb +0 -61
- data/lib/ruqqus/post.rb +0 -85
- data/lib/ruqqus/user.rb +0 -96
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ddc434cd09ab2ab6f3c4460bd6d309e4311af4c6349e5fba9cdb89cc257133ab
|
4
|
+
data.tar.gz: 47f92d628a870e88ef893dea295c86e4bfd9b770b7fafa23caf988ba8bd3c798
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c01173a089510e8deb760dcab13144cfa5e5678ddfa5a2e136066c0de3d61cb192b30eadb461d28de588ba711c6ebe66d1c7b92fd83a8e2dff3694d51fb58611
|
7
|
+
data.tar.gz: 05ac950bf17799b45cfef60b3734f432c05906ab7caf8adf8b7e14898b53e46027284a671b9db32a70f45500382ff44dbec84e7b5fd12932ff76351e86d245ee
|
data/.gitignore
CHANGED
@@ -164,4 +164,8 @@ 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
|
171
|
+
/test.rb
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,53 @@
|
|
2
2
|
|
3
3
|
Documentation for library API changes.
|
4
4
|
|
5
|
-
|
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
|
+
|
19
|
+
## Version 1.1.2
|
20
|
+
|
21
|
+
* Implemented enumerating comments of guilds and posts
|
22
|
+
|
23
|
+
## Version 1.1.1
|
24
|
+
|
25
|
+
* BUGFIX: Added acceptance of variable args to `Token#to_json`
|
26
|
+
* BUGFIX: Fixed regex validator in `ruqqus-oauth` for the client ID
|
27
|
+
* Separated the client ID/secret from the `Token` class, and placed within `Client` to now handle this logic
|
28
|
+
|
29
|
+
## Version 1.1.0
|
30
|
+
|
31
|
+
* Implemented `Ruqqus::Token` class to handle OAuth2 authentication
|
32
|
+
* Added `Ruqqus::Client` class as the primary object for API usage
|
33
|
+
* Implemented post creation
|
34
|
+
* Implemented comment creation and deletion
|
35
|
+
* Implementing querying for reserved usernames/guilds
|
36
|
+
* Implemented retrieving posts of guilds
|
37
|
+
* Implemented retrieving posts and comments of users
|
38
|
+
* Implemented voting on posts and comments as a user
|
39
|
+
* Implemented enumerating all guilds
|
40
|
+
* Implemented enumerating through all posts
|
41
|
+
* Refactored `Ruqqus.user_info` to `Ruqqus::Client#user`
|
42
|
+
* Refactored `Ruqqus.guild_info` to `Ruqqus::Client#guild`
|
43
|
+
* Refactored `Ruqqus.post_info` to `Ruqqus::Client#post`
|
44
|
+
* Refactored `Ruqqus.comment_info` to `Ruqqus::Client#comment`
|
45
|
+
* Many improvements in code and better adhesion to DRY principles
|
46
|
+
* Added helper method to automate uploading and creating image posts on Ruqqus via Imgur
|
47
|
+
|
48
|
+
## Version 1.0.1
|
49
|
+
|
50
|
+
* Changed validation for `fomr_url` methods in `Post` and `Comment` to also accept relative URIs.
|
51
|
+
|
52
|
+
## Version 1.0.0
|
6
53
|
|
7
54
|
* 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,156 @@ 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 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(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)
|
75
|
+
```
|
76
|
+
|
77
|
+
The token will automatically refresh itself as-needed, but you will need to handle storing its new value for repeated
|
78
|
+
uses. To facilitate this and make it easier, there is a callback that can be subscribed to which will be called each
|
79
|
+
time the access key is updated.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# Load an existing token that has already been authorized
|
83
|
+
token = Ruqqus::Token.load_json('./token.json')
|
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|
|
90
|
+
t.save_json('./token.json')
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
The token obtains sensitive material, and due to the security issues of storing it in plain text, this functionality is
|
95
|
+
left to the user. The token is essentially the equivalent of your user credentials for Ruqqus, so bear that in mind how
|
96
|
+
and where you store this information so that it is not compromised.
|
97
|
+
|
34
98
|
## Usage
|
35
99
|
|
36
|
-
|
100
|
+
See the [documentation](https://www.rubydoc.info/gems/ruqqus) for a complete API reference (100% coverage).
|
101
|
+
|
102
|
+
### Features
|
103
|
+
|
104
|
+
The bulk of the API is obviously related to performing actions as a user, such as posting, commenting, voting, etc.. The
|
105
|
+
following highlights some of these features.
|
106
|
+
|
107
|
+
* Vote on posts and comments
|
108
|
+
* Create posts (text/link/image)
|
109
|
+
* Automated image upload via anonymous upload to Imgur ([API Key](https://imgur.com/account/settings/apps) required)
|
110
|
+
* Create/edit/delete comments
|
111
|
+
* Enumerate all existing guilds
|
112
|
+
* Enumerate all posts in a guild
|
113
|
+
* Enumerate all posts on the "front page", "all", etc., with sorting and filtering
|
114
|
+
* Enumerate all posts/comments of users (excluding private/banned/blocked accounts)
|
115
|
+
|
116
|
+
#### Misc. Examples
|
117
|
+
|
118
|
+
Some random examples displaying the ease in performing various operations.
|
119
|
+
|
120
|
+
##### Let's do some voting!
|
121
|
+
```ruby
|
122
|
+
client.each_user_post('captainmeta4') do |post|
|
123
|
+
# Upvote our fearless leader's posts
|
124
|
+
client.vote_post(post, 1)
|
125
|
+
end
|
126
|
+
|
127
|
+
client.each_user_comment('captainmeta4') do |comment|
|
128
|
+
# ...and his comments.
|
129
|
+
client.vote_comment(comment, 1)
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
##### Monitor For New Posts
|
134
|
+
```ruby
|
135
|
+
delay = 10
|
136
|
+
puts 'Watching for new posts, press Ctrl+C to quit'
|
137
|
+
loop do
|
138
|
+
time = Time.now
|
139
|
+
sleep(delay)
|
140
|
+
|
141
|
+
puts "Checking the front page for new posts since #{time}"
|
142
|
+
client.each_post(sort: :new, filter: :all) do |post|
|
143
|
+
# Stop checking post once we encounter one older than this iteration
|
144
|
+
break if post.created < time
|
145
|
+
# Do something about this new post showing up in All
|
146
|
+
puts "Found a new post: '#{post.title}'"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
```
|
150
|
+
##### Download all images from a guild
|
151
|
+
```ruby
|
152
|
+
require 'open-uri'
|
153
|
+
domains = %w[i.ruqqus.com i.imgur.com]
|
154
|
+
|
155
|
+
Dir.mkdir('./tree_pics') unless Dir.exist?('./tree_pics')
|
156
|
+
client.each_guild_post('Trees', sort: :new) do |post|
|
157
|
+
next unless domains.include?(post.domain)
|
158
|
+
ext = File.extname(post.url)
|
159
|
+
ext = '.jpg' if ext.empty? # We don't care, just an example
|
160
|
+
path = File.join('./tree_pics', post.id + ext)
|
161
|
+
URI.open(post.url) { |src| File.open(path, 'wb') { |dst| dst.write(src.read) } }
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
165
|
+
### Types
|
166
|
+
|
167
|
+
The Ruqqus API exposes four primary types:
|
37
168
|
|
38
169
|
* Users
|
39
170
|
* Guilds
|
40
171
|
* Posts
|
41
172
|
* Comments
|
42
173
|
|
43
|
-
|
174
|
+
Nearly all client operations interact with one or more of these entities in some way. Each of these types also has an
|
175
|
+
`#id` property that can be used with other related API functions, such as voting, replying, deleting, etc. The full
|
176
|
+
documentation listing all properties they obtain can be found [here](https://www.rubydoc.info/gems/ruqqus), but the API
|
177
|
+
is rather intuitive. Here are some samples of their basic
|
178
|
+
usage.
|
44
179
|
|
45
|
-
|
180
|
+
#### Users
|
46
181
|
|
47
182
|
Obtain information about users.
|
48
183
|
|
49
184
|
```ruby
|
50
|
-
user =
|
185
|
+
user = client.user_info('foreverzer0')
|
51
186
|
|
52
187
|
# Get user's total rep (as well as separate for comments/posts)
|
53
188
|
user.total_rep
|
@@ -62,12 +197,12 @@ user.created
|
|
62
197
|
#=> 2020-06-16 21:59:04 -0400
|
63
198
|
```
|
64
199
|
|
65
|
-
|
200
|
+
#### Guilds
|
66
201
|
|
67
202
|
Obtain information about guilds.
|
68
203
|
|
69
204
|
```ruby
|
70
|
-
guild =
|
205
|
+
guild = client.guild('Ruby')
|
71
206
|
|
72
207
|
# Query the number of members, description, accent color, etc.
|
73
208
|
guild.member_count
|
@@ -82,14 +217,16 @@ guild.nsfw?
|
|
82
217
|
#=> false
|
83
218
|
```
|
84
219
|
|
85
|
-
|
220
|
+
#### Posts
|
86
221
|
|
87
|
-
Obtain information about posts.
|
222
|
+
Obtain information about posts.
|
88
223
|
|
89
224
|
```ruby
|
90
|
-
|
91
|
-
#
|
92
|
-
|
225
|
+
# Post IDs can be found within any link to a post.
|
226
|
+
# https://ruqqus.com/post/<POST ID>/<POST TITLE>
|
227
|
+
|
228
|
+
post_id = '2e0x'
|
229
|
+
post = client.post(post_id)
|
93
230
|
|
94
231
|
# Obtain relevant information pertaining the guilds on Ruqqus
|
95
232
|
|
@@ -106,27 +243,27 @@ post.score
|
|
106
243
|
#=> 10
|
107
244
|
```
|
108
245
|
|
109
|
-
|
246
|
+
#### Comments
|
110
247
|
|
111
248
|
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
249
|
|
113
250
|
```ruby
|
114
|
-
|
115
|
-
#
|
116
|
-
comment = Ruqqus::Comment.from_url('https://ruqqus.com/post/1wbo/hi-im-josh-roehl-singer-and/67mt')
|
251
|
+
# Post IDs can be found within any link to a post.
|
252
|
+
# https://ruqqus.com/post/<POST ID>/<POST TITLE>/<COMMENT ID>
|
117
253
|
|
118
|
-
|
254
|
+
comment_id = '67mt'
|
255
|
+
comment = client.comment(comment_id)
|
256
|
+
|
257
|
+
client.post(comment.post).title
|
119
258
|
#=> "Hi. I'm Josh Roehl, singer and songwriter of the hit song \"Endless Summer\". I am hosting an AMA here."
|
120
259
|
|
121
260
|
comment.body
|
122
261
|
#=> "I'm fully aware that I'm not a very good singer. Let's call it half-singing, half-rapping."
|
123
262
|
|
124
|
-
comment.
|
263
|
+
client.user(comment.author_name).ban_reason
|
125
264
|
#=> "Spam"
|
126
265
|
```
|
127
266
|
|
128
|
-
[Documentation](https://www.rubydoc.info/gems/ruqqus)
|
129
|
-
|
130
267
|
## Contributing
|
131
268
|
|
132
269
|
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,25 @@
|
|
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
|
+
* Embed comment/posts API
|
12
|
+
|
13
|
+
# Missing API features
|
14
|
+
|
15
|
+
Some API features that would be beneficial to have implemented
|
16
|
+
|
17
|
+
* Flagging
|
18
|
+
* Post deletion/edit (!)
|
19
|
+
* Notifications (!)
|
20
|
+
* NSFW/NSFW toggling
|
21
|
+
* Kicking/Yanking
|
22
|
+
* User Settings
|
23
|
+
* Guild Settings
|
24
|
+
* Guild join/leave
|
25
|
+
* 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-Za-z0-9_-]+$/, "Invalid client ID") }
|
45
|
+
end
|
46
|
+
|
47
|
+
def ask_client_secret(prompt)
|
48
|
+
prompt.ask('Client Secret: ') { |q| q.validate(/^[A-Za-z0-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
|
+
|