slack_message 3.0.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62f8b7feb3289a37b79cb887dc4e7846c037a8c72290138afe611682a066a884
4
- data.tar.gz: 73e391d721ace67ad31ae69b7a17eae3578d4f85a2bcd111a5cb1966b68c85cd
3
+ metadata.gz: 2b4cc88c84b2a958d75262b0d1fea97de8b7b694555159e4962a4a9fa16c55f6
4
+ data.tar.gz: 750afd5d7321952cc9dbf344d6db3659f85d916f988ca7ac3e56ec615cde4b34
5
5
  SHA512:
6
- metadata.gz: cb28febfda107047351955825d1cd84dafdf553147b9f0101573944f3d5290c6d3e84a4d1190e63888f6c8ba851ac70da9eb774f1a01a04372d19520b7df8779
7
- data.tar.gz: 3438883916204aec8a16a8dfa80b52506d4f640fc69161ad6e8efea1b5a136e97fc15f8b990c2529f3bbfb1f4380a989a0827cbe8f810af08a42bf3a2b6df382
6
+ metadata.gz: 7eb4393b8e51b899a65abe790f4e217522441274bc6cc418ea21e43257716d218ad87fbddfce2fd7521f4305840312967249f4faf20261f5f7b665ec01d55896
7
+ data.tar.gz: ce790defaa92aeea75683771a85ef827386a1275524ac6006139aa3db58e387797ae5f8b8623e6896f07c5af0e6fd881b2878bac1cadf1be398333ab20066a2c
@@ -11,7 +11,7 @@ jobs:
11
11
  strategy:
12
12
  matrix:
13
13
  os: [ubuntu-latest, macos-latest]
14
- ruby-version: [3.0, 2.7, 2.6, 2.5]
14
+ ruby-version: ['3.1', '3.0', '2.7', '2.6', '2.5']
15
15
  runs-on: ${{ matrix.os }}
16
16
 
17
17
  steps:
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.1.0] - TBD
4
+ - Methods from the calling context can now be called within a section block.
5
+
6
+ ## [3.0.2] - 2022-04-16
7
+ - Fix tests on ruby 3.0.
8
+ - More adjustments and additions to docs.
9
+ - Add warnings when overriding notification text and context block.
10
+
11
+ ## [3.0.1] - 2021-12-22
12
+ - Major overhaul of error handling and expansion on which errors trigger
13
+ friendly messages for users.
14
+ - More additions to the docs, and working github pages integration.
15
+ - It's my birthday!
16
+
3
17
  ## [3.0.0] - 2021-12-19
4
18
  - Return a more structured object from successful message sends.
5
19
  - Add the ability to edit or delete a message.
data/README.md CHANGED
@@ -12,13 +12,18 @@ SlackMessage.post_to('#general') do
12
12
  text "We did it @here! :thumbsup:"
13
13
  end
14
14
  ```
15
+
16
+ ### The Docs
15
17
 
16
- #### Posting
18
+ You'll find much more information about how to use SlackMessage by visiting
19
+ [the docs](https://jmmastey.github.io/slack_message).
20
+
17
21
 
18
- SlackMessage is able to build all kinds of rich messages for you, and has been
19
- a real joy to use for the author at least. To understand a bit more about the
20
- possibilities of blocks, you should play around with Slack's [Block Kit
21
- Builder](https://app.slack.com/block-kit-builder/). There are lots of options:
22
+ ### A Rich DSL Focused on Maintainability
23
+
24
+ SlackMessage is able to build all kinds of rich messages for you. It focuses on
25
+ writing code that looks similar to the output messages themselves, with as
26
+ little repetition and cruft as possible.
22
27
 
23
28
  ```ruby
24
29
  SlackMessage.post_to('#general') do
@@ -48,12 +53,7 @@ SlackMessage.post_to('#general') do
48
53
  end
49
54
  ```
50
55
 
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
-
56
- #### Opinionated Stances
56
+ ### Opinionated Stances
57
57
 
58
58
  This gem is intended to stay simple. Other Slack gems have lots of config
59
59
  options and abilities, which makes them powerful, but makes them a pain to use.
@@ -61,6 +61,8 @@ options and abilities, which makes them powerful, but makes them a pain to use.
61
61
  Accordingly, SlackMessage is developed with some strong opinions in mind:
62
62
 
63
63
  * SlackMessage has no dependencies. Your lockfile is enough of a mess already.
64
+ * A DSL that focuses on code that is easy to write, read, and maintain. Only
65
+ features that can be implemented with that in mind are included.
64
66
  * The code to build a message should look a lot like the message itself. Code
65
67
  that is simple to read and understand is a priority.
66
68
  * Webhooks are passé. Only Slack Apps are supported now.
@@ -71,28 +73,28 @@ Accordingly, SlackMessage is developed with some strong opinions in mind:
71
73
  to look it up as an email address.
72
74
  * A few little hacks on the block syntax, such as adding a `blank_line` (which
73
75
  doesn't exist in the API), or leading spaces.
74
- * Configuration is kept as simple as possible. But, as much heavy lifting as
75
- possible should occur just once via configuration and not on every call.
76
+ * Configuration is kept simple, with helpers for frequently reused bots.
76
77
 
77
- Some behaviors that are still planned but not yet added:
78
+ Some changes that are still planned or desired, but not yet added:
78
79
 
79
80
  * any interactive elements at all: https://api.slack.com/interactivity/handling
80
81
  * multiple recipients: https://api.slack.com/methods/conversations.open
81
- * more interesting return types for your message
82
- * richer text formatting (for instance, `ul` is currently a hack)
83
- * more mrkdwn syntax, like quotes or code blocks
84
- * more and better organized testing capability
82
+ * more mrkdwn syntax, like quotes or code blocks https://api.slack.com/reference/surfaces/formatting#line-breaks
83
+ * more and better organized testing capability (for scheduled messages, editing, deleting)
85
84
  * 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)
85
+ * easier way to dump / load message responses
86
+ * updated docs w/ links to BlockBuilder
87
87
 
88
- Contributing
89
- ------------
88
+ ### Contributing
90
89
 
91
- Contributions are very welcome. Fork, fix, submit pull. Since simplicity of API is a strong priority, so opening an issue to discuss possible interface changes would be wise.
90
+ Contributions are very welcome. Fork, fix, submit pull. Since simplicity of API
91
+ is a strong priority, so opening an issue to discuss possible interface changes
92
+ would be wise.
92
93
 
93
- Contribution is expected to conform to the [Contributor Covenant](https://github.com/jmmastey/slack_message/blob/master/CODE_OF_CONDUCT.md).
94
+ Contribution is expected to conform to the [Contributor
95
+ Covenant](https://github.com/jmmastey/slack_message/blob/master/CODE_OF_CONDUCT.md).
94
96
 
95
- License
96
- ------------
97
+ ### License
97
98
 
98
- This software is released under the [MIT License](https://github.com/jmmastey/slack_message/blob/master/MIT-LICENSE).
99
+ This software is released under the [MIT
100
+ License](https://github.com/jmmastey/slack_message/blob/master/MIT-LICENSE).
@@ -95,18 +95,18 @@ end
95
95
  You will now see warnings detailing all params sent to the API.
96
96
 
97
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
- }
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
110
  ```
111
111
 
112
112
  Note this includes data that is not included in `SlackMessage.build`.
@@ -101,7 +101,7 @@ end
101
101
 
102
102
  ### Scheduling a Message
103
103
 
104
- To schedule a message, simply provide a `at` parameter to your post. Provide
104
+ To schedule a message, simply provide an `at` parameter to your post. Provide
105
105
  either a time object that responds to `to_i`, or an integer that represents a
106
106
  [unix timestamp](https://en.wikipedia.org/wiki/Unix_time) for the time at which
107
107
  you want your message posted.
@@ -121,8 +121,12 @@ nor can they be scheduled more than 120 days into the future.
121
121
 
122
122
  ### Best Practices
123
123
 
124
- Talk about having coherent methods that post a message, rather than a block
125
- that includes lots of indirection or ternaries.
124
+ From experience, building messages with maintainability in mind is key. Adding
125
+ lots of flow control and indirection will undermine your ability to understand
126
+ and change messages later.
127
+
128
+ For simple messages, using implicit sections is perfectly fine. However, if you
129
+ intend to create several sections, it's usually better to just declare them.
126
130
 
127
131
  See the [API documentation for
128
132
  chat.postMessage](https://api.slack.com/methods/chat.postMessage) or
@@ -1,10 +1,15 @@
1
- ### The Message DSL
1
+ ## The Message DSL
2
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.
3
+ A pretty good number of the elements available in BlockKit are usable in
4
+ SlackMessage. There are also a few elements that haven't been implemented in
5
+ the official API, but are too useful to be missing.
4
6
 
5
- #### Basic Text
7
+ ### Basic Text
6
8
 
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.
9
+ While BlockKit officially requires that any elements are contained within a
10
+ section element, that requirement is relaxed in SlackMessage. If you don't
11
+ specify a section, one will silently be created to encapsulate your code.
12
+ That's the secret behind the most basic messages in these docs.
8
13
 
9
14
  ```ruby
10
15
  SlackMessage.build do
@@ -108,7 +113,51 @@ end
108
113
  Note that between the two newlines in the above example is a unicode emspace,
109
114
  which the API will respect as a line worth rendering.
110
115
 
111
- #### Buttons
116
+ ### Explicitly Declared Sections
117
+
118
+ Adding more sections is trivial. Simply declare each section and it will be
119
+ separated in the rendered message. This can often occur when looping.
120
+
121
+ ```ruby
122
+ SlackMessage.build do
123
+ pet_types.each do |type, breeds|
124
+ section do
125
+ text "*#{type}:* #{breeds.join(", ")}"
126
+ end
127
+ end
128
+ end
129
+ ```
130
+
131
+ It can also be useful to add a visual divider (similar to an `hr` in HTML)
132
+ between sections. To add one of these, use the `divider` helper. You can also
133
+ add a divider at the end of all the sections, but it often looks silly.
134
+
135
+ ```ruby
136
+ SlackMessage.build do
137
+ section do
138
+ text "*Topsiders:* Emily, Elsie, Derick"
139
+ end
140
+
141
+ divider
142
+
143
+ section do
144
+ text "*Undergrounders:* Kristina, Lauren, Different Emily"
145
+ end
146
+ end
147
+
148
+ # => [
149
+ # {:type=>"section", :text=>{:type=>"mrkdwn", :text=>"*Topsiders:* Emily, Elsie, Derick"}},
150
+ # {:type=>"divider"},
151
+ # {:type=>"section", :text=>{:type=>"mrkdwn", :text=>"*Undergrounders:* Kristina, Lauren, Different Emily"}}
152
+ # ]
153
+ ```
154
+
155
+ Note that a divider can only occur between sections, not within a single
156
+ section. Because of how implicit sections are built, it may look like this
157
+ works for simple messages. But, you may have troubles when you start adding
158
+ more complicated elements to your messages.
159
+
160
+ ### Buttons
112
161
 
113
162
  BlockKit allows you to specify a button to the right of a section / block. That
114
163
  button will be aligned outside the normal space for a section, and is meant to
@@ -132,10 +181,10 @@ end
132
181
  Slack allows three styles for buttons: `default`, `primary`, and `danger`.
133
182
  These correspond to gray, green and red buttons respectively. If not specified,
134
183
  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.
184
+ confusing when there is a style specifically named default, but in my
185
+ experience, a colorful button is way more common.
137
186
 
138
- You can override the button style by specifying the style with your link button.
187
+ You can override button style by specifying the style with your link button.
139
188
 
140
189
  ```ruby
141
190
  SlackMessage.build do
@@ -152,62 +201,114 @@ end
152
201
  # :style=>:danger}}]
153
202
  ```
154
203
 
155
- #### Lists and List Items
204
+ ### Ordered and Unordered Lists
156
205
 
157
- - list_item, ul, ol
206
+ The Slack API doesn't have native support for HTML-style ordered and unordered
207
+ lists, but there are convenience methods in SlackMessage to render a close
208
+ approximation.
158
209
 
159
- ### Including Multiple Sections
210
+ ```ruby
211
+ SlackMessage.build do
212
+ section do
213
+ text '*Pet Goodness Tiers*'
214
+
215
+ ol([
216
+ 'tiny pigs',
217
+ 'reptiles',
218
+ 'dogs',
219
+ 'cats',
220
+ ])
221
+ end
160
222
 
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.
223
+ section do
224
+ text '_voted by_'
225
+ ul(['Joe', 'Emily', 'Sophia', 'Matt'])
226
+ end
227
+ end
228
+ ```
229
+
230
+ Because Slack automatically collapses leading whitespace, indention of lists is
231
+ handled using unicode emspaces. Bullets for unordered lists are also unicode
232
+ characters to avoid being read as markdown.
233
+
234
+ ### List Items (e.g. HTML dt & dd)
235
+
236
+ When trying to represent title / value lists, you can use the "list item" block
237
+ type to pass a set of values. Slack does not allow you to customize how many
238
+ items are shown per line, so you'll just have to work with it.
163
239
 
164
240
  ```ruby
165
241
  SlackMessage.build do
166
- pet_types.each do |type, breeds|
167
- section do
168
- text "*#{type}:* #{breeds.join(", ")}"
169
- end
170
- end
242
+ text 'Import results are available!'
243
+
244
+ list_item 'Import Date', Date.today.to_s
245
+ list_item 'Items Imported', 55_000
246
+ list_item 'Errors', 23
247
+ list_item 'Bad Values', errors.map(&:to_s)
171
248
  end
172
249
  ```
173
250
 
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.
251
+ ### Images and Accessory Images
252
+
253
+ There are two main types of images in Slack: the
254
+ [image](https://imgur.com/XEUap1r) and the [accessory
255
+ image](https://imgur.com/zuhjBDq). In short, `accessory_image` is part of a
256
+ section itself and is shown alongside an existing block, while `image` is shown
257
+ as its own top-level element.
258
+
259
+ Accordingly, an accessory image should be used within a section. An accessory
260
+ image can accept alt-text, which is also a best practice, for usability
261
+ reasons.
177
262
 
178
263
  ```ruby
179
264
  SlackMessage.build do
180
265
  section do
181
- text "*Topsiders:* Emily, Elsie, Derick"
266
+ text 'Looks like the coffee machine is empty.'
267
+ accessory_image 'https://your.com/empty_coffee_logo.jpg', alt_text: 'logo of a forlorn coffee cup'
182
268
  end
269
+ end
270
+ ```
183
271
 
184
- divider
272
+ Only one accessory image can be used per section, and declaring more will issue
273
+ a warning and simply override the previous accessory image.
185
274
 
275
+ By contrast, `image` can be used many times, and will create a new top-level
276
+ section for each call. Image accepts alt-text, but also a title, which will
277
+ be displayed above the image (along with an icon to collapse the image).
278
+
279
+ ```ruby
280
+ SlackMessage.build do
186
281
  section do
187
- text "*Undergrounders:* Kristina, Lauren, Different Emily"
282
+ text 'Most Recent Tiny Pig Images'
188
283
  end
189
- end
190
284
 
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
- # ]
285
+ image 'https://your.com/employee_pets/best/pig_1.jpg', alt_text: 'a tiny pig eating ice cream', title: 'Spider Pig'
286
+ image 'https://your.com/employee_pets/best/pig_2.jpg', alt_text: 'a tiny pig smiling mischeviously', title: 'Porkeypine'
287
+ image 'https://your.com/employee_pets/best/pig_3.jpg', alt_text: 'a tiny pig with wellies and an umbrella', title: 'Albert Sweinstein'
288
+ end
196
289
  ```
197
290
 
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.
291
+ Sectionless messages can also use `accessory_image`, but this can get confusing
292
+ since they could potentially be interspersed with top-level images, so I
293
+ wouldn't recommend it.
202
294
 
203
- ### Images
204
- - image, accessory_image
295
+ ```ruby
296
+ # the difference between these two image styles is confusing, and the call
297
+ # to `image` closed the current section and started a new one.
298
+ SlackMessage.build do
299
+ text 'Looks like the coffee machine is empty.'
300
+ accessory_image 'https://your.com/empty_coffee_logo.jpg'
301
+
302
+ text '*Most Recent Coffee Maker Surveillance Photo*'
303
+ image 'https://your.com/surveillance/coffee/current.jpg'
304
+ end
305
+ ```
205
306
 
206
307
  ### Footers (Context)
207
308
 
208
309
  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.
310
+ will be rendered in italics and small text. It can support both links and
311
+ emoji, and is useful for providing minor details for your message.
211
312
 
212
313
  ```ruby
213
314
  SlackMessage.build do
@@ -225,15 +326,16 @@ end
225
326
  # }]
226
327
  ```
227
328
 
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.
329
+ Context does not belong to a section, and only one can be added to your entire
330
+ slack message. Specifying another `context` will issue a warning and overwrite
331
+ any previous call.
230
332
 
231
333
  ### Bot Customization
232
334
 
233
335
  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.
336
+ the Slack app whose API key you configured. As seen before, it's possible to
337
+ override those default names and icons in configuration. However, it can also
338
+ be customized per-message.
237
339
 
238
340
  ```ruby
239
341
  SlackMessage.build do
@@ -247,9 +349,9 @@ end
247
349
  ```
248
350
 
249
351
  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.
352
+ To view the change to the message payload from these calls, use `debug` mode.
251
353
 
252
- The `bot_icon` can be specified as either an emoji (`:example:`), or a URL
354
+ The `bot_icon` can be specified as either an emoji (`:shipit:`), or a URL
253
355
  pointing to an image (`http://mysite.com/shipit.png`). Any other value seems to
254
356
  cause an error.
255
357
 
@@ -276,7 +378,9 @@ end
276
378
  ```
277
379
 
278
380
  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.
381
+ you will need to enable debugging to see how it changes what is sent to the
382
+ API. Only one custom notification is allowed, so further calls will issue a
383
+ warning and replace the previous notification text.
280
384
 
281
385
  ---
282
386
 
@@ -1,4 +1,4 @@
1
- ### Updating a Previous Message
1
+ ## Updating a Previous Message
2
2
 
3
3
  After you've posted a message, you may want to edit it later. Interactive bots,
4
4
  for instance, may want to repeatedly update a message.
@@ -52,12 +52,11 @@ class SomeWorker < ApplicationWorker
52
52
  end
53
53
  ```
54
54
 
55
- #### Storing Response Objects for Later
55
+ ### Storing Response Objects for Later
56
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.
57
+ Since updates are likely to occur long after you post the original message, you
58
+ may want to persist a reference to the message until you need to update it
59
+ later. As one option, you could serialize the response object for later.
61
60
 
62
61
  ```ruby
63
62
  # initially
@@ -74,7 +73,7 @@ SlackMessage.update(message) do
74
73
  end
75
74
  ```
76
75
 
77
- #### Updating Scheduled Messages
76
+ ### Updating Scheduled Messages
78
77
 
79
78
  Sadly, there's currently no way to edit a scheduled message. You'll receive an
80
79
  error if you attempt to call `update` on a scheduled message.
@@ -1,26 +1,26 @@
1
- ### Deleting Messages
1
+ ## Deleting Messages
2
2
 
3
3
  Deleting a message is much like editing a message, only simpler. Just like when
4
4
  you edit a message, you'll need a reference to the message you posted.
5
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
6
  ```ruby
11
7
  message = SlackMessage.post_to('#general') do
12
8
  text "Testing: #{SLACK_SECRET_KEY}"
13
9
  end
14
10
  ```
15
11
 
16
- Now you can simply call the `delete` method to make up for your mistakes.
12
+ Then you can simply call the `delete` method to make up for your mistakes.
17
13
 
18
14
  ```ruby
19
15
  SlackMessage.delete(message)
20
16
  ```
21
17
 
18
+ *Important Note: It's not possible to delete a message sent directly to a user.
19
+ It's also not possible to delete a scheduled message once it's already posted.
20
+ Don't send anything you don't want your boss to read.*
21
+
22
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.
23
+ database to be removed at a later date.
24
24
 
25
25
  ```ruby
26
26
  # initially
@@ -47,7 +47,7 @@ SlackMessage.post_to('hello@joemastey.com') do
47
47
  end
48
48
  ```
49
49
 
50
- #### Using @channel or @here
50
+ ### Using @channel or @here
51
51
 
52
52
  Not really a feature, but Slack will respect usage of `@here` and `@channel`.
53
53
 
data/docs/_config.yml ADDED
@@ -0,0 +1,6 @@
1
+ plugins:
2
+ - jekyll-relative-links
3
+ relative_links:
4
+ enabled: true
5
+ collections: true
6
+ theme: jekyll-theme-midnight
data/docs/index.md CHANGED
@@ -1,7 +1,53 @@
1
- * [Configuration](https://jmmastey.github.io/slack_message/01_configuration)
1
+ * [Getting Started / Configuration](https://jmmastey.github.io/slack_message/01_configuration)
2
2
  * [Posting a Message](https://jmmastey.github.io/slack_message/02_posting_a_message)
3
3
  * [The SlackMessage DSL](https://jmmastey.github.io/slack_message/03_message_dsl)
4
4
  * [Editing Messages](https://jmmastey.github.io/slack_message/04_editing_messages)
5
5
  * [Deleting Messages](https://jmmastey.github.io/slack_message/05_deleting_messages)
6
6
  * [Mentions / Notifying Users](https://jmmastey.github.io/slack_message/06_notifying_users)
7
7
  * [Testing](https://jmmastey.github.io/slack_message/07_testing)
8
+
9
+ SlackMessage is a gem that makes it easy to send messages to Slack from your
10
+ application. _Really_ easy.
11
+
12
+ ```ruby
13
+ SlackMessage.post_to('#general') do
14
+ text "We did it @here! :thumbsup:"
15
+ end
16
+ ```
17
+
18
+ And not just simple messages. You can compose complicated messages quickly in a
19
+ DSL that's focused on usability and maintainability. It can be tough to
20
+ maintain code in other similar gems, but not here.
21
+
22
+ ```ruby
23
+ SlackMessage.post_to('hello@joemastey.com') do
24
+ section do
25
+ text "A job has generated some output for you to review."
26
+ text 'And More' * 10
27
+ link_button "See Results", "https://google.com"
28
+ end
29
+
30
+ section do
31
+ text ":unlock-new: New Data Summary"
32
+
33
+ list_item "Date", "09/05/2021"
34
+ list_item "Total Imported", 45_004
35
+ list_item "Total Errors", 5
36
+ end
37
+
38
+ divider
39
+
40
+ section do
41
+ text "See more here: #{link('result', 'https://google.com')}"
42
+ end
43
+
44
+ context "Kicked off by <hello@joemastey.com> at **9:05am**"
45
+ end
46
+ ```
47
+
48
+ It has no dependencies and minimal configuration needs, so you can get up and
49
+ running quickly.
50
+
51
+ ---
52
+
53
+ Next: [Get Started](https://jmmastey.github.io/slack_message/01_configuration)
@@ -10,6 +10,10 @@ module SlackMessage::Api
10
10
  raise ArgumentError, "Tried to find profile by invalid email address '#{email}'"
11
11
  end
12
12
 
13
+ if SlackMessage::Configuration.debugging?
14
+ warn [email, profile].inspect
15
+ end
16
+
13
17
  response = look_up_user_by_email(email, profile)
14
18
 
15
19
  if response.code != "200"
@@ -24,14 +28,7 @@ module SlackMessage::Api
24
28
  raise SlackMessage::ApiError, "Unable to parse JSON response from Slack API\n#{response.body}"
25
29
  end
26
30
 
27
- if payload.include?("error") && payload["error"] == "invalid_auth"
28
- raise SlackMessage::ApiError, "Received an error because your authentication token isn't properly configured."
29
- elsif payload.include?("error") && payload["error"] == "users_not_found"
30
- raise SlackMessage::ApiError, "Couldn't find a user with the email '#{email}'."
31
- elsif payload.include?("error")
32
- raise SlackMessage::ApiError, "Received error response from Slack during user lookup:\n#{response.body}"
33
- end
34
-
31
+ SlackMessage::ErrorHandling.raise_user_lookup_errors(response, target, profile)
35
32
  payload["user"]["id"]
36
33
  end
37
34
 
@@ -69,26 +66,8 @@ module SlackMessage::Api
69
66
  end
70
67
 
71
68
  response = post_message(profile, params)
72
- body = JSON.parse(response.body)
73
- error = body.fetch("error", "")
74
-
75
- # TODO: if a scheduled message w/ a short timer is "time_in_past", warn the user?
76
-
77
- # let's try to be helpful about error messages
78
- if ["token_revoked", "token_expired", "invalid_auth", "not_authed"].include?(error)
79
- raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' is wrong."
80
- elsif ["no_permission", "ekm_access_denied"].include?(error)
81
- raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' isn't allowed to post messages."
82
- elsif error == "channel_not_found"
83
- raise SlackMessage::ApiError, "Tried to send Slack message to non-existent channel or user '#{target}'"
84
- elsif error == "invalid_arguments"
85
- raise SlackMessage::ApiError, "Tried to send Slack message with invalid payload."
86
- elsif response.code == "302"
87
- raise SlackMessage::ApiError, "Got 302 response while posting to Slack. Check your API key for profile '#{profile[:handle]}'."
88
- elsif response.code != "200"
89
- raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
90
- end
91
69
 
70
+ SlackMessage::ErrorHandling.raise_post_response_errors(response, params, profile)
92
71
  SlackMessage::Response.new(response, profile[:handle])
93
72
  end
94
73
 
@@ -97,7 +76,7 @@ module SlackMessage::Api
97
76
  channel: message.channel,
98
77
  ts: message.timestamp,
99
78
  blocks: payload.render,
100
- text: payload.custom_notification # TODO: ???
79
+ text: payload.custom_notification
101
80
  }
102
81
 
103
82
  if params[:blocks].length == 0
@@ -112,8 +91,7 @@ module SlackMessage::Api
112
91
  body = JSON.parse(response.body)
113
92
  error = body.fetch("error", "")
114
93
 
115
- # TODO: error messaging
116
-
94
+ SlackMessage::ErrorHandling.raise_post_response_errors(response, message, profile)
117
95
  SlackMessage::Response.new(response, profile[:handle])
118
96
  end
119
97
 
@@ -130,10 +108,13 @@ module SlackMessage::Api
130
108
  }
131
109
  end
132
110
 
133
- response = delete_message(profile, params)
111
+ if SlackMessage::Configuration.debugging?
112
+ warn params.inspect
113
+ end
134
114
 
135
- # TODO error handling (incl for already scheduled-and-sent messages)
115
+ response = delete_message(profile, params)
136
116
 
117
+ SlackMessage::ErrorHandling.raise_delete_response_errors(response, message, profile)
137
118
  response
138
119
  end
139
120
 
@@ -63,6 +63,13 @@ class SlackMessage::Dsl
63
63
 
64
64
  text = self.enrich_text(text)
65
65
 
66
+
67
+ previous_context = @body.find { |element| element[:type] && element[:type] == "context" }
68
+ if previous_context
69
+ previous_text = previous_context[:elements].first[:text]
70
+ warn "WARNING: Overriding previous context in section: #{previous_text}"
71
+ end
72
+
66
73
  @body.push({ type: "context", elements: [{
67
74
  type: "mrkdwn", text: text
68
75
  }]})
@@ -94,6 +101,10 @@ class SlackMessage::Dsl
94
101
  end
95
102
 
96
103
  def notification_text(msg)
104
+ if @custom_notification
105
+ warn "WARNING: Overriding previous custom notification text: #{@custom_notification}"
106
+ end
107
+
97
108
  @custom_notification = msg
98
109
  end
99
110
 
@@ -261,6 +272,10 @@ class SlackMessage::Dsl
261
272
 
262
273
  body
263
274
  end
275
+
276
+ def method_missing(meth, *args, &blk)
277
+ @parent.send meth, *args, &blk
278
+ end
264
279
  end
265
280
 
266
281
  class List
@@ -0,0 +1,124 @@
1
+ class SlackMessage::ErrorHandling
2
+ PERMISSIONS_ERRORS = ["token_revoked", "token_expired", "invalid_auth", "not_authed",
3
+ "team_access_not_granted", "no_permission", "missing_scope",
4
+ "not_allowed_token_type", "ekm_access_denied"]
5
+
6
+ def self.raise_post_response_errors(response, params, profile)
7
+ body = JSON.parse(response.body)
8
+ error = body.fetch("error", "")
9
+
10
+ if ["invalid_blocks", "invalid_blocks_format"].include?(error)
11
+ raise SlackMessage::ApiError, "Couldn't send Slack message because the serialized message had an invalid format"
12
+ elsif error == "channel_not_found"
13
+ raise SlackMessage::ApiError, "Tried to send Slack message to non-existent channel or user '#{params[:channel]}'"
14
+
15
+ # scheduling messages
16
+ elsif error == "invalid_time"
17
+ raise SlackMessage::ApiError, "Couldn't schedule Slack message because you requested an invalid time '#{params[:post_at]}'"
18
+ elsif error == "time_in_past"
19
+ raise SlackMessage::ApiError, "Couldn't schedule Slack message because you requested a time in the past (or too close to now) '#{params[:post_at]}'"
20
+ elsif error == "time_too_far"
21
+ raise SlackMessage::ApiError, "Couldn't schedule Slack message because you requested a time more than 120 days in the future '#{params[:post_at]}'"
22
+
23
+
24
+ elsif PERMISSIONS_ERRORS.include?(error)
25
+ raise SlackMessage::ApiError, "Couldn't send Slack message because the API key for profile '#{profile[:handle]}' is wrong, or the app has insufficient permissions (#{error})"
26
+ elsif error == "message_too_long"
27
+ raise SlackMessage::ApiError, "Tried to send Slack message, but the message was too long"
28
+ elsif error == "invalid_arguments"
29
+ raise SlackMessage::ApiError, "Tried to send Slack message with invalid payload"
30
+ elsif ["rate_limited", "ratelimited"].include?(error)
31
+ raise SlackMessage::ApiError, "Couldn't send Slack message because you've reached your rate limit"
32
+ elsif response.code == "302"
33
+ raise SlackMessage::ApiError, "Got 302 response while posting to Slack. Check your API key for profile '#{profile[:handle]}'"
34
+ elsif response.code != "200"
35
+ raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
36
+ elsif !(error.nil? || error == "")
37
+ raise SlackMessage::ApiError, "Received error response from Slack during message posting:\n#{response.body}"
38
+ end
39
+ end
40
+
41
+ def self.raise_update_response_errors(response, message, profile)
42
+ body = JSON.parse(response.body)
43
+ error = body.fetch("error", "")
44
+
45
+ if ["invalid_blocks", "invalid_blocks_format"].include?(error)
46
+ raise SlackMessage::ApiError, "Couldn't update Slack message because the serialized message had an invalid format"
47
+ elsif error == "channel_not_found"
48
+ raise SlackMessage::ApiError, "Tried to update Slack message to non-existent channel or user '#{message.channel}'"
49
+
50
+ elsif error == "message_not_found"
51
+ raise SlackMessage::ApiError, "Tried to update Slack message, but the message wasn't found (timestamp '#{message.timestamp}' for channel '#{message.channel}'"
52
+ elsif error == "cant_update_message"
53
+ raise SlackMessage::ApiError, "Couldn't update message because the message type isn't able to be updated, or #{profile[:handle]} isn't allowed to update it"
54
+ elsif error == "edit_window_closed"
55
+ raise SlackMessage::ApiError, "Couldn't update message because it's too old"
56
+
57
+
58
+ elsif PERMISSIONS_ERRORS.include?(error)
59
+ raise SlackMessage::ApiError, "Couldn't update Slack message because the API key for profile '#{profile[:handle]}' is wrong, or the app has insufficient permissions (#{error})"
60
+ elsif error == "message_too_long"
61
+ raise SlackMessage::ApiError, "Tried to update Slack message, but the message was too long"
62
+ elsif error == "invalid_arguments"
63
+ raise SlackMessage::ApiError, "Tried to update Slack message with invalid payload"
64
+ elsif ["rate_limited", "ratelimited"].include?(error)
65
+ raise SlackMessage::ApiError, "Couldn't update Slack message because you've reached your rate limit"
66
+ elsif response.code == "302"
67
+ raise SlackMessage::ApiError, "Got 302 response while updating a message. Check your API key for profile '#{profile[:handle]}'"
68
+ elsif response.code != "200"
69
+ raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
70
+ elsif !(error.nil? || error == "")
71
+ raise SlackMessage::ApiError, "Received error response from Slack during message update:\n#{response.body}"
72
+ end
73
+ end
74
+
75
+ def self.raise_delete_response_errors(response, message, profile)
76
+ body = JSON.parse(response.body)
77
+ error = body.fetch("error", "")
78
+
79
+ if error == "channel_not_found"
80
+ raise SlackMessage::ApiError, "Tried to delete Slack message in non-existent channel '#{message.channel}'"
81
+
82
+ elsif error == "invalid_scheduled_message_id"
83
+ raise SlackMessage::ApiError, "Can't delete message because the ID was invalid, or the message has already posted (#{message.scheduled_message_id})"
84
+ elsif error == "message_not_found"
85
+ raise SlackMessage::ApiError, "Tried to delete Slack message, but the message wasn't found (timestamp '#{message.timestamp}' for channel '#{message.channel}')"
86
+ elsif error == "cant_delete_message"
87
+ raise SlackMessage::ApiError, "Can't delete message because '#{profile[:handle]}' doesn't have permission to"
88
+ elsif error == "compliance_exports_prevent_deletion"
89
+ raise SlackMessage::ApiError, "Can't delete message because team compliance settings prevent it"
90
+
91
+
92
+ elsif PERMISSIONS_ERRORS.include?(error)
93
+ raise SlackMessage::ApiError, "Couldn't delete Slack message because the API key for profile '#{profile[:handle]}' is wrong, or the app has insufficient permissions (#{error})"
94
+ elsif ["rate_limited", "ratelimited"].include?(error)
95
+ raise SlackMessage::ApiError, "Couldn't delete Slack message because you've reached your rate limit"
96
+ elsif response.code == "302"
97
+ raise SlackMessage::ApiError, "Got 302 response while deleting a message. Check your API key for profile '#{profile[:handle]}'"
98
+ elsif response.code != "200"
99
+ raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
100
+ elsif !(error.nil? || error == "")
101
+ raise SlackMessage::ApiError, "Received error response from Slack during message delete:\n#{response.body}"
102
+ end
103
+ end
104
+
105
+ def self.raise_user_lookup_response_errors(payload)
106
+ error = payload["error"]
107
+
108
+ if error == "users_not_found"
109
+ raise SlackMessage::ApiError, "Couldn't find a user with the email '#{email}'"
110
+
111
+
112
+ elsif PERMISSIONS_ERRORS.include?(error)
113
+ raise SlackMessage::ApiError, "Couldn't look up users because the API key for profile '#{profile[:handle]}' is wrong, or the app has insufficient permissions (#{error})"
114
+ elsif error
115
+ raise SlackMessage::ApiError, "Received error response from Slack during user lookup:\n#{response.body}"
116
+ elsif response.code == "302"
117
+ raise SlackMessage::ApiError, "Got 302 response during user lookup. Check your API key for profile '#{profile[:handle]}'"
118
+ elsif response.code != "200"
119
+ raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
120
+ elsif !(error.nil? || error == "")
121
+ raise SlackMessage::ApiError, "Received error response from Slack during user lookup:\n#{response.body}"
122
+ end
123
+ end
124
+ end
@@ -14,12 +14,15 @@ require 'rspec/mocks'
14
14
  # it can be cleaned up properly.
15
15
  #
16
16
 
17
- # TODO: testing for scheduled messages, editing and deleting
17
+ # TODO: test helpers for scheduled messages, editing and deleting, and
18
+ # notification text. And realistically, overhaul all this.
18
19
 
19
20
  module SlackMessage::RSpec
20
21
  extend RSpec::Matchers::DSL
21
22
 
22
23
  @@listeners = []
24
+ @@custom_response = {}
25
+ @@response_code = '200'
23
26
 
24
27
  def self.register_expectation_listener(expectation_instance)
25
28
  @@listeners << expectation_instance
@@ -29,6 +32,11 @@ module SlackMessage::RSpec
29
32
  @@listeners.delete(expectation_instance)
30
33
  end
31
34
 
35
+ def self.reset_custom_responses
36
+ @@custom_response = {}
37
+ @@response_code = '200'
38
+ end
39
+
32
40
  FauxResponse = Struct.new(:code, :body)
33
41
 
34
42
  def self.included(_)
@@ -38,22 +46,24 @@ module SlackMessage::RSpec
38
46
  listener.record_call(params.merge(profile: profile))
39
47
  end
40
48
 
41
- response = {"ok"=>true,
42
- "channel"=>"D12345678",
43
- "ts"=>"1635863996.002300",
44
- "message"=>
45
- {"type"=>"message", "subtype"=>"bot_message",
46
- "text"=>"foo",
47
- "ts"=>"1635863996.002300",
48
- "username"=>"SlackMessage",
49
- "icons"=>{"emoji"=>":successkid:"},
50
- "bot_id"=>"B1234567890",
51
- "blocks"=>
52
- [{"type"=>"section",
53
- "block_id"=>"hAh7",
54
- "text"=>{"type"=>"mrkdwn", "text"=>"foo", "verbatim"=>false}}]}}
55
-
56
- return FauxResponse.new('200', response.to_json)
49
+ response = {
50
+ "ok" => true,
51
+ "channel" => "C12345678",
52
+ "ts" => "1635863996.002300",
53
+ "message" => { "type"=>"message", "subtype"=>"bot_message",
54
+ "text"=>"foo",
55
+ "ts"=>"1635863996.002300",
56
+ "username"=>"SlackMessage",
57
+ "icons"=>{"emoji"=>":successkid:"},
58
+ "bot_id"=>"B1234567890",
59
+ "blocks"=> [{"type"=>"section",
60
+ "block_id"=>"hAh7",
61
+ "text"=>{"type"=>"mrkdwn", "text"=>"foo", "verbatim"=>false}}
62
+ ]
63
+ }
64
+ }.merge(@@custom_response).to_json
65
+
66
+ return FauxResponse.new(@@response_code, response)
57
67
  end
58
68
 
59
69
  SlackMessage::Api.undef_method(:look_up_user_by_email)
@@ -63,6 +73,18 @@ module SlackMessage::RSpec
63
73
  end
64
74
  end
65
75
 
76
+ def self.respond_with(response = {}, code: '200')
77
+ raise ArgumentError, "custom response must be a hash" unless response.is_a? Hash
78
+
79
+ @@custom_response = response
80
+ @@response_code = code
81
+ end
82
+
83
+ def self.reset_mock_response
84
+ @@custom_response = {}
85
+ @@response_code = '200'
86
+ end
87
+
66
88
  # w/ channel
67
89
  matcher :post_slack_message_to do |expected|
68
90
  match do |actual|
data/lib/slack_message.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module SlackMessage
2
2
  require 'slack_message/response'
3
3
  require 'slack_message/dsl'
4
+ require 'slack_message/error_handling'
4
5
  require 'slack_message/api'
5
6
  require 'slack_message/configuration'
6
7
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'slack_message'
3
- gem.version = "3.0.0"
3
+ gem.version = "3.1.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'
@@ -1,8 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe SlackMessage do
4
+
4
5
  describe "DSL" do
5
6
  describe "#build" do
7
+ def outer_method
8
+ "foo"
9
+ end
10
+
6
11
  it "renders some JSON" do
7
12
  SlackMessage.configure do |config|
8
13
  config.clear_profiles!
@@ -12,11 +17,17 @@ RSpec.describe SlackMessage do
12
17
  expected_output = [
13
18
  { type: "section",
14
19
  text: { text: "foo", type: "mrkdwn" }
15
- }
20
+ },
21
+ { type: "section",
22
+ text: { text: "foo", type: "mrkdwn" }
23
+ },
16
24
  ]
17
25
 
18
26
  output = SlackMessage.build do
19
- text "foo"
27
+ text outer_method()
28
+ section do
29
+ text outer_method()
30
+ end
20
31
  end
21
32
 
22
33
  expect(output).to eq(expected_output)
@@ -49,6 +60,13 @@ RSpec.describe SlackMessage do
49
60
  end
50
61
  end
51
62
 
63
+ fit do
64
+ SlackMessage.build do
65
+ notification_text 'one'
66
+ notification_text 'two'
67
+ end
68
+ end
69
+
52
70
  it "can assert expectations against posts" do
53
71
  expect {
54
72
  SlackMessage.post_to('#lieutenant') { text "foo" }
@@ -111,10 +129,6 @@ RSpec.describe SlackMessage do
111
129
  }.to post_slack_message_with_icon_matching(/thisperson/)
112
130
  end
113
131
 
114
- it "lets you assert notification text" do
115
- # TODO :|
116
- end
117
-
118
132
  it "can assert more generally too tbh" do
119
133
  expect {
120
134
  SlackMessage.post_to('#general') { text "foo" }
@@ -123,8 +137,6 @@ RSpec.describe SlackMessage do
123
137
  end
124
138
 
125
139
  describe "API convenience" do
126
- let(:profile) { SlackMessage::Configuration.profile(:default) }
127
-
128
140
  before do
129
141
  SlackMessage.configure do |config|
130
142
  config.clear_profiles!
@@ -158,5 +170,57 @@ RSpec.describe SlackMessage do
158
170
  end
159
171
  end
160
172
 
161
- # tests for actual sending methods? what would actually be useful?
173
+ describe "error handling" do
174
+ before do
175
+ SlackMessage.configure do |config|
176
+ config.clear_profiles!
177
+ config.add_profile(name: 'default profile', api_token: 'abc123')
178
+ config.add_profile(:schmoebot, name: 'Schmoe', api_token: 'abc123', icon: ':schmoebot:', default_channel: '#schmoes')
179
+ end
180
+ end
181
+
182
+ after do
183
+ SlackMessage::RSpec.reset_mock_response
184
+ end
185
+
186
+ it "raises nice error messages when API methods return errors" do
187
+ SlackMessage::RSpec.respond_with({'error' => 'nuffin'})
188
+
189
+ expect {
190
+ SlackMessage.post_to('#general') { text 'nuh uh' }
191
+ }.to raise_error(SlackMessage::ApiError)
192
+
193
+ expect {
194
+ SlackMessage.post_as(:schmoebot) { text 'nuh uh' }
195
+ }.to raise_error(SlackMessage::ApiError)
196
+ end
197
+
198
+ it "raises for redirects" do
199
+ SlackMessage::RSpec.respond_with(code: '302')
200
+
201
+ expect {
202
+ SlackMessage.post_to('#general') { text 'nuh uh' }
203
+ }.to raise_error(SlackMessage::ApiError)
204
+ end
205
+
206
+ it "raises errors w/ updates too" do
207
+ message = SlackMessage.post_to('#general') { text 'nuh uh' }
208
+
209
+ SlackMessage::RSpec.respond_with({'error' => 'bad choice'})
210
+
211
+ expect {
212
+ SlackMessage.update(message) { text 'nuh uh' }
213
+ }.to raise_error(SlackMessage::ApiError)
214
+ end
215
+
216
+ it "even raises errors during deletes" do
217
+ message = SlackMessage.post_to('#general') { text 'nuh uh' }
218
+
219
+ SlackMessage::RSpec.respond_with({'error' => 'bad choice'})
220
+
221
+ expect {
222
+ SlackMessage.delete(message) { text 'nuh uh' }
223
+ }.to raise_error(SlackMessage::ApiError)
224
+ end
225
+ end
162
226
  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: 3.0.0
4
+ version: 3.1.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-20 00:00:00.000000000 Z
11
+ date: 2022-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -73,11 +73,13 @@ files:
73
73
  - docs/05_deleting_messages.md
74
74
  - docs/06_notifying_users.md
75
75
  - docs/07_testing.md
76
+ - docs/_config.yml
76
77
  - docs/index.md
77
78
  - lib/slack_message.rb
78
79
  - lib/slack_message/api.rb
79
80
  - lib/slack_message/configuration.rb
80
81
  - lib/slack_message/dsl.rb
82
+ - lib/slack_message/error_handling.rb
81
83
  - lib/slack_message/response.rb
82
84
  - lib/slack_message/rspec.rb
83
85
  - slack_message.gemspec