ruqqus 0.1.0 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|