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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e92b2faa8e8b8118a0e168cef488d64b94255c841d774909647ede3d9a9faf6b
4
- data.tar.gz: d494af801908aa9f5cf25141b012df26e23a69f8ef9389c3c8bb580c46641133
3
+ metadata.gz: 4b476b987543e7a38fd316de55186ddee859dfc1bd71817b14ddd13b2cb79f59
4
+ data.tar.gz: 80932a2d8e1bcac51239ea7724cd42f419dadfb7061fd939f359c2f4a5743777
5
5
  SHA512:
6
- metadata.gz: 6fceee6aeb36f7fc6b855c1ce90f647b67ce480c78e62ec2fe45bbfc347e6c9efa6ca910804c51b0edb12321953d96014ac137572d2140328109444f68512ca4
7
- data.tar.gz: a9fe4697d85ac2f91197a39dacc46d1740c251f8472dcb91e8ba2132caac5c9f029cb5f88894f39542e1436ce554562d87108661863c2574655e64c866c5115e
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
- ## [Unreleased]
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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- slack_message (2.1.0)
4
+ slack_message (2.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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 on how to make use of that API. For instance:
21
+ opinionated stances about usage to try to minimize the pain of integrating
22
+ with it. For example:
23
23
 
24
- * No dependencies. Your lockfile is enough of a mess already.
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 to
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 should be as simple as possible. But as much work as possible
34
- should be moved from callers into configuration.
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
- user_email = 'hello@joemastey.com'
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 stop API calls for posting messages, and will allow you access to
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 fairly simple. Other gems have lots of config
295
- options and abilities, which is wonderful, but overall complicates usage. If
296
- you want to add a feature, open an issue on Github first to see if it's likely
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.
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
- * allow custom http_options in configuration
352
+ * custom http_options in configuration
305
353
  * more of BlockKit's options
306
- * any interactive elements at all (I don't understand them yet)
354
+ * any interactive elements at all
307
355
  * editing / updating messages
308
356
  * multiple recipients
309
- * more interesting return types for your message (probably related to the above)
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
  ------------
@@ -2,47 +2,51 @@ require 'net/http'
2
2
  require 'net/https'
3
3
  require 'json'
4
4
 
5
- class SlackMessage::Api
6
- def self.user_id_for(email, profile)
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
- response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
14
- http.request(request)
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:\n#{response.body}"
28
+ raise SlackMessage::ApiError, "Received an error because your authentication token isn't properly configured."
29
+ elsif payload.include?("error") && payload["error"] == "users_not_found"
30
+ raise SlackMessage::ApiError, "Couldn't find a user with the email '#{email}'."
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 self.post(payload, target, profile)
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.notification_text,
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
- # mostly test harness
78
- def self.post_message(profile, params)
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
@@ -1,19 +1,21 @@
1
1
  class SlackMessage::Dsl
2
- attr_reader :body, :default_section, :custom_bot_name, :custom_bot_icon
3
- attr_accessor :notification_text
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
- @default_section = Section.new(self)
14
- @custom_bot_name = nil
15
- @custom_bot_icon = nil
16
- @notification_text = nil
12
+ @body = []
13
+ @profile = profile
14
+ @default_section = Section.new(self)
15
+
16
+ @custom_bot_name = nil
17
+ @custom_bot_icon = nil
18
+ @custom_notification = nil
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, "tried to create a context block without a value"
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
- # custom bot name
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, "tried to create text node without a value"
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, "please pass an array" unless elements.respond_to?(:map)
162
+ raise ArgumentError, "Please pass an array when creating a ul." unless elements.respond_to?(:map)
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, "please pass an array" unless elements.respond_to?(:map)
171
+ raise ArgumentError, "Please pass an array when creating an ol." unless elements.respond_to?(:map)
147
172
 
148
- text(
149
- elements.map.with_index(1) { |text, idx| "#{EMSPACE}#{idx}. #{text}" }.join("\n")
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, "can't create a list item for '#{title}' without a value"
234
+ raise ArgumentError, "Can't create a list item for '#{title}' without a value."
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.notification_text
226
- @parent.notification_text = body[:text][: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
@@ -30,7 +30,7 @@ module SlackMessage::RSpec
30
30
  FauxResponse = Struct.new(:code, :body)
31
31
 
32
32
  def self.included(_)
33
- SlackMessage::Api.singleton_class.undef_method(:post_message)
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
- payload = Dsl.new(block).tap do |instance|
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
- profile = Configuration.profile(as)
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 =~ /^\S{1,}@\S{2,}\.\S{2,}$/
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
- Dsl.new(block).tap do |instance|
52
+ def self.build(profile_name = :default, &block)
53
+ profile = Configuration.profile(profile_name)
54
+
55
+ Dsl.new(block, profile).tap do |instance|
43
56
  instance.instance_eval(&block)
44
57
  end.send(:render)
45
58
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'slack_message'
3
- gem.version = "2.1.0"
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"
@@ -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.1.0
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-02 00:00:00.000000000 Z
11
+ date: 2021-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -58,6 +58,7 @@ executables: []
58
58
  extensions: []
59
59
  extra_rdoc_files: []
60
60
  files:
61
+ - ".github/workflows/main.yml"
61
62
  - ".gitignore"
62
63
  - ".ruby-version"
63
64
  - CHANGELOG.md
@@ -89,7 +90,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
90
  requirements:
90
91
  - - ">="
91
92
  - !ruby/object:Gem::Version
92
- version: '0'
93
+ version: 2.6.0
93
94
  required_rubygems_version: !ruby/object:Gem::Requirement
94
95
  requirements:
95
96
  - - ">="