ruqqus 1.0.0 → 1.1.0

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,148 @@
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
+
41
+ @client_id = client_id
42
+ @client_secret = client_secret
43
+ @data = JSON.parse(resp.body, symbolize_names: true)
44
+
45
+ raise(Ruqqus::Error, 'failed to grant access for token') if @data[:oauth_error]
46
+ end
47
+
48
+
49
+ def access_token
50
+ @data[:access_token]
51
+ end
52
+
53
+ def refresh_token
54
+ @data[:refresh_token]
55
+ end
56
+
57
+ def type
58
+ @data[:token_type]
59
+ end
60
+
61
+ def expires
62
+ Time.at(@data[:expires_at])
63
+ end
64
+
65
+ def scopes
66
+ @data[:scopes].split(',').map(&:to_sym)
67
+ end
68
+
69
+ ##
70
+ # Refreshes the access token and resets its time of expiration.
71
+ #
72
+ # @return [void]
73
+ def refresh
74
+ headers = { 'User-Agent': Client::USER_AGENT, Authorization: "Bearer #{access_token}" }
75
+ params = { client_id: @client_id, client_secret: @client_secret, refresh_token: refresh_token, grant_type: 'refresh' }
76
+ resp = RestClient.post('https://ruqqus.com/oauth/grant', params, headers )
77
+
78
+ data = JSON.parse(resp.body, symbolize_names: true)
79
+ raise(Ruqqus::Error, 'failed to refresh authentication token') unless resp.code == 200 || data[:oauth_error]
80
+ @data.merge!(data)
81
+ @refreshed&.call(self)
82
+ end
83
+
84
+ ##
85
+ # Sets a callback block that will be invoked when the token is refresh. This can be used to automate saving the
86
+ # token after its gets updated.
87
+ #
88
+ # @yieldparam token [Token] yields the token to the block.
89
+ #
90
+ # @return [self]
91
+ #
92
+ # @example Auto-save updated token
93
+ # token = Token.load_json('./token.json')
94
+ # token.on_refresh { |t| t.save_json('./token.json') }
95
+ def on_refresh(&block)
96
+ raise(LocalJumpError, "block required") unless block_given?
97
+ @refreshed = block
98
+ self
99
+ end
100
+
101
+ ##
102
+ # @return [Boolean] `true` if token is expired, otherwise `false`.
103
+ def expired?
104
+ expires <= Time.now
105
+ end
106
+
107
+ ##
108
+ # @return [String] the object as a JSON-formatted string.
109
+ def to_json
110
+ { client_id: @client_id, client_secret: @client_secret, data: @data }.to_json
111
+ end
112
+
113
+ ##
114
+ # Saves this token in JSON format to the specified file.
115
+ #
116
+ # @param filename [String] the path to a file where the token will be written to.
117
+ # @return [Integer] the number of bytes written.
118
+ # @note **Security Alert:** The token is essentially the equivalent to login credentials in regards to security,
119
+ # so it is important to not share or store it somewhere that it can be easily compromised.
120
+ def save_json(filename)
121
+ File.open(filename, 'wb') { |io| io.write(to_json) }
122
+ end
123
+
124
+ ##
125
+ # Loads a token in JSON format from a file.
126
+ #
127
+ # @param filename [String] the path to a file where the token is written to.
128
+ # @return [Token] a newly created {Token} instance.
129
+ def self.load_json(filename)
130
+ from_json(File.read(filename))
131
+ end
132
+
133
+ ##
134
+ # Loads the object from a JSON-formatted string.
135
+ #
136
+ # @param json [String] a JSON string representing the object.
137
+ #
138
+ # @return [Object] the loaded object.
139
+ def self.from_json(payload)
140
+ data = JSON.parse(payload, symbolize_names: true)
141
+ token = allocate
142
+ token.instance_variable_set(:@client_id, data[:client_id])
143
+ token.instance_variable_set(:@client_secret, data[:client_secret])
144
+ token.instance_variable_set(:@data, data[:data])
145
+ token
146
+ end
147
+ end
148
+ 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'
File without changes
@@ -0,0 +1,38 @@
1
+
2
+ module Ruqqus
3
+
4
+ ##
5
+ # Describes a comment in a post.
6
+ class Comment < Submission
7
+
8
+ ##
9
+ # @return [Integer] the level of "nesting" in the comment tree, starting at `1` when in direct reply to the post.
10
+ def level
11
+ @data[:level]
12
+ end
13
+
14
+ ##
15
+ # @return [String] the unique ID of the parent for this comment.
16
+ def parent_id
17
+ @data[:parent]
18
+ end
19
+
20
+ ##
21
+ # @return [Boolean] `true` if {#parent_id} refers to a comment, otherwise `false` if a post.
22
+ def parent_comment?
23
+ level > 1
24
+ end
25
+
26
+ ##
27
+ # @return [Boolean] `true` if {#parent_id} refers to a post, otherwise `false` if a comment.
28
+ def parent_post?
29
+ level == 1
30
+ end
31
+
32
+ ##
33
+ # @return [String] the ID of the post this comment belongs to.
34
+ def post_id
35
+ @data[:post]
36
+ end
37
+ end
38
+ end
File without changes
@@ -1,4 +1,3 @@
1
- require 'json'
2
1
 
3
2
  module Ruqqus
4
3
 
@@ -46,10 +45,13 @@ module Ruqqus
46
45
  ##
47
46
  # Loads the object from a JSON-formatted string.
48
47
  #
48
+ # @param json [String,Hash] a JSON string representing the object.
49
+ #
49
50
  # @return [Object] the loaded object.
50
51
  def self.from_json(json)
51
52
  obj = allocate
52
- obj.instance_variable_set(:@data, JSON.parse(json, symbolize_names: true))
53
+ data = json.is_a?(Hash) ? json : JSON.parse(json, symbolize_names: true)
54
+ obj.instance_variable_set(:@data, data)
53
55
  obj
54
56
  end
55
57
 
@@ -1,4 +1,3 @@
1
- require_relative 'submission'
2
1
 
3
2
  module Ruqqus
4
3
 
@@ -6,10 +5,6 @@ module Ruqqus
6
5
  # Represents a post on Ruqqus.
7
6
  class Post < Submission
8
7
 
9
- ##
10
- # Captures the ID of a post from a Ruqqus URL
11
- POST_REGEX = /ruqqus.com\/post\/([A-Za-z0-9]+)\/?.*/.freeze
12
-
13
8
  ##
14
9
  # @return [Title?] the title assigned to the author, or `nil` if none is defined.
15
10
  def author_title
@@ -41,12 +36,6 @@ module Ruqqus
41
36
  @data[:original_guild_name]
42
37
  end
43
38
 
44
- ##
45
- # @return [Guild] the guild this post was originally posted in.
46
- def original_guild
47
- @original_guild ||= Ruqqus.guild(original_guild_name)
48
- end
49
-
50
39
  ##
51
40
  # @return [String?] the URL of the post's thumbnail image, or `nil` if none exists.
52
41
  def thumb_url
@@ -59,27 +48,5 @@ module Ruqqus
59
48
  #noinspection RubyYardReturnMatch
60
49
  @data[:url]&.empty? ? nil : @data[:url]
61
50
  end
62
-
63
- ##
64
- # @return [String] the string representation of the object.
65
- def to_s
66
- @data[:title] || inspect
67
- end
68
-
69
- ##
70
- # Creates a new {Post} instance from the specified URL.
71
- #
72
- # @param url [String] a URL link to a post.
73
- #
74
- # @return [Post] the {Post} instance the URL links to.
75
- #
76
- # @raise [ArgumentError] then `url` is `nil`.
77
- # @raise [Ruqqus::Error] when the link is not for a Ruqqus post.
78
- def self.from_url(url)
79
- raise(ArgumentError, 'url cannot be nil') unless url
80
- match = POST_REGEX.match(url)
81
- raise(ArgumentError, 'invalid URL for a post') unless match
82
- Ruqqus.post($1)
83
- end
84
51
  end
85
52
  end
@@ -1,4 +1,3 @@
1
- require_relative 'item_base'
2
1
 
3
2
  module Ruqqus
4
3
 
@@ -13,13 +12,6 @@ module Ruqqus
13
12
  @data[:author]
14
13
  end
15
14
 
16
- ##
17
- # @return [String] the creator of the item, or `nil` if deleted account.
18
- def author
19
- #noinspection RubyYardReturnMatch
20
- @author ||= author_name ? Ruqqus.user(author_name) : nil
21
- end
22
-
23
15
  ##
24
16
  # @return [String] the text body of the item.
25
17
  def body
@@ -111,22 +103,15 @@ module Ruqqus
111
103
  end
112
104
 
113
105
  ##
114
- # @return [Guild?] the guild this item is contained within.
115
- def guild
116
- #noinspection RubyYardReturnMatch
117
- @guild ||= guild_name ? Ruqqus.guild(guild_name) : nil
106
+ # @return [String] the name/title of this item.
107
+ def title
108
+ @data[:title]
118
109
  end
119
110
 
120
111
  ##
121
- # @return [String] a unique ID associated with this item.
122
- def id
112
+ # @return [String] the string representation of the object.
113
+ def to_s
123
114
  @data[:id]
124
115
  end
125
-
126
- ##
127
- # @return [String] the name/title of this item.
128
- def title
129
- @data[:title]
130
- end
131
116
  end
132
117
  end
File without changes
@@ -1,6 +1,3 @@
1
- require_relative 'badge'
2
- require_relative 'title'
3
- require_relative 'item_base'
4
1
 
5
2
  module Ruqqus
6
3
 
@@ -2,7 +2,7 @@ module Ruqqus
2
2
 
3
3
  ##
4
4
  # The Ruqqus gem version.
5
- VERSION = '1.0.0'.freeze
5
+ VERSION = '1.1.0'.freeze
6
6
 
7
7
  ##
8
8
  # Lulz
@@ -7,8 +7,8 @@ Gem::Specification.new do |spec|
7
7
  spec.version = Ruqqus::VERSION
8
8
  spec.authors = ['ForeverZer0']
9
9
  spec.email = ['efreed09@gmail.com']
10
- spec.summary = %q{A Ruby API implementation for Ruqqus, an open-source platform for online communities, free of censorship and moderator abuse by design.}
11
- spec.description = %q{A Ruby API implementation for Ruqqus, an open-source platform for online communities, free of censorship and moderator abuse by design. 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.}
10
+ spec.summary = %q{A Ruby API implementation for Ruqqus, an open-source platform for online communities.}
11
+ spec.description = %q{A Ruby API implementation for Ruqqus, an open-source platform for online communities, free of censorship and moderator abuse by design.}
12
12
  spec.homepage = 'https://github.com/ForeverZer0/ruqqus'
13
13
  spec.license = 'MIT'
14
14
  spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
@@ -35,6 +35,8 @@ Gem::Specification.new do |spec|
35
35
  # Dependencies
36
36
  spec.add_runtime_dependency('rest-client', '~> 2.1')
37
37
 
38
+ spec.add_development_dependency('mechanize', '~> 2.7')
38
39
  spec.add_development_dependency('rake', '~> 13.0')
40
+ spec.add_development_dependency('tty-prompt', '~> 0.22')
39
41
  spec.add_development_dependency('yard', '~> 0.9')
40
42
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruqqus
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ForeverZer0
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-28 00:00:00.000000000 Z
11
+ date: 2020-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mechanize
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.7'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -38,6 +52,20 @@ dependencies:
38
52
  - - "~>"
39
53
  - !ruby/object:Gem::Version
40
54
  version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-prompt
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.22'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.22'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: yard
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -53,12 +81,11 @@ dependencies:
53
81
  - !ruby/object:Gem::Version
54
82
  version: '0.9'
55
83
  description: A Ruby API implementation for Ruqqus, an open-source platform for online
56
- communities, free of censorship and moderator abuse by design. While platform is
57
- still in Beta at this time and the public API for it is still quite limited, this
58
- gem will be actively updated as it continues to grow and is developed.
84
+ communities, free of censorship and moderator abuse by design.
59
85
  email:
60
86
  - efreed09@gmail.com
61
- executables: []
87
+ executables:
88
+ - ruqqus-oauth
62
89
  extensions: []
63
90
  extra_rdoc_files: []
64
91
  files:
@@ -70,17 +97,23 @@ files:
70
97
  - LICENSE.txt
71
98
  - README.md
72
99
  - Rakefile
100
+ - TODO.md
73
101
  - bin/console
74
102
  - bin/setup
103
+ - exe/ruqqus-oauth
75
104
  - lib/ruqqus.rb
76
- - lib/ruqqus/badge.rb
77
- - lib/ruqqus/comment.rb
78
- - lib/ruqqus/guild.rb
79
- - lib/ruqqus/item_base.rb
80
- - lib/ruqqus/post.rb
81
- - lib/ruqqus/submission.rb
82
- - lib/ruqqus/title.rb
83
- - lib/ruqqus/user.rb
105
+ - lib/ruqqus/client.rb
106
+ - lib/ruqqus/routes.rb
107
+ - lib/ruqqus/token.rb
108
+ - lib/ruqqus/types.rb
109
+ - lib/ruqqus/types/badge.rb
110
+ - lib/ruqqus/types/comment.rb
111
+ - lib/ruqqus/types/guild.rb
112
+ - lib/ruqqus/types/item_base.rb
113
+ - lib/ruqqus/types/post.rb
114
+ - lib/ruqqus/types/submission.rb
115
+ - lib/ruqqus/types/title.rb
116
+ - lib/ruqqus/types/user.rb
84
117
  - lib/ruqqus/version.rb
85
118
  - ruqqus.gemspec
86
119
  homepage: https://github.com/ForeverZer0/ruqqus
@@ -112,5 +145,5 @@ rubygems_version: 3.1.4
112
145
  signing_key:
113
146
  specification_version: 4
114
147
  summary: A Ruby API implementation for Ruqqus, an open-source platform for online
115
- communities, free of censorship and moderator abuse by design.
148
+ communities.
116
149
  test_files: []