slack_message 1.9.0 → 2.2.2

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: f4d33554e024d1c51aeb2bb6ee5397c16142dbcc1a314720f30e7fab3d12325d
4
- data.tar.gz: 6e297c58c27ca6109d51fc5b267c3248928deaf7422d57e6b9e9aa0533e525b5
3
+ metadata.gz: 58dc8d1f101b642daabf6e686fc0e79973d73189acc3431f96d85de326d9c501
4
+ data.tar.gz: 68bd076ad93dc8cc4529be972968fbe5944fba78a8589938b8b2ab317d516b05
5
5
  SHA512:
6
- metadata.gz: bf1e3caebf3d58c238c46be5f3b62a925ade7d077998a7a73ac1c9fdb3d5f0859ecb6c0f338ad2cc666a7157d166cd8cda4fa91e9e5dacb3a77bf168cb9ccf50
7
- data.tar.gz: '08de1a2fe10e3da489169e11d478e7afb93fd557e6da00d1af1a42f6697a351f3b281ea66aafd80594738f6b25457fdb18c9a5bf766caee339d2b7cc5db51e02'
6
+ metadata.gz: d152ad36ec98acea2e85fb8c03be17aa180b291793e1028626dc7822142a218d493b64b9afda34e7b99a9ee3f795f2c5cff8ae75f53a9808857b128cf0efb708
7
+ data.tar.gz: 9bb12ce0e108c81a002afe44a4feb9d8879874705918d0d3fe5c5a263ef74b766272a5c28160cd5b62a8eea9ec85e2b6f37792257e2bfb0e436d83bac64bd9a6
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ strategy:
12
+ matrix:
13
+ os: [ubuntu-latest, macos-latest]
14
+ ruby-version: [3.0, 2.7, 2.6, 2.5, 2.4, 2.3]
15
+ runs-on: ${{ matrix.os }}
16
+
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - name: Set up Ruby ${{ matrix.ruby-version }}
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: ${{ matrix.ruby-version }}
23
+ bundler-cache: true
24
+ - name: Install dependencies
25
+ run: bundle install
26
+ - name: Run tests
27
+ run: bundle exec rspec
data/CHANGELOG.md CHANGED
@@ -1,6 +1,33 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## [2.2.2] - 2021-11-30
4
+ - Add github workflow for automatic CI runs. Stolen from another project.
5
+
6
+ ## [2.2.1] - 2021-11-20
7
+ - Trying to fetch user ID for a string that isn't email-like raises an error.
8
+ - In tests, fetching user IDs is mocked out to prevent network requests.
9
+ - Tightened up and clarified README.
10
+ - Some internal cleanup and restructuring of modules.
11
+
12
+ ## [2.2.0] - 2021-11-20
13
+ - When sending text, it is now possible to mention users and have their user
14
+ IDs automatically converted using `<email@email.com>` within text nodes.
15
+ - It's now possible to override notification text.
16
+ - Errors received from the Slack API now raise `SlackMessage::ApiError`.
17
+ - Re-exposed a top-level method for getting user IDs, `SlackMessage.user_id`.
18
+ - Raising some better errors when no message payload is present.
19
+ - Using `build` now requires a profile, so configuration must exist.
20
+
21
+ ## [2.1.0] - 2021-11-01
22
+ - Change to use Slack Apps for all profiles. This should allow growth toward
23
+ updating messages, working with interactive messages etc.
24
+ - As a result, allow custom icons per profile / message.
25
+ - When sending a message, the first `text` block is used for the notification
26
+ content. Should resolve "this content cannot be displayed".
27
+ - Significant restructuring of README.
28
+
29
+ ## [2.0.0] - 2021-11-01
30
+ - Yeah that was all broken.
4
31
 
5
32
  ## [1.9.0] - 2021-10-27
6
33
  - Add many validations so that trying to add e.g. empty text won't succeed.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slack_message (1.9.0)
4
+ slack_message (2.2.2)
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,8 +302,8 @@ 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 {
@@ -245,6 +317,14 @@ expect {
245
317
  expect {
246
318
  SlackMessage.post_as(:schmoebot) { text "foo" }
247
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/)
248
328
 
249
329
  expect {
250
330
  SlackMessage.post_to('#general') { text "foo" }
@@ -256,45 +336,32 @@ so I'm guessing there are some bugs. Also, because the content of a message
256
336
  gets turned into a complex JSON object, matching against content isn't capable
257
337
  of very complicated regexes.
258
338
 
259
- Opinionated Stances
260
- ------------
261
-
262
- Slack's API has a lot of options available to you! But this gem takes some
263
- opinionated stances on how to make use of that API. For instance:
264
-
265
- * Unless you request otherwise, text is always rendered using `mrkdwn`. If you
266
- want plaintext, you'll need to ask for it.
267
- * Generally, same goes for the `emoji` flag on almost every text element.
268
- * It's possible to ask for a `blank_line` in sections, even though that concept
269
- isn't real. In this case, a text line containing only an emspace is rendered.
270
- * It's easy to configure a bot for consistent name / channel use. My previous
271
- use of SlackNotifier led to frequently inconsistent names.
272
-
273
339
  What it Doesn't Do
274
340
  ------------
275
341
 
276
- This gem is intended to stay fairly simple. Other gems have lots of config
277
- options and abilities, which is wonderful, but overall complicates usage. If
278
- you want to add a feature, open an issue on Github first to see if it's likely
279
- to be merged.
280
-
281
- Since this gem was built out of an existing need that _didn't_ include most of
282
- the block API, I'd be inclined to merge features that sustainably expand the
283
- 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.
284
348
 
285
- Also, some behaviors that are still planned but not yet added:
349
+ Some behaviors that are still planned but not yet added:
286
350
 
287
351
  * some API documentation amirite?
288
- * allow custom http_options in configuration
352
+ * custom http_options in configuration
289
353
  * more of BlockKit's options
290
- * any interactive elements at all (I don't understand them yet)
354
+ * any interactive elements at all
355
+ * editing / updating messages
356
+ * multiple recipients
291
357
  * more interesting return types for your message
292
- * 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
293
360
 
294
361
  Contributing
295
362
  ------------
296
363
 
297
- Contributions are very welcome. Fork, fix, submit pulls.
364
+ Contributions are very welcome. Fork, fix, submit pull.
298
365
 
299
366
  Contribution is expected to conform to the [Contributor Covenant](https://github.com/jmmastey/slack_message/blob/master/CODE_OF_CONDUCT.md).
300
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
 
@@ -53,9 +58,11 @@ class SlackMessage::Dsl
53
58
  finalize_default_section
54
59
 
55
60
  if text == "" || text.nil?
56
- raise ArgumentError, "tried to create a context block without a value"
61
+ raise ArgumentError, "Cannot create a context block without a value."
57
62
  end
58
63
 
64
+ text = self.enrich_text(text)
65
+
59
66
  @body.push({ type: "context", elements: [{
60
67
  type: "mrkdwn", text: text
61
68
  }]})
@@ -76,12 +83,20 @@ class SlackMessage::Dsl
76
83
 
77
84
  # end delegation
78
85
 
79
- # custom bot name
86
+ # bot / notification overrides
80
87
 
81
88
  def bot_name(name)
82
89
  @custom_bot_name = name
83
90
  end
84
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
+
85
100
  # end bot name
86
101
 
87
102
  def render
@@ -93,6 +108,20 @@ class SlackMessage::Dsl
93
108
  @caller_self.send meth, *args, &blk
94
109
  end
95
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
+
96
125
  private
97
126
 
98
127
  # when doing things that would generate new top-levels, first try
@@ -102,22 +131,25 @@ class SlackMessage::Dsl
102
131
  @body.push(default_section.render)
103
132
  end
104
133
 
105
- @default_section = Section.new
134
+ @default_section = Section.new(self)
106
135
  end
107
136
 
108
137
  class Section
109
138
  attr_reader :body
110
139
 
111
- def initialize
140
+ def initialize(parent)
141
+ @parent = parent
112
142
  @body = { type: "section" }
113
143
  @list = List.new
114
144
  end
115
145
 
116
146
  def text(msg)
117
147
  if msg == "" || msg.nil?
118
- raise ArgumentError, "tried to create text node without a value"
148
+ raise ArgumentError, "Cannot create a text node without a value."
119
149
  end
120
150
 
151
+ msg = @parent.enrich_text(msg)
152
+
121
153
  if @body.include?(:text)
122
154
  @body[:text][:text] << "\n#{msg}"
123
155
 
@@ -127,19 +159,21 @@ class SlackMessage::Dsl
127
159
  end
128
160
 
129
161
  def ul(elements)
130
- raise ArgumentError, "please pass an array" unless elements.respond_to?(:map)
162
+ raise ArgumentError, "Please pass an array when creating a ul." unless elements.respond_to?(:map)
131
163
 
132
- text(
133
- elements.map { |text| "#{EMSPACE}• #{text}" }.join("\n")
134
- )
164
+ msg = elements.map { |text| "#{EMSPACE}• #{text}" }.join("\n")
165
+ msg = @parent.enrich_text(msg)
166
+
167
+ text(msg)
135
168
  end
136
169
 
137
170
  def ol(elements)
138
- raise ArgumentError, "please pass an array" unless elements.respond_to?(:map)
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)
139
175
 
140
- text(
141
- elements.map.with_index(1) { |text, idx| "#{EMSPACE}#{idx}. #{text}" }.join("\n")
142
- )
176
+ text(msg)
143
177
  end
144
178
 
145
179
  # styles: default, primary, danger
@@ -197,9 +231,10 @@ class SlackMessage::Dsl
197
231
 
198
232
  def list_item(title, value)
199
233
  if value == "" || value.nil?
200
- raise ArgumentError, "can't create a list item for '#{title}' without a value"
234
+ raise ArgumentError, "Can't create a list item for '#{title}' without a value."
201
235
  end
202
236
 
237
+ value = @parent.enrich_text(value)
203
238
  @list.add(title, value)
204
239
  end
205
240
 
@@ -212,7 +247,16 @@ class SlackMessage::Dsl
212
247
  end
213
248
 
214
249
  def render
250
+ unless has_content?
251
+ raise ArgumentError, "Can't create a section with no content."
252
+ end
253
+
215
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
+
216
260
  body
217
261
  end
218
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.9.0"
3
+ gem.version = "2.2.2"
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.9.0
4
+ version: 2.2.2
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-28 00:00:00.000000000 Z
11
+ date: 2021-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -58,6 +58,7 @@ executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
60
60
  files:
61
+ - ".github/workflows/main.yml"
61
62
  - ".gitignore"
62
63
  - ".ruby-version"
63
64
  - CHANGELOG.md