slack_message 1.9.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +1 -1
- data/README.md +95 -77
- data/lib/slack_message/api.rb +39 -18
- data/lib/slack_message/configuration.rb +10 -22
- data/lib/slack_message/dsl.rb +18 -5
- data/lib/slack_message/rspec.rb +78 -5
- data/lib/slack_message.rb +6 -10
- data/slack_message.gemspec +1 -1
- data/spec/slack_message_spec.rb +46 -24
- 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: e92b2faa8e8b8118a0e168cef488d64b94255c841d774909647ede3d9a9faf6b
|
4
|
+
data.tar.gz: d494af801908aa9f5cf25141b012df26e23a69f8ef9389c3c8bb580c46641133
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6fceee6aeb36f7fc6b855c1ce90f647b67ce480c78e62ec2fe45bbfc347e6c9efa6ca910804c51b0edb12321953d96014ac137572d2140328109444f68512ca4
|
7
|
+
data.tar.gz: a9fe4697d85ac2f91197a39dacc46d1740c251f8472dcb91e8ba2132caac5c9f029cb5f88894f39542e1436ce554562d87108661863c2574655e64c866c5115e
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,17 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [2.1.0] - 2021-11-01
|
6
|
+
- Change to use Slack Apps for all profiles. This should allow growth toward
|
7
|
+
updating messages, working with interactive messages etc.
|
8
|
+
- As a result, allow custom icons per profile / message.
|
9
|
+
- When sending a message, the first `text` block is used for the notification
|
10
|
+
content. Should resolve "this content cannot be displayed".
|
11
|
+
- Significant restructuring of README.
|
12
|
+
|
13
|
+
## [2.0.0] - 2021-11-01
|
14
|
+
- Yeah that was all broken.
|
15
|
+
|
5
16
|
## [1.9.0] - 2021-10-27
|
6
17
|
- Add many validations so that trying to add e.g. empty text won't succeed.
|
7
18
|
Previously that would be accepted but return `invalid_blocks` from the API.
|
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,105 @@ 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
|
+
|
21
|
+
Slack's API has a lot of options available to you! But this gem takes some
|
22
|
+
opinionated stances on how to make use of that API. For instance:
|
23
|
+
|
24
|
+
* No dependencies. Your lockfile is enough of a mess already.
|
25
|
+
* Webhooks are passé. Only Slack Apps are supported now.
|
26
|
+
* Unless you request otherwise, text is always rendered using `mrkdwn`. If you
|
27
|
+
want plaintext, you'll need to ask for it. Same for the `emoji` flag.
|
28
|
+
* As many API semantics as possible are hidden. For instance, if you post to
|
29
|
+
something that looks like an email address, `slack_message` is going to try to
|
30
|
+
look it up as an email address.
|
31
|
+
* A few little hacks on the block syntax, such as adding a `blank_line` (which
|
32
|
+
doesn't exist in the API), or leading spaces.
|
33
|
+
* Configuration should be as simple as possible. But as much work as possible
|
34
|
+
should be moved from callers into configuration.
|
19
35
|
|
20
36
|
Usage
|
21
37
|
------------
|
22
38
|
|
23
39
|
### Configuration
|
24
40
|
|
25
|
-
To get started, you'll need to
|
26
|
-
to
|
27
|
-
|
41
|
+
To get started, you'll need to create a Slack App with some appropriate
|
42
|
+
permissions. It used to be possible to use the Webhook API, but that's long
|
43
|
+
since been deprecated, and apps are pretty [straightforward to
|
44
|
+
create](https://api.slack.com/tutorials/tracks/getting-a-token).
|
45
|
+
|
46
|
+
Generally, make sure your token has permissions for `users:read` and `chat:write`.
|
28
47
|
|
29
48
|
```ruby
|
30
49
|
SlackMessage.configure do |config|
|
31
|
-
|
50
|
+
api_token = 'xoxb-11111111111-2222222222-33333333333333333'
|
32
51
|
|
33
|
-
config.add_profile(
|
52
|
+
config.add_profile(api_token: api_token)
|
34
53
|
end
|
35
54
|
```
|
36
55
|
|
37
|
-
You should
|
38
|
-
|
39
|
-
`config/initializers/slack_message.rb`.
|
56
|
+
You should keep your token in a safe place like `ENV`. If using this gem with
|
57
|
+
Rails, place this code in somewhere like `config/initializers/slack_message.rb`.
|
40
58
|
|
41
59
|
#### Additional Profiles
|
42
60
|
|
43
|
-
If
|
44
|
-
|
45
|
-
|
61
|
+
If your app uses slack messages for several different purposes, it's common to
|
62
|
+
want to post to different channels as different names / icons / etc. To do that
|
63
|
+
more easily and consistently, you can specify multiple profiles:
|
46
64
|
|
47
65
|
```ruby
|
48
66
|
SlackMessage.configure do |config|
|
67
|
+
api_token = 'xoxb-11111111111-2222222222-33333333333333333'
|
68
|
+
|
49
69
|
# default profile
|
50
|
-
config.add_profile(name: 'Slack Notifier'
|
70
|
+
config.add_profile(api_token: api_token, name: 'Slack Notifier')
|
51
71
|
|
52
72
|
# additional profiles (see below for usage)
|
53
|
-
config.add_profile(:prod_alert_bot,
|
54
|
-
|
73
|
+
config.add_profile(:prod_alert_bot,
|
74
|
+
name: 'Prod Alert Bot'
|
75
|
+
icon: ':mooseandsquirrel:'
|
76
|
+
)
|
77
|
+
config.add_profile(:sidekiq_bot,
|
78
|
+
api_token: ENV.fetch('SIDEKIQ_SLACK_APP_API_TOKEN'),
|
79
|
+
name: 'Sidekiq Bot',
|
80
|
+
)
|
55
81
|
end
|
56
82
|
```
|
57
83
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
84
|
+
A number of parameters are available to make it simpler to use a profile without
|
85
|
+
specifying repetitive information. Most all have corresponding options when
|
86
|
+
composing a message:
|
87
|
+
|
88
|
+
| Config | Default | Value |
|
89
|
+
|-----------------|-----------------|-----------------------------------------------------------------|
|
90
|
+
| api_token | None | Your Slack App API Key. |
|
91
|
+
| name | From Slack App | The bot name for your message. |
|
92
|
+
| icon | From Slack App | Profile icon for your message. Specify as :emoji: or image URL. |
|
93
|
+
| default_channel | None (optional) | Channel / user to post to by default. |
|
94
|
+
|
95
|
+
|
96
|
+
Setting a `default_channel` specifically will allow you to use `post_as`, which
|
97
|
+
is a convenient shortcut for bots that repeatedly post to one channel as a
|
98
|
+
consistent identity:
|
62
99
|
|
63
100
|
```ruby
|
64
101
|
SlackMessage.configure do |config|
|
65
|
-
config.add_profile(:
|
66
|
-
|
67
|
-
|
102
|
+
config.add_profile(:red_alert_bot,
|
103
|
+
api_token: ENV.fetch('SLACK_API_TOKEN'),
|
104
|
+
name: 'Red Alerts',
|
105
|
+
icon: ':klaxon:',
|
68
106
|
default_channel: '#red_alerts'
|
69
107
|
)
|
70
108
|
end
|
71
109
|
|
72
|
-
SlackMessage.post_as(:
|
110
|
+
SlackMessage.post_as(:red_alert_bot) do
|
73
111
|
text ":ambulance: weeooo weeooo something went wrong"
|
74
112
|
end
|
75
113
|
```
|
76
114
|
|
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
|
-
```
|
115
|
+
There's no reason you can't use the same API key for several profiles. Profiles
|
116
|
+
are most useful to create consistent name / icon setups for apps with many bots.
|
93
117
|
|
94
118
|
### Posting Messages
|
95
119
|
|
@@ -104,14 +128,12 @@ end
|
|
104
128
|
That's it! SlackMessage will automatically serialize for the API like this:
|
105
129
|
|
106
130
|
```json
|
107
|
-
[{"type":"section","text":{"type":"mrkdwn","text":"We did it! :thumbsup:"}}]
|
131
|
+
[{"type":"section","text":{"type":"mrkdwn","text":"We did it @here! :thumbsup:"}}]
|
108
132
|
```
|
109
133
|
|
110
134
|
Details like remembering that Slack made a mystifying decision to force you to
|
111
135
|
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:
|
136
|
+
for you. Building up messages is meant to be as user-friendly as possible:
|
115
137
|
|
116
138
|
```ruby
|
117
139
|
SlackMessage.build do
|
@@ -147,20 +169,21 @@ automatically:
|
|
147
169
|
]
|
148
170
|
```
|
149
171
|
|
150
|
-
|
151
|
-
|
172
|
+
It's just as easy to send messages directly to users. SlackMessage will look for
|
173
|
+
targets that are email-addressish, and look them up for you automatically:
|
152
174
|
|
153
175
|
```ruby
|
154
|
-
|
155
|
-
|
176
|
+
user_email = 'hello@joemastey.com'
|
177
|
+
|
178
|
+
SlackMessage.post_to(user_email) do
|
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
|
@@ -195,20 +218,21 @@ For now you'll need to read a bit of the source code to get the entire API. Sorr
|
|
195
218
|
working on it.
|
196
219
|
|
197
220
|
If you've defined multiple profiles in configuration, you can specify which to
|
198
|
-
use for your message by specifying
|
221
|
+
use for your message by specifying its name:
|
199
222
|
|
200
223
|
```ruby
|
201
224
|
SlackMessage.post_to('#general', as: :sidekiq_bot) do
|
202
225
|
text ":octagonal_sign: A job has failed permanently and needs to be rescued."
|
203
|
-
link_button "Sidekiq Dashboard",
|
226
|
+
link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
|
204
227
|
end
|
205
228
|
```
|
206
229
|
|
207
|
-
You can also
|
230
|
+
You can also override profile bot details when sending a message:
|
208
231
|
|
209
232
|
```ruby
|
210
233
|
SlackMessage.post_to('#general') do
|
211
234
|
bot_name "CoffeeBot"
|
235
|
+
bot_icon ":coffee:"
|
212
236
|
|
213
237
|
text ":coffee::clock: Time to take a break!"
|
214
238
|
end
|
@@ -245,6 +269,14 @@ expect {
|
|
245
269
|
expect {
|
246
270
|
SlackMessage.post_as(:schmoebot) { text "foo" }
|
247
271
|
}.to post_slack_message_as('Schmoe Bot')
|
272
|
+
|
273
|
+
expect {
|
274
|
+
SlackMessage.post_as(:schmoebot) { text "foo" }
|
275
|
+
}.to post_slack_message_with_icon(':schmoebot:')
|
276
|
+
|
277
|
+
expect {
|
278
|
+
SlackMessage.post_as(:schmoebot) { text "foo" }
|
279
|
+
}.to post_slack_message_with_icon_matching(/gravatar/)
|
248
280
|
|
249
281
|
expect {
|
250
282
|
SlackMessage.post_to('#general') { text "foo" }
|
@@ -256,45 +288,31 @@ so I'm guessing there are some bugs. Also, because the content of a message
|
|
256
288
|
gets turned into a complex JSON object, matching against content isn't capable
|
257
289
|
of very complicated regexes.
|
258
290
|
|
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
291
|
What it Doesn't Do
|
274
292
|
------------
|
275
293
|
|
276
294
|
This gem is intended to stay fairly simple. Other gems have lots of config
|
277
295
|
options and abilities, which is wonderful, but overall complicates usage. If
|
278
296
|
you want to add a feature, open an issue on Github first to see if it's likely
|
279
|
-
to be merged.
|
280
|
-
|
281
|
-
|
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.
|
297
|
+
to be merged. This gem was built out of an existing need that _didn't_ include
|
298
|
+
most of the block API, but I'd be inclined to merge features that sustainably
|
299
|
+
expand the DSL to include more useful features.
|
284
300
|
|
285
|
-
|
301
|
+
Some behaviors that are still planned but not yet added:
|
286
302
|
|
287
303
|
* some API documentation amirite?
|
288
304
|
* allow custom http_options in configuration
|
289
305
|
* more of BlockKit's options
|
290
306
|
* any interactive elements at all (I don't understand them yet)
|
291
|
-
*
|
292
|
-
*
|
307
|
+
* editing / updating messages
|
308
|
+
* multiple recipients
|
309
|
+
* more interesting return types for your message (probably related to the above)
|
310
|
+
* richer text formatting (for instance, `ul` is currently a hack)
|
293
311
|
|
294
312
|
Contributing
|
295
313
|
------------
|
296
314
|
|
297
|
-
Contributions are very welcome. Fork, fix, submit
|
315
|
+
Contributions are very welcome. Fork, fix, submit pull.
|
298
316
|
|
299
317
|
Contribution is expected to conform to the [Contributor Covenant](https://github.com/jmmastey/slack_message/blob/master/CODE_OF_CONDUCT.md).
|
300
318
|
|
data/lib/slack_message/api.rb
CHANGED
@@ -3,13 +3,11 @@ require 'net/https'
|
|
3
3
|
require 'json'
|
4
4
|
|
5
5
|
class SlackMessage::Api
|
6
|
-
def self.user_id_for(email)
|
7
|
-
token = SlackMessage.configuration.api_token
|
8
|
-
|
6
|
+
def self.user_id_for(email, profile)
|
9
7
|
uri = URI("https://slack.com/api/users.lookupByEmail?email=#{email}")
|
10
8
|
request = Net::HTTP::Get.new(uri).tap do |req|
|
11
|
-
req['Authorization'] = "Bearer #{
|
12
|
-
req['Content-type'] = "application/json"
|
9
|
+
req['Authorization'] = "Bearer #{profile[:api_token]}"
|
10
|
+
req['Content-type'] = "application/json; charset=utf-8"
|
13
11
|
end
|
14
12
|
|
15
13
|
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
@@ -38,26 +36,37 @@ class SlackMessage::Api
|
|
38
36
|
end
|
39
37
|
|
40
38
|
def self.post(payload, target, profile)
|
41
|
-
profile[:url] = profile[:url]
|
42
|
-
|
43
|
-
uri = URI.parse(profile[:url])
|
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.notification_text,
|
48
44
|
}
|
49
45
|
|
50
|
-
|
46
|
+
icon = payload.custom_bot_icon || profile[:icon]
|
47
|
+
if icon =~ /^:\w+:$/
|
48
|
+
params[:icon_emoji] = icon
|
49
|
+
elsif icon =~ /^(https?:\/\/)?[0-9a-z]+\.[-_0-9a-z]+/ # very naive regex, I know. it'll be fine.
|
50
|
+
params[:icon_url] = icon
|
51
|
+
elsif !(icon.nil? || icon == '')
|
52
|
+
raise ArgumentError, "Couldn't figure out icon '#{icon}'. Try :emoji: or a URL."
|
53
|
+
end
|
54
|
+
|
55
|
+
response = post_message(profile, params)
|
56
|
+
body = JSON.parse(response.body)
|
57
|
+
error = body.fetch("error", "")
|
51
58
|
|
52
59
|
# let's try to be helpful about error messages
|
53
|
-
if
|
54
|
-
raise "Couldn't send slack message because the
|
55
|
-
elsif
|
60
|
+
if ["token_revoked", "token_expired", "invalid_auth", "not_authed"].include?(error)
|
61
|
+
raise "Couldn't send slack message because the API key for profile '#{profile[:handle]}' is wrong."
|
62
|
+
elsif ["no_permission", "ekm_access_denied"].include?(error)
|
63
|
+
raise "Couldn't send slack message because the API key for profile '#{profile[:handle]}' isn't allowed to post messages."
|
64
|
+
elsif error == "channel_not_found"
|
56
65
|
raise "Tried to send Slack message to non-existent channel or user '#{target}'"
|
57
|
-
elsif
|
66
|
+
elsif error == "invalid_arguments"
|
58
67
|
raise "Tried to send Slack message with invalid payload."
|
59
68
|
elsif response.code == "302"
|
60
|
-
raise "Got 302 response while posting to Slack. Check your
|
69
|
+
raise "Got 302 response while posting to Slack. Check your API key for profile '#{profile[:handle]}'."
|
61
70
|
elsif response.code != "200"
|
62
71
|
raise "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
|
63
72
|
end
|
@@ -65,7 +74,19 @@ class SlackMessage::Api
|
|
65
74
|
response
|
66
75
|
end
|
67
76
|
|
68
|
-
|
69
|
-
|
77
|
+
# mostly test harness
|
78
|
+
def self.post_message(profile, params)
|
79
|
+
uri = URI("https://slack.com/api/chat.postMessage")
|
80
|
+
request = Net::HTTP::Post.new(uri).tap do |req|
|
81
|
+
req['Authorization'] = "Bearer #{profile[:api_token]}"
|
82
|
+
req['Content-type'] = "application/json; charset=utf-8"
|
83
|
+
req.body = params.to_json
|
84
|
+
end
|
85
|
+
|
86
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
|
87
|
+
http.request(request)
|
88
|
+
end
|
70
89
|
end
|
90
|
+
|
91
|
+
private_class_method :post_message
|
71
92
|
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,5 +1,6 @@
|
|
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
|
3
|
+
attr_accessor :notification_text
|
3
4
|
|
4
5
|
EMSPACE = " " # unicode emspace
|
5
6
|
|
@@ -9,8 +10,10 @@ class SlackMessage::Dsl
|
|
9
10
|
@caller_self = eval("self", block.binding)
|
10
11
|
|
11
12
|
@body = []
|
12
|
-
@default_section = Section.new
|
13
|
+
@default_section = Section.new(self)
|
13
14
|
@custom_bot_name = nil
|
15
|
+
@custom_bot_icon = nil
|
16
|
+
@notification_text = nil
|
14
17
|
end
|
15
18
|
|
16
19
|
# allowable top-level entities within a block
|
@@ -18,7 +21,7 @@ class SlackMessage::Dsl
|
|
18
21
|
def section(&block)
|
19
22
|
finalize_default_section
|
20
23
|
|
21
|
-
section = Section.new.tap do |s|
|
24
|
+
section = Section.new(self).tap do |s|
|
22
25
|
s.instance_eval(&block)
|
23
26
|
end
|
24
27
|
|
@@ -82,6 +85,10 @@ class SlackMessage::Dsl
|
|
82
85
|
@custom_bot_name = name
|
83
86
|
end
|
84
87
|
|
88
|
+
def bot_icon(icon)
|
89
|
+
@custom_bot_icon = icon
|
90
|
+
end
|
91
|
+
|
85
92
|
# end bot name
|
86
93
|
|
87
94
|
def render
|
@@ -102,13 +109,14 @@ class SlackMessage::Dsl
|
|
102
109
|
@body.push(default_section.render)
|
103
110
|
end
|
104
111
|
|
105
|
-
@default_section = Section.new
|
112
|
+
@default_section = Section.new(self)
|
106
113
|
end
|
107
114
|
|
108
115
|
class Section
|
109
116
|
attr_reader :body
|
110
117
|
|
111
|
-
def initialize
|
118
|
+
def initialize(parent)
|
119
|
+
@parent = parent
|
112
120
|
@body = { type: "section" }
|
113
121
|
@list = List.new
|
114
122
|
end
|
@@ -213,6 +221,11 @@ class SlackMessage::Dsl
|
|
213
221
|
|
214
222
|
def render
|
215
223
|
body[:fields] = @list.render if @list.any?
|
224
|
+
|
225
|
+
if body[:text] && body[:text][:text] && !@parent.notification_text
|
226
|
+
@parent.notification_text = body[:text][:text]
|
227
|
+
end
|
228
|
+
|
216
229
|
body
|
217
230
|
end
|
218
231
|
end
|
data/lib/slack_message/rspec.rb
CHANGED
@@ -30,13 +30,28 @@ module SlackMessage::RSpec
|
|
30
30
|
FauxResponse = Struct.new(:code, :body)
|
31
31
|
|
32
32
|
def self.included(_)
|
33
|
-
SlackMessage::Api.singleton_class.undef_method(:
|
34
|
-
SlackMessage::Api.define_singleton_method(:
|
33
|
+
SlackMessage::Api.singleton_class.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)
|
40
55
|
end
|
41
56
|
end
|
42
57
|
|
@@ -102,12 +117,55 @@ module SlackMessage::RSpec
|
|
102
117
|
supports_block_expectations
|
103
118
|
end
|
104
119
|
|
120
|
+
# icon matcher
|
121
|
+
matcher :post_slack_message_with_icon do |expected|
|
122
|
+
match do |actual|
|
123
|
+
@instance ||= PostTo.new
|
124
|
+
@instance.with_icon(expected)
|
125
|
+
|
126
|
+
actual.call
|
127
|
+
@instance.enforce_expectations
|
128
|
+
end
|
129
|
+
|
130
|
+
chain :with_content_matching do |content|
|
131
|
+
@instance ||= PostTo.new
|
132
|
+
@instance.with_content_matching(content)
|
133
|
+
end
|
134
|
+
|
135
|
+
failure_message { @instance.failure_message }
|
136
|
+
failure_message_when_negated { @instance.failure_message_when_negated }
|
137
|
+
|
138
|
+
supports_block_expectations
|
139
|
+
end
|
140
|
+
|
141
|
+
matcher :post_slack_message_with_icon_matching do |expected|
|
142
|
+
match do |actual|
|
143
|
+
@instance ||= PostTo.new
|
144
|
+
@instance.with_icon_matching(expected)
|
145
|
+
|
146
|
+
actual.call
|
147
|
+
@instance.enforce_expectations
|
148
|
+
end
|
149
|
+
|
150
|
+
chain :with_content_matching do |content|
|
151
|
+
@instance ||= PostTo.new
|
152
|
+
@instance.with_content_matching(content)
|
153
|
+
end
|
154
|
+
|
155
|
+
failure_message { @instance.failure_message }
|
156
|
+
failure_message_when_negated { @instance.failure_message_when_negated }
|
157
|
+
|
158
|
+
supports_block_expectations
|
159
|
+
end
|
160
|
+
|
105
161
|
class PostTo
|
106
162
|
def initialize
|
107
163
|
@captured_calls = []
|
108
164
|
@content = nil
|
109
165
|
@channel = nil
|
110
166
|
@profile = nil
|
167
|
+
@icon = nil
|
168
|
+
@icon_matching = nil
|
111
169
|
|
112
170
|
SlackMessage::RSpec.register_expectation_listener(self)
|
113
171
|
end
|
@@ -120,6 +178,15 @@ module SlackMessage::RSpec
|
|
120
178
|
@channel = channel
|
121
179
|
end
|
122
180
|
|
181
|
+
def with_icon(icon)
|
182
|
+
@icon = icon
|
183
|
+
end
|
184
|
+
|
185
|
+
def with_icon_matching(icon)
|
186
|
+
raise ArgumentError unless icon.is_a? Regexp
|
187
|
+
@icon_matching = icon
|
188
|
+
end
|
189
|
+
|
123
190
|
def with_content_matching(content)
|
124
191
|
raise ArgumentError unless content.is_a? Regexp
|
125
192
|
@content = content
|
@@ -134,8 +201,10 @@ module SlackMessage::RSpec
|
|
134
201
|
|
135
202
|
@captured_calls
|
136
203
|
.filter { |call| !@channel || call[:channel] == @channel }
|
137
|
-
.filter { |call| !@profile || [call[:profile], call[:username]].include?(@profile) }
|
204
|
+
.filter { |call| !@profile || [call[:profile][:handle], call[:username]].include?(@profile) }
|
138
205
|
.filter { |call| !@content || call.fetch(:blocks).to_s =~ @content }
|
206
|
+
.filter { |call| !@icon || call.fetch(:icon_emoji, call.fetch(:icon_url, '')) == @icon }
|
207
|
+
.filter { |call| !@icon_matching || call.fetch(:icon_emoji, call.fetch(:icon_url, '')) =~ @icon_matching }
|
139
208
|
.any?
|
140
209
|
end
|
141
210
|
|
@@ -154,6 +223,10 @@ module SlackMessage::RSpec
|
|
154
223
|
concat << "post a slack message to '#{@channel}'"
|
155
224
|
elsif @profile
|
156
225
|
concat << "post a slack message as '#{@profile}'"
|
226
|
+
elsif @icon
|
227
|
+
concat << "post a slack message with icon '#{@icon}'"
|
228
|
+
elsif @icon_matching
|
229
|
+
concat << "post a slack message with icon matching '#{@icon_matching.inspect}'"
|
157
230
|
else
|
158
231
|
concat << "post a slack message"
|
159
232
|
end
|
data/lib/slack_message.rb
CHANGED
@@ -11,19 +11,15 @@ module SlackMessage
|
|
11
11
|
configuration.configure(&block)
|
12
12
|
end
|
13
13
|
|
14
|
-
def self.user_id_for(email) # spooky undocumented public method 👻
|
15
|
-
Api::user_id_for(email)
|
16
|
-
end
|
17
|
-
|
18
14
|
def self.post_to(target, as: :default, &block)
|
19
15
|
payload = Dsl.new(block).tap do |instance|
|
20
16
|
instance.instance_eval(&block)
|
21
17
|
end
|
22
18
|
|
23
|
-
profile = Configuration.profile(as
|
24
|
-
target = user_id_for(target) if target =~ /^\S{1,}@\S{2,}\.\S{2,}$/
|
19
|
+
profile = Configuration.profile(as)
|
20
|
+
target = Api::user_id_for(target, profile) if target =~ /^\S{1,}@\S{2,}\.\S{2,}$/
|
25
21
|
|
26
|
-
Api.post(payload
|
22
|
+
Api.post(payload, target, profile)
|
27
23
|
end
|
28
24
|
|
29
25
|
def self.post_as(profile_name, &block)
|
@@ -31,15 +27,15 @@ module SlackMessage
|
|
31
27
|
instance.instance_eval(&block)
|
32
28
|
end
|
33
29
|
|
34
|
-
profile = Configuration.profile(profile_name
|
30
|
+
profile = Configuration.profile(profile_name)
|
35
31
|
if profile[:default_channel].nil?
|
36
32
|
raise ArgumentError, "Sorry, you need to specify a default_channel for profile #{profile_name} to use post_as"
|
37
33
|
end
|
38
34
|
|
39
35
|
target = profile[:default_channel]
|
40
|
-
target = user_id_for(target) if target =~ /^\S{1,}@\S{2,}\.\S{2,}$/
|
36
|
+
target = Api::user_id_for(target, profile) if target =~ /^\S{1,}@\S{2,}\.\S{2,}$/
|
41
37
|
|
42
|
-
Api.post(payload
|
38
|
+
Api.post(payload, target, profile)
|
43
39
|
end
|
44
40
|
|
45
41
|
def self.build(&block)
|
data/slack_message.gemspec
CHANGED
data/spec/slack_message_spec.rb
CHANGED
@@ -2,13 +2,23 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe SlackMessage do
|
4
4
|
describe "API convenience" do
|
5
|
+
before do
|
6
|
+
SlackMessage.configure do |config|
|
7
|
+
config.add_profile(name: 'default profile', api_token: 'abc123')
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
SlackMessage.configuration.reset
|
13
|
+
end
|
14
|
+
|
5
15
|
it "can grab user IDs" do
|
6
|
-
|
16
|
+
profile = SlackMessage::Configuration.profile(:default)
|
7
17
|
allow(Net::HTTP).to receive(:start).and_return(
|
8
18
|
double(code: "200", body: '{ "user": { "id": "ABC123" }}')
|
9
19
|
)
|
10
20
|
|
11
|
-
result = SlackMessage.user_id_for("hello@joemastey.com")
|
21
|
+
result = SlackMessage::Api.user_id_for("hello@joemastey.com", profile)
|
12
22
|
expect(result).to eq("ABC123")
|
13
23
|
end
|
14
24
|
end
|
@@ -36,28 +46,10 @@ RSpec.describe SlackMessage do
|
|
36
46
|
SlackMessage.configuration.reset
|
37
47
|
end
|
38
48
|
|
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
49
|
it "lets you add and fetch profiles" do
|
58
50
|
SlackMessage.configure do |config|
|
59
|
-
config.add_profile(name: 'default profile',
|
60
|
-
config.add_profile(:nonstandard, name: 'another profile',
|
51
|
+
config.add_profile(name: 'default profile', api_token: 'abc123')
|
52
|
+
config.add_profile(:nonstandard, name: 'another profile', api_token: 'abc123')
|
61
53
|
end
|
62
54
|
|
63
55
|
expect(SlackMessage.configuration.profile(:default)[:name]).to eq('default profile')
|
@@ -73,7 +65,7 @@ RSpec.describe SlackMessage do
|
|
73
65
|
before do
|
74
66
|
SlackMessage.configure do |config|
|
75
67
|
config.clear_profiles!
|
76
|
-
config.add_profile(name: 'default profile',
|
68
|
+
config.add_profile(name: 'default profile', api_token: 'abc123')
|
77
69
|
end
|
78
70
|
end
|
79
71
|
|
@@ -97,7 +89,8 @@ RSpec.describe SlackMessage do
|
|
97
89
|
|
98
90
|
it "lets you assert by profile name" do
|
99
91
|
SlackMessage.configure do |config|
|
100
|
-
config.
|
92
|
+
config.clear_profiles!
|
93
|
+
config.add_profile(:schmoebot, name: 'Schmoe', api_token: 'abc123', icon: ':schmoebot:', default_channel: '#schmoes')
|
101
94
|
end
|
102
95
|
|
103
96
|
expect {
|
@@ -113,6 +106,35 @@ RSpec.describe SlackMessage do
|
|
113
106
|
}.to post_slack_message_as('Schmoe').with_content_matching(/foo/)
|
114
107
|
end
|
115
108
|
|
109
|
+
it "lets you assert by profile image" do
|
110
|
+
SlackMessage.configure do |config|
|
111
|
+
config.clear_profiles!
|
112
|
+
config.add_profile(:schmoebot, name: 'Schmoe', api_token: 'abc123', icon: ':schmoebot:', default_channel: '#schmoes')
|
113
|
+
end
|
114
|
+
|
115
|
+
expect {
|
116
|
+
SlackMessage.post_as(:schmoebot) { text "foo" }
|
117
|
+
}.to post_slack_message_with_icon(':schmoebot:')
|
118
|
+
|
119
|
+
expect {
|
120
|
+
SlackMessage.post_as(:schmoebot) do
|
121
|
+
bot_icon ':schmalternate:'
|
122
|
+
text "foo"
|
123
|
+
end
|
124
|
+
}.to post_slack_message_with_icon(':schmalternate:')
|
125
|
+
|
126
|
+
expect {
|
127
|
+
SlackMessage.post_as(:schmoebot) do
|
128
|
+
bot_icon 'https://thispersondoesnotexist.com/image'
|
129
|
+
text "foo"
|
130
|
+
end
|
131
|
+
}.to post_slack_message_with_icon_matching(/thisperson/)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "lets you assert notification text" do
|
135
|
+
# TODO :|
|
136
|
+
end
|
137
|
+
|
116
138
|
it "can assert more generally too tbh" do
|
117
139
|
expect {
|
118
140
|
SlackMessage.post_to('#general') { text "foo" }
|
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.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Mastey
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|