ruqqus 0.1.0 → 1.1.3

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