slack_message 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="