slack_message 2.1.0 → 2.3.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/.github/workflows/main.yml +27 -0
- data/CHANGELOG.md +21 -1
- data/Gemfile.lock +1 -1
- data/README.md +74 -25
- data/lib/slack_message/api.rb +43 -26
- data/lib/slack_message/dsl.rb +54 -23
- data/lib/slack_message/rspec.rb +7 -1
- data/lib/slack_message.rb +23 -10
- data/slack_message.gemspec +3 -1
- data/spec/slack_message_spec.rb +36 -26
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b476b987543e7a38fd316de55186ddee859dfc1bd71817b14ddd13b2cb79f59
|
4
|
+
data.tar.gz: 80932a2d8e1bcac51239ea7724cd42f419dadfb7061fd939f359c2f4a5743777
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c6273ff440e4aa2fd9c4dba40c7e45e4c3840d02d7619dafb11d516f9a3541172499e0e81f1966103a89332bb3911fb96818984fadc0195faa27c9b5e1692fcb
|
7
|
+
data.tar.gz: fc1a7df622d0cfd0c10311a70478cba43b893d7cde189d4f55dd997196e544145f8ce855a4b82af920f4320314907a6fe91bb39fddf3ba5e9460d4b8a4cbb33a
|
@@ -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]
|
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,26 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [
|
3
|
+
## [2.3.0] - 2021-11-30
|
4
|
+
- Formally require minimum version of ruby. It wouldn't have worked anyway,
|
5
|
+
but worth actually specifying.
|
6
|
+
|
7
|
+
## [2.2.2] - 2021-11-30
|
8
|
+
- Add github workflow for automatic CI runs. Stolen from another project.
|
9
|
+
|
10
|
+
## [2.2.1] - 2021-11-20
|
11
|
+
- Trying to fetch user ID for a string that isn't email-like raises an error.
|
12
|
+
- In tests, fetching user IDs is mocked out to prevent network requests.
|
13
|
+
- Tightened up and clarified README.
|
14
|
+
- Some internal cleanup and restructuring of modules.
|
15
|
+
|
16
|
+
## [2.2.0] - 2021-11-20
|
17
|
+
- When sending text, it is now possible to mention users and have their user
|
18
|
+
IDs automatically converted using `<email@email.com>` within text nodes.
|
19
|
+
- It's now possible to override notification text.
|
20
|
+
- Errors received from the Slack API now raise `SlackMessage::ApiError`.
|
21
|
+
- Re-exposed a top-level method for getting user IDs, `SlackMessage.user_id`.
|
22
|
+
- Raising some better errors when no message payload is present.
|
23
|
+
- Using `build` now requires a profile, so configuration must exist.
|
4
24
|
|
5
25
|
## [2.1.0] - 2021-11-01
|
6
26
|
- Change to use Slack Apps for all profiles. This should allow growth toward
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -15,23 +15,25 @@ end
|
|
15
15
|
|
16
16
|
To install, just add `slack_message` to your bundle and you're ready to go.
|
17
17
|
|
18
|
-
Opinionated Stances
|
19
|
-
------------
|
18
|
+
#### Opinionated Stances
|
20
19
|
|
21
20
|
Slack's API has a lot of options available to you! But this gem takes some
|
22
|
-
opinionated stances
|
21
|
+
opinionated stances about usage to try to minimize the pain of integrating
|
22
|
+
with it. For example:
|
23
23
|
|
24
|
-
*
|
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.
|
25
27
|
* Webhooks are passé. Only Slack Apps are supported now.
|
26
28
|
* Unless you request otherwise, text is always rendered using `mrkdwn`. If you
|
27
29
|
want plaintext, you'll need to ask for it. Same for the `emoji` flag.
|
28
30
|
* 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
|
30
|
-
look it up as an email address.
|
31
|
+
something that looks like an email address, `slack_message` is going to try
|
32
|
+
to look it up as an email address.
|
31
33
|
* A few little hacks on the block syntax, such as adding a `blank_line` (which
|
32
34
|
doesn't exist in the API), or leading spaces.
|
33
|
-
* Configuration
|
34
|
-
should
|
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.
|
35
37
|
|
36
38
|
Usage
|
37
39
|
------------
|
@@ -173,9 +175,7 @@ It's just as easy to send messages directly to users. SlackMessage will look for
|
|
173
175
|
targets that are email-addressish, and look them up for you automatically:
|
174
176
|
|
175
177
|
```ruby
|
176
|
-
|
177
|
-
|
178
|
-
SlackMessage.post_to(user_email) do
|
178
|
+
SlackMessage.post_to('hello@joemastey.com') do
|
179
179
|
text "You specifically did it! :thumbsup:"
|
180
180
|
end
|
181
181
|
```
|
@@ -207,15 +207,13 @@ SlackMessage.post_to('#general') do
|
|
207
207
|
text "See more here: #{link('result', 'https://google.com')}"
|
208
208
|
end
|
209
209
|
|
210
|
-
text ":rocketship: hello@joemastey.com"
|
210
|
+
text ":rocketship: <hello@joemastey.com>"
|
211
211
|
|
212
212
|
context ":custom_slack_emoji: An example footer *with some markdown*."
|
213
213
|
end
|
214
214
|
```
|
215
215
|
|
216
216
|
SlackMessage will compose this into Block Kit syntax and send it on its way!
|
217
|
-
For now you'll need to read a bit of the source code to get the entire API. Sorry,
|
218
|
-
working on it.
|
219
217
|
|
220
218
|
If you've defined multiple profiles in configuration, you can specify which to
|
221
219
|
use for your message by specifying its name:
|
@@ -223,6 +221,7 @@ use for your message by specifying its name:
|
|
223
221
|
```ruby
|
224
222
|
SlackMessage.post_to('#general', as: :sidekiq_bot) do
|
225
223
|
text ":octagonal_sign: A job has failed permanently and needs to be rescued."
|
224
|
+
|
226
225
|
link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
|
227
226
|
end
|
228
227
|
```
|
@@ -238,6 +237,55 @@ SlackMessage.post_to('#general') do
|
|
238
237
|
end
|
239
238
|
```
|
240
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
|
+
|
241
289
|
### Testing
|
242
290
|
|
243
291
|
You can do some basic testing against SlackMessage, at least if you use RSpec!
|
@@ -254,8 +302,8 @@ RSpec.configure do |config|
|
|
254
302
|
end
|
255
303
|
```
|
256
304
|
|
257
|
-
This will
|
258
|
-
some custom matchers:
|
305
|
+
This will prevent API calls from leaking in your tests, and will allow you
|
306
|
+
access to some custom matchers:
|
259
307
|
|
260
308
|
```ruby
|
261
309
|
expect {
|
@@ -291,23 +339,24 @@ of very complicated regexes.
|
|
291
339
|
What it Doesn't Do
|
292
340
|
------------
|
293
341
|
|
294
|
-
This gem is intended to stay
|
295
|
-
options and abilities, which
|
296
|
-
you want to add a feature, open an issue on Github first to see if it's
|
297
|
-
to be merged. This gem was built out of an existing need that _didn't_
|
298
|
-
|
299
|
-
expand the DSL to include more useful features.
|
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.
|
300
348
|
|
301
349
|
Some behaviors that are still planned but not yet added:
|
302
350
|
|
303
351
|
* some API documentation amirite?
|
304
|
-
*
|
352
|
+
* custom http_options in configuration
|
305
353
|
* more of BlockKit's options
|
306
|
-
* any interactive elements at all
|
354
|
+
* any interactive elements at all
|
307
355
|
* editing / updating messages
|
308
356
|
* multiple recipients
|
309
|
-
* more interesting return types for your message
|
357
|
+
* more interesting return types for your message
|
310
358
|
* richer text formatting (for instance, `ul` is currently a hack)
|
359
|
+
* more and better organized testing capability
|
311
360
|
|
312
361
|
Contributing
|
313
362
|
------------
|
data/lib/slack_message/api.rb
CHANGED
@@ -2,47 +2,51 @@ require 'net/http'
|
|
2
2
|
require 'net/https'
|
3
3
|
require 'json'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
uri = URI("https://slack.com/api/users.lookupByEmail?email=#{email}")
|
8
|
-
request = Net::HTTP::Get.new(uri).tap do |req|
|
9
|
-
req['Authorization'] = "Bearer #{profile[:api_token]}"
|
10
|
-
req['Content-type'] = "application/json; charset=utf-8"
|
11
|
-
end
|
5
|
+
module SlackMessage::Api
|
6
|
+
extend self
|
12
7
|
|
13
|
-
|
14
|
-
|
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}'"
|
15
11
|
end
|
16
12
|
|
13
|
+
response = look_up_user_by_email(email, profile)
|
14
|
+
|
17
15
|
if response.code != "200"
|
18
|
-
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}"
|
19
17
|
elsif response.body == ""
|
20
|
-
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."
|
21
19
|
end
|
22
20
|
|
23
21
|
begin
|
24
22
|
payload = JSON.parse(response.body)
|
25
23
|
rescue
|
26
|
-
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}"
|
27
25
|
end
|
28
26
|
|
29
27
|
if payload.include?("error") && payload["error"] == "invalid_auth"
|
30
|
-
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}'."
|
31
31
|
elsif payload.include?("error")
|
32
|
-
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}"
|
33
33
|
end
|
34
34
|
|
35
35
|
payload["user"]["id"]
|
36
36
|
end
|
37
37
|
|
38
|
-
def
|
38
|
+
def post(payload, target, profile)
|
39
39
|
params = {
|
40
40
|
channel: target,
|
41
41
|
username: payload.custom_bot_name || profile[:name],
|
42
42
|
blocks: payload.render,
|
43
|
-
text: payload.
|
43
|
+
text: payload.custom_notification,
|
44
44
|
}
|
45
45
|
|
46
|
+
if params[:blocks].length == 0
|
47
|
+
raise ArgumentError, "Tried to send an entirely empty message."
|
48
|
+
end
|
49
|
+
|
46
50
|
icon = payload.custom_bot_icon || profile[:icon]
|
47
51
|
if icon =~ /^:\w+:$/
|
48
52
|
params[:icon_emoji] = icon
|
@@ -58,24 +62,39 @@ class SlackMessage::Api
|
|
58
62
|
|
59
63
|
# let's try to be helpful about error messages
|
60
64
|
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."
|
65
|
+
raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' is wrong."
|
62
66
|
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."
|
67
|
+
raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' isn't allowed to post messages."
|
64
68
|
elsif error == "channel_not_found"
|
65
|
-
raise "Tried to send Slack message to non-existent channel or user '#{target}'"
|
69
|
+
raise SlackMessage::ApiError, "Tried to send Slack message to non-existent channel or user '#{target}'"
|
66
70
|
elsif error == "invalid_arguments"
|
67
|
-
raise "Tried to send Slack message with invalid payload."
|
71
|
+
raise SlackMessage::ApiError, "Tried to send Slack message with invalid payload."
|
68
72
|
elsif response.code == "302"
|
69
|
-
raise "Got 302 response while posting to Slack. Check your API key for profile '#{profile[:handle]}'."
|
73
|
+
raise SlackMessage::ApiError, "Got 302 response while posting to Slack. Check your API key for profile '#{profile[:handle]}'."
|
70
74
|
elsif response.code != "200"
|
71
|
-
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}"
|
72
76
|
end
|
73
77
|
|
74
78
|
response
|
75
79
|
end
|
76
80
|
|
77
|
-
|
78
|
-
|
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)
|
79
98
|
uri = URI("https://slack.com/api/chat.postMessage")
|
80
99
|
request = Net::HTTP::Post.new(uri).tap do |req|
|
81
100
|
req['Authorization'] = "Bearer #{profile[:api_token]}"
|
@@ -87,6 +106,4 @@ class SlackMessage::Api
|
|
87
106
|
http.request(request)
|
88
107
|
end
|
89
108
|
end
|
90
|
-
|
91
|
-
private_class_method :post_message
|
92
109
|
end
|
data/lib/slack_message/dsl.rb
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
class SlackMessage::Dsl
|
2
|
-
attr_reader :body, :default_section, :custom_bot_name, :custom_bot_icon
|
3
|
-
attr_accessor :
|
2
|
+
attr_reader :body, :default_section, :custom_bot_name, :custom_bot_icon, :profile
|
3
|
+
attr_accessor :custom_notification
|
4
4
|
|
5
|
-
EMSPACE = " " # unicode emspace
|
5
|
+
EMSPACE = " " # unicode emspace, Slack won't compress this
|
6
6
|
|
7
|
-
def initialize(block)
|
7
|
+
def initialize(block, profile)
|
8
8
|
# Delegate missing methods to caller scope. Thanks 2008:
|
9
9
|
# https://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
|
10
10
|
@caller_self = eval("self", block.binding)
|
11
11
|
|
12
|
-
@body
|
13
|
-
@
|
14
|
-
@
|
15
|
-
|
16
|
-
@
|
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
|
17
19
|
end
|
18
20
|
|
19
21
|
# allowable top-level entities within a block
|
@@ -56,9 +58,11 @@ class SlackMessage::Dsl
|
|
56
58
|
finalize_default_section
|
57
59
|
|
58
60
|
if text == "" || text.nil?
|
59
|
-
raise ArgumentError, "
|
61
|
+
raise ArgumentError, "Cannot create a context block without a value."
|
60
62
|
end
|
61
63
|
|
64
|
+
text = self.enrich_text(text)
|
65
|
+
|
62
66
|
@body.push({ type: "context", elements: [{
|
63
67
|
type: "mrkdwn", text: text
|
64
68
|
}]})
|
@@ -79,7 +83,7 @@ class SlackMessage::Dsl
|
|
79
83
|
|
80
84
|
# end delegation
|
81
85
|
|
82
|
-
#
|
86
|
+
# bot / notification overrides
|
83
87
|
|
84
88
|
def bot_name(name)
|
85
89
|
@custom_bot_name = name
|
@@ -89,6 +93,10 @@ class SlackMessage::Dsl
|
|
89
93
|
@custom_bot_icon = icon
|
90
94
|
end
|
91
95
|
|
96
|
+
def notification_text(msg)
|
97
|
+
@custom_notification = msg
|
98
|
+
end
|
99
|
+
|
92
100
|
# end bot name
|
93
101
|
|
94
102
|
def render
|
@@ -100,6 +108,20 @@ class SlackMessage::Dsl
|
|
100
108
|
@caller_self.send meth, *args, &blk
|
101
109
|
end
|
102
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
|
+
|
103
125
|
private
|
104
126
|
|
105
127
|
# when doing things that would generate new top-levels, first try
|
@@ -123,9 +145,11 @@ class SlackMessage::Dsl
|
|
123
145
|
|
124
146
|
def text(msg)
|
125
147
|
if msg == "" || msg.nil?
|
126
|
-
raise ArgumentError, "
|
148
|
+
raise ArgumentError, "Cannot create a text node without a value."
|
127
149
|
end
|
128
150
|
|
151
|
+
msg = @parent.enrich_text(msg)
|
152
|
+
|
129
153
|
if @body.include?(:text)
|
130
154
|
@body[:text][:text] << "\n#{msg}"
|
131
155
|
|
@@ -135,19 +159,21 @@ class SlackMessage::Dsl
|
|
135
159
|
end
|
136
160
|
|
137
161
|
def ul(elements)
|
138
|
-
raise ArgumentError, "
|
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)
|
139
166
|
|
140
|
-
text(
|
141
|
-
elements.map { |text| "#{EMSPACE}• #{text}" }.join("\n")
|
142
|
-
)
|
167
|
+
text(msg)
|
143
168
|
end
|
144
169
|
|
145
170
|
def ol(elements)
|
146
|
-
raise ArgumentError, "
|
171
|
+
raise ArgumentError, "Please pass an array when creating an ol." unless elements.respond_to?(:map)
|
147
172
|
|
148
|
-
text(
|
149
|
-
|
150
|
-
|
173
|
+
msg = elements.map.with_index(1) { |text, idx| "#{EMSPACE}#{idx}. #{text}" }.join("\n")
|
174
|
+
msg = @parent.enrich_text(msg)
|
175
|
+
|
176
|
+
text(msg)
|
151
177
|
end
|
152
178
|
|
153
179
|
# styles: default, primary, danger
|
@@ -205,9 +231,10 @@ class SlackMessage::Dsl
|
|
205
231
|
|
206
232
|
def list_item(title, value)
|
207
233
|
if value == "" || value.nil?
|
208
|
-
raise ArgumentError, "
|
234
|
+
raise ArgumentError, "Can't create a list item for '#{title}' without a value."
|
209
235
|
end
|
210
236
|
|
237
|
+
value = @parent.enrich_text(value)
|
211
238
|
@list.add(title, value)
|
212
239
|
end
|
213
240
|
|
@@ -220,10 +247,14 @@ class SlackMessage::Dsl
|
|
220
247
|
end
|
221
248
|
|
222
249
|
def render
|
250
|
+
unless has_content?
|
251
|
+
raise ArgumentError, "Can't create a section with no content."
|
252
|
+
end
|
253
|
+
|
223
254
|
body[:fields] = @list.render if @list.any?
|
224
255
|
|
225
|
-
if body[:text] && body[:text][:text] && !@parent.
|
226
|
-
@parent.notification_text
|
256
|
+
if body[:text] && body[:text][:text] && !@parent.custom_notification
|
257
|
+
@parent.notification_text(body[:text][:text])
|
227
258
|
end
|
228
259
|
|
229
260
|
body
|
data/lib/slack_message/rspec.rb
CHANGED
@@ -30,7 +30,7 @@ module SlackMessage::RSpec
|
|
30
30
|
FauxResponse = Struct.new(:code, :body)
|
31
31
|
|
32
32
|
def self.included(_)
|
33
|
-
SlackMessage::Api.
|
33
|
+
SlackMessage::Api.undef_method(:post_message)
|
34
34
|
SlackMessage::Api.define_singleton_method(:post_message) do |profile, params|
|
35
35
|
@@listeners.each do |listener|
|
36
36
|
listener.record_call(params.merge(profile: profile))
|
@@ -53,6 +53,12 @@ module SlackMessage::RSpec
|
|
53
53
|
|
54
54
|
return FauxResponse.new('200', response.to_json)
|
55
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)
|
61
|
+
end
|
56
62
|
end
|
57
63
|
|
58
64
|
# w/ channel
|
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,35 +16,43 @@ module SlackMessage
|
|
11
16
|
configuration.configure(&block)
|
12
17
|
end
|
13
18
|
|
19
|
+
def self.user_id(email, profile_name = :default)
|
20
|
+
profile = Configuration.profile(profile_name)
|
21
|
+
Api.user_id_for(email, profile)
|
22
|
+
end
|
23
|
+
|
14
24
|
def self.post_to(target, as: :default, &block)
|
15
|
-
|
25
|
+
profile = Configuration.profile(as)
|
26
|
+
|
27
|
+
payload = Dsl.new(block, profile).tap do |instance|
|
16
28
|
instance.instance_eval(&block)
|
17
29
|
end
|
18
30
|
|
19
|
-
|
20
|
-
target = Api::user_id_for(target, profile) if target =~ /^\S{1,}@\S{2,}\.\S{2,}$/
|
31
|
+
target = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
|
21
32
|
|
22
33
|
Api.post(payload, target, profile)
|
23
34
|
end
|
24
35
|
|
25
36
|
def self.post_as(profile_name, &block)
|
26
|
-
payload = Dsl.new(block).tap do |instance|
|
27
|
-
instance.instance_eval(&block)
|
28
|
-
end
|
29
|
-
|
30
37
|
profile = Configuration.profile(profile_name)
|
31
38
|
if profile[:default_channel].nil?
|
32
39
|
raise ArgumentError, "Sorry, you need to specify a default_channel for profile #{profile_name} to use post_as"
|
33
40
|
end
|
34
41
|
|
42
|
+
payload = Dsl.new(block, profile).tap do |instance|
|
43
|
+
instance.instance_eval(&block)
|
44
|
+
end
|
45
|
+
|
35
46
|
target = profile[:default_channel]
|
36
|
-
target = Api::user_id_for(target, profile) if target =~
|
47
|
+
target = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
|
37
48
|
|
38
49
|
Api.post(payload, target, profile)
|
39
50
|
end
|
40
51
|
|
41
|
-
def self.build(&block)
|
42
|
-
|
52
|
+
def self.build(profile_name = :default, &block)
|
53
|
+
profile = Configuration.profile(profile_name)
|
54
|
+
|
55
|
+
Dsl.new(block, profile).tap do |instance|
|
43
56
|
instance.instance_eval(&block)
|
44
57
|
end.send(:render)
|
45
58
|
end
|
data/slack_message.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = 'slack_message'
|
3
|
-
gem.version = "2.
|
3
|
+
gem.version = "2.3.0"
|
4
4
|
gem.summary = "A nice DSL for composing rich messages in Slack"
|
5
5
|
gem.authors = ["Joe Mastey"]
|
6
6
|
gem.email = 'hello@joemastey.com'
|
@@ -18,6 +18,8 @@ Gem::Specification.new do |gem|
|
|
18
18
|
"source_code_uri" => "http://github.com/jmmastey/slack_message",
|
19
19
|
}
|
20
20
|
|
21
|
+
gem.required_ruby_version = '>= 2.6.0'
|
22
|
+
|
21
23
|
gem.add_development_dependency "rspec", "3.10.0"
|
22
24
|
gem.add_development_dependency "pry", "0.14.1"
|
23
25
|
gem.add_development_dependency "rb-readline", "0.5.5"
|
data/spec/slack_message_spec.rb
CHANGED
@@ -1,31 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe SlackMessage do
|
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
|
-
|
15
|
-
it "can grab user IDs" do
|
16
|
-
profile = SlackMessage::Configuration.profile(:default)
|
17
|
-
allow(Net::HTTP).to receive(:start).and_return(
|
18
|
-
double(code: "200", body: '{ "user": { "id": "ABC123" }}')
|
19
|
-
)
|
20
|
-
|
21
|
-
result = SlackMessage::Api.user_id_for("hello@joemastey.com", profile)
|
22
|
-
expect(result).to eq("ABC123")
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
4
|
describe "DSL" do
|
27
5
|
describe "#build" do
|
28
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
|
+
|
29
12
|
expected_output = [
|
30
13
|
{ type: "section",
|
31
14
|
text: { text: "foo", type: "mrkdwn" }
|
@@ -42,12 +25,9 @@ RSpec.describe SlackMessage do
|
|
42
25
|
end
|
43
26
|
|
44
27
|
describe "configuration" do
|
45
|
-
after do
|
46
|
-
SlackMessage.configuration.reset
|
47
|
-
end
|
48
|
-
|
49
28
|
it "lets you add and fetch profiles" do
|
50
29
|
SlackMessage.configure do |config|
|
30
|
+
config.clear_profiles!
|
51
31
|
config.add_profile(name: 'default profile', api_token: 'abc123')
|
52
32
|
config.add_profile(:nonstandard, name: 'another profile', api_token: 'abc123')
|
53
33
|
end
|
@@ -141,4 +121,34 @@ RSpec.describe SlackMessage do
|
|
141
121
|
}.to post_to_slack.with_content_matching(/foo/)
|
142
122
|
end
|
143
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
|
144
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: 2.
|
4
|
+
version: 2.3.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-
|
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
|
@@ -89,7 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
89
90
|
requirements:
|
90
91
|
- - ">="
|
91
92
|
- !ruby/object:Gem::Version
|
92
|
-
version:
|
93
|
+
version: 2.6.0
|
93
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
95
|
requirements:
|
95
96
|
- - ">="
|