ruqqus 0.1.0 → 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 +4 -4
- data/.gitignore +167 -4
- data/.yardopts +6 -0
- data/CHANGELOG.md +49 -3
- data/Gemfile +2 -5
- data/README.md +241 -8
- data/Rakefile +2 -7
- data/TODO.md +25 -0
- data/exe/ruqqus-oauth +98 -0
- data/lib/ruqqus.rb +273 -2
- data/lib/ruqqus/client.rb +563 -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/types/badge.rb +61 -0
- data/lib/ruqqus/types/comment.rb +44 -0
- data/lib/ruqqus/types/guild.rb +103 -0
- data/lib/ruqqus/types/item_base.rb +68 -0
- data/lib/ruqqus/types/post.rb +70 -0
- data/lib/ruqqus/types/submission.rb +139 -0
- data/lib/ruqqus/types/title.rb +48 -0
- data/lib/ruqqus/types/user.rb +118 -0
- data/lib/ruqqus/version.rb +14 -1
- data/ruqqus.gemspec +19 -7
- metadata +73 -9
@@ -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'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Ruqqus
|
2
|
+
##
|
3
|
+
# Describes a trophy that can be earned/issued to an account for specific accomplishments.
|
4
|
+
class Badge
|
5
|
+
|
6
|
+
##
|
7
|
+
# @!attribute [r] name
|
8
|
+
# @return [String?] the name of the badge.
|
9
|
+
|
10
|
+
##
|
11
|
+
# @!attribute [r] text
|
12
|
+
# @return [String?] a brief description of the badge.
|
13
|
+
|
14
|
+
##
|
15
|
+
# @!attribute [r] url
|
16
|
+
# @return [String?] the URL associated with the badge, or `nil` if not defined.
|
17
|
+
|
18
|
+
##
|
19
|
+
# @!attribute [r] created_utc
|
20
|
+
# @return [Integer?] the time the badge was earned in seconds since the Unix epoch, or `0` if not defined.
|
21
|
+
|
22
|
+
##
|
23
|
+
# @!attribute [r] created
|
24
|
+
# @return [Time?] the time the badge was earned, or `nil` if not defined.
|
25
|
+
|
26
|
+
##
|
27
|
+
# Creates a new instance of the {Badge} class.
|
28
|
+
#
|
29
|
+
# @param data [Hash] the parsed JSON payload defining this instance.
|
30
|
+
def initialize(data)
|
31
|
+
@data = data || raise(ArgumentError, 'data cannot be nil')
|
32
|
+
end
|
33
|
+
|
34
|
+
def name
|
35
|
+
@data[:name]
|
36
|
+
end
|
37
|
+
|
38
|
+
def text
|
39
|
+
@data[:text]
|
40
|
+
end
|
41
|
+
|
42
|
+
def url
|
43
|
+
@data[:url]
|
44
|
+
end
|
45
|
+
|
46
|
+
def created_utc
|
47
|
+
@data[:created_utc]
|
48
|
+
end
|
49
|
+
|
50
|
+
def created
|
51
|
+
#noinspection RubyYardReturnMatch
|
52
|
+
@data[:created_utc] ? Time.at(@data[:created_utc]) : nil
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# @return [String] the string representation of the object.
|
57
|
+
def to_s
|
58
|
+
@data[:text] || inspect
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -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
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Ruqqus
|
2
|
+
|
3
|
+
##
|
4
|
+
# Represents a Ruqqus guild.
|
5
|
+
class Guild < ItemBase
|
6
|
+
|
7
|
+
##
|
8
|
+
# @!attribute [r] name
|
9
|
+
# @return [String] the name of the guild.
|
10
|
+
|
11
|
+
##
|
12
|
+
# @!attribute [r] member_count
|
13
|
+
# @return [Integer] the number of members subscribed to the guild.
|
14
|
+
|
15
|
+
##
|
16
|
+
# @!attribute [r] fullname
|
17
|
+
# @return [String] the full ID of the guild.
|
18
|
+
|
19
|
+
##
|
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.
|
42
|
+
|
43
|
+
##
|
44
|
+
# @return [Boolean] `true` if the guild contains adult content and flagged as NSFW, otherwise `false`.
|
45
|
+
def nsfw?
|
46
|
+
@data[:over_18]
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# @return [Boolean] `true` if guild is private and required membership to view content, otherwise `false`.
|
51
|
+
def private?
|
52
|
+
!!@data[:is_private]
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# @return [Boolean] `true` if posting is restricted byy guild masters, otherwise `false`.
|
57
|
+
def restricted?
|
58
|
+
!!@data[:is_restricted]
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# @return [String] the string representation of the object.
|
63
|
+
def to_s
|
64
|
+
@data[:name] || inspect
|
65
|
+
end
|
66
|
+
|
67
|
+
def description
|
68
|
+
@data[:description]
|
69
|
+
end
|
70
|
+
|
71
|
+
def banner_url
|
72
|
+
@data[:banner_url]
|
73
|
+
end
|
74
|
+
|
75
|
+
def description_html
|
76
|
+
@data[:description_html]
|
77
|
+
end
|
78
|
+
|
79
|
+
def profile_url
|
80
|
+
@data[:profile_url]
|
81
|
+
end
|
82
|
+
|
83
|
+
def color
|
84
|
+
@data[:color]
|
85
|
+
end
|
86
|
+
|
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]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
module Ruqqus
|
3
|
+
|
4
|
+
##
|
5
|
+
# @abstract
|
6
|
+
# Base class for all all major API types.
|
7
|
+
class ItemBase
|
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
|
+
|
25
|
+
##
|
26
|
+
# @return [Boolean] `true` if item has been banned, otherwise `false`.
|
27
|
+
def banned?
|
28
|
+
!!@data[:is_banned]
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
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
|
+
|
37
|
+
def created_utc
|
38
|
+
@data[:created_utc]
|
39
|
+
end
|
40
|
+
|
41
|
+
def created
|
42
|
+
Time.at(created_utc)
|
43
|
+
end
|
44
|
+
|
45
|
+
def id
|
46
|
+
@data[:id]
|
47
|
+
end
|
48
|
+
|
49
|
+
def permalink
|
50
|
+
@data[:permalink]
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Loads the object from a JSON-formatted string.
|
55
|
+
#
|
56
|
+
# @param json [String,Hash] a JSON string representing the object.
|
57
|
+
#
|
58
|
+
# @return [Object] the loaded object.
|
59
|
+
def self.from_json(json)
|
60
|
+
obj = allocate
|
61
|
+
data = json.is_a?(Hash) ? json : JSON.parse(json, symbolize_names: true)
|
62
|
+
obj.instance_variable_set(:@data, data)
|
63
|
+
obj
|
64
|
+
end
|
65
|
+
|
66
|
+
private_class_method :new
|
67
|
+
end
|
68
|
+
end
|