slack_message 1.9.0 → 2.2.2
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/.github/workflows/main.yml +27 -0
- data/CHANGELOG.md +28 -1
- data/Gemfile.lock +1 -1
- data/README.md +152 -85
- data/lib/slack_message/api.rb +70 -32
- data/lib/slack_message/configuration.rb +10 -22
- data/lib/slack_message/dsl.rb +65 -21
- 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 +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58dc8d1f101b642daabf6e686fc0e79973d73189acc3431f96d85de326d9c501
|
4
|
+
data.tar.gz: 68bd076ad93dc8cc4529be972968fbe5944fba78a8589938b8b2ab317d516b05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
## [
|
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
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,8 +302,8 @@ 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 {
|
@@ -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
|
277
|
-
options and abilities, which
|
278
|
-
you want to add a feature, open an issue on Github first to see if it's
|
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.
|
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
|
-
|
349
|
+
Some behaviors that are still planned but not yet added:
|
286
350
|
|
287
351
|
* some API documentation amirite?
|
288
|
-
*
|
352
|
+
* custom http_options in configuration
|
289
353
|
* more of BlockKit's options
|
290
|
-
* any interactive elements at all
|
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
|
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
|
|
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
|
|
@@ -53,9 +58,11 @@ class SlackMessage::Dsl
|
|
53
58
|
finalize_default_section
|
54
59
|
|
55
60
|
if text == "" || text.nil?
|
56
|
-
raise ArgumentError, "
|
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
|
-
#
|
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, "
|
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, "
|
162
|
+
raise ArgumentError, "Please pass an array when creating a ul." unless elements.respond_to?(:map)
|
131
163
|
|
132
|
-
text(
|
133
|
-
|
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, "
|
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, "
|
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
|
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.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-
|
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
|