redd 0.8.8 → 0.9.0.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CONTRIBUTING.md +63 -0
  4. data/Guardfile +7 -0
  5. data/README.md +6 -5
  6. data/Rakefile +1 -1
  7. data/TODO.md +423 -0
  8. data/bin/console +91 -77
  9. data/bin/guard +2 -0
  10. data/lib/redd.rb +7 -5
  11. data/lib/redd/api_client.rb +2 -3
  12. data/lib/redd/auth_strategies/auth_strategy.rb +7 -2
  13. data/lib/redd/auth_strategies/script.rb +7 -0
  14. data/lib/redd/auth_strategies/userless.rb +7 -0
  15. data/lib/redd/auth_strategies/web.rb +6 -1
  16. data/lib/redd/client.rb +0 -3
  17. data/lib/redd/errors.rb +56 -0
  18. data/lib/redd/middleware.rb +10 -8
  19. data/lib/redd/models/access.rb +30 -18
  20. data/lib/redd/models/comment.rb +185 -27
  21. data/lib/redd/models/front_page.rb +16 -36
  22. data/lib/redd/models/gildable.rb +1 -1
  23. data/lib/redd/models/inboxable.rb +13 -3
  24. data/lib/redd/models/listing.rb +27 -6
  25. data/lib/redd/models/live_thread.rb +76 -23
  26. data/lib/redd/models/live_update.rb +46 -0
  27. data/lib/redd/models/messageable.rb +1 -1
  28. data/lib/redd/models/mod_action.rb +59 -0
  29. data/lib/redd/models/model.rb +23 -0
  30. data/lib/redd/models/moderatable.rb +6 -6
  31. data/lib/redd/models/modmail.rb +61 -0
  32. data/lib/redd/models/modmail_conversation.rb +154 -0
  33. data/lib/redd/models/modmail_message.rb +35 -0
  34. data/lib/redd/models/more_comments.rb +29 -5
  35. data/lib/redd/models/multireddit.rb +63 -20
  36. data/lib/redd/models/paginated_listing.rb +113 -0
  37. data/lib/redd/models/postable.rb +11 -13
  38. data/lib/redd/models/private_message.rb +78 -11
  39. data/lib/redd/models/replyable.rb +2 -2
  40. data/lib/redd/models/reportable.rb +14 -0
  41. data/lib/redd/models/searchable.rb +2 -2
  42. data/lib/redd/models/self.rb +17 -0
  43. data/lib/redd/models/session.rb +75 -31
  44. data/lib/redd/models/submission.rb +309 -56
  45. data/lib/redd/models/subreddit.rb +330 -103
  46. data/lib/redd/models/trophy.rb +34 -0
  47. data/lib/redd/models/user.rb +185 -46
  48. data/lib/redd/models/wiki_page.rb +37 -16
  49. data/lib/redd/utilities/error_handler.rb +13 -13
  50. data/lib/redd/utilities/unmarshaller.rb +7 -5
  51. data/lib/redd/version.rb +1 -1
  52. data/redd.gemspec +18 -15
  53. metadata +82 -16
  54. data/lib/redd/error.rb +0 -53
  55. data/lib/redd/models/basic_model.rb +0 -80
  56. data/lib/redd/models/lazy_model.rb +0 -75
  57. data/lib/redd/models/mod_mail.rb +0 -142
  58. data/lib/redd/utilities/stream.rb +0 -61
@@ -1,22 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'basic_model'
4
- require_relative 'lazy_model'
3
+ require_relative 'model'
5
4
 
6
5
  module Redd
7
6
  module Models
8
7
  # Represents a live thread.
9
- class LiveThread < LazyModel
10
- # An update in a live thread.
11
- class LiveUpdate < BasicModel; end
12
-
13
- # Get a LiveThread from its id.
14
- # @param id [String] the id
15
- # @return [LiveThread]
16
- def self.from_id(client, id)
17
- new(client, id: id)
18
- end
19
-
8
+ class LiveThread < Model
20
9
  # Get the updates from the thread.
21
10
  # @param params [Hash] a list of params to send with the request
22
11
  # @option params [String] :after return results after the given fullname
@@ -25,7 +14,7 @@ module Redd
25
14
  # @option params [1..100] :limit the maximum number of things to return
26
15
  # @return [Listing]
27
16
  def updates(**params)
28
- @client.model(:get, "/live/#{get_attribute(:id)}", params)
17
+ client.model(:get, "/live/#{read_attribute(:id)}", params)
29
18
  end
30
19
 
31
20
  # Configure the settings of this live thread
@@ -35,26 +24,38 @@ module Redd
35
24
  # @option params [String] :resources the new resources
36
25
  # @option params [String] :title the thread title
37
26
  def configure(**params)
38
- @client.post("/api/live/#{get_attribute(:id)}/edit", params)
27
+ client.post("/api/live/#{read_attribute(:id)}/edit", params)
39
28
  end
40
29
 
41
30
  # Add an update to this live event.
42
31
  # @param body [String] the update text
43
32
  def update(body)
44
- @client.post("/api/live/#{get_attribute(:id)}/update", body: body)
33
+ client.post("/api/live/#{read_attribute(:id)}/update", body: body)
34
+ end
35
+
36
+ # Strike out a live thread update.
37
+ # @param live_update [LiveUpdate] the update to strike out
38
+ def strike_update(live_update)
39
+ client.post("/api/live/#{read_attribute(:id)}/strike_update", id: live_update.name)
40
+ end
41
+
42
+ # Delete a live thread update.
43
+ # @param live_update [LiveUpdate] the update to strike out
44
+ def delete_update(live_update)
45
+ client.post("/api/live/#{read_attribute(:id)}/delete_update", id: live_update.name)
45
46
  end
46
47
 
47
48
  # @return [Array<User>] the contributors to this thread
48
49
  def contributors
49
- @client.get("/live/#{get_attribute(:id)}/contributors").body[0][:data].map do |user|
50
- User.new(@client, user)
50
+ client.get("/live/#{read_attribute(:id)}/contributors").body[0][:data].map do |user|
51
+ User.new(client, user)
51
52
  end
52
53
  end
53
54
 
54
55
  # @return [Array<User>] users invited to contribute to this thread
55
56
  def invited_contributors
56
- @client.get("/live/#{get_attribute(:id)}/contributors").body[1][:data].map do |user|
57
- User.new(@client, user)
57
+ client.get("/live/#{read_attribute(:id)}/contributors").body[1][:data].map do |user|
58
+ User.new(client, user)
58
59
  end
59
60
  end
60
61
 
@@ -67,13 +68,65 @@ module Redd
67
68
  #
68
69
  # @return [Listing<Submission>]
69
70
  def discussions(**params)
70
- @client.model(:get, "/live/#{get_attribute(:id)}/discussions", params)
71
+ client.model(:get, "/live/#{read_attribute(:id)}/discussions", params)
71
72
  end
72
73
 
74
+ # @!attribute [r] id
75
+ # @return [String] the thread id
76
+ property :id, :required
77
+
78
+ # @!attribute [r] name
79
+ # @return [String] the thread fullname
80
+ property :name, default: ->() { "LiveUpdateEvent_#{read_attribute(:id)}" }
81
+
82
+ # @!attribute [r] description
83
+ # @return [String] the live thread description
84
+ property :description
85
+
86
+ # @!attribute [r] description_html
87
+ # @return [String] the html-rendered thread description
88
+ property :description_html
89
+
90
+ # @!attribute [r] title
91
+ # @return [String] the live thread title
92
+ property :title
93
+
94
+ # @!attribute [r] created_at
95
+ # @return [String] the live thread creation time
96
+ property :created_at, from: :created_utc, with: ->(t) { Time.at(t) }
97
+
98
+ # @!attribute [r] websocket_url
99
+ # @return [String] the websocket url for listening to updates
100
+ property :websocket_url
101
+
102
+ # @!attribute [r] state
103
+ # @return [String] the thread state (e.g. "live")
104
+ property :state
105
+
106
+ # @!attribute [r] nsfw?
107
+ # @return [Boolean] whether the thread is nsfw
108
+ property :nsfw?, from: :nsfw
109
+
110
+ # @!attribute [r] viewer_count
111
+ # @return [Integer] the thread viewer count
112
+ property :viewer_count
113
+
114
+ # @!attribute [r] viewer_count_fuzzed?
115
+ # @return [Boolean] whether the viewer count is fuzzed
116
+ property :viewer_count_fuzzed?, from: :viewer_count_fuzzed
117
+
118
+ # @!attribute [r] resources
119
+ # @return [String] the thread's resources section
120
+ property :resources
121
+
122
+ # @!attribute [r] resources_html
123
+ # @return [String] the html-rendered thread resources
124
+ property :resources_html
125
+
73
126
  private
74
127
 
75
- def default_loader
76
- @client.get("/live/#{@attributes.fetch(:id)}/about").body[:data]
128
+ def lazer_reload
129
+ client.get("/live/#{read_attribute(:id)}/about").body[:data]
77
130
  end
78
131
  end
79
132
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'model'
4
+
5
+ module Redd
6
+ module Models
7
+ # A live thread update.
8
+ class LiveUpdate < Model
9
+ # @!attribute [r] id
10
+ # @return [String] the update id
11
+ property :id, :required
12
+
13
+ # @!attribute [r] name
14
+ # @return [String] the update fullname
15
+ property :name, default: ->() { "LiveUpdate_#{read_attribute(:id)}" }
16
+
17
+ # @!attribute [r] body
18
+ # @return [String] the update body
19
+ property :body
20
+
21
+ # @!attribute [r] body_html
22
+ # @return [String] the html-rendered update body
23
+ property :body_html
24
+
25
+ # @!attribute [r] embeds
26
+ # @return [Array]
27
+ property :embeds
28
+
29
+ # @!attribute [r] mobile_embeds
30
+ # @return [Array]
31
+ property :mobile_embeds
32
+
33
+ # @!attribute [r] author
34
+ # @return [User] the poster of the update
35
+ property :author, with: ->(n) { User.new(client, name: n) }
36
+
37
+ # @!attribute [r] created_at
38
+ # @return [Time] the post time
39
+ property :created_at, from: :created_utc, with: ->(t) { Time.at(t) }
40
+
41
+ # @!attribute [r] stricken?
42
+ # @return [Boolean] whether the update is stricken
43
+ property :stricken?, from: :stricken
44
+ end
45
+ end
46
+ end
@@ -13,7 +13,7 @@ module Redd
13
13
  def send_message(to:, subject:, text:, from: nil)
14
14
  params = { to: to, subject: subject, text: text }
15
15
  params[:from_sr] = from.display_name if from
16
- @client.post('/api/compose', params)
16
+ client.post('/api/compose', params)
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'model'
4
+
5
+ module Redd
6
+ module Models
7
+ # Represents a moderator action, part of a moderation log.
8
+ # @see Subreddit#mod_log
9
+ class ModAction < Model
10
+ # @!attribute [r] description
11
+ # @return [String] the action description
12
+ property :description
13
+
14
+ # @!attribute [r] target_title
15
+ # @return [String] the title of the item that was targeted
16
+ property :target_title
17
+
18
+ # @!attribute [r] target_body
19
+ # @return [String] the body of the item that was targeted
20
+ property :target_body
21
+
22
+ # @!attribute [r] target_permalink
23
+ # @return [String] the **relative** permalink to the item
24
+ property :target_permalink
25
+
26
+ # @!attribute [r] target_author
27
+ # @return [User] the target user
28
+ property :target_author, with: ->(n) { User.new(client, name: n) }
29
+
30
+ # @!attribute [r] mod_id36
31
+ # @return [String] the id of the moderator that performed this action
32
+ property :mod_id36
33
+
34
+ # @!attribute [r] created_at
35
+ # @return [Time] the time when the action was done
36
+ property :created_at, from: :created_utc, with: ->(t) { Time.at(t) }
37
+
38
+ # @!attribute [r] subreddit
39
+ # @return [Subreddit] the subreddit the action was performed on
40
+ property :subreddit, with: ->(n) { Subreddit.new(client, display_name: n) }
41
+
42
+ # @!attribute [r] subreddit_name_prefixed
43
+ # @return [String] the subreddit name, prefixed with a "r/"
44
+ property :subreddit_name_prefixed
45
+
46
+ # @!attribute [r] subreddit_id36
47
+ # @return [String] the subreddit's id
48
+ property :subreddit_id36, from: :sr_id36
49
+
50
+ # @!attribute [r] details
51
+ # @return [String] the action details
52
+ property :details
53
+
54
+ # @!attribute [r] action
55
+ # @return [String] the action type
56
+ property :action
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'lazy_lazer'
4
+
5
+ module Redd
6
+ module Models
7
+ # A base model class.
8
+ class Model
9
+ include LazyLazer
10
+
11
+ # @return [Client] the model's client
12
+ attr_reader :client
13
+
14
+ # Create a new Model.
15
+ # @param client [Client] the model's client
16
+ # @param attributes [Hash] the model's attributes
17
+ def initialize(client, attributes = {})
18
+ super(attributes)
19
+ @client = client
20
+ end
21
+ end
22
+ end
23
+ end
@@ -6,25 +6,25 @@ module Redd
6
6
  module Moderatable
7
7
  # Approve a submission.
8
8
  def approve
9
- @client.post('/api/approve', id: get_attribute(:name))
9
+ client.post('/api/approve', id: read_attribute(:name))
10
10
  end
11
11
 
12
12
  # Remove a submission.
13
13
  # @param spam [Boolean] whether or not this item is removed due to it being spam
14
14
  def remove(spam: false)
15
- @client.post('/api/remove', id: get_attribute(:name), spam: spam)
15
+ client.post('/api/remove', id: read_attribute(:name), spam: spam)
16
16
  end
17
17
 
18
18
  # Distinguish a link or comment with a sigil to show that it has been created by a moderator.
19
19
  # @param how [:yes, :no, :admin, :special, :sticky] how to distinguish the thing
20
20
  # @note :sticky is for comments. see {Submission#make_sticky} for posts.
21
21
  def distinguish(how = :yes)
22
- params = { id: get_attribute(:name), how: how }
22
+ params = { id: read_attribute(:name), how: how }
23
23
  if how == :sticky
24
24
  params[:how] = :yes
25
25
  params[:sticky] = true
26
26
  end
27
- @client.post('/api/distinguish', params)
27
+ client.post('/api/distinguish', params)
28
28
  end
29
29
 
30
30
  # Remove the sigil that shows a thing was created by a moderator.
@@ -34,12 +34,12 @@ module Redd
34
34
 
35
35
  # Stop getting any moderator-related reports on the thing.
36
36
  def ignore_reports
37
- @client.post('/api/ignore_reports', id: get_attribute(:name))
37
+ client.post('/api/ignore_reports', id: read_attribute(:name))
38
38
  end
39
39
 
40
40
  # Start getting moderator-related reports on the thing again.
41
41
  def unignore_reports
42
- @client.post('/api/unignore_reports', id: get_attribute(:name))
42
+ client.post('/api/unignore_reports', id: read_attribute(:name))
43
43
  end
44
44
  end
45
45
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'model'
4
+ require_relative 'subreddit'
5
+
6
+ module Redd
7
+ module Models
8
+ # A container for the new modmail.
9
+ class Modmail < Model
10
+ # @return [Hash<Symbol, Integer>] the number of unread messages in each category
11
+ def unread_count
12
+ client.get('/api/mod/conversations/unread/count').body
13
+ end
14
+
15
+ # @return [Array<Subreddit>] moderated subreddits that are enrolled in the new modmail
16
+ def enrolled
17
+ client.get('/api/mod/conversations/subreddits').body[:subreddits].map do |_, s|
18
+ Subreddit.new(client, s.merge(last_updated: s.delete(:lastUpdated)))
19
+ end
20
+ end
21
+
22
+ # Get the conversations
23
+ # @param subreddits [Subreddit, Array<Subreddit>] the subreddits to limit to
24
+ # @param params [Hash] additional request parameters
25
+ # @option params [String] :after base36 modmail conversation id
26
+ # @option params [Integer] :limit an integer (default: 25)
27
+ # @option params [:recent, :mod, :user, :unread] :sort the sort order
28
+ # @option params [:new, :inprogress, :mod, :notifications, :archived, :highlighted, :all]
29
+ # :state the state to limit the conversations by
30
+ # @return [Array<ModmailConversation>] the conversations
31
+ def conversations(subreddits: nil, **params)
32
+ params[:entity] = Array(subreddits).map(&:display_name).join(',') if subreddits
33
+ client.get('/api/mod/conversations', **params)
34
+ .body[:conversations]
35
+ .values
36
+ .map { |conv| ModmailConversation.new(client, conv) }
37
+ end
38
+
39
+ # Create a new conversation.
40
+ # @param from [Subreddit] the subreddit to send the conversation from
41
+ # @param to [User] the person to send the message to
42
+ # @param subject [String] the message subject
43
+ # @param body [String] the message body
44
+ # @return [ModmailConversation] the created conversation
45
+ def create(from:, to:, subject:, body:, hidden: false)
46
+ ModmailConversation.new(client, client.post(
47
+ '/api/mod/conversations',
48
+ srName: from.display_name, to: to.name,
49
+ subject: subject, body: body, isAuthorHidden: hidden
50
+ ).body[:conversation])
51
+ end
52
+
53
+ # Get a conversation from its base36 id.
54
+ # @param id [String] the conversation's id
55
+ # @return [ModmailConversation]
56
+ def get(id)
57
+ ModmailConversation.new(client, id: id)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require_relative 'model'
5
+
6
+ module Redd
7
+ module Models
8
+ # Represents a conversation in the new modmail.
9
+ class ModmailConversation < Model
10
+ # Add a reply to the ongoing conversation.
11
+ # @param body [String] the message body (probably markdown)
12
+ # @param hidden [Boolean] whether the message is hidden
13
+ # @param internal [Boolean] whether the message is internal
14
+ def reply(body, hidden: false, internal: false)
15
+ # TODO: merge response into the conversation
16
+ client.post(
17
+ "/api/mod/conversations/#{read_attribute(:id)}",
18
+ body: body, isAuthorHidden: hidden, isInternal: internal
19
+ ).body
20
+ end
21
+
22
+ # Mark this conversation as read.
23
+ def mark_as_read
24
+ client.post('/api/mod/conversations/read', conversationIds: [read_attribute(:id)])
25
+ end
26
+
27
+ # Mark this conversation as unread.
28
+ def mark_as_unread
29
+ client.post('/api/mod/conversations/unread', conversationIds: [read_attribute(:id)])
30
+ end
31
+
32
+ # Mark this conversation as archived.
33
+ def archive
34
+ perform_action(:post, 'archive')
35
+ end
36
+
37
+ # Removed this conversation from archived.
38
+ def unarchive
39
+ perform_action(:post, 'unarchive')
40
+ end
41
+
42
+ # Highlight this conversation.
43
+ def highlight
44
+ perform_action(:post, 'highlight')
45
+ end
46
+
47
+ # Remove the highlight on this conversation.
48
+ def unhighlight
49
+ perform_action(:delete, 'highlight')
50
+ end
51
+
52
+ # Mute this conversation.
53
+ def mute
54
+ perform_action(:post, 'mute')
55
+ end
56
+
57
+ # Unmute this conversation.
58
+ def unmute
59
+ perform_action(:post, 'unmute')
60
+ end
61
+
62
+ # @!attribute [r] id
63
+ # @return [String] the conversation id
64
+ property :id, :required
65
+
66
+ # @!attribute [r] messages
67
+ # @return [Array<ModmailMessage>] the modmail messages
68
+ property :messages, with: ->(hsh) { hsh.values.map { |m| ModmailMessage.new(client, m) } }
69
+
70
+ # @!attribute [r] user
71
+ # @return [Object] FIXME: details about the user the conversation deals with
72
+ property :user
73
+
74
+ # @!attribute [r] auto?
75
+ # @return [Boolean]
76
+ property :auto?, from: :isAuto
77
+
78
+ # @!attribute [r] message_ids
79
+ # @return [Array<String>] the conversation's message ids
80
+ property :message_ids, from: :objIds
81
+
82
+ # @!attribute [r] replyable?
83
+ # @return [Boolean] whether you can reply to this conversation
84
+ property :replyable?, from: :isRepliable
85
+
86
+ # @!attribute [r] last_user_update
87
+ # @return [Time] the time of last user update
88
+ property :last_user_update, from: :lastUserUpdate, with: ->(t) { Time.parse(t) }
89
+
90
+ # @!attribute [r] internal?
91
+ # @return [Boolean]
92
+ property :internal?, from: :isInternal
93
+
94
+ # @!attribute [r] last_mod_update
95
+ # @return [Time] the time of last mod update
96
+ property :last_mod_update, from: :lastModUpdate, with: ->(t) { Time.parse(t) }
97
+
98
+ # @!attribute [r] last_updated
99
+ # @return [Time] the time of last update
100
+ property :last_updated, from: :lastUpdated, with: ->(t) { Time.parse(t) }
101
+
102
+ # @!attribute [r] authors
103
+ # @return [Array<Hash>] FIXME: apply conversions
104
+ property :authors
105
+
106
+ # @!attribute [r] owner
107
+ # @return [Hash] FIXME: do shit
108
+ property :owner
109
+
110
+ # @!attribute [r] highlighted?
111
+ # @return [Boolean] whether the conversation is highlighted
112
+ property :highlighted?, from: :isHighlighted
113
+
114
+ # @!attribute [r] subject
115
+ # @return [String] the conversation subject
116
+ property :subject
117
+
118
+ # @!attribute [r] participant
119
+ # @return [Hash] FIXME: do shit
120
+ property :participant
121
+
122
+ # @!attribute [r] state
123
+ # @return [Integer]
124
+ property :state
125
+
126
+ # @!attribute [r] last_unread
127
+ # @return [Object]
128
+ property :last_unread, from: :lastUnread
129
+
130
+ # @!attribute [r] message_count
131
+ # @return [Integer] the message count
132
+ property :message_count, from: :numMessages
133
+
134
+ private
135
+
136
+ def lazer_reload
137
+ fully_loaded!
138
+ response = client.get("/api/mod/conversations/#{read_attribute(:id)}").body
139
+ response[:conversation].merge(
140
+ messages: response[:messages],
141
+ user: response[:user],
142
+ modActions: response[:modActions]
143
+ )
144
+ end
145
+
146
+ # Perform an action on a conversation.
147
+ # @param method [:post, :delete] the method to use
148
+ # @param action [String] the name of the action
149
+ def perform_action(method, action)
150
+ client.send(method, "/api/mod/conversations/#{read_attribute(:id)}/#{action}")
151
+ end
152
+ end
153
+ end
154
+ end