slack_message 1.8.1 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94e7433c5374c318cd955ad43f6a02705b62876498253c4e7587a7ba5ae5277f
4
- data.tar.gz: 0ca96c1266c9dc55af30c6df7f07e2ea86a64bc5fad99258a10efe10e4127e9b
3
+ metadata.gz: '008fdfc2e46435b3bb49aae67e30b3ef354ca0c923c935c918088ee25eec7bda'
4
+ data.tar.gz: 2fe7d85c853361d12f02f58d19e4bef43ffc81d3683ef5f8b3776657eb86c5ae
5
5
  SHA512:
6
- metadata.gz: 2f4c15c91c6a7741c1f387b820cc36689cd7530233e9582fb3db73407d9f39eb69656a071b65789cd33ec359d5fa1478efe212336a3834202c30f96269885895
7
- data.tar.gz: ed86a645c481c0c91510acedc5b0abd2bf3923f9ea33e9a23671db0e107005c07f64a1dfc52f4f5f47f826630712756bd5ce43ddfd06622a10ed24afa7731465
6
+ metadata.gz: 1c5cc5b43f1813b55b6d1433da88ca661bc692a21785ffad1c2e13c459f0d2b4cb6bbf6251f5e774924a73e8e60f8c2517deaf71a0a1f74f57b2094e822e82ac
7
+ data.tar.gz: 0bb8d97ef6338703e601bd964405c090823ceded7cfa3045d42618a575397ca057591f6aad1cad6d1a74a7f4b877de11bced2d566424c75b53d80d2ce018cda0
data/CHANGELOG.md CHANGED
@@ -1,6 +1,34 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [2.2.1] - 2021-11-20
4
+ - Trying to fetch user ID for a string that isn't email-like raises an error.
5
+ - In tests, fetching user IDs is mocked out to prevent network requests.
6
+ - Tightened up and clarified README.
7
+ - Some internal cleanup and restructuring of modules.
8
+
9
+ ## [2.2.0] - 2021-11-20
10
+ - When sending text, it is now possible to mention users and have their user
11
+ IDs automatically converted using `<email@email.com>` within text nodes.
12
+ - It's now possible to override notification text.
13
+ - Errors received from the Slack API now raise `SlackMessage::ApiError`.
14
+ - Re-exposed a top-level method for getting user IDs, `SlackMessage.user_id`.
15
+ - Raising some better errors when no message payload is present.
16
+ - Using `build` now requires a profile, so configuration must exist.
17
+
18
+ ## [2.1.0] - 2021-11-01
19
+ - Change to use Slack Apps for all profiles. This should allow growth toward
20
+ updating messages, working with interactive messages etc.
21
+ - As a result, allow custom icons per profile / message.
22
+ - When sending a message, the first `text` block is used for the notification
23
+ content. Should resolve "this content cannot be displayed".
24
+ - Significant restructuring of README.
25
+
26
+ ## [2.0.0] - 2021-11-01
27
+ - Yeah that was all broken.
28
+
29
+ ## [1.9.0] - 2021-10-27
30
+ - Add many validations so that trying to add e.g. empty text won't succeed.
31
+ Previously that would be accepted but return `invalid_blocks` from the API.
4
32
 
5
33
  ## [1.8.1] - 2021-10-08
6
34
  - Cleaned that rspec code a bit, added more matchers for real world use.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slack_message (1.8.0)
4
+ slack_message (2.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -3,8 +3,7 @@ SlackMessage: a Friendly DSL for Slack
3
3
 
4
4
  SlackMessage is a wrapper over the [Block Kit
5
5
  API](https://app.slack.com/block-kit-builder/) to make it easy to read and
6
- write messages to slack in your ruby application. It has zero dependencies and
7
- is built to be opinionated to keep your configuration needs low.
6
+ write messages to slack in your ruby application.
8
7
 
9
8
  Posting a message to Slack should be this easy:
10
9
 
@@ -16,80 +15,107 @@ end
16
15
 
17
16
  To install, just add `slack_message` to your bundle and you're ready to go.
18
17
 
18
+ #### Opinionated Stances
19
+
20
+ Slack's API has a lot of options available to you! But this gem takes some
21
+ opinionated stances about usage to try to minimize the pain of integrating
22
+ with it. For example:
23
+
24
+ * SlackMessage has no dependencies. Your lockfile is enough of a mess already.
25
+ * The code to build a message should look a lot like the message itself. Code
26
+ that is simple to read and understand is a priority.
27
+ * Webhooks are passé. Only Slack Apps are supported now.
28
+ * Unless you request otherwise, text is always rendered using `mrkdwn`. If you
29
+ want plaintext, you'll need to ask for it. Same for the `emoji` flag.
30
+ * As many API semantics as possible are hidden. For instance, if you post to
31
+ something that looks like an email address, `slack_message` is going to try
32
+ to look it up as an email address.
33
+ * A few little hacks on the block syntax, such as adding a `blank_line` (which
34
+ doesn't exist in the API), or leading spaces.
35
+ * Configuration is kept as simple as possible. But, as much heavy lifting as
36
+ possible should occur just once via configuration and not on every call.
19
37
 
20
38
  Usage
21
39
  ------------
22
40
 
23
41
  ### Configuration
24
42
 
25
- To get started, you'll need to configure at least one profile to use to post
26
- to slack. Get a [Webhook URL](https://slack.com/help/articles/115005265063-Incoming-webhooks-for-Slack)
27
- from Slack and configure it like this:
43
+ To get started, you'll need to create a Slack App with some appropriate
44
+ permissions. It used to be possible to use the Webhook API, but that's long
45
+ since been deprecated, and apps are pretty [straightforward to
46
+ create](https://api.slack.com/tutorials/tracks/getting-a-token).
47
+
48
+ Generally, make sure your token has permissions for `users:read` and `chat:write`.
28
49
 
29
50
  ```ruby
30
51
  SlackMessage.configure do |config|
31
- webhook_url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX'
52
+ api_token = 'xoxb-11111111111-2222222222-33333333333333333'
32
53
 
33
- config.add_profile(name: 'Slack Notifier', url: webhook_url)
54
+ config.add_profile(api_token: api_token)
34
55
  end
35
56
  ```
36
57
 
37
- You should probably keep that webhook in a safe place like `ENV`. If using this
38
- gem with Rails, place this code in somewhere like
39
- `config/initializers/slack_message.rb`.
58
+ You should keep your token in a safe place like `ENV`. If using this gem with
59
+ Rails, place this code in somewhere like `config/initializers/slack_message.rb`.
40
60
 
41
61
  #### Additional Profiles
42
62
 
43
- If you want to post to multiple different webhook addresses (say, if you have
44
- several different bots that post to different channels as different identities),
45
- you can configure those profiles as well, by giving each of them a name:
63
+ If your app uses slack messages for several different purposes, it's common to
64
+ want to post to different channels as different names / icons / etc. To do that
65
+ more easily and consistently, you can specify multiple profiles:
46
66
 
47
67
  ```ruby
48
68
  SlackMessage.configure do |config|
69
+ api_token = 'xoxb-11111111111-2222222222-33333333333333333'
70
+
49
71
  # default profile
50
- config.add_profile(name: 'Slack Notifier', url: ENV['SLACK_WEBHOOK_URL'])
72
+ config.add_profile(api_token: api_token, name: 'Slack Notifier')
51
73
 
52
74
  # additional profiles (see below for usage)
53
- config.add_profile(:prod_alert_bot, name: 'Prod Alert Bot', url: ENV['SLACK_PROD_ALERT_WEBHOOK_URL'])
54
- config.add_profile(:sidekiq_bot, name: 'Sidekiq Bot', url: ENV['SLACK_SIDEKIQ_WEBHOOK_URL'])
75
+ config.add_profile(:prod_alert_bot,
76
+ name: 'Prod Alert Bot'
77
+ icon: ':mooseandsquirrel:'
78
+ )
79
+ config.add_profile(:sidekiq_bot,
80
+ api_token: ENV.fetch('SIDEKIQ_SLACK_APP_API_TOKEN'),
81
+ name: 'Sidekiq Bot',
82
+ )
55
83
  end
56
84
  ```
57
85
 
58
- If you frequently ping the same channel with the same bot, and don't want to
59
- continually specify the channel name, you can specify a default channel and
60
- post using the `post_as` method. It is otherwise identical to `post_to`, but
61
- allows you to omit the channel argument:
86
+ A number of parameters are available to make it simpler to use a profile without
87
+ specifying repetitive information. Most all have corresponding options when
88
+ composing a message:
89
+
90
+ | Config | Default | Value |
91
+ |-----------------|-----------------|-----------------------------------------------------------------|
92
+ | api_token | None | Your Slack App API Key. |
93
+ | name | From Slack App | The bot name for your message. |
94
+ | icon | From Slack App | Profile icon for your message. Specify as :emoji: or image URL. |
95
+ | default_channel | None (optional) | Channel / user to post to by default. |
96
+
97
+
98
+ Setting a `default_channel` specifically will allow you to use `post_as`, which
99
+ is a convenient shortcut for bots that repeatedly post to one channel as a
100
+ consistent identity:
62
101
 
63
102
  ```ruby
64
103
  SlackMessage.configure do |config|
65
- config.add_profile(:prod_alert_bot,
66
- name: 'Prod Alert Bot',
67
- url: ENV['SLACK_PROD_ALERT_WEBHOOK_URL'],
104
+ config.add_profile(:red_alert_bot,
105
+ api_token: ENV.fetch('SLACK_API_TOKEN'),
106
+ name: 'Red Alerts',
107
+ icon: ':klaxon:',
68
108
  default_channel: '#red_alerts'
69
109
  )
70
110
  end
71
111
 
72
- SlackMessage.post_as(:prod_alert_bot) do
112
+ SlackMessage.post_as(:red_alert_bot) do
73
113
  text ":ambulance: weeooo weeooo something went wrong"
74
114
  end
75
115
  ```
76
116
 
77
- Note that `post_as` does not allow you to choose a channel (because that's just
78
- the same as using `post_to`), so you really do have to specify `default_channel`.
79
-
80
- #### Configuring User Search
81
-
82
- Slack's API no longer allows you to send DMs to users by username. You need to
83
- look up a user's internal ID and send to that ID. Thankfully, there is a lookup
84
- by email endpoint for this. If you'd like to post messages to users by their
85
- email address, you'll need a
86
- [separate API Token](https://api.slack.com/tutorials/tracks/getting-a-token):
87
-
88
- ```ruby
89
- SlackMessage.configure do |config|
90
- config.api_token = 'xoxb-11111111111-2222222222-33333333333333333'
91
- end
92
- ```
117
+ There's no reason you can't use the same API key for several profiles. Profiles
118
+ are most useful to create consistent name / icon setups for apps with many bots.
93
119
 
94
120
  ### Posting Messages
95
121
 
@@ -104,14 +130,12 @@ end
104
130
  That's it! SlackMessage will automatically serialize for the API like this:
105
131
 
106
132
  ```json
107
- [{"type":"section","text":{"type":"mrkdwn","text":"We did it! :thumbsup:"}}]
133
+ [{"type":"section","text":{"type":"mrkdwn","text":"We did it @here! :thumbsup:"}}]
108
134
  ```
109
135
 
110
136
  Details like remembering that Slack made a mystifying decision to force you to
111
137
  request "mrkdwn", or requiring your text to be wrapped into a section are handled
112
- for you.
113
-
114
- Building up messages is meant to be as user-friendly as possible:
138
+ for you. Building up messages is meant to be as user-friendly as possible:
115
139
 
116
140
  ```ruby
117
141
  SlackMessage.build do
@@ -147,20 +171,19 @@ automatically:
147
171
  ]
148
172
  ```
149
173
 
150
- If you've configured an API key for user search (see above in configuration),
151
- it's just as easy to send messages directly to users:
174
+ It's just as easy to send messages directly to users. SlackMessage will look for
175
+ targets that are email-addressish, and look them up for you automatically:
152
176
 
153
177
  ```ruby
154
178
  SlackMessage.post_to('hello@joemastey.com') do
155
- text "We did it! :thumbsup:"
179
+ text "You specifically did it! :thumbsup:"
156
180
  end
157
181
  ```
158
182
 
159
183
  SlackMessage is able to build all kinds of rich messages for you, and has been
160
184
  a real joy to use for the author at least. To understand a bit more about the
161
- possibilities of blocks, see Slack's [Block Kit
162
- Builder](https://app.slack.com/block-kit-builder/) to understand the structure
163
- better. There are lots of options:
185
+ possibilities of blocks, you should play around with Slack's [Block Kit
186
+ Builder](https://app.slack.com/block-kit-builder/). There are lots of options:
164
187
 
165
188
  ```ruby
166
189
  SlackMessage.post_to('#general') do
@@ -184,36 +207,85 @@ SlackMessage.post_to('#general') do
184
207
  text "See more here: #{link('result', 'https://google.com')}"
185
208
  end
186
209
 
187
- text ":rocketship: hello@joemastey.com"
210
+ text ":rocketship: <hello@joemastey.com>"
188
211
 
189
212
  context ":custom_slack_emoji: An example footer *with some markdown*."
190
213
  end
191
214
  ```
192
215
 
193
216
  SlackMessage will compose this into Block Kit syntax and send it on its way!
194
- For now you'll need to read a bit of the source code to get the entire API. Sorry,
195
- working on it.
196
217
 
197
218
  If you've defined multiple profiles in configuration, you can specify which to
198
- use for your message by specifying their name:
219
+ use for your message by specifying its name:
199
220
 
200
221
  ```ruby
201
222
  SlackMessage.post_to('#general', as: :sidekiq_bot) do
202
223
  text ":octagonal_sign: A job has failed permanently and needs to be rescued."
203
- link_button "Sidekiq Dashboard", "https://yoursite.com/sidekiq", style: :danger
224
+
225
+ link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
204
226
  end
205
227
  ```
206
228
 
207
- You can also use a custom name when sending a message:
229
+ You can also override profile bot details when sending a message:
208
230
 
209
231
  ```ruby
210
232
  SlackMessage.post_to('#general') do
211
233
  bot_name "CoffeeBot"
234
+ bot_icon ":coffee:"
212
235
 
213
236
  text ":coffee::clock: Time to take a break!"
214
237
  end
215
238
  ```
216
239
 
240
+ #### Notifying Users
241
+
242
+ There are several supported ways to tag and notify users. Mentioned above, it's
243
+ possible to DM a user by email:
244
+
245
+ ```ruby
246
+ SlackMessage.post_to('hello@joemastey.com') do
247
+ text "Hi there!"
248
+ end
249
+ ```
250
+
251
+ You can also mention a user by email within a channel by wrapping their name
252
+ in tags:
253
+
254
+ ```ruby
255
+ SlackMessage.post_to('#general') do
256
+ bot_name "CoffeeBot"
257
+ bot_icon ":coffee:"
258
+
259
+ text ":coffee: It's your turn to make coffee <hello@joemastey.com>."
260
+ end
261
+ ```
262
+
263
+ Emails that are not wrapped in tags will be rendered as normal email addresses.
264
+ Additionally, Slack will automatically convert a number of channel names and
265
+ tags you're probably already used to:
266
+
267
+ ```ruby
268
+ SlackMessage.post_to('#general') do
269
+ bot_name "CoffeeBot"
270
+ bot_icon ":coffee:"
271
+
272
+ text "@here There's no coffee left! Let #general know when you fix it."
273
+ end
274
+ ```
275
+
276
+ By default, the desktop notification for a message will be the text of the
277
+ message itself. However, you can customize desktop notifications if you prefer:
278
+
279
+ ```ruby
280
+ SlackMessage.post_to('hello@joemastey.com') do
281
+ bot_name "CoffeeBot"
282
+ bot_icon ":coffee:"
283
+
284
+ notification_text "It's a coffee emergency!"
285
+ text "There's no coffee left!"
286
+ end
287
+ ```
288
+
217
289
  ### Testing
218
290
 
219
291
  You can do some basic testing against SlackMessage, at least if you use RSpec!
@@ -230,13 +302,33 @@ RSpec.configure do |config|
230
302
  end
231
303
  ```
232
304
 
233
- This will stop API calls for posting messages, and will allow you access to
234
- some custom matchers:
305
+ This will prevent API calls from leaking in your tests, and will allow you
306
+ access to some custom matchers:
235
307
 
236
308
  ```ruby
237
309
  expect {
238
310
  SlackMessage.post_to('#general') { text "foo" }
239
311
  }.to post_slack_message_to('#general').with_content_matching(/foo/)
312
+
313
+ expect {
314
+ SlackMessage.post_as(:schmoebot) { text "foo" }
315
+ }.to post_slack_message_as(:schmoebot)
316
+
317
+ expect {
318
+ SlackMessage.post_as(:schmoebot) { text "foo" }
319
+ }.to post_slack_message_as('Schmoe Bot')
320
+
321
+ expect {
322
+ SlackMessage.post_as(:schmoebot) { text "foo" }
323
+ }.to post_slack_message_with_icon(':schmoebot:')
324
+
325
+ expect {
326
+ SlackMessage.post_as(:schmoebot) { text "foo" }
327
+ }.to post_slack_message_with_icon_matching(/gravatar/)
328
+
329
+ expect {
330
+ SlackMessage.post_to('#general') { text "foo" }
331
+ }.to post_to_slack
240
332
  ```
241
333
 
242
334
  Be forewarned, I'm frankly not that great at more complicated RSpec matchers,
@@ -244,45 +336,32 @@ so I'm guessing there are some bugs. Also, because the content of a message
244
336
  gets turned into a complex JSON object, matching against content isn't capable
245
337
  of very complicated regexes.
246
338
 
247
- Opinionated Stances
248
- ------------
249
-
250
- Slack's API has a lot of options available to you! But this gem takes some
251
- opinionated stances on how to make use of that API. For instance:
252
-
253
- * Unless you request otherwise, text is always rendered using `mrkdwn`. If you
254
- want plaintext, you'll need to ask for it.
255
- * Generally, same goes for the `emoji` flag on almost every text element.
256
- * It's possible to ask for a `blank_line` in sections, even though that concept
257
- isn't real. In this case, a text line containing only an emspace is rendered.
258
- * It's easy to configure a bot for consistent name / channel use. My previous
259
- use of SlackNotifier led to frequently inconsistent names.
260
-
261
339
  What it Doesn't Do
262
340
  ------------
263
341
 
264
- This gem is intended to stay fairly simple. Other gems have lots of config
265
- options and abilities, which is wonderful, but overall complicates usage. If
266
- you want to add a feature, open an issue on Github first to see if it's likely
267
- to be merged.
268
-
269
- Since this gem was built out of an existing need that _didn't_ include most of
270
- the block API, I'd be inclined to merge features that sustainably expand the
271
- DSL to include more of the block API itself.
342
+ This gem is intended to stay simple. Other Slack gems have lots of config
343
+ options and abilities, which makes them powerful, but makes them a pain to use.
344
+ If you want to add a feature, open an issue on Github first to see if it's
345
+ likely to be merged. This gem was built out of an existing need that _didn't_
346
+ include all of the block API, but I'd be inclined to merge features that
347
+ sustainably expand the DSL to include more useful features.
272
348
 
273
- Also, some behaviors that are still planned but not yet added:
349
+ Some behaviors that are still planned but not yet added:
274
350
 
275
351
  * some API documentation amirite?
276
- * allow custom http_options in configuration
352
+ * custom http_options in configuration
277
353
  * more of BlockKit's options
278
- * any interactive elements at all (I don't understand them yet)
354
+ * any interactive elements at all
355
+ * editing / updating messages
356
+ * multiple recipients
279
357
  * more interesting return types for your message
280
- * richer text formatting (ul is currently a hack)
358
+ * richer text formatting (for instance, `ul` is currently a hack)
359
+ * more and better organized testing capability
281
360
 
282
361
  Contributing
283
362
  ------------
284
363
 
285
- Contributions are very welcome. Fork, fix, submit pulls.
364
+ Contributions are very welcome. Fork, fix, submit pull.
286
365
 
287
366
  Contribution is expected to conform to the [Contributor Covenant](https://github.com/jmmastey/slack_message/blob/master/CODE_OF_CONDUCT.md).
288
367
 
@@ -2,70 +2,108 @@ require 'net/http'
2
2
  require 'net/https'
3
3
  require 'json'
4
4
 
5
- class SlackMessage::Api
6
- def self.user_id_for(email)
7
- token = SlackMessage.configuration.api_token
5
+ module SlackMessage::Api
6
+ extend self
8
7
 
9
- uri = URI("https://slack.com/api/users.lookupByEmail?email=#{email}")
10
- request = Net::HTTP::Get.new(uri).tap do |req|
11
- req['Authorization'] = "Bearer #{token}"
12
- req['Content-type'] = "application/json"
8
+ def user_id_for(email, profile)
9
+ unless email =~ SlackMessage::EMAIL_PATTERN
10
+ raise ArgumentError, "Tried to find profile by invalid email address '#{email}'"
13
11
  end
14
12
 
15
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
16
- http.request(request)
17
- end
13
+ response = look_up_user_by_email(email, profile)
18
14
 
19
15
  if response.code != "200"
20
- raise "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
16
+ raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
21
17
  elsif response.body == ""
22
- raise "Received empty 200 response from Slack when looking up user info. Check your API key."
18
+ raise SlackMessage::ApiError, "Received empty 200 response from Slack when looking up user info. Check your API key."
23
19
  end
24
20
 
25
21
  begin
26
22
  payload = JSON.parse(response.body)
27
23
  rescue
28
- raise "Unable to parse JSON response from Slack API\n#{response.body}"
24
+ raise SlackMessage::ApiError, "Unable to parse JSON response from Slack API\n#{response.body}"
29
25
  end
30
26
 
31
27
  if payload.include?("error") && payload["error"] == "invalid_auth"
32
- raise "Received an error because your authentication token isn't properly configured:\n#{response.body}"
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}'."
33
31
  elsif payload.include?("error")
34
- raise "Received error response from Slack during user lookup:\n#{response.body}"
32
+ raise SlackMessage::ApiError, "Received error response from Slack during user lookup:\n#{response.body}"
35
33
  end
36
34
 
37
35
  payload["user"]["id"]
38
36
  end
39
37
 
40
- def self.post(payload, target, profile)
41
- profile[:url] = profile[:url]
42
-
43
- uri = URI.parse(profile[:url])
38
+ def post(payload, target, profile)
44
39
  params = {
45
40
  channel: target,
46
- username: profile[:name],
47
- blocks: payload
41
+ username: payload.custom_bot_name || profile[:name],
42
+ blocks: payload.render,
43
+ text: payload.custom_notification,
48
44
  }
49
45
 
50
- response = execute_post_form(uri, params, profile[:handle])
46
+ if params[:blocks].length == 0
47
+ raise ArgumentError, "Tried to send an entirely empty message."
48
+ end
49
+
50
+ icon = payload.custom_bot_icon || profile[:icon]
51
+ if icon =~ /^:\w+:$/
52
+ params[:icon_emoji] = icon
53
+ elsif icon =~ /^(https?:\/\/)?[0-9a-z]+\.[-_0-9a-z]+/ # very naive regex, I know. it'll be fine.
54
+ params[:icon_url] = icon
55
+ elsif !(icon.nil? || icon == '')
56
+ raise ArgumentError, "Couldn't figure out icon '#{icon}'. Try :emoji: or a URL."
57
+ end
58
+
59
+ response = post_message(profile, params)
60
+ body = JSON.parse(response.body)
61
+ error = body.fetch("error", "")
51
62
 
52
63
  # let's try to be helpful about error messages
53
- if response.body == "invalid_token"
54
- raise "Couldn't send slack message because the URL for profile '#{profile[:handle]}' is wrong."
55
- elsif response.body == "channel_not_found"
56
- raise "Tried to send Slack message to non-existent channel or user '#{target}'"
57
- elsif response.body == "missing_text_or_fallback_or_attachments"
58
- raise "Tried to send Slack message with invalid payload."
64
+ if ["token_revoked", "token_expired", "invalid_auth", "not_authed"].include?(error)
65
+ raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' is wrong."
66
+ elsif ["no_permission", "ekm_access_denied"].include?(error)
67
+ raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' isn't allowed to post messages."
68
+ elsif error == "channel_not_found"
69
+ raise SlackMessage::ApiError, "Tried to send Slack message to non-existent channel or user '#{target}'"
70
+ elsif error == "invalid_arguments"
71
+ raise SlackMessage::ApiError, "Tried to send Slack message with invalid payload."
59
72
  elsif response.code == "302"
60
- raise "Got 302 response while posting to Slack. Check your webhook URL for '#{profile[:handle]}'."
73
+ raise SlackMessage::ApiError, "Got 302 response while posting to Slack. Check your API key for profile '#{profile[:handle]}'."
61
74
  elsif response.code != "200"
62
- raise "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
75
+ raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
63
76
  end
64
77
 
65
78
  response
66
79
  end
67
80
 
68
- def self.execute_post_form(uri, params, _profile)
69
- Net::HTTP.post_form uri, { payload: params.to_json }
81
+ private
82
+
83
+ # mostly for test harnesses
84
+
85
+ def look_up_user_by_email(email, profile)
86
+ uri = URI("https://slack.com/api/users.lookupByEmail?email=#{email}")
87
+ request = Net::HTTP::Get.new(uri).tap do |req|
88
+ req['Authorization'] = "Bearer #{profile[:api_token]}"
89
+ req['Content-type'] = "application/json; charset=utf-8"
90
+ end
91
+
92
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
93
+ http.request(request)
94
+ end
95
+ end
96
+
97
+ def post_message(profile, params)
98
+ uri = URI("https://slack.com/api/chat.postMessage")
99
+ request = Net::HTTP::Post.new(uri).tap do |req|
100
+ req['Authorization'] = "Bearer #{profile[:api_token]}"
101
+ req['Content-type'] = "application/json; charset=utf-8"
102
+ req.body = params.to_json
103
+ end
104
+
105
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
106
+ http.request(request)
107
+ end
70
108
  end
71
109
  end
@@ -1,9 +1,7 @@
1
1
  module SlackMessage::Configuration
2
- @@api_token = nil
3
2
  @@profiles = {}
4
3
 
5
4
  def self.reset
6
- @@api_token = nil
7
5
  @@profiles = {}
8
6
  end
9
7
 
@@ -13,39 +11,29 @@ module SlackMessage::Configuration
13
11
 
14
12
  ###
15
13
 
16
- def self.api_token=(token)
17
- @@api_token = token
18
- end
19
-
20
- def self.api_token
21
- unless @@api_token.is_a? String
22
- raise ArgumentError, "Please set an API token to use API features."
23
- end
24
-
25
- @@api_token
26
- end
27
-
28
- ###
29
-
30
14
  def self.clear_profiles! # test harness, mainly
31
15
  @@profiles = {}
32
16
  end
33
17
 
34
- def self.add_profile(handle = :default, name:, url:, default_channel: nil)
18
+ def self.add_profile(handle = :default, api_token:, name: nil, icon: nil, default_channel: nil)
35
19
  if @@profiles.include?(handle)
36
20
  warn "WARNING: Overriding profile '#{handle}' in SlackMessage config"
37
21
  end
38
22
 
39
- @@profiles[handle] = { name: name, url: url, handle: handle, default_channel: default_channel }
23
+ @@profiles[handle] = {
24
+ handle: handle,
25
+ api_token: api_token,
26
+ name: name,
27
+ icon: icon,
28
+ default_channel: default_channel
29
+ }
40
30
  end
41
31
 
42
- def self.profile(handle, custom_name: nil)
32
+ def self.profile(handle)
43
33
  unless @@profiles.include?(handle)
44
34
  raise ArgumentError, "Unknown SlackMessage profile '#{handle}'."
45
35
  end
46
36
 
47
- @@profiles[handle].tap do |profile|
48
- profile[:name] = custom_name if !custom_name.nil?
49
- end
37
+ @@profiles[handle]
50
38
  end
51
39
  end
@@ -1,16 +1,21 @@
1
1
  class SlackMessage::Dsl
2
- attr_reader :body, :default_section, :custom_bot_name
2
+ attr_reader :body, :default_section, :custom_bot_name, :custom_bot_icon, :profile
3
+ attr_accessor :custom_notification
3
4
 
4
- EMSPACE = " " # unicode emspace
5
+ EMSPACE = " " # unicode emspace, Slack won't compress this
5
6
 
6
- def initialize(block)
7
+ def initialize(block, profile)
7
8
  # Delegate missing methods to caller scope. Thanks 2008:
8
9
  # https://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
9
10
  @caller_self = eval("self", block.binding)
10
11
 
11
- @body = []
12
- @default_section = Section.new
13
- @custom_bot_name = nil
12
+ @body = []
13
+ @profile = profile
14
+ @default_section = Section.new(self)
15
+
16
+ @custom_bot_name = nil
17
+ @custom_bot_icon = nil
18
+ @custom_notification = nil
14
19
  end
15
20
 
16
21
  # allowable top-level entities within a block
@@ -18,7 +23,7 @@ class SlackMessage::Dsl
18
23
  def section(&block)
19
24
  finalize_default_section
20
25
 
21
- section = Section.new.tap do |s|
26
+ section = Section.new(self).tap do |s|
22
27
  s.instance_eval(&block)
23
28
  end
24
29
 
@@ -52,6 +57,12 @@ class SlackMessage::Dsl
52
57
  def context(text)
53
58
  finalize_default_section
54
59
 
60
+ if text == "" || text.nil?
61
+ raise ArgumentError, "Cannot create a context block without a value."
62
+ end
63
+
64
+ text = self.enrich_text(text)
65
+
55
66
  @body.push({ type: "context", elements: [{
56
67
  type: "mrkdwn", text: text
57
68
  }]})
@@ -72,12 +83,20 @@ class SlackMessage::Dsl
72
83
 
73
84
  # end delegation
74
85
 
75
- # custom bot name
86
+ # bot / notification overrides
76
87
 
77
88
  def bot_name(name)
78
89
  @custom_bot_name = name
79
90
  end
80
91
 
92
+ def bot_icon(icon)
93
+ @custom_bot_icon = icon
94
+ end
95
+
96
+ def notification_text(msg)
97
+ @custom_notification = msg
98
+ end
99
+
81
100
  # end bot name
82
101
 
83
102
  def render
@@ -89,6 +108,20 @@ class SlackMessage::Dsl
89
108
  @caller_self.send meth, *args, &blk
90
109
  end
91
110
 
111
+ # replace emails w/ real user IDs
112
+ def enrich_text(text_body)
113
+ text_body.scan(SlackMessage::EMAIL_TAG_PATTERN).each do |email_tag|
114
+ raw_email = email_tag.gsub(/[><]/, '')
115
+ user_id = SlackMessage::Api::user_id_for(raw_email, profile)
116
+
117
+ text_body.gsub!(email_tag, "<@#{user_id}>") if user_id
118
+ rescue SlackMessage::ApiError => e
119
+ # swallow errors for not-found users
120
+ end
121
+
122
+ text_body
123
+ end
124
+
92
125
  private
93
126
 
94
127
  # when doing things that would generate new top-levels, first try
@@ -98,18 +131,25 @@ class SlackMessage::Dsl
98
131
  @body.push(default_section.render)
99
132
  end
100
133
 
101
- @default_section = Section.new
134
+ @default_section = Section.new(self)
102
135
  end
103
136
 
104
137
  class Section
105
138
  attr_reader :body
106
139
 
107
- def initialize
140
+ def initialize(parent)
141
+ @parent = parent
108
142
  @body = { type: "section" }
109
143
  @list = List.new
110
144
  end
111
145
 
112
146
  def text(msg)
147
+ if msg == "" || msg.nil?
148
+ raise ArgumentError, "Cannot create a text node without a value."
149
+ end
150
+
151
+ msg = @parent.enrich_text(msg)
152
+
113
153
  if @body.include?(:text)
114
154
  @body[:text][:text] << "\n#{msg}"
115
155
 
@@ -119,17 +159,21 @@ class SlackMessage::Dsl
119
159
  end
120
160
 
121
161
  def ul(elements)
122
- raise Arguments, "please pass an array" unless elements.respond_to?(:map)
123
- text(
124
- elements.map { |text| "#{EMSPACE}• #{text}" }.join("\n")
125
- )
162
+ raise ArgumentError, "Please pass an array when creating a ul." unless elements.respond_to?(:map)
163
+
164
+ msg = elements.map { |text| "#{EMSPACE}• #{text}" }.join("\n")
165
+ msg = @parent.enrich_text(msg)
166
+
167
+ text(msg)
126
168
  end
127
169
 
128
170
  def ol(elements)
129
- raise Arguments, "please pass an array" unless elements.respond_to?(:map)
130
- text(
131
- elements.map.with_index(1) { |text, idx| "#{EMSPACE}#{idx}. #{text}" }.join("\n")
132
- )
171
+ raise ArgumentError, "Please pass an array when creating an ol." unless elements.respond_to?(:map)
172
+
173
+ msg = elements.map.with_index(1) { |text, idx| "#{EMSPACE}#{idx}. #{text}" }.join("\n")
174
+ msg = @parent.enrich_text(msg)
175
+
176
+ text(msg)
133
177
  end
134
178
 
135
179
  # styles: default, primary, danger
@@ -186,6 +230,11 @@ class SlackMessage::Dsl
186
230
  end
187
231
 
188
232
  def list_item(title, value)
233
+ if value == "" || value.nil?
234
+ raise ArgumentError, "Can't create a list item for '#{title}' without a value."
235
+ end
236
+
237
+ value = @parent.enrich_text(value)
189
238
  @list.add(title, value)
190
239
  end
191
240
 
@@ -198,7 +247,16 @@ class SlackMessage::Dsl
198
247
  end
199
248
 
200
249
  def render
250
+ unless has_content?
251
+ raise ArgumentError, "Can't create a section with no content."
252
+ end
253
+
201
254
  body[:fields] = @list.render if @list.any?
255
+
256
+ if body[:text] && body[:text][:text] && !@parent.custom_notification
257
+ @parent.notification_text(body[:text][:text])
258
+ end
259
+
202
260
  body
203
261
  end
204
262
  end
@@ -30,13 +30,34 @@ module SlackMessage::RSpec
30
30
  FauxResponse = Struct.new(:code, :body)
31
31
 
32
32
  def self.included(_)
33
- SlackMessage::Api.singleton_class.undef_method(:execute_post_form)
34
- SlackMessage::Api.define_singleton_method(:execute_post_form) do |uri, params, profile|
33
+ SlackMessage::Api.undef_method(:post_message)
34
+ SlackMessage::Api.define_singleton_method(:post_message) do |profile, params|
35
35
  @@listeners.each do |listener|
36
- listener.record_call(params.merge(profile: profile, uri: uri))
36
+ listener.record_call(params.merge(profile: profile))
37
37
  end
38
38
 
39
- return FauxResponse.new('200', 'ok')
39
+ response = {"ok"=>true,
40
+ "channel"=>"D12345678",
41
+ "ts"=>"1635863996.002300",
42
+ "message"=>
43
+ {"type"=>"message", "subtype"=>"bot_message",
44
+ "text"=>"foo",
45
+ "ts"=>"1635863996.002300",
46
+ "username"=>"SlackMessage",
47
+ "icons"=>{"emoji"=>":successkid:"},
48
+ "bot_id"=>"B1234567890",
49
+ "blocks"=>
50
+ [{"type"=>"section",
51
+ "block_id"=>"hAh7",
52
+ "text"=>{"type"=>"mrkdwn", "text"=>"foo", "verbatim"=>false}}]}}
53
+
54
+ return FauxResponse.new('200', response.to_json)
55
+ end
56
+
57
+ SlackMessage::Api.undef_method(:look_up_user_by_email)
58
+ SlackMessage::Api.define_singleton_method(:look_up_user_by_email) do |email, profile|
59
+ response = {"ok"=>true, "user"=>{"id"=>"U5432CBA"}}
60
+ return FauxResponse.new('200', response.to_json)
40
61
  end
41
62
  end
42
63
 
@@ -102,12 +123,55 @@ module SlackMessage::RSpec
102
123
  supports_block_expectations
103
124
  end
104
125
 
126
+ # icon matcher
127
+ matcher :post_slack_message_with_icon do |expected|
128
+ match do |actual|
129
+ @instance ||= PostTo.new
130
+ @instance.with_icon(expected)
131
+
132
+ actual.call
133
+ @instance.enforce_expectations
134
+ end
135
+
136
+ chain :with_content_matching do |content|
137
+ @instance ||= PostTo.new
138
+ @instance.with_content_matching(content)
139
+ end
140
+
141
+ failure_message { @instance.failure_message }
142
+ failure_message_when_negated { @instance.failure_message_when_negated }
143
+
144
+ supports_block_expectations
145
+ end
146
+
147
+ matcher :post_slack_message_with_icon_matching do |expected|
148
+ match do |actual|
149
+ @instance ||= PostTo.new
150
+ @instance.with_icon_matching(expected)
151
+
152
+ actual.call
153
+ @instance.enforce_expectations
154
+ end
155
+
156
+ chain :with_content_matching do |content|
157
+ @instance ||= PostTo.new
158
+ @instance.with_content_matching(content)
159
+ end
160
+
161
+ failure_message { @instance.failure_message }
162
+ failure_message_when_negated { @instance.failure_message_when_negated }
163
+
164
+ supports_block_expectations
165
+ end
166
+
105
167
  class PostTo
106
168
  def initialize
107
169
  @captured_calls = []
108
170
  @content = nil
109
171
  @channel = nil
110
172
  @profile = nil
173
+ @icon = nil
174
+ @icon_matching = nil
111
175
 
112
176
  SlackMessage::RSpec.register_expectation_listener(self)
113
177
  end
@@ -120,6 +184,15 @@ module SlackMessage::RSpec
120
184
  @channel = channel
121
185
  end
122
186
 
187
+ def with_icon(icon)
188
+ @icon = icon
189
+ end
190
+
191
+ def with_icon_matching(icon)
192
+ raise ArgumentError unless icon.is_a? Regexp
193
+ @icon_matching = icon
194
+ end
195
+
123
196
  def with_content_matching(content)
124
197
  raise ArgumentError unless content.is_a? Regexp
125
198
  @content = content
@@ -134,8 +207,10 @@ module SlackMessage::RSpec
134
207
 
135
208
  @captured_calls
136
209
  .filter { |call| !@channel || call[:channel] == @channel }
137
- .filter { |call| !@profile || [call[:profile], call[:username]].include?(@profile) }
210
+ .filter { |call| !@profile || [call[:profile][:handle], call[:username]].include?(@profile) }
138
211
  .filter { |call| !@content || call.fetch(:blocks).to_s =~ @content }
212
+ .filter { |call| !@icon || call.fetch(:icon_emoji, call.fetch(:icon_url, '')) == @icon }
213
+ .filter { |call| !@icon_matching || call.fetch(:icon_emoji, call.fetch(:icon_url, '')) =~ @icon_matching }
139
214
  .any?
140
215
  end
141
216
 
@@ -154,6 +229,10 @@ module SlackMessage::RSpec
154
229
  concat << "post a slack message to '#{@channel}'"
155
230
  elsif @profile
156
231
  concat << "post a slack message as '#{@profile}'"
232
+ elsif @icon
233
+ concat << "post a slack message with icon '#{@icon}'"
234
+ elsif @icon_matching
235
+ concat << "post a slack message with icon matching '#{@icon_matching.inspect}'"
157
236
  else
158
237
  concat << "post a slack message"
159
238
  end
data/lib/slack_message.rb CHANGED
@@ -3,6 +3,11 @@ module SlackMessage
3
3
  require 'slack_message/api'
4
4
  require 'slack_message/configuration'
5
5
 
6
+ EMAIL_TAG_PATTERN = /<[^@ \t\r\n\<]+@[^@ \t\r\n]+\.[^@ \t\r\n]+>/
7
+ EMAIL_PATTERN = /^\S{1,}@\S{2,}\.\S{2,}$/
8
+
9
+ class ApiError < RuntimeError; end
10
+
6
11
  def self.configuration
7
12
  Configuration
8
13
  end
@@ -11,39 +16,43 @@ module SlackMessage
11
16
  configuration.configure(&block)
12
17
  end
13
18
 
14
- def self.user_id_for(email) # spooky undocumented public method 👻
15
- Api::user_id_for(email)
19
+ def self.user_id(email, profile_name = :default)
20
+ profile = Configuration.profile(profile_name)
21
+ Api.user_id_for(email, profile)
16
22
  end
17
23
 
18
24
  def self.post_to(target, as: :default, &block)
19
- payload = Dsl.new(block).tap do |instance|
25
+ profile = Configuration.profile(as)
26
+
27
+ payload = Dsl.new(block, profile).tap do |instance|
20
28
  instance.instance_eval(&block)
21
29
  end
22
30
 
23
- profile = Configuration.profile(as, custom_name: payload.custom_bot_name)
24
- target = user_id_for(target) if target =~ /^\S{1,}@\S{2,}\.\S{2,}$/
31
+ target = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
25
32
 
26
- Api.post(payload.render, target, profile)
33
+ Api.post(payload, target, profile)
27
34
  end
28
35
 
29
36
  def self.post_as(profile_name, &block)
30
- payload = Dsl.new(block).tap do |instance|
31
- instance.instance_eval(&block)
32
- end
33
-
34
- profile = Configuration.profile(profile_name, custom_name: payload.custom_bot_name)
37
+ profile = Configuration.profile(profile_name)
35
38
  if profile[:default_channel].nil?
36
39
  raise ArgumentError, "Sorry, you need to specify a default_channel for profile #{profile_name} to use post_as"
37
40
  end
38
41
 
42
+ payload = Dsl.new(block, profile).tap do |instance|
43
+ instance.instance_eval(&block)
44
+ end
45
+
39
46
  target = profile[:default_channel]
40
- target = user_id_for(target) if target =~ /^\S{1,}@\S{2,}\.\S{2,}$/
47
+ target = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
41
48
 
42
- Api.post(payload.render, target, profile)
49
+ Api.post(payload, target, profile)
43
50
  end
44
51
 
45
- def self.build(&block)
46
- Dsl.new(block).tap do |instance|
52
+ def self.build(profile_name = :default, &block)
53
+ profile = Configuration.profile(profile_name)
54
+
55
+ Dsl.new(block, profile).tap do |instance|
47
56
  instance.instance_eval(&block)
48
57
  end.send(:render)
49
58
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'slack_message'
3
- gem.version = "1.8.1"
3
+ gem.version = "2.2.1"
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,21 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe SlackMessage do
4
- describe "API convenience" do
5
- it "can grab user IDs" do
6
- SlackMessage.configure { |c| c.api_token = "asdf" }
7
- allow(Net::HTTP).to receive(:start).and_return(
8
- double(code: "200", body: '{ "user": { "id": "ABC123" }}')
9
- )
10
-
11
- result = SlackMessage.user_id_for("hello@joemastey.com")
12
- expect(result).to eq("ABC123")
13
- end
14
- end
15
-
16
4
  describe "DSL" do
17
5
  describe "#build" do
18
6
  it "renders some JSON" do
7
+ SlackMessage.configure do |config|
8
+ config.clear_profiles!
9
+ config.add_profile(name: 'default profile', api_token: 'abc123')
10
+ end
11
+
19
12
  expected_output = [
20
13
  { type: "section",
21
14
  text: { text: "foo", type: "mrkdwn" }
@@ -32,32 +25,11 @@ RSpec.describe SlackMessage do
32
25
  end
33
26
 
34
27
  describe "configuration" do
35
- after do
36
- SlackMessage.configuration.reset
37
- end
38
-
39
- it "allows you to set an API key" do
40
- SlackMessage.configure do |config|
41
- config.api_token = "abc123"
42
- end
43
-
44
- expect(SlackMessage.configuration.api_token).to eq("abc123")
45
- end
46
-
47
- it "raises errors for missing configuration" do
48
- SlackMessage.configure do |config|
49
- config.api_token = nil
50
- end
51
-
52
- expect {
53
- SlackMessage.configuration.api_token
54
- }.to raise_error(ArgumentError)
55
- end
56
-
57
28
  it "lets you add and fetch profiles" do
58
29
  SlackMessage.configure do |config|
59
- config.add_profile(name: 'default profile', url: 'http://hooks.slack.com/1234/')
60
- config.add_profile(:nonstandard, name: 'another profile', url: 'http://hooks.slack.com/1234/')
30
+ config.clear_profiles!
31
+ config.add_profile(name: 'default profile', api_token: 'abc123')
32
+ config.add_profile(:nonstandard, name: 'another profile', api_token: 'abc123')
61
33
  end
62
34
 
63
35
  expect(SlackMessage.configuration.profile(:default)[:name]).to eq('default profile')
@@ -73,7 +45,7 @@ RSpec.describe SlackMessage do
73
45
  before do
74
46
  SlackMessage.configure do |config|
75
47
  config.clear_profiles!
76
- config.add_profile(name: 'default profile', url: 'http://hooks.slack.com/1234/')
48
+ config.add_profile(name: 'default profile', api_token: 'abc123')
77
49
  end
78
50
  end
79
51
 
@@ -97,7 +69,8 @@ RSpec.describe SlackMessage do
97
69
 
98
70
  it "lets you assert by profile name" do
99
71
  SlackMessage.configure do |config|
100
- config.add_profile(:schmoebot, name: 'Schmoe', url: 'http://hooks.slack.com/1234/', default_channel: '#schmoes')
72
+ config.clear_profiles!
73
+ config.add_profile(:schmoebot, name: 'Schmoe', api_token: 'abc123', icon: ':schmoebot:', default_channel: '#schmoes')
101
74
  end
102
75
 
103
76
  expect {
@@ -113,10 +86,69 @@ RSpec.describe SlackMessage do
113
86
  }.to post_slack_message_as('Schmoe').with_content_matching(/foo/)
114
87
  end
115
88
 
89
+ it "lets you assert by profile image" do
90
+ SlackMessage.configure do |config|
91
+ config.clear_profiles!
92
+ config.add_profile(:schmoebot, name: 'Schmoe', api_token: 'abc123', icon: ':schmoebot:', default_channel: '#schmoes')
93
+ end
94
+
95
+ expect {
96
+ SlackMessage.post_as(:schmoebot) { text "foo" }
97
+ }.to post_slack_message_with_icon(':schmoebot:')
98
+
99
+ expect {
100
+ SlackMessage.post_as(:schmoebot) do
101
+ bot_icon ':schmalternate:'
102
+ text "foo"
103
+ end
104
+ }.to post_slack_message_with_icon(':schmalternate:')
105
+
106
+ expect {
107
+ SlackMessage.post_as(:schmoebot) do
108
+ bot_icon 'https://thispersondoesnotexist.com/image'
109
+ text "foo"
110
+ end
111
+ }.to post_slack_message_with_icon_matching(/thisperson/)
112
+ end
113
+
114
+ it "lets you assert notification text" do
115
+ # TODO :|
116
+ end
117
+
116
118
  it "can assert more generally too tbh" do
117
119
  expect {
118
120
  SlackMessage.post_to('#general') { text "foo" }
119
121
  }.to post_to_slack.with_content_matching(/foo/)
120
122
  end
121
123
  end
124
+
125
+ describe "API convenience" do
126
+ let(:profile) { SlackMessage::Configuration.profile(:default) }
127
+
128
+ before do
129
+ SlackMessage.configure do |config|
130
+ config.clear_profiles!
131
+ config.add_profile(name: 'default profile', api_token: 'abc123')
132
+ end
133
+ end
134
+
135
+ it "converts user IDs within text when tagged properly" do
136
+ allow(SlackMessage::Api).to receive(:user_id_for).and_return('ABC123')
137
+
138
+ expect {
139
+ SlackMessage.post_to('#general') { text("Working: <hello@joemastey.com> ") }
140
+ }.to post_to_slack.with_content_matching(/ABC123/)
141
+
142
+ expect {
143
+ SlackMessage.post_to('#general') { text("Not Tagged: hello@joemastey.com ") }
144
+ }.to post_to_slack.with_content_matching(/hello@joemastey.com/)
145
+
146
+
147
+ allow(SlackMessage::Api).to receive(:user_id_for).and_raise(SlackMessage::ApiError)
148
+
149
+ expect {
150
+ SlackMessage.post_to('#general') { text("Not User: <nuffin@nuffin.nuffin>") }
151
+ }.to post_to_slack.with_content_matching(/\<nuffin@nuffin.nuffin\>/)
152
+ end
153
+ end
122
154
  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: 1.8.1
4
+ version: 2.2.1
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-10-08 00:00:00.000000000 Z
11
+ date: 2021-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec