neonredd 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.md +22 -0
  4. data/README.md +1 -0
  5. data/Rakefile +5 -0
  6. data/Redd.LICENSE.md +22 -0
  7. data/RedditKit.LICENSE.md +9 -0
  8. data/lib/redd/access.rb +76 -0
  9. data/lib/redd/clients/base/account.rb +20 -0
  10. data/lib/redd/clients/base/identity.rb +22 -0
  11. data/lib/redd/clients/base/none.rb +27 -0
  12. data/lib/redd/clients/base/privatemessages.rb +33 -0
  13. data/lib/redd/clients/base/read.rb +114 -0
  14. data/lib/redd/clients/base/stream.rb +82 -0
  15. data/lib/redd/clients/base/submit.rb +19 -0
  16. data/lib/redd/clients/base/utilities.rb +143 -0
  17. data/lib/redd/clients/base/wikiread.rb +33 -0
  18. data/lib/redd/clients/base.rb +181 -0
  19. data/lib/redd/clients/installed.rb +56 -0
  20. data/lib/redd/clients/script.rb +40 -0
  21. data/lib/redd/clients/userless.rb +32 -0
  22. data/lib/redd/clients/web.rb +59 -0
  23. data/lib/redd/error.rb +151 -0
  24. data/lib/redd/objects/base.rb +39 -0
  25. data/lib/redd/objects/comment.rb +22 -0
  26. data/lib/redd/objects/labeled_multi.rb +13 -0
  27. data/lib/redd/objects/listing.rb +29 -0
  28. data/lib/redd/objects/more_comments.rb +10 -0
  29. data/lib/redd/objects/private_message.rb +28 -0
  30. data/lib/redd/objects/submission.rb +140 -0
  31. data/lib/redd/objects/subreddit.rb +329 -0
  32. data/lib/redd/objects/thing/editable.rb +22 -0
  33. data/lib/redd/objects/thing/hideable.rb +18 -0
  34. data/lib/redd/objects/thing/inboxable.rb +25 -0
  35. data/lib/redd/objects/thing/messageable.rb +38 -0
  36. data/lib/redd/objects/thing/moderatable.rb +43 -0
  37. data/lib/redd/objects/thing/refreshable.rb +14 -0
  38. data/lib/redd/objects/thing/saveable.rb +21 -0
  39. data/lib/redd/objects/thing/votable.rb +33 -0
  40. data/lib/redd/objects/thing.rb +26 -0
  41. data/lib/redd/objects/user.rb +52 -0
  42. data/lib/redd/objects/wiki_page.rb +15 -0
  43. data/lib/redd/rate_limit.rb +88 -0
  44. data/lib/redd/response/parse_json.rb +18 -0
  45. data/lib/redd/response/raise_error.rb +16 -0
  46. data/lib/redd/version.rb +4 -0
  47. data/lib/redd.rb +50 -0
  48. data/neonredd.gemspec +33 -0
  49. data/spec/redd/objects/base_spec.rb +1 -0
  50. data/spec/redd/response/raise_error_spec.rb +11 -0
  51. data/spec/redd_spec.rb +5 -0
  52. data/spec/spec_helper.rb +71 -0
  53. metadata +225 -0
@@ -0,0 +1,181 @@
1
+ require 'faraday'
2
+ require_relative '../version'
3
+ require_relative '../response/raise_error'
4
+ require_relative '../response/parse_json'
5
+ require_relative '../rate_limit'
6
+ require_relative '../access'
7
+
8
+ module Redd
9
+ # The module containing the multiple types of clients.
10
+ module Clients
11
+ # The basic client to inherit from. Don't use this directly, prefer
12
+ # {Redd.it}
13
+ class Base
14
+ # @!parse include Utilities
15
+ # @!parse include Account
16
+ # @!parse include Identity
17
+ # @!parse include None
18
+ # @!parse include Privatemessages
19
+ # @!parse include Read
20
+ # @!parse include Submit
21
+ # @!parse include Stream
22
+ # @!parse include Wikiread
23
+ %w(
24
+ utilities account identity none privatemessages read submit stream
25
+ wikiread
26
+ ).each do |mixin_name|
27
+ require_relative "base/#{mixin_name}"
28
+ camel_case = mixin_name.split('_').map(&:capitalize).join
29
+ include const_get(camel_case)
30
+ end
31
+
32
+ # @!attribute [r] user_agent
33
+ # @return [String] The user-agent used to communicate with reddit.
34
+ attr_reader :user_agent
35
+
36
+ # @!attribute [r] rate_limit
37
+ # @return [#after_limit] The handler that takes care of rate limiting.
38
+ attr_reader :rate_limit
39
+
40
+ # @!attribute [r] auth_endpoint
41
+ # @return [String] The site to connect to for authentication.
42
+ attr_reader :auth_endpoint
43
+
44
+ # @!attribute [r] api_endpoint
45
+ # @return [String] The site to make API requests with.
46
+ attr_reader :api_endpoint
47
+
48
+ # @!attribute [rw] access
49
+ # @return [Access] The access object to make API requests with.
50
+ attr_accessor :access
51
+
52
+ # Create a Client.
53
+ #
54
+ # @param [Hash] options The options to create the client with.
55
+ # @option options [String] :user_agent The User-Agent string to use in
56
+ # the header of every request.
57
+ # @option options [#after_limit] :rate_limit The handler that takes care
58
+ # of rate limiting.
59
+ # @option options [String] :auth_endpoint The main domain to authenticate
60
+ # with.
61
+ # @option options [String] :api_endpoint The main domain to make requests
62
+ # with.
63
+ # @note HTTPS is mandatory for OAuth2.
64
+ def initialize(**options)
65
+ @user_agent = options[:user_agent] || "Redd/Ruby, v#{Redd::VERSION}"
66
+ @rate_limit = options[:rate_limit] || RateLimit.new(1)
67
+ @auth_endpoint = options[:auth_endpoint] || 'https://www.reddit.com/'
68
+ @api_endpoint = options[:api_endpoint] || 'https://oauth.reddit.com/'
69
+ @access = Access.new(expires_at: Time.at(0))
70
+ end
71
+
72
+ # @!method get(path, params = {})
73
+ # @!method post(path, params = {})
74
+ # @!method put(path, params = {})
75
+ # @!method patch(path, params = {})
76
+ # @!method delete(path, params = {})
77
+ #
78
+ # Sends the request to the given path with the given params and return
79
+ # the body of the response.
80
+ # @param [String] path The path under the api_endpoint to request.
81
+ # @param [Hash] params The parameters to send with the request.
82
+ # @return [Faraday::Response] The response.
83
+ [:get, :post, :put, :patch, :delete].each do |meth|
84
+ define_method(meth) do |path, params = {}|
85
+ @rate_limit.after_limit do
86
+ final_params = default_params.merge(params)
87
+ connection.send(meth, path, final_params)
88
+ end
89
+ end
90
+ end
91
+
92
+ # @param [Access] new_access The access to use.
93
+ # @yield The client with the given access.
94
+ def with(new_access)
95
+ old_access = @access
96
+ @access = new_access
97
+ response = yield(self)
98
+ @access = old_access
99
+ response
100
+ end
101
+
102
+ # Obtain a new access token using a refresh token.
103
+ # @return [Access] The refreshed information.
104
+ def refresh_access!
105
+ response = auth_connection.post(
106
+ '/api/v1/access_token',
107
+ grant_type: 'refresh_token',
108
+ refresh_token: access.refresh_token
109
+ )
110
+ access.refreshed!(response.body)
111
+ end
112
+
113
+ # Dispose of an access or refresh token when you're done with it.
114
+ # @param [Boolean] remove_refresh_token Whether or not to remove all
115
+ # tokens associated with the user.
116
+ def revoke_access!(remove_refresh_token = false)
117
+ token_type = remove_refresh_token ? :refresh_token : :access_token
118
+ token = access.send(token_type)
119
+ @access = nil
120
+ auth_connection.post(
121
+ '/api/v1/revoke_token',
122
+ token: token,
123
+ token_type_hint: token_type
124
+ )
125
+ end
126
+
127
+ private
128
+
129
+ # @return [Faraday::RackBuilder] The middleware to use when creating the
130
+ # connection.
131
+ def middleware
132
+ @middleware ||= Faraday::RackBuilder.new do |builder|
133
+ builder.use Response::RaiseError
134
+ builder.use Response::ParseJson
135
+ builder.use Faraday::Request::Multipart
136
+ builder.use Faraday::Request::UrlEncoded
137
+ builder.adapter Faraday.default_adapter
138
+ end
139
+ end
140
+
141
+ # @return [Hash] The minimum parameters to send with every request.
142
+ def default_params
143
+ @default_params ||= { api_type: 'json' }
144
+ end
145
+
146
+ # @return [Hash] A hash of the headers used.
147
+ def default_headers
148
+ {
149
+ 'User-Agent' => @user_agent,
150
+ 'Authorization' => "bearer #{@access.access_token}"
151
+ }
152
+ end
153
+
154
+ # @return [Faraday::Connection] A new or existing connection.
155
+ def connection
156
+ @connection ||= Faraday.new(
157
+ @api_endpoint,
158
+ headers: default_headers,
159
+ builder: middleware
160
+ )
161
+ end
162
+
163
+ # @return [Hash] A hash of the headers with basic auth.
164
+ def auth_headers
165
+ {
166
+ 'User-Agent' => @user_agent,
167
+ 'Authorization' => Faraday.basic_auth(@client_id, @secret)
168
+ }
169
+ end
170
+
171
+ # @return [Faraday::Connection] A new or existing connection.
172
+ def auth_connection
173
+ @auth_connection ||= Faraday.new(
174
+ @auth_endpoint,
175
+ headers: auth_headers,
176
+ builder: middleware
177
+ )
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,56 @@
1
+ require 'cgi'
2
+ require_relative 'base'
3
+
4
+ module Redd
5
+ module Clients
6
+ # The client for installed apps that can't keep a secret.
7
+ # It might even work with Rubymotion (fingers crossed).
8
+ class Installed < Base
9
+ # @!attribute [r] client_id
10
+ attr_reader :client_id
11
+
12
+ # @!attribute [r] redirect_uri
13
+ attr_reader :redirect_uri
14
+
15
+ # @param [Hash] options The options to create the client with.
16
+ # @see Base#initialize
17
+ # @see Redd.it
18
+ def initialize(client_id, redirect_uri, **options)
19
+ @client_id = client_id
20
+ @redirect_uri = redirect_uri
21
+ super(**options)
22
+ end
23
+
24
+ # @param [String] state A random string to double-check later.
25
+ # @param [Array<String>] scope The scope to request access to.
26
+ # @param [:temporary, :permanent] duration
27
+ # @return [String] The url to redirect the user to.
28
+ # rubocop:disable Metrics/MethodLength
29
+ def auth_url(state, scope = ['identity'], duration = :temporary)
30
+ query = {
31
+ response_type: 'token',
32
+ client_id: @client_id,
33
+ redirect_uri: @redirect_uri,
34
+ state: state,
35
+ scope: scope.join(','),
36
+ duration: duration
37
+ }
38
+ url = URI.join(auth_endpoint, '/api/v1/authorize')
39
+ url.query = URI.encode_www_form(query)
40
+ url.to_s
41
+ end
42
+
43
+ # Authorize using the url fragment.
44
+ # @param [String] fragment The part of the url after the "#".
45
+ # @return [Access] The access given by reddit.
46
+ def authorize!(fragment)
47
+ parsed = CGI.parse(fragment)
48
+ @access = Access.new(
49
+ access_token: parsed[:access_token].first,
50
+ expires_in: parsed[:expires_in].first,
51
+ scope: parsed[:scope]
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'base'
2
+
3
+ module Redd
4
+ module Clients
5
+ # The client for an account you own (e.g. bots).
6
+ class Script < Base
7
+ # @!attribute [r] client_id
8
+ attr_reader :client_id
9
+
10
+ # @!attribute [r] username
11
+ attr_reader :username
12
+
13
+ # @param [Hash] options The options to create the client with.
14
+ # @see Base#initialize
15
+ # @see Redd.it
16
+ def initialize(client_id, secret, username, password, **options)
17
+ @client_id = client_id
18
+ @secret = secret
19
+ @username = username
20
+ @password = password
21
+ super(**options)
22
+ end
23
+
24
+ # Authorize using the given data.
25
+ # @return [Access] The access given by reddit.
26
+ def authorize!
27
+ response = auth_connection.post(
28
+ '/api/v1/access_token',
29
+ grant_type: 'password',
30
+ username: @username,
31
+ password: @password
32
+ )
33
+
34
+ @access = Access.new(response.body)
35
+ end
36
+
37
+ alias refresh_access! authorize!
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'base'
2
+
3
+ module Redd
4
+ module Clients
5
+ # The client that doesn't need a user to function.
6
+ # @note Of course, that means many editing methods throw an error.
7
+ class Userless < Base
8
+ # @!attribute [r] client_id
9
+ attr_reader :client_id
10
+
11
+ # @param [Hash] options The options to create the client with.
12
+ # @see Base#initialize
13
+ # @see Redd.it
14
+ def initialize(client_id, secret, **options)
15
+ @client_id = client_id
16
+ @secret = secret
17
+ super(**options)
18
+ end
19
+
20
+ # Authorize using the given data.
21
+ # @return [Access] The access given by reddit.
22
+ def authorize!
23
+ response = auth_connection.post(
24
+ '/api/v1/access_token',
25
+ grant_type: 'client_credentials'
26
+ )
27
+
28
+ @access = Access.new(response.body)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,59 @@
1
+ require 'uri'
2
+ require_relative 'base'
3
+
4
+ module Redd
5
+ module Clients
6
+ # The client for a web-based flow (e.g. "login with reddit")
7
+ class Web < Base
8
+ # @!attribute [r] client_id
9
+ attr_reader :client_id
10
+
11
+ # @!attribute [r] redirect_uri
12
+ attr_reader :redirect_uri
13
+
14
+ # @param [Hash] options The options to create the client with.
15
+ # @see Base#initialize
16
+ # @see Redd.it
17
+ def initialize(client_id, secret, redirect_uri, **options)
18
+ @client_id = client_id
19
+ @secret = secret
20
+ @redirect_uri = redirect_uri
21
+ super(**options)
22
+ end
23
+
24
+ # @param [String] state A random string to double-check later.
25
+ # @param [Array<String>] scope The scope to request access to.
26
+ # @param [:temporary, :permanent] duration
27
+ # @return [String] The url to redirect the user to.
28
+ # rubocop:disable Metrics/MethodLength
29
+ def auth_url(state, scope = ['identity'], duration = :temporary)
30
+ query = {
31
+ response_type: 'code',
32
+ client_id: @client_id,
33
+ redirect_uri: @redirect_uri,
34
+ state: state,
35
+ scope: scope.join(','),
36
+ duration: duration
37
+ }
38
+
39
+ url = URI.join(auth_endpoint, '/api/v1/authorize')
40
+ url.query = URI.encode_www_form(query)
41
+ url.to_s
42
+ end
43
+
44
+ # Authorize using the code given.
45
+ # @param [String] code The code from the get params.
46
+ # @return [Access] The access given by reddit.
47
+ def authorize!(code)
48
+ response = auth_connection.post(
49
+ '/api/v1/access_token',
50
+ grant_type: 'authorization_code',
51
+ code: code,
52
+ redirect_uri: @redirect_uri
53
+ )
54
+
55
+ @access = Access.new(response.body)
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/redd/error.rb ADDED
@@ -0,0 +1,151 @@
1
+ module Redd
2
+ # An error from reddit
3
+ # TODO: Move Error to an Errors module in next minor version?
4
+ class Error < StandardError
5
+ attr_reader :code
6
+ attr_reader :headers
7
+ attr_reader :body
8
+
9
+ def initialize(env)
10
+ @code = env[:status]
11
+ @headers = env[:response_headers]
12
+ @body = env[:body]
13
+ end
14
+
15
+ def self.from_response(env) # rubocop:disable all
16
+ status = env[:status]
17
+ body = parse_error(env[:body]).to_s
18
+ case status
19
+ when 200
20
+ case body
21
+ when /access_denied/i then OAuth2AccessDenied
22
+ when /unsupported_response_type/i then InvalidResponseType
23
+ when /unsupported_grant_type/i then InvalidGrantType
24
+ when /invalid_scope/i then InvalidScope
25
+ when /invalid_request/i then InvalidRequest
26
+ when /no_text/i then NoTokenGiven
27
+ when /invalid_grant/i then ExpiredCode
28
+ when /wrong_password/i then InvalidCredentials
29
+ when /bad_captcha/i then InvalidCaptcha
30
+ when /ratelimit/i then RateLimited
31
+ when /quota_filled/i then QuotaFilled
32
+ when /bad_css_name/i then InvalidClassName
33
+ when /too_old/i then Archived
34
+ when /too_much_flair_css/i then TooManyClassNames
35
+ when /user_required/i then AuthenticationRequired
36
+ end
37
+ when 400 then BadRequest
38
+ when 401 then InvalidOAuth2Credentials
39
+ when 403
40
+ if /user_required/i =~ body
41
+ AuthenticationRequired
42
+ else
43
+ PermissionDenied
44
+ end
45
+ when 404 then NotFound
46
+ when 409 then Conflict
47
+ when 500 then InternalServerError
48
+ when 502 then BadGateway
49
+ when 503 then ServiceUnavailable
50
+ when 504 then TimedOut
51
+ end
52
+ end
53
+
54
+ def self.parse_error(body) # rubocop:disable all
55
+ return body unless body.is_a?(Hash)
56
+
57
+ if body.key?(:json) && body[:json].key?(:errors)
58
+ body[:json][:errors].first
59
+ elsif body.key?(:jquery)
60
+ body[:jquery]
61
+ elsif body.key?(:error)
62
+ body[:error]
63
+ elsif body.key?(:code) && body[:code] == 'NO_TEXT'
64
+ 'NO_TEXT'
65
+ end
66
+ end
67
+
68
+ # This item has been archived and can no longer be edited.
69
+ Archived = Class.new(Error)
70
+
71
+ AuthenticationRequired = Class.new(Error)
72
+
73
+ # Bad Gateway. Either a network or a reddit error. Either way, try again.
74
+ BadGateway = Class.new(Error)
75
+
76
+ BadRequest = Class.new(Error)
77
+
78
+ Conflict = Class.new(Error)
79
+
80
+ # You already received an access token using this code. The user should
81
+ # grant you access again to get a new code.
82
+ ExpiredCode = Class.new(Error)
83
+
84
+ # There is an issue on reddit's end. Try again.
85
+ InternalServerError = Class.new(Error)
86
+
87
+ InvalidCaptcha = Class.new(Error)
88
+
89
+ InvalidClassName = Class.new(Error)
90
+
91
+ # Either your username or your password is wrong.
92
+ InvalidCredentials = Class.new(Error)
93
+
94
+ InvalidGrantType = Class.new(Error)
95
+
96
+ InvalidMultiredditName = Class.new(Error)
97
+
98
+ # Your client id or your secret is wrong.
99
+ InvalidOAuth2Credentials = Class.new(Error)
100
+
101
+ InvalidResponseType = Class.new(Error)
102
+
103
+ InvalidRequest = Class.new(Error)
104
+
105
+ # You don't have the proper scope to perform this request.
106
+ InvalidScope = Class.new(Error)
107
+
108
+ # Looks like we didn't get a JSON response. Raise this error.
109
+ JSONError = Class.new(Error)
110
+
111
+ # Four, oh four! The thing you're looking for wasn't found.
112
+ NotFound = Class.new(Error)
113
+
114
+ # No access token was given.
115
+ NoTokenGiven = Class.new(Error)
116
+
117
+ OAuth2AccessDenied = Class.new(Error)
118
+
119
+ PermissionDenied = Class.new(Error)
120
+
121
+ # Raised when the client needs to wait before making another request
122
+ class RateLimited < Error
123
+ # @!attribute [r] time
124
+ # @return [Integer] the seconds to wait before making another request.
125
+ attr_reader :time
126
+
127
+ def initialize(env)
128
+ super
129
+ @time = env[:body][:json][:ratelimit] || 3600
130
+ end
131
+ end
132
+
133
+ # This seems to be a more OAuth2-focused error.
134
+ class QuotaFilled < RateLimited
135
+ def initialize(env)
136
+ super
137
+ @time = 3600
138
+ end
139
+ end
140
+
141
+ RequestError = Class.new(Error)
142
+
143
+ # Issue on reddit's end. Try again.
144
+ ServiceUnavailable = Class.new(Error)
145
+
146
+ # The connection timed out. Try again.
147
+ TimedOut = Class.new(Error)
148
+
149
+ TooManyClassNames = Class.new(Error)
150
+ end
151
+ end
@@ -0,0 +1,39 @@
1
+ require 'hashie'
2
+ require 'forwardable'
3
+
4
+ module Redd
5
+ # A bunch of objects that can hold properties.
6
+ module Objects
7
+ # A base for all objects to inherit from.
8
+ class Base < Hashie::Hash
9
+ include Hashie::Extensions::MergeInitializer
10
+ include Hashie::Extensions::MethodReader
11
+ include Hashie::Extensions::MethodQuery
12
+ include Hashie::Extensions::DeepMerge
13
+
14
+ # The `delete` method is called `delete_path` because it conflicts with
15
+ # Hash#delete.
16
+ extend Forwardable
17
+ def_delegators :@client, :get, :post, :put, :delete_path
18
+
19
+ # @!attribute [r] client
20
+ # @return [Clients::Base] The client that used to make requests.
21
+ attr_reader :client
22
+
23
+ # @param [Clients::Base] client The client instance.
24
+ # @param [Hash] attributes A hash of attributes.
25
+ def initialize(client, attributes = {})
26
+ @client = client
27
+ super(attributes)
28
+ end
29
+
30
+ # Define an alias for a property.
31
+ # @param [Symbol] new_name The alias.
32
+ # @param [Symbol] old_name The existing property.
33
+ def self.alias_property(new_name, old_name)
34
+ define_method(new_name) { send(old_name) }
35
+ define_method(:"#{new_name}?") { send(old_name) }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'thing'
2
+
3
+ module Redd
4
+ module Objects
5
+ # A comment that can be made on a link.
6
+ class Comment < Thing
7
+ include Thing::Editable
8
+ include Thing::Inboxable
9
+ include Thing::Moderatable
10
+ include Thing::Refreshable
11
+ include Thing::Saveable
12
+ include Thing::Votable
13
+
14
+ alias_property :reports_count, :num_reports
15
+
16
+ # @return [Listing] The comment's replies.
17
+ def replies
18
+ @replies ||= (client.object_from_body(self[:replies]) || [])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Redd
2
+ module Objects
3
+ # A comment that can be made on a link.
4
+ class LabeledMulti < Base
5
+ # @see Objects::Base
6
+ def initialize(client, attributes = {})
7
+ attr_dup = attributes.dup
8
+ attr_dup[:subreddits].map! { |sub| sub[:name] }
9
+ super(client, attr_dup)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ module Redd
2
+ module Objects
3
+ # A collection of reddit things.
4
+ # @see https://www.reddit.com/dev/api#listings
5
+ class Listing < Array
6
+ # @!attribute [r] before
7
+ # @return [String] The id of the object before the listing.
8
+ attr_reader :before
9
+
10
+ # @!attribute [r] after
11
+ # @return [String] The id of the object after the listing.
12
+ attr_reader :after
13
+
14
+ # @param [Clients::Base] client The client to expand the comments with.
15
+ # @param [{:before => String, :after => String,
16
+ # :children => Array<Hash>}] attributes The data to initialize the
17
+ # class with.
18
+ # @todo Only call Clients::Base#object_from_body when item is being
19
+ # accessed.
20
+ def initialize(client, attributes)
21
+ @before = attributes[:before]
22
+ @after = attributes[:after]
23
+ attributes[:children].each do |child|
24
+ self << (client.object_from_body(child) || child)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ module Redd
2
+ module Objects
3
+ # The model for a morecomments object
4
+ class MoreComments < Array
5
+ def initialize(_, attributes)
6
+ super(attributes[:children])
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'thing'
2
+
3
+ module Redd
4
+ module Objects
5
+ # The model for private messages
6
+ class PrivateMessage < Thing
7
+ include Thing::Inboxable
8
+
9
+ alias_property :from, :author
10
+ alias_property :to, :dest
11
+
12
+ # Block the sender of the message from sending any more.
13
+ def block_sender!
14
+ post('/api/block', id: fullname)
15
+ end
16
+
17
+ # Mark the message as read.
18
+ def mark_as_read
19
+ post('/api/read_message', id: fullname)
20
+ end
21
+
22
+ # Mark the message as unread and add orangered to account.
23
+ def mark_as_unread
24
+ post('/api/unread_message', id: fullname)
25
+ end
26
+ end
27
+ end
28
+ end