redd 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +18 -8
- data/bin/console +42 -16
- data/lib/redd.rb +50 -19
- data/lib/redd/api_client.rb +18 -27
- data/lib/redd/auth_strategies/auth_strategy.rb +4 -3
- data/lib/redd/auth_strategies/script.rb +6 -1
- data/lib/redd/auth_strategies/userless.rb +6 -1
- data/lib/redd/auth_strategies/web.rb +4 -4
- data/lib/redd/models/access.rb +6 -1
- data/lib/redd/models/basic_model.rb +12 -55
- data/lib/redd/models/comment.rb +24 -28
- data/lib/redd/models/front_page.rb +1 -1
- data/lib/redd/models/gildable.rb +13 -0
- data/lib/redd/models/inboxable.rb +3 -3
- data/lib/redd/models/lazy_model.rb +7 -8
- data/lib/redd/models/listing.rb +6 -7
- data/lib/redd/models/live_thread.rb +9 -11
- data/lib/redd/models/mod_mail.rb +19 -24
- data/lib/redd/models/more_comments.rb +1 -1
- data/lib/redd/models/multireddit.rb +9 -13
- data/lib/redd/models/postable.rb +3 -3
- data/lib/redd/models/private_message.rb +18 -9
- data/lib/redd/models/searchable.rb +1 -1
- data/lib/redd/models/session.rb +31 -2
- data/lib/redd/models/submission.rb +26 -14
- data/lib/redd/models/subreddit.rb +115 -19
- data/lib/redd/models/user.rb +21 -9
- data/lib/redd/models/wiki_page.rb +8 -11
- data/lib/redd/utilities/unmarshaller.rb +3 -2
- data/lib/redd/version.rb +1 -1
- data/redd.gemspec +1 -1
- metadata +4 -5
- data/TODO.md +0 -8
- data/lib/redd/auth_strategies/installed.rb +0 -22
@@ -4,37 +4,13 @@ module Redd
|
|
4
4
|
module Models
|
5
5
|
# The base class for all models.
|
6
6
|
class BasicModel
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
#
|
13
|
-
|
14
|
-
def from_response(client, hash)
|
15
|
-
new(client, hash)
|
16
|
-
end
|
17
|
-
|
18
|
-
# @abstract Create an instance from a value.
|
19
|
-
# @param _client [APIClient] the api client to initialize the object with
|
20
|
-
# @param _value [Object] the object to coerce
|
21
|
-
# @return [BasicModel]
|
22
|
-
def from_id(_client, _value)
|
23
|
-
# TODO: abstract this out?
|
24
|
-
raise "coercion not implemented for #{name}"
|
25
|
-
end
|
26
|
-
|
27
|
-
# @return [Hash<Symbol, #from_id>] a mapping of keys to models
|
28
|
-
def coerced_attributes
|
29
|
-
@coerced_attributes ||= {}
|
30
|
-
end
|
31
|
-
|
32
|
-
# Mark an attribute to coerce.
|
33
|
-
# @param name [Symbol] the attribute to coerce
|
34
|
-
# @param model [#from_id, nil] a model to coerce it to
|
35
|
-
def coerce_attribute(name, model = nil)
|
36
|
-
coerced_attributes[name] = model
|
37
|
-
end
|
7
|
+
# @abstract Create an instance from a value.
|
8
|
+
# @param _client [APIClient] the api client to initialize the object with
|
9
|
+
# @param _value [Object] the object to coerce
|
10
|
+
# @return [BasicModel]
|
11
|
+
def self.from_id(_client, _value)
|
12
|
+
# TODO: abstract this out?
|
13
|
+
raise "coercion not implemented for #{name}"
|
38
14
|
end
|
39
15
|
|
40
16
|
# @return [APIClient] the client the model was initialized with
|
@@ -46,13 +22,11 @@ module Redd
|
|
46
22
|
def initialize(client, attributes = {})
|
47
23
|
@client = client
|
48
24
|
@attributes = attributes
|
49
|
-
@to_coerce = self.class.coerced_attributes.keys
|
50
25
|
after_initialize
|
51
26
|
end
|
52
27
|
|
53
28
|
# @return [Hash] a Hash representation of the object
|
54
29
|
def to_h
|
55
|
-
coerce_all_attributes
|
56
30
|
@attributes
|
57
31
|
end
|
58
32
|
|
@@ -61,6 +35,11 @@ module Redd
|
|
61
35
|
[self]
|
62
36
|
end
|
63
37
|
|
38
|
+
# @return [String] an easily readable representation of the object
|
39
|
+
def inspect
|
40
|
+
"#{super}\n" + @attributes.map { |a, v| " #{a}: #{v}" }.join("\n")
|
41
|
+
end
|
42
|
+
|
64
43
|
# Checks whether an attribute is supported by method_missing.
|
65
44
|
# @param method_name [Symbol] the method name or attribute to check
|
66
45
|
# @param include_private [Boolean] whether to also include private methods
|
@@ -83,25 +62,6 @@ module Redd
|
|
83
62
|
# @abstract Lets us plug in custom code without making a mess
|
84
63
|
def after_initialize; end
|
85
64
|
|
86
|
-
# Coerces an attribute into a class using the {.from_id} method.
|
87
|
-
# @param attribute [Symbol] the attribute to coerce
|
88
|
-
def coerce_attribute(attribute)
|
89
|
-
return unless @to_coerce.include?(attribute) && @attributes.include?(attribute)
|
90
|
-
klass = self.class.coerced_attributes.fetch(attribute)
|
91
|
-
@attributes[attribute] =
|
92
|
-
if klass.nil?
|
93
|
-
@client.unmarshal(@attributes[attribute])
|
94
|
-
else
|
95
|
-
klass.from_id(@client, @attributes[attribute])
|
96
|
-
end
|
97
|
-
@to_coerce.delete(attribute)
|
98
|
-
end
|
99
|
-
|
100
|
-
# Coerce every attribute that can be coerced.
|
101
|
-
def coerce_all_attributes
|
102
|
-
@to_coerce.each { |a| coerce_attribute(a) }
|
103
|
-
end
|
104
|
-
|
105
65
|
# Remove a trailing '?' from a symbol name.
|
106
66
|
# @param method_name [Symbol] the symbol to "depredicate"
|
107
67
|
# @return [Symbol] the symbol but with the '?' removed
|
@@ -113,9 +73,6 @@ module Redd
|
|
113
73
|
# @param name [Symbol] the attribute to check and get
|
114
74
|
# @return [Object] the value of the attribute
|
115
75
|
def get_attribute(name)
|
116
|
-
# Coerce the attribute if it exists and needs to be coerced.
|
117
|
-
coerce_attribute(name) if @to_coerce.include?(name) && @attributes.key?(name)
|
118
|
-
# Fetch the attribute, raising a KeyError if it doesn't exist.
|
119
76
|
@attributes.fetch(name)
|
120
77
|
end
|
121
78
|
end
|
data/lib/redd/models/comment.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'lazy_model'
|
4
|
+
require_relative 'gildable'
|
4
5
|
require_relative 'inboxable'
|
5
6
|
require_relative 'moderatable'
|
6
7
|
require_relative 'postable'
|
@@ -14,49 +15,44 @@ module Redd
|
|
14
15
|
module Models
|
15
16
|
# A comment.
|
16
17
|
class Comment < LazyModel
|
18
|
+
include Gildable
|
17
19
|
include Inboxable
|
18
20
|
include Moderatable
|
19
21
|
include Postable
|
20
22
|
include Replyable
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# Make a Comment from its id.
|
26
|
-
# @option hash [String] :name the comment's fullname (e.g. t1_abc123)
|
27
|
-
# @option hash [String] :id the comment's id (e.g. abc123)
|
24
|
+
# Create a Comment from its fullname.
|
25
|
+
# @param client [APIClient] the api client to initialize the object with
|
26
|
+
# @param id [String] the fullname
|
28
27
|
# @return [Comment]
|
29
|
-
def self.
|
30
|
-
|
31
|
-
|
32
|
-
# Ensure we have the comment's id.
|
33
|
-
id = hash.fetch(:id) { hash.fetch(:name).sub('t1_', '') }
|
34
|
-
|
35
|
-
# If we have the link_id, we can load the listing with replies.
|
36
|
-
if hash.key?(:link_id)
|
37
|
-
link_id = hash[:link_id].sub('t3_', '')
|
38
|
-
return new(client, hash) do |c|
|
39
|
-
# The second half contains a single-item listing containing the comment
|
40
|
-
c.get("/comments/#{link_id}/_/#{id}").body[1][:data][:children][0][:data]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# We can only load the comment in isolation if we don't have the link_id.
|
45
|
-
new(client, hash) do |c|
|
46
|
-
# Returns a single-item listing containing the comment
|
47
|
-
c.get('/api/info', id: "t1_#{id}").body[:data][:children][0][:data]
|
48
|
-
end
|
28
|
+
def self.from_id(client, id)
|
29
|
+
new(client, name: id)
|
49
30
|
end
|
50
31
|
|
51
32
|
private
|
52
33
|
|
53
34
|
def after_initialize
|
54
35
|
@attributes[:replies] =
|
55
|
-
if
|
56
|
-
|
36
|
+
if @attributes[:replies] == ''
|
37
|
+
nil
|
57
38
|
else
|
58
39
|
@client.unmarshal(@attributes[:replies])
|
59
40
|
end
|
41
|
+
@attributes[:author] = User.from_id(@client, @attributes.fetch(:author))
|
42
|
+
@attributes[:subreddit] = Subreddit.from_id(@client, @attributes.fetch(:subreddit))
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_loader
|
46
|
+
# Ensure we have the comment's id.
|
47
|
+
id = @attributes.fetch(:id) { @attributes.fetch(:name).sub('t1_', '') }
|
48
|
+
|
49
|
+
# If we have the link_id, we can load the listing with replies.
|
50
|
+
if @attributes.key?(:link_id)
|
51
|
+
link_id = @attributes[:link_id].sub('t3_', '')
|
52
|
+
return @client.get("/comments/#{link_id}/_/#{id}").body[1][:data][:children][0][:data]
|
53
|
+
end
|
54
|
+
# We can only load the comment in isolation if we don't have the link_id.
|
55
|
+
@client.get('/api/info', id: "t1_#{id}").body[:data][:children][0][:data]
|
60
56
|
end
|
61
57
|
end
|
62
58
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Redd
|
4
|
+
module Models
|
5
|
+
# A model that can be gilded.
|
6
|
+
module Gildable
|
7
|
+
# Gift a user one month of reddit gold for their link or comment.
|
8
|
+
def gild
|
9
|
+
@client.post("/api/v1/gold/gild/#{get_attribute(:name)}")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -6,17 +6,17 @@ module Redd
|
|
6
6
|
module Inboxable
|
7
7
|
# Block the user that sent this item.
|
8
8
|
def block
|
9
|
-
@client.post('/api/block', id: get_attribute(:
|
9
|
+
@client.post('/api/block', id: get_attribute(:name))
|
10
10
|
end
|
11
11
|
|
12
12
|
# Mark this thing as read.
|
13
13
|
def mark_as_read
|
14
|
-
@client.post('/api/read_message', id: get_attribute(:
|
14
|
+
@client.post('/api/read_message', id: get_attribute(:name))
|
15
15
|
end
|
16
16
|
|
17
17
|
# Mark one or more messages as unread.
|
18
18
|
def mark_as_unread
|
19
|
-
@client.post('/api/unread_message', id: get_attribute(:
|
19
|
+
@client.post('/api/unread_message', id: get_attribute(:name))
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -18,16 +18,10 @@ module Redd
|
|
18
18
|
@definitely_fully_loaded = false
|
19
19
|
end
|
20
20
|
|
21
|
-
# @return [Boolean] whether the model can be to be lazily initialized
|
22
|
-
def lazy?
|
23
|
-
!@lazy_loader.nil?
|
24
|
-
end
|
25
|
-
|
26
21
|
# Force the object to make a request to reddit.
|
27
22
|
# @return [self]
|
28
23
|
def force_load
|
29
|
-
|
30
|
-
@attributes.merge!(@lazy_loader.call(@client))
|
24
|
+
@attributes.merge!(@lazy_loader ? @lazy_loader.call(@client) : default_loader)
|
31
25
|
@definitely_fully_loaded = true
|
32
26
|
after_initialize
|
33
27
|
self
|
@@ -60,9 +54,14 @@ module Redd
|
|
60
54
|
|
61
55
|
private
|
62
56
|
|
57
|
+
# @abstract A lazy loader to use when one is not provided.
|
58
|
+
def default_loader
|
59
|
+
{}
|
60
|
+
end
|
61
|
+
|
63
62
|
# Make sure the model is loaded at least once.
|
64
63
|
def ensure_fully_loaded
|
65
|
-
force_load
|
64
|
+
force_load unless @definitely_fully_loaded
|
66
65
|
end
|
67
66
|
|
68
67
|
# Gets the attribute and loads it if it may be available from the response.
|
data/lib/redd/models/listing.rb
CHANGED
@@ -9,13 +9,6 @@ module Redd
|
|
9
9
|
class Listing < BasicModel
|
10
10
|
include Enumerable
|
11
11
|
|
12
|
-
# Make a Listing from a basic Hash.
|
13
|
-
# @return [Listing]
|
14
|
-
def self.from_response(client, hash)
|
15
|
-
hash[:children].map! { |el| client.unmarshal(el) }
|
16
|
-
new(client, hash)
|
17
|
-
end
|
18
|
-
|
19
12
|
# @return [Array<Comment, Submission, PrivateMessage>] an array representation of self
|
20
13
|
def to_ary
|
21
14
|
get_attribute(:children)
|
@@ -26,6 +19,12 @@ module Redd
|
|
26
19
|
get_attribute(:children).public_send(method_name, *args, &block)
|
27
20
|
end
|
28
21
|
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def after_initialize
|
26
|
+
@attributes.fetch(:children).map! { |el| @client.unmarshal(el) }
|
27
|
+
end
|
29
28
|
end
|
30
29
|
end
|
31
30
|
end
|
@@ -10,19 +10,11 @@ module Redd
|
|
10
10
|
# An update in a live thread.
|
11
11
|
class LiveUpdate < BasicModel; end
|
12
12
|
|
13
|
-
# Get a Conversation from its id.
|
14
|
-
# @option hash [String] :id the base36 id (e.g. abc123)
|
15
|
-
# @return [Conversation]
|
16
|
-
def self.from_response(client, hash)
|
17
|
-
id = hash.fetch(:id)
|
18
|
-
new(client, hash) { |c| c.get("/live/#{id}/about").body[:data] }
|
19
|
-
end
|
20
|
-
|
21
13
|
# Get a LiveThread from its id.
|
22
14
|
# @param id [String] the id
|
23
15
|
# @return [LiveThread]
|
24
16
|
def self.from_id(client, id)
|
25
|
-
|
17
|
+
new(client, id: id)
|
26
18
|
end
|
27
19
|
|
28
20
|
# Get the updates from the thread.
|
@@ -55,14 +47,14 @@ module Redd
|
|
55
47
|
# @return [Array<User>] the contributors to this thread
|
56
48
|
def contributors
|
57
49
|
@client.get("/live/#{get_attribute(:id)}/contributors").body[0][:data].map do |user|
|
58
|
-
User.
|
50
|
+
User.new(@client, user)
|
59
51
|
end
|
60
52
|
end
|
61
53
|
|
62
54
|
# @return [Array<User>] users invited to contribute to this thread
|
63
55
|
def invited_contributors
|
64
56
|
@client.get("/live/#{get_attribute(:id)}/contributors").body[1][:data].map do |user|
|
65
|
-
User.
|
57
|
+
User.new(@client, user)
|
66
58
|
end
|
67
59
|
end
|
68
60
|
|
@@ -77,6 +69,12 @@ module Redd
|
|
77
69
|
def discussions(**params)
|
78
70
|
@client.model(:get, "/live/#{get_attribute(:id)}/discussions", params)
|
79
71
|
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def default_loader
|
76
|
+
@client.get("/live/#{@attributes.fetch(:id)}/about").body[:data]
|
77
|
+
end
|
80
78
|
end
|
81
79
|
end
|
82
80
|
end
|
data/lib/redd/models/mod_mail.rb
CHANGED
@@ -11,26 +11,11 @@ module Redd
|
|
11
11
|
# Represents a conversation in the new modmail.
|
12
12
|
# TODO: add modmail-specific user type
|
13
13
|
class Conversation < LazyModel
|
14
|
-
# Get a Conversation from its id.
|
15
|
-
# @option hash [String] :id the base36 id (e.g. abc123)
|
16
|
-
# @return [Conversation]
|
17
|
-
def self.from_response(client, hash)
|
18
|
-
id = hash.fetch(:id)
|
19
|
-
new(client, hash) do |c|
|
20
|
-
response = c.get("/api/mod/conversations/#{id}").body
|
21
|
-
response[:conversation].merge(
|
22
|
-
messages: response[:messages].values.map { |m| Message.from_response(c, m) },
|
23
|
-
user: response[:user],
|
24
|
-
mod_actions: response[:modActions]
|
25
|
-
)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
14
|
# Get a Conversation from its id.
|
30
15
|
# @param id [String] the base36 id (e.g. abc123)
|
31
16
|
# @return [Conversation]
|
32
17
|
def self.from_id(client, id)
|
33
|
-
|
18
|
+
new(client, id: id)
|
34
19
|
end
|
35
20
|
|
36
21
|
# Add a reply to the ongoing conversation.
|
@@ -84,6 +69,15 @@ module Redd
|
|
84
69
|
|
85
70
|
private
|
86
71
|
|
72
|
+
def default_loader
|
73
|
+
response = @client.get("/api/mod/conversations/#{@attributes[:id]}").body
|
74
|
+
response[:conversation].merge(
|
75
|
+
messages: response[:messages].values.map { |m| Message.new(@client, m) },
|
76
|
+
user: response[:user],
|
77
|
+
mod_actions: response[:modActions]
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
87
81
|
# Perform an action on a conversation.
|
88
82
|
# @param method [:post, :delete] the method to use
|
89
83
|
# @param action [String] the name of the action
|
@@ -104,21 +98,22 @@ module Redd
|
|
104
98
|
# @return [Array<Subreddit>] moderated subreddits that are enrolled in the new modmail
|
105
99
|
def enrolled
|
106
100
|
@client.get('/api/mod/conversations/subreddits').body[:subreddits].map do |_, s|
|
107
|
-
Subreddit.
|
101
|
+
Subreddit.new(@client, s.merge(last_updated: s.delete(:lastUpdated)))
|
108
102
|
end
|
109
103
|
end
|
110
104
|
|
111
105
|
# Get the conversations
|
112
|
-
# @param after [String] base36 modmail conversation id
|
113
106
|
# @param subreddits [Subreddit, Array<Subreddit>] the subreddits to limit to
|
114
|
-
# @param
|
115
|
-
# @
|
116
|
-
# @
|
117
|
-
#
|
107
|
+
# @param params [Hash] additional request parameters
|
108
|
+
# @option params [String] :after base36 modmail conversation id
|
109
|
+
# @option params [Integer] :limit an integer (default: 25)
|
110
|
+
# @option params [:recent, :mod, :user, :unread] :sort the sort order
|
111
|
+
# @option params [:new, :inprogress, :mod, :notifications, :archived, :highlighted, :all]
|
112
|
+
# :state the state to limit the conversations by
|
118
113
|
def conversations(subreddits: nil, **params)
|
119
114
|
params[:entity] = Array(subreddits).map(&:display_name).join(',') if subreddits
|
120
115
|
@client.get('/api/mod/conversations', **params).body[:conversations].map do |_, conv|
|
121
|
-
Conversation.
|
116
|
+
Conversation.new(@client, conv)
|
122
117
|
end
|
123
118
|
end
|
124
119
|
|
@@ -129,7 +124,7 @@ module Redd
|
|
129
124
|
# @param body [String] the message body
|
130
125
|
# @return [Conversation] the created conversation
|
131
126
|
def create(from:, to:, subject:, body:, hidden: false)
|
132
|
-
Conversation.
|
127
|
+
Conversation.new(@client, @client.post(
|
133
128
|
'/api/mod/conversations',
|
134
129
|
srName: from.display_name, to: to.name,
|
135
130
|
subject: subject, body: body, isAuthorHidden: hidden
|
@@ -24,7 +24,7 @@ module Redd
|
|
24
24
|
# @param sort [String] the sort order of the returned comments
|
25
25
|
# @param lookup [Hash] a hash of comments to add future replies to
|
26
26
|
# @param depth [Number] the maximum recursion depth
|
27
|
-
# @return [Array<Comment, MoreComments>] the expanded comments or
|
27
|
+
# @return [Array<Comment, MoreComments>] the expanded comments or self if past depth
|
28
28
|
def recursive_expand(link:, sort: 'best', lookup: {}, depth: 10)
|
29
29
|
return [self] if depth == 0
|
30
30
|
|
@@ -6,20 +6,12 @@ module Redd
|
|
6
6
|
module Models
|
7
7
|
# A multi.
|
8
8
|
class Multireddit < LazyModel
|
9
|
-
# Make a Multireddit from its path.
|
10
|
-
# @option hash [String] :path the multi's path
|
11
|
-
# @return [Multireddit]
|
12
|
-
def self.from_response(client, hash)
|
13
|
-
path = hash.fetch(:path)
|
14
|
-
new(client, hash) { |c| c.get("/api/multi#{path}").body[:data] }
|
15
|
-
end
|
16
|
-
|
17
9
|
# Create a Multireddit from its path.
|
18
10
|
# @param client [APIClient] the api client to initialize the object with
|
19
|
-
# @param id [String] the multi's path (
|
11
|
+
# @param id [String] the multi's path (with a leading and trailing slash)
|
20
12
|
# @return [Multireddit]
|
21
13
|
def self.from_id(client, id)
|
22
|
-
|
14
|
+
new(client, path: id)
|
23
15
|
end
|
24
16
|
|
25
17
|
# Get the appropriate listing.
|
@@ -56,9 +48,13 @@ module Redd
|
|
56
48
|
private
|
57
49
|
|
58
50
|
def after_initialize
|
59
|
-
|
60
|
-
|
61
|
-
|
51
|
+
@attributes[:subreddits].map! do |subreddit|
|
52
|
+
Subreddit.new(client, display_name: subreddit[:name])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def default_loader
|
57
|
+
@client.get("/api/multi#{@attributes.fetch(:path)}").body[:data]
|
62
58
|
end
|
63
59
|
end
|
64
60
|
end
|