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 +4 -4
- data/CHANGELOG.md +29 -1
- data/Gemfile.lock +1 -1
- data/README.md +164 -85
- data/lib/slack_message/api.rb +70 -32
- data/lib/slack_message/configuration.rb +10 -22
- data/lib/slack_message/dsl.rb +76 -18
- data/lib/slack_message/rspec.rb +84 -5
- data/lib/slack_message.rb +24 -15
- data/slack_message.gemspec +1 -1
- data/spec/slack_message_spec.rb +70 -38
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '008fdfc2e46435b3bb49aae67e30b3ef354ca0c923c935c918088ee25eec7bda'
|
4
|
+
data.tar.gz: 2fe7d85c853361d12f02f58d19e4bef43ffc81d3683ef5f8b3776657eb86c5ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c5cc5b43f1813b55b6d1433da88ca661bc692a21785ffad1c2e13c459f0d2b4cb6bbf6251f5e774924a73e8e60f8c2517deaf71a0a1f74f57b2094e822e82ac
|
7
|
+
data.tar.gz: 0bb8d97ef6338703e601bd964405c090823ceded7cfa3045d42618a575397ca057591f6aad1cad6d1a74a7f4b877de11bced2d566424c75b53d80d2ce018cda0
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,34 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [
|
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
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.
|
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
|
26
|
-
to
|
27
|
-
|
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
|
-
|
52
|
+
api_token = 'xoxb-11111111111-2222222222-33333333333333333'
|
32
53
|
|
33
|
-
config.add_profile(
|
54
|
+
config.add_profile(api_token: api_token)
|
34
55
|
end
|
35
56
|
```
|
36
57
|
|
37
|
-
You should
|
38
|
-
|
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
|
44
|
-
|
45
|
-
|
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'
|
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,
|
54
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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(:
|
66
|
-
|
67
|
-
|
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(:
|
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
|
-
|
78
|
-
|
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
|
-
|
151
|
-
|
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 "
|
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,
|
162
|
-
Builder](https://app.slack.com/block-kit-builder/)
|
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
|
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
|
-
|
224
|
+
|
225
|
+
link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
|
204
226
|
end
|
205
227
|
```
|
206
228
|
|
207
|
-
You can also
|
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
|
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
|
265
|
-
options and abilities, which
|
266
|
-
you want to add a feature, open an issue on Github first to see if it's
|
267
|
-
to be merged.
|
268
|
-
|
269
|
-
|
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
|
-
|
349
|
+
Some behaviors that are still planned but not yet added:
|
274
350
|
|
275
351
|
* some API documentation amirite?
|
276
|
-
*
|
352
|
+
* custom http_options in configuration
|
277
353
|
* more of BlockKit's options
|
278
|
-
* any interactive elements at all
|
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
|
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
|
|
data/lib/slack_message/api.rb
CHANGED
@@ -2,70 +2,108 @@ require 'net/http'
|
|
2
2
|
require 'net/https'
|
3
3
|
require 'json'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
token = SlackMessage.configuration.api_token
|
5
|
+
module SlackMessage::Api
|
6
|
+
extend self
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
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 =
|
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
|
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
|
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
|
-
|
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
|
54
|
-
raise "Couldn't send slack message because the
|
55
|
-
elsif
|
56
|
-
raise "
|
57
|
-
elsif
|
58
|
-
raise "Tried to send Slack message
|
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
|
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
|
-
|
69
|
-
|
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,
|
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] = {
|
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
|
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]
|
48
|
-
profile[:name] = custom_name if !custom_name.nil?
|
49
|
-
end
|
37
|
+
@@profiles[handle]
|
50
38
|
end
|
51
39
|
end
|
data/lib/slack_message/dsl.rb
CHANGED
@@ -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
|
-
@
|
13
|
-
@
|
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
|
-
#
|
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
|
123
|
-
|
124
|
-
|
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
|
130
|
-
|
131
|
-
|
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
|
data/lib/slack_message/rspec.rb
CHANGED
@@ -30,13 +30,34 @@ module SlackMessage::RSpec
|
|
30
30
|
FauxResponse = Struct.new(:code, :body)
|
31
31
|
|
32
32
|
def self.included(_)
|
33
|
-
SlackMessage::Api.
|
34
|
-
SlackMessage::Api.define_singleton_method(:
|
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
|
36
|
+
listener.record_call(params.merge(profile: profile))
|
37
37
|
end
|
38
38
|
|
39
|
-
|
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.
|
15
|
-
|
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
|
-
|
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
|
-
|
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
|
33
|
+
Api.post(payload, target, profile)
|
27
34
|
end
|
28
35
|
|
29
36
|
def self.post_as(profile_name, &block)
|
30
|
-
|
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 =~
|
47
|
+
target = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
|
41
48
|
|
42
|
-
Api.post(payload
|
49
|
+
Api.post(payload, target, profile)
|
43
50
|
end
|
44
51
|
|
45
|
-
def self.build(&block)
|
46
|
-
|
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
|
data/slack_message.gemspec
CHANGED
data/spec/slack_message_spec.rb
CHANGED
@@ -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.
|
60
|
-
config.add_profile(
|
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',
|
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.
|
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:
|
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-
|
11
|
+
date: 2021-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|