slack_message 2.4.0 → 3.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b97efc9f2b80be4ee01c89d4c82aab83bb79983077ec8bfa87f0026ea5387f37
4
- data.tar.gz: ae251f82220f9c15e45d3065f4e8cd84f5ad913c630a48d6cf9713df33b41b34
3
+ metadata.gz: 62f8b7feb3289a37b79cb887dc4e7846c037a8c72290138afe611682a066a884
4
+ data.tar.gz: 73e391d721ace67ad31ae69b7a17eae3578d4f85a2bcd111a5cb1966b68c85cd
5
5
  SHA512:
6
- metadata.gz: c2e5a3f499fe2e73f245ff2163284f73825f3d051c4cc06aee03c39184eb01e22d6ce5d426f1dde77a0f89e6eacd1764b3a49b1f0548a368490588a7dc0bd757
7
- data.tar.gz: d2417ed29a6b0e3b88133e9e679a965104d2b9030945d7241af106b54dabc471190d73902f4a4bceae02a469f24eeeeb6e95d91f1b8c9817dc0f69ee41a6524d
6
+ metadata.gz: cb28febfda107047351955825d1cd84dafdf553147b9f0101573944f3d5290c6d3e84a4d1190e63888f6c8ba851ac70da9eb774f1a01a04372d19520b7df8779
7
+ data.tar.gz: 3438883916204aec8a16a8dfa80b52506d4f640fc69161ad6e8efea1b5a136e97fc15f8b990c2529f3bbfb1f4380a989a0827cbe8f810af08a42bf3a2b6df382
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.0.0] - 2021-12-19
4
+ - Return a more structured object from successful message sends.
5
+ - Add the ability to edit or delete a message.
6
+ - Complete overhaul of docs because they were too large.
7
+
3
8
  ## [2.4.0] - 2021-12-13
4
9
  - Add ability to schedule messages, plus some guard rails around that.
5
10
  - Add ability to debug by logging out the total set of params sent to the API.
data/README.md CHANGED
@@ -48,6 +48,11 @@ SlackMessage.post_to('#general') do
48
48
  end
49
49
  ```
50
50
 
51
+ ### The Docs
52
+
53
+ You'll find much more information about how to use SlackMessage by visiting
54
+ [the docs](https://jmmastey.github.io/slack_message).
55
+
51
56
  #### Opinionated Stances
52
57
 
53
58
  This gem is intended to stay simple. Other Slack gems have lots of config
@@ -75,8 +80,10 @@ Some behaviors that are still planned but not yet added:
75
80
  * multiple recipients: https://api.slack.com/methods/conversations.open
76
81
  * more interesting return types for your message
77
82
  * richer text formatting (for instance, `ul` is currently a hack)
83
+ * more mrkdwn syntax, like quotes or code blocks
78
84
  * more and better organized testing capability
79
85
  * posting ephemeral messages: https://api.slack.com/methods/chat.postEphemeral
86
+ * some Rspec test harness for scheduled messages, editing, deleting (probably going to need a test overhaul)
80
87
 
81
88
  Contributing
82
89
  ------------
@@ -0,0 +1,116 @@
1
+ ## Getting Started / Configuration
2
+
3
+ To get started sending messages, you'll first need to create a Slack App with
4
+ some appropriate permissions. It used to be possible to use the Webhook API,
5
+ but that's long since been deprecated, and apps are pretty [straightforward to
6
+ create](https://api.slack.com/tutorials/tracks/getting-a-token).
7
+
8
+ Generally, make sure your token has permissions for _at least_ `users:read` and
9
+ `chat:write`. Then, define a default profile for SlackMessage to use for
10
+ posting.
11
+
12
+ ```ruby
13
+ SlackMessage.configure do |config|
14
+ api_token = 'xoxb-11111111111-2222222222-33333333333333333'
15
+
16
+ config.add_profile(api_token: api_token)
17
+ end
18
+ ```
19
+
20
+ You should keep your token in a safe place like `ENV`. If using this gem with
21
+ Rails, place this code in somewhere like
22
+ `config/initializers/slack_message.rb`.
23
+
24
+ ### Additional Profiles
25
+
26
+ If your app uses slack messages for several different purposes, it's common to
27
+ want to post to different channels as different names / icons / etc. To do that
28
+ more easily and consistently, you can specify multiple profiles.
29
+
30
+ ```ruby
31
+ SlackMessage.configure do |config|
32
+ api_token = 'xoxb-11111111111-2222222222-33333333333333333'
33
+
34
+ # default profile
35
+ config.add_profile(api_token: api_token, name: 'Slack Notifier')
36
+
37
+ # additional profiles (see below for usage)
38
+ config.add_profile(:prod_alert_bot,
39
+ name: 'Prod Alert Bot'
40
+ icon: ':mooseandsquirrel:'
41
+ )
42
+ config.add_profile(:sidekiq_bot,
43
+ api_token: ENV.fetch('SIDEKIQ_SLACK_APP_API_TOKEN'),
44
+ name: 'Sidekiq Bot',
45
+ )
46
+ end
47
+ ```
48
+
49
+ A number of parameters are available to make it simpler to use a profile
50
+ without specifying repetitive information. You can generally also specify this
51
+ information on a per-message basis.
52
+
53
+ | Config | Default | Value |
54
+ |-----------------|-----------------|-----------------------------------------------------------------|
55
+ | api_token | None | Your Slack App API Key. |
56
+ | name | From Slack App | The bot name for your message. |
57
+ | icon | From Slack App | Profile icon for your message. Specify as :emoji: or image URL. |
58
+ | default_channel | None (optional) | Channel / user to post to by default. |
59
+
60
+
61
+ Setting a `default_channel` specifically will allow you to use `post_as`, which
62
+ is a convenient shortcut for bots that repeatedly post to one channel as a
63
+ consistent identity.
64
+
65
+ ```ruby
66
+ SlackMessage.configure do |config|
67
+ config.add_profile(:red_alert_bot,
68
+ api_token: ENV.fetch('SLACK_API_TOKEN'),
69
+ name: 'Red Alerts',
70
+ icon: ':klaxon:',
71
+ default_channel: '#red_alerts'
72
+ )
73
+ end
74
+
75
+ SlackMessage.post_as(:red_alert_bot) do
76
+ text ":ambulance: weeooo weeooo something went wrong"
77
+ end
78
+ ```
79
+
80
+ There's no reason you can't use the same API key for several profiles. Profiles
81
+ are most useful to create consistent name / icon setups for apps with many
82
+ bots.
83
+
84
+ ### Debug Mode
85
+
86
+ If you'd like to get more information about the messages you send, you can set
87
+ SlackMessage to debug mode.
88
+
89
+ ```ruby
90
+ SlackMessage.configure do |config|
91
+ config.debug
92
+ end
93
+ ```
94
+
95
+ You will now see warnings detailing all params sent to the API.
96
+
97
+ ```ruby
98
+ {
99
+ :channel=>"#general",
100
+ :username=>"Builds",
101
+ :blocks=>[
102
+ {:type=>"section", :text=>{
103
+ :type=>"mrkdwn",
104
+ :text=>"Build Stability is Looking Ruff :dog:"
105
+ }}
106
+ ],
107
+ :text=>"Build Issues",
108
+ :post_at=>1639421171,
109
+ }
110
+ ```
111
+
112
+ Note this includes data that is not included in `SlackMessage.build`.
113
+
114
+ ---
115
+
116
+ Next: [Posting a Message](https://jmmastey.github.io/slack_message/02_posting_a_message)
@@ -0,0 +1,134 @@
1
+ ## Posting Messages
2
+
3
+ As mentioned at the outset, posting a message to Slack is dang easy.
4
+
5
+ ```ruby
6
+ SlackMessage.post_to('#general') do
7
+ text "We did it @here! :thumbsup:"
8
+ end
9
+ ```
10
+
11
+ That's it! SlackMessage will automatically serialize for the API.
12
+
13
+ ```json
14
+ [{"type":"section","text":{"type":"mrkdwn","text":"We did it @here! :thumbsup:"}}]
15
+ ```
16
+
17
+ Details like remembering that Slack made a mystifying decision to force you to
18
+ request "mrkdwn", or requiring your text to be wrapped into a section are
19
+ handled for you. Building up messages is meant to be as user-friendly as
20
+ possible.
21
+
22
+ ```ruby
23
+ SlackMessage.build do
24
+ text "haiku are easy"
25
+ text "but sometimes they don't make sense"
26
+ text "refrigerator"
27
+
28
+ context "- unknown author"
29
+ end
30
+ ```
31
+
32
+ SlackMessage will combine your text declarations and add any necessary wrappers
33
+ automatically.
34
+
35
+ ```json
36
+ [
37
+ {
38
+ "type": "section",
39
+ "text": {
40
+ "type": "mrkdwn",
41
+ "text": "haiku are easy\nbut sometimes they don't make sense\nrefrigerator"
42
+ }
43
+ },
44
+ {
45
+ "type": "context",
46
+ "elements": [
47
+ {
48
+ "type": "mrkdwn",
49
+ "text": "- unknown author"
50
+ }
51
+ ]
52
+ }
53
+ ]
54
+ ```
55
+
56
+ ### Direct Messages
57
+
58
+ It's just as easy to send messages directly to users. SlackMessage will look
59
+ for targets that are email-addressish, and look them up for you automatically.
60
+
61
+ ```ruby
62
+ SlackMessage.post_to('hello@joemastey.com') do
63
+ text "You specifically did it! :thumbsup:"
64
+ end
65
+ ```
66
+
67
+ SlackMessage will compose this into Block Kit syntax and send it on its way!
68
+
69
+ ### Multiple Profiles
70
+
71
+ If you've defined multiple profiles in configuration, you can specify which to
72
+ use for your message by specifying its name.
73
+
74
+ ```ruby
75
+ SlackMessage.post_to('#general', as: :sidekiq_bot) do
76
+ text ":octagonal_sign: A job has failed permanently and needs to be rescued."
77
+
78
+ link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
79
+ end
80
+ ```
81
+
82
+ You can also override profile bot details when sending a message.
83
+
84
+ ```ruby
85
+ SlackMessage.post_to('#general') do
86
+ bot_name "CoffeeBot"
87
+ bot_icon ":coffee:"
88
+
89
+ text ":coffee::clock: Time to take a break!"
90
+ end
91
+ ```
92
+
93
+ Finally, if your profile specifies a `default_channel`, you can also post with
94
+ the `post_as` shorthand.
95
+
96
+ ```ruby
97
+ SlackMessage.post_as(:coffeebot) do
98
+ text ":coffee::clock: Time to take a break!"
99
+ end
100
+ ```
101
+
102
+ ### Scheduling a Message
103
+
104
+ To schedule a message, simply provide a `at` parameter to your post. Provide
105
+ either a time object that responds to `to_i`, or an integer that represents a
106
+ [unix timestamp](https://en.wikipedia.org/wiki/Unix_time) for the time at which
107
+ you want your message posted.
108
+
109
+ ```ruby
110
+ SlackMessage.post_to('hello@joemastey.com', at: 20.seconds.from_now) do
111
+ text "From the top of the key. :basketball:"
112
+ end
113
+
114
+ SlackMessage.post_as(:basketball_bot, at: 20.seconds.from_now) do
115
+ text "Boom shakalaka! :explosion:"
116
+ end
117
+ ```
118
+
119
+ Please note that scheduled messages can't specify a `bot_name` or `bot_icon`,
120
+ nor can they be scheduled more than 120 days into the future.
121
+
122
+ ### Best Practices
123
+
124
+ Talk about having coherent methods that post a message, rather than a block
125
+ that includes lots of indirection or ternaries.
126
+
127
+ See the [API documentation for
128
+ chat.postMessage](https://api.slack.com/methods/chat.postMessage) or
129
+ [chat.scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) for
130
+ more information on posting messages.
131
+
132
+ ---
133
+
134
+ Next: [The SlackMessage DSL](https://jmmastey.github.io/slack_message/03_message_dsl)
@@ -0,0 +1,283 @@
1
+ ### The Message DSL
2
+
3
+ A pretty good number of the elements available in BlockKit are usable in SlackMessage. There are also a few elements that haven't been implemented in the official API, but are too useful to be missing.
4
+
5
+ #### Basic Text
6
+
7
+ While BlockKit officially requires that any elements are contained within a section element, that requirement is relaxed in SlackMessage. If you don't specify a section, one will silently be created to encapsulate your code. That's the secret behind the most basic messages in these docs.
8
+
9
+ ```ruby
10
+ SlackMessage.build do
11
+ text "couldn't be easier"
12
+ end
13
+
14
+ # => [{:type=>"section",
15
+ # :text=>{:type=>"mrkdwn", :text=>"couldn't be easier"}
16
+ # }]
17
+ ```
18
+
19
+ This is equivalent to the more verbose version with a declared section.
20
+
21
+ ```ruby
22
+ SlackMessage.build do
23
+ section do
24
+ text "could be easier"
25
+ end
26
+ end
27
+
28
+ # => [{:type=>"section",
29
+ # :text=>{:type=>"mrkdwn", :text=>"could be easier"}
30
+ # }]
31
+ ```
32
+
33
+ Text elements are the most basic type of element. Adding multiple text calls
34
+ will add a newline between text calls, which will cause a line break
35
+ appropriately.
36
+
37
+ ```ruby
38
+ SlackMessage.build do
39
+ text "one fish, two fish"
40
+ text "red fish, blue fish"
41
+ end
42
+
43
+ # => [{:type=>"section",
44
+ # :text=>{:type=>"mrkdwn", :text=>"one fish, two fish\nred fish, blue fish"}
45
+ # }]
46
+ ```
47
+
48
+ Slack uses a [faux-markdown syntax called
49
+ mrkdwn](https://api.slack.com/reference/surfaces/formatting#basics), which you
50
+ may be familiar with by typing in the Slack app itself. The API will
51
+ automatically render mrkdwn appropriately.
52
+
53
+ ```ruby
54
+ SlackMessage.build do
55
+ text "*Favorite Colors*"
56
+ text "_John_: ~red~ actually blue."
57
+ end
58
+
59
+ # => [{:type=>"section",
60
+ # :text=>{:type=>"mrkdwn", :text=>"*Favorite Colors*\n_John_: ~red~ actually blue."}}]
61
+ ```
62
+
63
+ Rendering emoji in messages is possible using either a) real unicode emoji in
64
+ your message, or b) using the `:emojiname:` syntax, which supports any emoji
65
+ that would work in your Slack app itself, including custom emoji.
66
+
67
+ ```ruby
68
+ SlackMessage.build do
69
+ text ":shipit_squirrel:🚀 time to gooo :tada:"
70
+ end
71
+
72
+ # => [{:type=>"section",
73
+ # :text=>{:type=>"mrkdwn", :text=>":shipit_squirrel:🚀 time to gooo :tada:"}
74
+ # }]
75
+ ```
76
+
77
+ To add a link using Slack's non-markdown link syntax, use the `link` helper
78
+ method interpolated into a text element. Using the `link` helper as its own
79
+ element won't work, as the method simply returns a string that has to be
80
+ included into a text element specifically.
81
+
82
+ ```ruby
83
+ SlackMessage.build do
84
+ text "Your #{link('build', 'https://google.com')} is ready."
85
+ end
86
+
87
+ # => [{:type=>"section",
88
+ # :text=>{:type=>"mrkdwn", :text=>"Your <https://google.com|build> is ready."}
89
+ # }]
90
+ ```
91
+
92
+ While the API squishes whitespace (much in the same way HTML does), it may
93
+ sometimes be useful to add a blank line between text _without_ adding a new
94
+ section to your message. To do so, use the pseudo-element `blank_line`.
95
+
96
+ ```ruby
97
+ SlackMessage.build do
98
+ text "don't let this line"
99
+ blank_line
100
+ text "touch this line."
101
+ end
102
+
103
+ # => => [{:type=>"section",
104
+ # :text=>{:type=>"mrkdwn", :text=>"don't let this line\n \ntouch this line."}
105
+ # }]
106
+ ```
107
+
108
+ Note that between the two newlines in the above example is a unicode emspace,
109
+ which the API will respect as a line worth rendering.
110
+
111
+ #### Buttons
112
+
113
+ BlockKit allows you to specify a button to the right of a section / block. That
114
+ button will be aligned outside the normal space for a section, and is meant to
115
+ link out of the app. To create one of these, use the `link_button` helper.
116
+
117
+ ```ruby
118
+ SlackMessage.build do
119
+ text "Your daily stats are ready @here"
120
+ link_button "Stats Dashboard", stats_dashboard_url
121
+ end
122
+
123
+ # => [{:type=>"section",
124
+ # :text=>{:type=>"mrkdwn", :text=>"Your daily stats are ready @here"},
125
+ # :accessory=>
126
+ # {:type=>"button",
127
+ # :url=>"http://yoursite.com/stats_dashboard",
128
+ # :text=>{:type=>"plain_text", :text=>"Stats Dashboard", :emoji=>true},
129
+ # :style=>:primary}}]
130
+ ```
131
+
132
+ Slack allows three styles for buttons: `default`, `primary`, and `danger`.
133
+ These correspond to gray, green and red buttons respectively. If not specified,
134
+ SlackMessage will use the `primary` style for buttons. I get that this could be
135
+ confusing when there is a default style, but in my experience, a colorful button
136
+ is way more common.
137
+
138
+ You can override the button style by specifying the style with your link button.
139
+
140
+ ```ruby
141
+ SlackMessage.build do
142
+ text "A job has failed catastrophically!"
143
+ link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
144
+ end
145
+
146
+ # => [{:type=>"section",
147
+ # :text=>{:type=>"mrkdwn", :text=>"A job has failed catastrophically!"},
148
+ # :accessory=>
149
+ # {:type=>"button",
150
+ # :url=>"https://yoursite.com/sidekiq",
151
+ # :text=>{:type=>"plain_text", :text=>"Sidekiq Dashboard", :emoji=>true},
152
+ # :style=>:danger}}]
153
+ ```
154
+
155
+ #### Lists and List Items
156
+
157
+ - list_item, ul, ol
158
+
159
+ ### Including Multiple Sections
160
+
161
+ Adding more sections is trivial. Simply declare each section and it will be
162
+ separated in the rendered message. This can often occur when looping.
163
+
164
+ ```ruby
165
+ SlackMessage.build do
166
+ pet_types.each do |type, breeds|
167
+ section do
168
+ text "*#{type}:* #{breeds.join(", ")}"
169
+ end
170
+ end
171
+ end
172
+ ```
173
+
174
+ It can also be useful to add a visual divider (similar to a `hr` in HTML)
175
+ between sections. To add one of these, use the `divider` helper. You can also
176
+ add a divider at the end of all the sections, but it often looks silly.
177
+
178
+ ```ruby
179
+ SlackMessage.build do
180
+ section do
181
+ text "*Topsiders:* Emily, Elsie, Derick"
182
+ end
183
+
184
+ divider
185
+
186
+ section do
187
+ text "*Undergrounders:* Kristina, Lauren, Different Emily"
188
+ end
189
+ end
190
+
191
+ # => [
192
+ # {:type=>"section", :text=>{:type=>"mrkdwn", :text=>"*Topsiders:* Emily, Elsie, Derick"}},
193
+ # {:type=>"divider"},
194
+ # {:type=>"section", :text=>{:type=>"mrkdwn", :text=>"*Undergrounders:* Kristina, Lauren, Different Emily"}}
195
+ # ]
196
+ ```
197
+
198
+ Note that a divider can only occur between sections, not within a single
199
+ section. Because of how implicit sections are built, it may look like this works
200
+ for simple messages. You may have troubles when you start adding more
201
+ complicated elements to your messages.
202
+
203
+ ### Images
204
+ - image, accessory_image
205
+
206
+ ### Footers (Context)
207
+
208
+ Slack allows you to add a small additional piece of text to your message, which
209
+ will be rendered in italics and small text. It can support both links and emoji,
210
+ and is useful for providing minor details for your message.
211
+
212
+ ```ruby
213
+ SlackMessage.build do
214
+ text "New coffee complaints have been added."
215
+ context "this complaint added by #{link('Joe Mastey', 'hello@joemastey.com')}."
216
+ end
217
+
218
+ # => [{:type=>"section",
219
+ # :text=>{:type=>"mrkdwn", :text=>"New coffee complaints have been added."}
220
+ # },
221
+ # {:type=>"context", :elements=>
222
+ # [{:type=>"mrkdwn",
223
+ # :text=>"this complaint added by <hello@joemastey.com|Joe Mastey>."
224
+ # }]
225
+ # }]
226
+ ```
227
+
228
+ Context does not belong to a section, and is per-message, not per-section.
229
+ Specifying more than one context will simply overwrite previous calls.
230
+
231
+ ### Bot Customization
232
+
233
+ By default - and with scheduled messages - Slack will use the name and icon of
234
+ the Slack app whose API key you configured. As seen before, it's
235
+ possible to override those default names and icons in configuration. However, it
236
+ can also be customized per-message.
237
+
238
+ ```ruby
239
+ SlackMessage.build do
240
+ bot_icon ":sad_robot:"
241
+ bot_name "BadNewsBuildBot"
242
+
243
+ text "The build is broken. @here"
244
+ end
245
+
246
+ # => [{:type=>"section", :text=>{:type=>"mrkdwn", :text=>"The build is broken. @here"}}]
247
+ ```
248
+
249
+ Notice that the bot details aren't shown in the output of the `build` command.
250
+ To view the changes these methods cause, use `debug` mode.
251
+
252
+ The `bot_icon` can be specified as either an emoji (`:example:`), or a URL
253
+ pointing to an image (`http://mysite.com/shipit.png`). Any other value seems to
254
+ cause an error.
255
+
256
+ ### Custom Notification Text
257
+
258
+ For users who have notifications turned on, Slack will provide a small message
259
+ preview when you send them a message. By default, this preview will take the
260
+ first several words from your message.
261
+
262
+ However, you can specify some custom notification text to be shown to the user.
263
+ This text supports basic emoji and formatting, but nothing complicated.
264
+
265
+ ```ruby
266
+ SlackMessage.build do
267
+ notification_text "Having issues with the build. :ohnoes:"
268
+
269
+ text "The build is broken. The error message was 'undefined method round for NilClass'"
270
+
271
+ # => [{:type=>"section",
272
+ # :text=>
273
+ # {:type=>"mrkdwn",
274
+ # :text=>"The build is broken. The error message was 'undefined method round for NilClass'"}}]
275
+ end
276
+ ```
277
+
278
+ Again notice that notification text is not set within the blocks themselves, so
279
+ you will need to enable debugging to see how it changes what is sent to the API.
280
+
281
+ ---
282
+
283
+ Next: [Editing Messages](https://jmmastey.github.io/slack_message/04_editing_messages)
@@ -0,0 +1,88 @@
1
+ ### Updating a Previous Message
2
+
3
+ After you've posted a message, you may want to edit it later. Interactive bots,
4
+ for instance, may want to repeatedly update a message.
5
+
6
+ Posting will always return an object representing your posted message.
7
+
8
+ ```ruby
9
+ message = SlackMessage.post_to('#general') do
10
+ text "Getting ready..."
11
+ end
12
+ ```
13
+
14
+ Then, you can use that response object to go back and rewrite the message a
15
+ little or a lot.
16
+
17
+ ```ruby
18
+ SlackMessage.update(message) do
19
+ text "Done!"
20
+ end
21
+ ```
22
+
23
+ The new message contents will be built and updated via the API. To give an
24
+ example, you could alert slack to a job status by updating your original
25
+ message.
26
+
27
+
28
+ ```ruby
29
+ class SomeWorker < ApplicationWorker
30
+ def perform
31
+ post_started_status
32
+
33
+ # ... perform work here
34
+
35
+ post_finished_status
36
+ end
37
+
38
+ private
39
+
40
+ def post_started_status
41
+ @message = SlackMessage.post_as(:job_worker) do
42
+ text "Beginning upload."
43
+ end
44
+ end
45
+
46
+ def post_finished_status
47
+ SlackMessage.update(@message) do
48
+ text "Finished upload! @here come and get it."
49
+ link_button "See Results", uploaded_data_url
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
55
+ #### Storing Response Objects for Later
56
+
57
+ Since updates are likely to occur after you've long since finished posting the
58
+ original message, you'll need to persist the message response somehow until you
59
+ need to update it later. As one option, you could serialize the response object
60
+ for later.
61
+
62
+ ```ruby
63
+ # initially
64
+ message = SlackMessage.post_to('#general') do
65
+ text "Starting..."
66
+ end
67
+ redis_connection.set(self.message_cache_key, Marshal.dump(message))
68
+
69
+
70
+ # later
71
+ message = Marshal.load(redis_connection.get(self.message_cache_key))
72
+ SlackMessage.update(message) do
73
+ text "Finished!"
74
+ end
75
+ ```
76
+
77
+ #### Updating Scheduled Messages
78
+
79
+ Sadly, there's currently no way to edit a scheduled message. You'll receive an
80
+ error if you attempt to call `update` on a scheduled message.
81
+
82
+ See the [API documentation for
83
+ chat.update](https://api.slack.com/methods/chat.update) for more information on
84
+ updating messages.
85
+
86
+ ---
87
+
88
+ Next: [Deleting Messages](https://jmmastey.github.io/slack_message/05_deleting_messages)
@@ -0,0 +1,45 @@
1
+ ### Deleting Messages
2
+
3
+ Deleting a message is much like editing a message, only simpler. Just like when
4
+ you edit a message, you'll need a reference to the message you posted.
5
+
6
+ *Important Note: It's not possible to delete a message sent directly to a user.
7
+ It's also not possible to delete a scheduled message once it's already posted.
8
+ Don't send anything you don't want your boss to read.*
9
+
10
+ ```ruby
11
+ message = SlackMessage.post_to('#general') do
12
+ text "Testing: #{SLACK_SECRET_KEY}"
13
+ end
14
+ ```
15
+
16
+ Now you can simply call the `delete` method to make up for your mistakes.
17
+
18
+ ```ruby
19
+ SlackMessage.delete(message)
20
+ ```
21
+
22
+ As with editing a message, it's possible to persist messages to redis / your
23
+ database and remove them using the timestamp and channel of your message.
24
+
25
+ ```ruby
26
+ # initially
27
+ message = SlackMessage.post_to('#general') do
28
+ text "Testing: #{SLACK_SECRET_KEY}"
29
+ end
30
+ redis_connection.set(self.message_cache_key, Marshal.dump(message))
31
+
32
+
33
+ # later
34
+ message = Marshal.load(redis_connection.get(self.message_cache_key))
35
+ SlackMessage.delete(message)
36
+ ```
37
+
38
+ See the [API documentation for
39
+ chat.delete](https://api.slack.com/methods/chat.delete) or
40
+ [chat.deleteScheduledMessage](https://api.slack.com/methods/chat.deleteScheduledMessage)
41
+ for more information on deleting messages.
42
+
43
+ ---
44
+
45
+ Next: [Mentions / Notifying Users](https://jmmastey.github.io/slack_message/06_notifying_users)
@@ -0,0 +1,62 @@
1
+ ## Mentions / Notifying Users
2
+
3
+ There are several supported ways to tag and notify users. As mentioned
4
+ initially, it's possible to DM a user by their account email.
5
+
6
+ ```ruby
7
+ SlackMessage.post_to('hello@joemastey.com') do
8
+ text "Hi there!"
9
+ end
10
+ ```
11
+
12
+ You can also mention a user by email within a channel by wrapping their name in
13
+ tags.
14
+
15
+ ```ruby
16
+ SlackMessage.post_to('#general') do
17
+ bot_name "CoffeeBot"
18
+ bot_icon ":coffee:"
19
+
20
+ text ":coffee: It's your turn to make coffee <hello@joemastey.com>."
21
+ end
22
+ ```
23
+
24
+ Emails that are not wrapped in tags will be rendered as normal email addresses.
25
+ Additionally, Slack will automatically convert a number of channel names and
26
+ tags you're probably already used to.
27
+
28
+ ```ruby
29
+ SlackMessage.post_to('#general') do
30
+ bot_name "CoffeeBot"
31
+ bot_icon ":coffee:"
32
+
33
+ text "@here There's no coffee left! Let #general know when you fix it."
34
+ end
35
+ ```
36
+
37
+ By default, the desktop notification for a message will be the text of the
38
+ message itself. However, you can customize desktop notifications if you prefer.
39
+
40
+ ```ruby
41
+ SlackMessage.post_to('hello@joemastey.com') do
42
+ bot_name "CoffeeBot"
43
+ bot_icon ":coffee:"
44
+
45
+ notification_text "It's a coffee emergency!"
46
+ text "There's no coffee left!"
47
+ end
48
+ ```
49
+
50
+ #### Using @channel or @here
51
+
52
+ Not really a feature, but Slack will respect usage of `@here` and `@channel`.
53
+
54
+ ```ruby
55
+ SlackMessage.post_to('#general') do
56
+ text "Hey @channel, don't forget to submit your drink requests."
57
+ end
58
+ ```
59
+
60
+ ---
61
+
62
+ Next: [Testing](https://jmmastey.github.io/slack_message/07_testing)
@@ -0,0 +1,49 @@
1
+ ## Testing
2
+
3
+ You can do some basic testing against SlackMessage, at least if you use RSpec!
4
+ You'll need to require and include the testing behavior in your spec_helper
5
+ file.
6
+
7
+ ```ruby
8
+ require 'slack_message/rspec'
9
+
10
+ RSpec.configure do |config|
11
+ include SlackMessage::RSpec
12
+
13
+ # your other spec_helper config
14
+ end
15
+ ```
16
+
17
+ This will prevent API calls from leaking in your tests, and will allow you
18
+ access to some custom matchers.
19
+
20
+ ```ruby
21
+ expect {
22
+ SlackMessage.post_to('#general') { text "foo" }
23
+ }.to post_slack_message_to('#general').with_content_matching(/foo/)
24
+
25
+ expect {
26
+ SlackMessage.post_as(:schmoebot) { text "foo" }
27
+ }.to post_slack_message_as(:schmoebot)
28
+
29
+ expect {
30
+ SlackMessage.post_as(:schmoebot) { text "foo" }
31
+ }.to post_slack_message_as('Schmoe Bot')
32
+
33
+ expect {
34
+ SlackMessage.post_as(:schmoebot) { text "foo" }
35
+ }.to post_slack_message_with_icon(':schmoebot:')
36
+
37
+ expect {
38
+ SlackMessage.post_as(:schmoebot) { text "foo" }
39
+ }.to post_slack_message_with_icon_matching(/gravatar/)
40
+
41
+ expect {
42
+ SlackMessage.post_to('#general') { text "foo" }
43
+ }.to post_to_slack
44
+ ```
45
+
46
+ Be forewarned, I'm frankly not that great at more complicated RSpec matchers,
47
+ so I'm guessing there are some bugs. Also, because the content of a message
48
+ gets turned into a complex JSON object, matching against content isn't capable
49
+ of very complicated regexes.
data/docs/index.md ADDED
@@ -0,0 +1,7 @@
1
+ * [Configuration](https://jmmastey.github.io/slack_message/01_configuration)
2
+ * [Posting a Message](https://jmmastey.github.io/slack_message/02_posting_a_message)
3
+ * [The SlackMessage DSL](https://jmmastey.github.io/slack_message/03_message_dsl)
4
+ * [Editing Messages](https://jmmastey.github.io/slack_message/04_editing_messages)
5
+ * [Deleting Messages](https://jmmastey.github.io/slack_message/05_deleting_messages)
6
+ * [Mentions / Notifying Users](https://jmmastey.github.io/slack_message/06_notifying_users)
7
+ * [Testing](https://jmmastey.github.io/slack_message/07_testing)
@@ -59,7 +59,7 @@ module SlackMessage::Api
59
59
  if !time.nil?
60
60
  params[:post_at] = time.to_i
61
61
 
62
- if params[:icon_url] || params[:icon_emoji]
62
+ if payload.custom_bot_name || payload.custom_bot_icon
63
63
  raise ArgumentError, "Sorry, setting an image / emoji icon for scheduled messages isn't supported."
64
64
  end
65
65
  end
@@ -72,6 +72,8 @@ module SlackMessage::Api
72
72
  body = JSON.parse(response.body)
73
73
  error = body.fetch("error", "")
74
74
 
75
+ # TODO: if a scheduled message w/ a short timer is "time_in_past", warn the user?
76
+
75
77
  # let's try to be helpful about error messages
76
78
  if ["token_revoked", "token_expired", "invalid_auth", "not_authed"].include?(error)
77
79
  raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' is wrong."
@@ -87,6 +89,51 @@ module SlackMessage::Api
87
89
  raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
88
90
  end
89
91
 
92
+ SlackMessage::Response.new(response, profile[:handle])
93
+ end
94
+
95
+ def update(payload, message, profile)
96
+ params = {
97
+ channel: message.channel,
98
+ ts: message.timestamp,
99
+ blocks: payload.render,
100
+ text: payload.custom_notification # TODO: ???
101
+ }
102
+
103
+ if params[:blocks].length == 0
104
+ raise ArgumentError, "Tried to send an entirely empty message."
105
+ end
106
+
107
+ if SlackMessage::Configuration.debugging?
108
+ warn params.inspect
109
+ end
110
+
111
+ response = update_message(profile, params)
112
+ body = JSON.parse(response.body)
113
+ error = body.fetch("error", "")
114
+
115
+ # TODO: error messaging
116
+
117
+ SlackMessage::Response.new(response, profile[:handle])
118
+ end
119
+
120
+ def delete(message, profile)
121
+ params = if message.scheduled?
122
+ {
123
+ channel: message.channel,
124
+ scheduled_message_id: message.scheduled_message_id,
125
+ }
126
+ else
127
+ {
128
+ channel: message.channel,
129
+ ts: message.timestamp,
130
+ }
131
+ end
132
+
133
+ response = delete_message(profile, params)
134
+
135
+ # TODO error handling (incl for already scheduled-and-sent messages)
136
+
90
137
  response
91
138
  end
92
139
 
@@ -108,7 +155,7 @@ module SlackMessage::Api
108
155
  end
109
156
 
110
157
  def post_message(profile, params)
111
- uri = if params[:post_at]
158
+ uri = if params.has_key?(:post_at)
112
159
  URI("https://slack.com/api/chat.scheduleMessage")
113
160
  else
114
161
  URI("https://slack.com/api/chat.postMessage")
@@ -124,4 +171,36 @@ module SlackMessage::Api
124
171
  http.request(request)
125
172
  end
126
173
  end
174
+
175
+ def update_message(profile, params)
176
+ uri = URI("https://slack.com/api/chat.update")
177
+
178
+ request = Net::HTTP::Post.new(uri).tap do |req|
179
+ req['Authorization'] = "Bearer #{profile[:api_token]}"
180
+ req['Content-type'] = "application/json; charset=utf-8"
181
+ req.body = params.to_json
182
+ end
183
+
184
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
185
+ http.request(request)
186
+ end
187
+ end
188
+
189
+ def delete_message(profile, params)
190
+ uri = if params.has_key?(:scheduled_message_id)
191
+ URI("https://slack.com/api/chat.deleteScheduledMessage")
192
+ else
193
+ URI("https://slack.com/api/chat.delete")
194
+ end
195
+
196
+ request = Net::HTTP::Post.new(uri).tap do |req|
197
+ req['Authorization'] = "Bearer #{profile[:api_token]}"
198
+ req['Content-type'] = "application/json; charset=utf-8"
199
+ req.body = params.to_json
200
+ end
201
+
202
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
203
+ http.request(request)
204
+ end
205
+ end
127
206
  end
@@ -0,0 +1,42 @@
1
+ class SlackMessage::Response
2
+ attr_reader :channel, :timestamp, :profile_handle, :scheduled_message_id, :original_response
3
+
4
+ def initialize(api_response, profile_handle)
5
+ @original_response = JSON.parse(api_response.body)
6
+ @ok = @original_response["ok"]
7
+ @channel = @original_response["channel"]
8
+
9
+ @timestamp = @original_response["ts"]
10
+ @scheduled_message_id = @original_response["scheduled_message_id"]
11
+
12
+ @profile_handle = profile_handle
13
+ end
14
+
15
+ def marshal_dump
16
+ [ @profile_handle, @channel, @timestamp, @original_response, @ok, @original_response ]
17
+ end
18
+
19
+ def marshal_load(data)
20
+ @profile_handle, @channel, @timestamp, @original_response, @ok, @original_response = data
21
+ end
22
+
23
+ def sent_to_user?
24
+ channel =~ /^D.*/ # users are D for DM, channels start w/ C
25
+ end
26
+
27
+ def scheduled?
28
+ !!scheduled_message_id
29
+ end
30
+
31
+ def inspect
32
+ identifier = if scheduled?
33
+ "scheduled_message_id=#{scheduled_message_id}"
34
+ else
35
+ "timestamp=#{timestamp}"
36
+ end
37
+
38
+ ok_msg = @ok ? "ok" : "error"
39
+
40
+ "<SlackMessage::Response #{ok_msg} profile_handle=:#{profile_handle} channel=#{channel} #{identifier}>"
41
+ end
42
+ end
@@ -14,6 +14,8 @@ require 'rspec/mocks'
14
14
  # it can be cleaned up properly.
15
15
  #
16
16
 
17
+ # TODO: testing for scheduled messages, editing and deleting
18
+
17
19
  module SlackMessage::RSpec
18
20
  extend RSpec::Matchers::DSL
19
21
 
data/lib/slack_message.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module SlackMessage
2
+ require 'slack_message/response'
2
3
  require 'slack_message/dsl'
3
4
  require 'slack_message/api'
4
5
  require 'slack_message/configuration'
@@ -39,16 +40,46 @@ module SlackMessage
39
40
  raise ArgumentError, "Sorry, you need to specify a default_channel for profile #{profile_name} to use post_as"
40
41
  end
41
42
 
43
+ target = profile[:default_channel]
42
44
  payload = Dsl.new(block, profile).tap do |instance|
43
45
  instance.instance_eval(&block)
44
46
  end
45
47
 
46
- target = profile[:default_channel]
47
48
  target = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
48
49
 
49
50
  Api.post(payload, target, profile, at)
50
51
  end
51
52
 
53
+ def self.update(message, &block)
54
+ unless message.is_a?(SlackMessage::Response)
55
+ raise ArgumentError, "You must pass in a SlackMessage::Response to update a message"
56
+ end
57
+
58
+ if message.scheduled?
59
+ raise ArgumentError, "Sorry, scheduled messages cannot be updated. You will need to delete the message and schedule a new one."
60
+ end
61
+
62
+ profile = Configuration.profile(message.profile_handle)
63
+ payload = Dsl.new(block, profile).tap do |instance|
64
+ instance.instance_eval(&block)
65
+ end
66
+
67
+ Api.update(payload, message, profile)
68
+ end
69
+
70
+ def self.delete(message)
71
+ unless message.is_a?(SlackMessage::Response)
72
+ raise ArgumentError, "You must pass in a SlackMessage::Response to delete a message"
73
+ end
74
+
75
+ if message.sent_to_user?
76
+ raise ArgumentError, "It's not possible to delete messages sent directly to users."
77
+ end
78
+
79
+ profile = Configuration.profile(message.profile_handle)
80
+ Api.delete(message, profile)
81
+ end
82
+
52
83
  def self.build(profile_name = :default, &block)
53
84
  profile = Configuration.profile(profile_name)
54
85
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'slack_message'
3
- gem.version = "2.4.0"
3
+ gem.version = "3.0.0"
4
4
  gem.summary = "A nice DSL for composing rich messages in Slack"
5
5
  gem.authors = ["Joe Mastey"]
6
6
  gem.email = 'hello@joemastey.com'
@@ -157,4 +157,6 @@ RSpec.describe SlackMessage do
157
157
  }.to post_to_slack.with_content_matching(/ABC123/)
158
158
  end
159
159
  end
160
+
161
+ # tests for actual sending methods? what would actually be useful?
160
162
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: slack_message
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Mastey
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-13 00:00:00.000000000 Z
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -66,10 +66,19 @@ files:
66
66
  - Gemfile
67
67
  - MIT-LICENSE
68
68
  - README.md
69
+ - docs/01_configuration.md
70
+ - docs/02_posting_a_message.md
71
+ - docs/03_message_dsl.md
72
+ - docs/04_editing_messages.md
73
+ - docs/05_deleting_messages.md
74
+ - docs/06_notifying_users.md
75
+ - docs/07_testing.md
76
+ - docs/index.md
69
77
  - lib/slack_message.rb
70
78
  - lib/slack_message/api.rb
71
79
  - lib/slack_message/configuration.rb
72
80
  - lib/slack_message/dsl.rb
81
+ - lib/slack_message/response.rb
73
82
  - lib/slack_message/rspec.rb
74
83
  - slack_message.gemspec
75
84
  - spec/slack_message_spec.rb