redd 0.8.8 → 0.9.0.pre.1

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