ruqqus 1.0.0 → 1.1.4
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 +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
@@ -0,0 +1,68 @@
|
|
1
|
+
module Ruqqus
|
2
|
+
|
3
|
+
##
|
4
|
+
# A module containing constants that define the method routes for the Ruqqus REST API.
|
5
|
+
module Routes
|
6
|
+
|
7
|
+
##
|
8
|
+
# The Ruqqus API version.
|
9
|
+
API_VERSION = 1
|
10
|
+
|
11
|
+
##
|
12
|
+
# The top-level site URL.
|
13
|
+
HOME = 'https://ruqqus.com'.freeze
|
14
|
+
|
15
|
+
##
|
16
|
+
# The base URL for the Ruqqus REST API.
|
17
|
+
API_BASE = "#{HOME}/api/v#{API_VERSION}".freeze
|
18
|
+
|
19
|
+
##
|
20
|
+
# The endpoint for the GET method to obtain user information.
|
21
|
+
USER = "#{API_BASE}/user/".freeze
|
22
|
+
|
23
|
+
##
|
24
|
+
# The endpoint for the GET method to obtain guild information.
|
25
|
+
GUILD = "#{API_BASE}/guild/".freeze
|
26
|
+
|
27
|
+
##
|
28
|
+
# The endpoint for the GET method to obtain post information.
|
29
|
+
POST = "#{API_BASE}/post/".freeze
|
30
|
+
|
31
|
+
##
|
32
|
+
# The endpoint for the GET method to obtain comment information.
|
33
|
+
COMMENT = "#{API_BASE}/comment/".freeze
|
34
|
+
|
35
|
+
##
|
36
|
+
# The endpoint for the POST method to place a vote on a post.
|
37
|
+
POST_VOTE = "#{API_BASE}/vote/post/".freeze
|
38
|
+
|
39
|
+
##
|
40
|
+
# The endpoint for the GET method to query guild availability.
|
41
|
+
GUILD_AVAILABLE = "#{HOME}/api/board_available/".freeze
|
42
|
+
|
43
|
+
##
|
44
|
+
# The endpoint for the GET method to query username availability.
|
45
|
+
USERNAME_AVAILABLE = "#{HOME}/api/is_available/".freeze
|
46
|
+
|
47
|
+
##
|
48
|
+
# The endpoint for the POST method to submit a post.
|
49
|
+
SUBMIT = "#{Routes::API_BASE}/submit/".freeze
|
50
|
+
|
51
|
+
##
|
52
|
+
# The endpoint for the GET method to get the current user.
|
53
|
+
IDENTITY = "#{Routes::API_BASE}/identity".freeze
|
54
|
+
|
55
|
+
##
|
56
|
+
# The endpoint for the GET method to get the guild listings.
|
57
|
+
GUILDS = "#{Routes::API_BASE}/guilds".freeze
|
58
|
+
|
59
|
+
##
|
60
|
+
# The endpoint for the GET method to get the front page listings.
|
61
|
+
FRONT_PAGE = "#{Routes::API_BASE}/front/listing".freeze
|
62
|
+
|
63
|
+
##
|
64
|
+
# The endpoint for the GET method to get all post listings.
|
65
|
+
ALL_LISTINGS = "#{Routes::API_BASE}/all/listing".freeze
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
data/lib/ruqqus/token.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
module Ruqqus
|
2
|
+
|
3
|
+
##
|
4
|
+
# Represents a Ruqqus [OAuth2](https://oauth.net/2/) access token.
|
5
|
+
class Token
|
6
|
+
|
7
|
+
##
|
8
|
+
# @!attribute [r] access_token
|
9
|
+
# @return [String] the access token value.
|
10
|
+
|
11
|
+
##
|
12
|
+
# @!attribute [r] refresh_token
|
13
|
+
# @return [String] the refresh token value.
|
14
|
+
|
15
|
+
##
|
16
|
+
# @!attribute [r] expires
|
17
|
+
# @return [Time] the time the token expires and will require a refresh.
|
18
|
+
|
19
|
+
##
|
20
|
+
# @!attribute [r] type
|
21
|
+
# @return [String] the token type to specify in the HTTP header.
|
22
|
+
|
23
|
+
##
|
24
|
+
# @!attribute [r] scopes
|
25
|
+
# @return [Array<Symbol>] an array of scopes this token authorizes.
|
26
|
+
|
27
|
+
##
|
28
|
+
# Grants access to a user account and returns an a newly created {Token} to use as authentication for it.
|
29
|
+
#
|
30
|
+
# @param client_id [String] the ID of client application.
|
31
|
+
# @param client_secret [String] the secret of the client application.
|
32
|
+
# @param code [String] the code received in the redirect response when the user requested API access.
|
33
|
+
# @param persist [Boolean] `true` if token will be reusable, otherwise `false`.
|
34
|
+
#
|
35
|
+
# @return [Token] a newly created {Token} object.
|
36
|
+
def initialize(client_id, client_secret, code, persist = true)
|
37
|
+
headers = { 'User-Agent': Client::USER_AGENT, 'Accept': 'application/json', 'Content-Type': 'application/json' }
|
38
|
+
params = { code: code, client_id: client_id, client_secret: client_secret, grant_type: 'code', permanent: persist }
|
39
|
+
resp = RestClient.post('https://ruqqus.com/oauth/grant', params, headers )
|
40
|
+
@data = JSON.parse(resp.body, symbolize_names: true)
|
41
|
+
|
42
|
+
raise(Ruqqus::Error, 'failed to grant access for token') if @data[:oauth_error]
|
43
|
+
end
|
44
|
+
|
45
|
+
def access_token
|
46
|
+
@data[:access_token]
|
47
|
+
end
|
48
|
+
|
49
|
+
def refresh_token
|
50
|
+
@data[:refresh_token]
|
51
|
+
end
|
52
|
+
|
53
|
+
def type
|
54
|
+
@data[:token_type]
|
55
|
+
end
|
56
|
+
|
57
|
+
def expires
|
58
|
+
Time.at(@data[:expires_at])
|
59
|
+
end
|
60
|
+
|
61
|
+
def scopes
|
62
|
+
@data[:scopes].split(',').map(&:to_sym)
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Refreshes the access token and resets its time of expiration.
|
67
|
+
#
|
68
|
+
# @return [void]
|
69
|
+
def refresh(client_id, client_secret)
|
70
|
+
headers = { 'User-Agent': Client::USER_AGENT, Authorization: "Bearer #{access_token}" }
|
71
|
+
params = { client_id: client_id, client_secret: client_secret, refresh_token: refresh_token, grant_type: 'refresh' }
|
72
|
+
resp = RestClient.post('https://ruqqus.com/oauth/grant', params, headers )
|
73
|
+
|
74
|
+
data = JSON.parse(resp.body, symbolize_names: true)
|
75
|
+
raise(Ruqqus::Error, 'failed to refresh authentication token') unless resp.code == 200 || data[:oauth_error]
|
76
|
+
@data.merge!(data)
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# @return [Boolean] `true` if token is expired, otherwise `false`.
|
81
|
+
def expired?
|
82
|
+
expires <= Time.now
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# @return [String] the object as a JSON-formatted string.
|
87
|
+
def to_json(*_unused_)
|
88
|
+
@data.to_json
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Saves this token in JSON format to the specified file.
|
93
|
+
#
|
94
|
+
# @param filename [String] the path to a file where the token will be written to.
|
95
|
+
# @return [Integer] the number of bytes written.
|
96
|
+
# @note **Security Alert:** The token is essentially the equivalent to login credentials in regards to security,
|
97
|
+
# so it is important to not share or store it somewhere that it can be easily compromised.
|
98
|
+
def save_json(filename)
|
99
|
+
File.open(filename, 'wb') { |io| io.write(to_json) }
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# Loads a token in JSON format from a file.
|
104
|
+
#
|
105
|
+
# @param filename [String] the path to a file where the token is written to.
|
106
|
+
# @return [Token] a newly created {Token} instance.
|
107
|
+
def self.load_json(filename)
|
108
|
+
from_json(File.read(filename))
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Loads the object from a JSON-formatted string.
|
113
|
+
#
|
114
|
+
# @param json [String,Hash] a JSON string representing the object, or the parsed Hash of the JSON (symbol keys).
|
115
|
+
#
|
116
|
+
# @return [Object] the loaded object.
|
117
|
+
def self.from_json(payload)
|
118
|
+
data = payload.is_a?(Hash) ? payload: JSON.parse(payload, symbolize_names: true)
|
119
|
+
token = allocate
|
120
|
+
token.instance_variable_set(:@data, data)
|
121
|
+
token
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/ruqqus/types.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
# Bootstrap script to load all concrete Ruqqus types
|
3
|
+
|
4
|
+
require_relative 'types/item_base'
|
5
|
+
require_relative 'types/submission'
|
6
|
+
require_relative 'types/comment'
|
7
|
+
require_relative 'types/guild'
|
8
|
+
require_relative 'types/post'
|
9
|
+
require_relative 'types/badge'
|
10
|
+
require_relative 'types/title'
|
11
|
+
require_relative 'types/user'
|
File without changes
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
module Ruqqus
|
3
|
+
|
4
|
+
##
|
5
|
+
# Describes a comment in a post.
|
6
|
+
class Comment < Submission
|
7
|
+
|
8
|
+
##
|
9
|
+
# @!attribute [r] level
|
10
|
+
# @return [Integer] the level of "nesting" in the comment tree, starting at `1` when in direct reply to the post.
|
11
|
+
|
12
|
+
##
|
13
|
+
# @!attribute parent_id
|
14
|
+
# @return [String] the unique ID of the parent for this comment.
|
15
|
+
|
16
|
+
##
|
17
|
+
# @!attribute [r] post_id
|
18
|
+
# @return [String] the ID of the post this comment belongs to.
|
19
|
+
|
20
|
+
##
|
21
|
+
# @return [Boolean] `true` if the comment's parent is comment, otherwise `false` if it is a post.
|
22
|
+
def parent_comment?
|
23
|
+
level > 1
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# @return [Boolean] `true` if the comment's parent is post, otherwise `false` if it is a comment.
|
28
|
+
def parent_post?
|
29
|
+
level == 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def level
|
33
|
+
@data[:level]
|
34
|
+
end
|
35
|
+
|
36
|
+
def parent_id
|
37
|
+
@data[:parent]
|
38
|
+
end
|
39
|
+
|
40
|
+
def post_id
|
41
|
+
@data[:post]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -5,28 +5,40 @@ module Ruqqus
|
|
5
5
|
class Guild < ItemBase
|
6
6
|
|
7
7
|
##
|
8
|
-
#
|
9
|
-
|
10
|
-
@data[:name]
|
11
|
-
end
|
8
|
+
# @!attribute [r] name
|
9
|
+
# @return [String] the name of the guild.
|
12
10
|
|
13
11
|
##
|
14
|
-
#
|
15
|
-
|
16
|
-
@data[:subscriber_count]&.to_i || 0
|
17
|
-
end
|
12
|
+
# @!attribute [r] member_count
|
13
|
+
# @return [Integer] the number of members subscribed to the guild.
|
18
14
|
|
19
15
|
##
|
20
|
-
#
|
21
|
-
|
22
|
-
@data[:mods_count]&.to_i || 0
|
23
|
-
end
|
16
|
+
# @!attribute [r] fullname
|
17
|
+
# @return [String] the full ID of the guild.
|
24
18
|
|
25
19
|
##
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
20
|
+
# @!attribute [r] guildmaster_count
|
21
|
+
# @return [Integer] the number of guild masters who moderate this guild.
|
22
|
+
|
23
|
+
##
|
24
|
+
# @!attribute [r] profile_url
|
25
|
+
# @return [String] the URL for the profile image associated with the guild.
|
26
|
+
|
27
|
+
##
|
28
|
+
# @!attribute [r] color
|
29
|
+
# @return [String] the accent color used for the guild, in HTML format.
|
30
|
+
|
31
|
+
##
|
32
|
+
# @!attribute [r] description
|
33
|
+
# @return [String] the description of the guild.
|
34
|
+
|
35
|
+
##
|
36
|
+
# @!attribute [r] description_html
|
37
|
+
# @return [String] the description of the guild in HTML format.
|
38
|
+
|
39
|
+
##
|
40
|
+
# @!attribute [r] banner_url
|
41
|
+
# @return [String] the URL for the banner image associated with the guild.
|
30
42
|
|
31
43
|
##
|
32
44
|
# @return [Boolean] `true` if the guild contains adult content and flagged as NSFW, otherwise `false`.
|
@@ -47,39 +59,45 @@ module Ruqqus
|
|
47
59
|
end
|
48
60
|
|
49
61
|
##
|
50
|
-
# @return [String] the
|
51
|
-
def
|
52
|
-
@data[:
|
62
|
+
# @return [String] the string representation of the object.
|
63
|
+
def to_s
|
64
|
+
@data[:name] || inspect
|
53
65
|
end
|
54
66
|
|
55
|
-
|
56
|
-
|
57
|
-
def description_html
|
58
|
-
@data[:description_html]
|
67
|
+
def description
|
68
|
+
@data[:description]
|
59
69
|
end
|
60
70
|
|
61
|
-
##
|
62
|
-
# @return [String] the URL for the banner image associated with the guild.
|
63
71
|
def banner_url
|
64
72
|
@data[:banner_url]
|
65
73
|
end
|
66
74
|
|
67
|
-
|
68
|
-
|
75
|
+
def description_html
|
76
|
+
@data[:description_html]
|
77
|
+
end
|
78
|
+
|
69
79
|
def profile_url
|
70
80
|
@data[:profile_url]
|
71
81
|
end
|
72
82
|
|
73
|
-
##
|
74
|
-
# @return [String] the accent color used for the guild, in HTML format.
|
75
83
|
def color
|
76
84
|
@data[:color]
|
77
85
|
end
|
78
86
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
87
|
+
def name
|
88
|
+
@data[:name]
|
89
|
+
end
|
90
|
+
|
91
|
+
def member_count
|
92
|
+
@data[:subscriber_count]&.to_i || 0
|
93
|
+
end
|
94
|
+
|
95
|
+
def guildmaster_count
|
96
|
+
@data[:mods_count]&.to_i || 0
|
97
|
+
end
|
98
|
+
|
99
|
+
def fullname
|
100
|
+
@data[:fullname]
|
83
101
|
end
|
84
102
|
end
|
85
103
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'json'
|
2
1
|
|
3
2
|
module Ruqqus
|
4
3
|
|
@@ -7,6 +6,22 @@ module Ruqqus
|
|
7
6
|
# Base class for all all major API types.
|
8
7
|
class ItemBase
|
9
8
|
|
9
|
+
##
|
10
|
+
# @!attribute [r] permalink
|
11
|
+
# @return [String] a relative link to this item.
|
12
|
+
|
13
|
+
##
|
14
|
+
# @!attribute [r] created_utc
|
15
|
+
# @return [Integer] the time the item was created, in seconds since the Unix epoch.
|
16
|
+
|
17
|
+
##
|
18
|
+
# @!attribute [r] created
|
19
|
+
# @return [Time] the time the item was created.
|
20
|
+
|
21
|
+
##
|
22
|
+
# @!attribute [r] id
|
23
|
+
# @return [String] a unique ID for this item.
|
24
|
+
|
10
25
|
##
|
11
26
|
# @return [Boolean] `true` if item has been banned, otherwise `false`.
|
12
27
|
def banned?
|
@@ -14,31 +29,23 @@ module Ruqqus
|
|
14
29
|
end
|
15
30
|
|
16
31
|
##
|
17
|
-
# @return [
|
32
|
+
# @return [Boolean] `true` if this object is equal to another, otherwise `false`.
|
33
|
+
def ==(other)
|
34
|
+
self.class == other.class && id == other.id
|
35
|
+
end
|
36
|
+
|
18
37
|
def created_utc
|
19
38
|
@data[:created_utc]
|
20
39
|
end
|
21
40
|
|
22
|
-
##
|
23
|
-
# @return [Time] the time the item was created.
|
24
41
|
def created
|
25
42
|
Time.at(created_utc)
|
26
43
|
end
|
27
44
|
|
28
|
-
##
|
29
|
-
# @return [String] a unique ID for this item.
|
30
45
|
def id
|
31
46
|
@data[:id]
|
32
47
|
end
|
33
48
|
|
34
|
-
##
|
35
|
-
# @return [Boolean] `true` if this object is equal to another, otherwise `false`.
|
36
|
-
def ==(other)
|
37
|
-
self.class == other.class && id == other.id
|
38
|
-
end
|
39
|
-
|
40
|
-
##
|
41
|
-
# @return [String] a relative link to this item.
|
42
49
|
def permalink
|
43
50
|
@data[:permalink]
|
44
51
|
end
|
@@ -46,10 +53,13 @@ module Ruqqus
|
|
46
53
|
##
|
47
54
|
# Loads the object from a JSON-formatted string.
|
48
55
|
#
|
56
|
+
# @param json [String,Hash] a JSON string representing the object.
|
57
|
+
#
|
49
58
|
# @return [Object] the loaded object.
|
50
59
|
def self.from_json(json)
|
51
60
|
obj = allocate
|
52
|
-
|
61
|
+
data = json.is_a?(Hash) ? json : JSON.parse(json, symbolize_names: true)
|
62
|
+
obj.instance_variable_set(:@data, data)
|
53
63
|
obj
|
54
64
|
end
|
55
65
|
|