slack_message 2.4.0 → 3.0.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: b97efc9f2b80be4ee01c89d4c82aab83bb79983077ec8bfa87f0026ea5387f37
4
- data.tar.gz: ae251f82220f9c15e45d3065f4e8cd84f5ad913c630a48d6cf9713df33b41b34
3
+ metadata.gz: 62f8b7feb3289a37b79cb887dc4e7846c037a8c72290138afe611682a066a884
4
+ data.tar.gz: 73e391d721ace67ad31ae69b7a17eae3578d4f85a2bcd111a5cb1966b68c85cd
5
5
  SHA512:
6
- metadata.gz: c2e5a3f499fe2e73f245ff2163284f73825f3d051c4cc06aee03c39184eb01e22d6ce5d426f1dde77a0f89e6eacd1764b3a49b1f0548a368490588a7dc0bd757
7
- data.tar.gz: d2417ed29a6b0e3b88133e9e679a965104d2b9030945d7241af106b54dabc471190d73902f4a4bceae02a469f24eeeeb6e95d91f1b8c9817dc0f69ee41a6524d
6
+ metadata.gz: cb28febfda107047351955825d1cd84dafdf553147b9f0101573944f3d5290c6d3e84a4d1190e63888f6c8ba851ac70da9eb774f1a01a04372d19520b7df8779
7
+ data.tar.gz: 3438883916204aec8a16a8dfa80b52506d4f640fc69161ad6e8efea1b5a136e97fc15f8b990c2529f3bbfb1f4380a989a0827cbe8f810af08a42bf3a2b6df382
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [3.0.0] - 2021-12-19
4
+ - Return a more structured object from successful message sends.
5
+ - Add the ability to edit or delete a message.
6
+ - Complete overhaul of docs because they were too large.
7
+
3
8
  ## [2.4.0] - 2021-12-13
4
9
  - Add ability to schedule messages, plus some guard rails around that.
5
10
  - Add ability to debug by logging out the total set of params sent to the API.
data/README.md CHANGED
@@ -48,6 +48,11 @@ SlackMessage.post_to('#general') do
48
48
  end
49
49
  ```
50
50
 
51
+ ### The Docs
52
+
53
+ You'll find much more information about how to use SlackMessage by visiting
54
+ [the docs](https://jmmastey.github.io/slack_message).
55
+
51
56
  #### Opinionated Stances
52
57
 
53
58
  This gem is intended to stay simple. Other Slack gems have lots of config
@@ -75,8 +80,10 @@ Some behaviors that are still planned but not yet added:
75
80
  * multiple recipients: https://api.slack.com/methods/conversations.open
76
81
  * more interesting return types for your message
77
82
  * richer text formatting (for instance, `ul` is currently a hack)
83
+ * more mrkdwn syntax, like quotes or code blocks
78
84
  * more and better organized testing capability
79
85
  * posting ephemeral messages: https://api.slack.com/methods/chat.postEphemeral
86
+ * some Rspec test harness for scheduled messages, editing, deleting (probably going to need a test overhaul)
80
87
 
81
88
  Contributing
82
89
  ------------
@@ -0,0 +1,116 @@
1
+ ## Getting Started / Configuration
2
+
3
+ To get started sending messages, you'll first need to create a Slack App with
4
+ some appropriate permissions. It used to be possible to use the Webhook API,
5
+ but that's long since been deprecated, and apps are pretty [straightforward to
6
+ create](https://api.slack.com/tutorials/tracks/getting-a-token).
7
+
8
+ Generally, make sure your token has permissions for _at least_ `users:read` and
9
+ `chat:write`. Then, define a default profile for SlackMessage to use for
10
+ posting.
11
+
12
+ ```ruby
13
+ SlackMessage.configure do |config|
14
+ api_token = 'xoxb-11111111111-2222222222-33333333333333333'
15
+
16
+ config.add_profile(api_token: api_token)
17
+ end
18
+ ```
19
+
20
+ You should keep your token in a safe place like `ENV`. If using this gem with
21
+ Rails, place this code in somewhere like
22
+ `config/initializers/slack_message.rb`.
23
+
24
+ ### Additional Profiles
25
+
26
+ If your app uses slack messages for several different purposes, it's common to
27
+ want to post to different channels as different names / icons / etc. To do that
28
+ more easily and consistently, you can specify multiple profiles.
29
+
30
+ ```ruby
31
+ SlackMessage.configure do |config|
32
+ api_token = 'xoxb-11111111111-2222222222-33333333333333333'
33
+
34
+ # default profile
35
+ config.add_profile(api_token: api_token, name: 'Slack Notifier')
36
+
37
+ # additional profiles (see below for usage)
38
+ config.add_profile(:prod_alert_bot,
39
+ name: 'Prod Alert Bot'
40
+ icon: ':mooseandsquirrel:'
41
+ )
42
+ config.add_profile(:sidekiq_bot,
43
+ api_token: ENV.fetch('SIDEKIQ_SLACK_APP_API_TOKEN'),
44
+ name: 'Sidekiq Bot',
45
+ )
46
+ end
47
+ ```
48
+
49
+ A number of parameters are available to make it simpler to use a profile
50
+ without specifying repetitive information. You can generally also specify this
51
+ information on a per-message basis.
52
+
53
+ | Config | Default | Value |
54
+ |-----------------|-----------------|-----------------------------------------------------------------|
55
+ | api_token | None | Your Slack App API Key. |
56
+ | name | From Slack App | The bot name for your message. |
57
+ | icon | From Slack App | Profile icon for your message. Specify as :emoji: or image URL. |
58
+ | default_channel | None (optional) | Channel / user to post to by default. |
59
+
60
+
61
+ Setting a `default_channel` specifically will allow you to use `post_as`, which
62
+ is a convenient shortcut for bots that repeatedly post to one channel as a
63
+ consistent identity.
64
+
65
+ ```ruby
66
+ SlackMessage.configure do |config|
67
+ config.add_profile(:red_alert_bot,
68
+ api_token: ENV.fetch('SLACK_API_TOKEN'),
69
+ name: 'Red Alerts',
70
+ icon: ':klaxon:',
71
+ default_channel: '#red_alerts'
72
+ )
73
+ end
74
+
75
+ SlackMessage.post_as(:red_alert_bot) do
76
+ text ":ambulance: weeooo weeooo something went wrong"
77
+ end
78
+ ```
79
+
80
+ There's no reason you can't use the same API key for several profiles. Profiles
81
+ are most useful to create consistent name / icon setups for apps with many
82
+ bots.
83
+
84
+ ### Debug Mode
85
+
86
+ If you'd like to get more information about the messages you send, you can set
87
+ SlackMessage to debug mode.
88
+
89
+ ```ruby
90
+ SlackMessage.configure do |config|
91
+ config.debug
92
+ end
93
+ ```
94
+
95
+ You will now see warnings detailing all params sent to the API.
96
+
97
+ ```ruby
98
+ {
99
+ :channel=>"#general",
100
+ :username=>"Builds",
101
+ :blocks=>[
102
+ {:type=>"section", :text=>{
103
+ :type=>"mrkdwn",
104
+ :text=>"Build Stability is Looking Ruff :dog:"
105
+ }}
106
+ ],
107
+ :text=>"Build Issues",
108
+ :post_at=>1639421171,
109
+ }
110
+ ```
111
+
112
+ Note this includes data that is not included in `SlackMessage.build`.
113
+
114
+ ---
115
+
116
+ Next: [Posting a Message](https://jmmastey.github.io/slack_message/02_posting_a_message)
@@ -0,0 +1,134 @@
1
+ ## Posting Messages
2
+
3
+ As mentioned at the outset, posting a message to Slack is dang easy.
4
+
5
+ ```ruby
6
+ SlackMessage.post_to('#general') do
7
+ text "We did it @here! :thumbsup:"
8
+ end
9
+ ```
10
+
11
+ That's it! SlackMessage will automatically serialize for the API.
12
+
13
+ ```json
14
+ [{"type":"section","text":{"type":"mrkdwn","text":"We did it @here! :thumbsup:"}}]
15
+ ```
16
+
17
+ Details like remembering that Slack made a mystifying decision to force you to
18
+ request "mrkdwn", or requiring your text to be wrapped into a section are
19
+ handled for you. Building up messages is meant to be as user-friendly as
20
+ possible.
21
+
22
+ ```ruby
23
+ SlackMessage.build do
24
+ text "haiku are easy"
25
+ text "but sometimes they don't make sense"
26
+ text "refrigerator"
27
+
28
+ context "- unknown author"
29
+ end
30
+ ```
31
+
32
+ SlackMessage will combine your text declarations and add any necessary wrappers
33
+ automatically.
34
+
35
+ ```json
36
+ [
37
+ {
38
+ "type": "section",
39
+ "text": {
40
+ "type": "mrkdwn",
41
+ "text": "haiku are easy\nbut sometimes they don't make sense\nrefrigerator"
42
+ }
43
+ },
44
+ {
45
+ "type": "context",
46
+ "elements": [
47
+ {
48
+ "type": "mrkdwn",
49
+ "text": "- unknown author"
50
+ }
51
+ ]
52
+ }
53
+ ]
54
+ ```
55
+
56
+ ### Direct Messages
57
+
58
+ It's just as easy to send messages directly to users. SlackMessage will look
59
+ for targets that are email-addressish, and look them up for you automatically.
60
+
61
+ ```ruby
62
+ SlackMessage.post_to('hello@joemastey.com') do
63
+ text "You specifically did it! :thumbsup:"
64
+ end
65
+ ```
66
+
67
+ SlackMessage will compose this into Block Kit syntax and send it on its way!
68
+
69
+ ### Multiple Profiles
70
+
71
+ If you've defined multiple profiles in configuration, you can specify which to
72
+ use for your message by specifying its name.
73
+
74
+ ```ruby
75
+ SlackMessage.post_to('#general', as: :sidekiq_bot) do
76
+ text ":octagonal_sign: A job has failed permanently and needs to be rescued."
77
+
78
+ link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
79
+ end
80
+ ```
81
+
82
+ You can also override profile bot details when sending a message.
83
+
84
+ ```ruby
85
+ SlackMessage.post_to('#general') do
86
+ bot_name "CoffeeBot"
87
+ bot_icon ":coffee:"
88
+
89
+ text ":coffee::clock: Time to take a break!"
90
+ end
91
+ ```
92
+
93
+ Finally, if your profile specifies a `default_channel`, you can also post with
94
+ the `post_as` shorthand.
95
+
96
+ ```ruby
97
+ SlackMessage.post_as(:coffeebot) do
98
+ text ":coffee::clock: Time to take a break!"
99
+ end
100
+ ```
101
+
102
+ ### Scheduling a Message
103
+
104
+ To schedule a message, simply provide a `at` parameter to your post. Provide
105
+ either a time object that responds to `to_i`, or an integer that represents a
106
+ [unix timestamp](https://en.wikipedia.org/wiki/Unix_time) for the time at which
107
+ you want your message posted.
108
+
109
+ ```ruby
110
+ SlackMessage.post_to('hello@joemastey.com', at: 20.seconds.from_now) do
111
+ text "From the top of the key. :basketball:"
112
+ end
113
+
114
+ SlackMessage.post_as(:basketball_bot, at: 20.seconds.from_now) do
115
+ text "Boom shakalaka! :explosion:"
116
+ end
117
+ ```
118
+
119
+ Please note that scheduled messages can't specify a `bot_name` or `bot_icon`,
120
+ nor can they be scheduled more than 120 days into the future.
121
+
122
+ ### Best Practices
123
+
124
+ Talk about having coherent methods that post a message, rather than a block
125
+ that includes lots of indirection or ternaries.
126
+
127
+ See the [API documentation for
128
+ chat.postMessage](https://api.slack.com/methods/chat.postMessage) or
129
+ [chat.scheduleMessage](https://api.slack.com/methods/chat.scheduleMessage) for
130
+ more information on posting messages.
131
+
132
+ ---
133
+
134
+ Next: [The SlackMessage DSL](https://jmmastey.github.io/slack_message/03_message_dsl)
@@ -0,0 +1,283 @@
1
+ ### The Message DSL
2
+
3
+ A pretty good number of the elements available in BlockKit are usable in SlackMessage. There are also a few elements that haven't been implemented in the official API, but are too useful to be missing.
4
+
5
+ #### Basic Text
6
+
7
+ While BlockKit officially requires that any elements are contained within a section element, that requirement is relaxed in SlackMessage. If you don't specify a section, one will silently be created to encapsulate your code. That's the secret behind the most basic messages in these docs.
8
+
9
+ ```ruby
10
+ SlackMessage.build do
11
+ text "couldn't be easier"
12
+ end
13
+
14
+ # => [{:type=>"section",
15
+ # :text=>{:type=>"mrkdwn", :text=>"couldn't be easier"}
16
+ # }]
17
+ ```
18
+
19
+ This is equivalent to the more verbose version with a declared section.
20
+
21
+ ```ruby
22
+ SlackMessage.build do
23
+ section do
24
+ text "could be easier"
25
+ end
26
+ end
27
+
28
+ # => [{:type=>"section",
29
+ # :text=>{:type=>"mrkdwn", :text=>"could be easier"}
30
+ # }]
31
+ ```
32
+
33
+ Text elements are the most basic type of element. Adding multiple text calls
34
+ will add a newline between text calls, which will cause a line break
35
+ appropriately.
36
+
37
+ ```ruby
38
+ SlackMessage.build do
39
+ text "one fish, two fish"
40
+ text "red fish, blue fish"
41
+ end
42
+
43
+ # => [{:type=>"section",
44
+ # :text=>{:type=>"mrkdwn", :text=>"one fish, two fish\nred fish, blue fish"}
45
+ # }]
46
+ ```
47
+
48
+ Slack uses a [faux-markdown syntax called
49
+ mrkdwn](https://api.slack.com/reference/surfaces/formatting#basics), which you
50
+ may be familiar with by typing in the Slack app itself. The API will
51
+ automatically render mrkdwn appropriately.
52
+
53
+ ```ruby
54
+ SlackMessage.build do
55
+ text "*Favorite Colors*"
56
+ text "_John_: ~red~ actually blue."
57
+ end
58
+
59
+ # => [{:type=>"section",
60
+ # :text=>{:type=>"mrkdwn", :text=>"*Favorite Colors*\n_John_: ~red~ actually blue."}}]
61
+ ```
62
+
63
+ Rendering emoji in messages is possible using either a) real unicode emoji in
64
+ your message, or b) using the `:emojiname:` syntax, which supports any emoji
65
+ that would work in your Slack app itself, including custom emoji.
66
+
67
+ ```ruby
68
+ SlackMessage.build do
69
+ text ":shipit_squirrel:🚀 time to gooo :tada:"
70
+ end
71
+
72
+ # => [{:type=>"section",
73
+ # :text=>{:type=>"mrkdwn", :text=>":shipit_squirrel:🚀 time to gooo :tada:"}
74
+ # }]
75
+ ```
76
+
77
+ To add a link using Slack's non-markdown link syntax, use the `link` helper
78
+ method interpolated into a text element. Using the `link` helper as its own
79
+ element won't work, as the method simply returns a string that has to be
80
+ included into a text element specifically.
81
+
82
+ ```ruby
83
+ SlackMessage.build do
84
+ text "Your #{link('build', 'https://google.com')} is ready."
85
+ end
86
+
87
+ # => [{:type=>"section",
88
+ # :text=>{:type=>"mrkdwn", :text=>"Your <https://google.com|build> is ready."}
89
+ # }]
90
+ ```
91
+
92
+ While the API squishes whitespace (much in the same way HTML does), it may
93
+ sometimes be useful to add a blank line between text _without_ adding a new
94
+ section to your message. To do so, use the pseudo-element `blank_line`.
95
+
96
+ ```ruby
97
+ SlackMessage.build do
98
+ text "don't let this line"
99
+ blank_line
100
+ text "touch this line."
101
+ end
102
+
103
+ # => => [{:type=>"section",
104
+ # :text=>{:type=>"mrkdwn", :text=>"don't let this line\n \ntouch this line."}
105
+ # }]
106
+ ```
107
+
108
+ Note that between the two newlines in the above example is a unicode emspace,
109
+ which the API will respect as a line worth rendering.
110
+
111
+ #### Buttons
112
+
113
+ BlockKit allows you to specify a button to the right of a section / block. That
114
+ button will be aligned outside the normal space for a section, and is meant to
115
+ link out of the app. To create one of these, use the `link_button` helper.
116
+
117
+ ```ruby
118
+ SlackMessage.build do
119
+ text "Your daily stats are ready @here"
120
+ link_button "Stats Dashboard", stats_dashboard_url
121
+ end
122
+
123
+ # => [{:type=>"section",
124
+ # :text=>{:type=>"mrkdwn", :text=>"Your daily stats are ready @here"},
125
+ # :accessory=>
126
+ # {:type=>"button",
127
+ # :url=>"http://yoursite.com/stats_dashboard",
128
+ # :text=>{:type=>"plain_text", :text=>"Stats Dashboard", :emoji=>true},
129
+ # :style=>:primary}}]
130
+ ```
131
+
132
+ Slack allows three styles for buttons: `default`, `primary`, and `danger`.
133
+ These correspond to gray, green and red buttons respectively. If not specified,
134
+ SlackMessage will use the `primary` style for buttons. I get that this could be
135
+ confusing when there is a default style, but in my experience, a colorful button
136
+ is way more common.
137
+
138
+ You can override the button style by specifying the style with your link button.
139
+
140
+ ```ruby
141
+ SlackMessage.build do
142
+ text "A job has failed catastrophically!"
143
+ link_button "Sidekiq Dashboard", sidekiq_dashboard_url, style: :danger
144
+ end
145
+
146
+ # => [{:type=>"section",
147
+ # :text=>{:type=>"mrkdwn", :text=>"A job has failed catastrophically!"},
148
+ # :accessory=>
149
+ # {:type=>"button",
150
+ # :url=>"https://yoursite.com/sidekiq",
151
+ # :text=>{:type=>"plain_text", :text=>"Sidekiq Dashboard", :emoji=>true},
152
+ # :style=>:danger}}]
153
+ ```
154
+
155
+ #### Lists and List Items
156
+
157
+ - list_item, ul, ol
158
+
159
+ ### Including Multiple Sections
160
+
161
+ Adding more sections is trivial. Simply declare each section and it will be
162
+ separated in the rendered message. This can often occur when looping.
163
+
164
+ ```ruby
165
+ SlackMessage.build do
166
+ pet_types.each do |type, breeds|
167
+ section do
168
+ text "*#{type}:* #{breeds.join(", ")}"
169
+ end
170
+ end
171
+ end
172
+ ```
173
+
174
+ It can also be useful to add a visual divider (similar to a `hr` in HTML)
175
+ between sections. To add one of these, use the `divider` helper. You can also
176
+ add a divider at the end of all the sections, but it often looks silly.
177
+
178
+ ```ruby
179
+ SlackMessage.build do
180
+ section do
181
+ text "*Topsiders:* Emily, Elsie, Derick"
182
+ end
183
+
184
+ divider
185
+
186
+ section do
187
+ text "*Undergrounders:* Kristina, Lauren, Different Emily"
188
+ end
189
+ end
190
+
191
+ # => [
192
+ # {:type=>"section", :text=>{:type=>"mrkdwn", :text=>"*Topsiders:* Emily, Elsie, Derick"}},
193
+ # {:type=>"divider"},
194
+ # {:type=>"section", :text=>{:type=>"mrkdwn", :text=>"*Undergrounders:* Kristina, Lauren, Different Emily"}}
195
+ # ]
196
+ ```
197
+
198
+ Note that a divider can only occur between sections, not within a single
199
+ section. Because of how implicit sections are built, it may look like this works
200
+ for simple messages. You may have troubles when you start adding more
201
+ complicated elements to your messages.
202
+
203
+ ### Images
204
+ - image, accessory_image
205
+
206
+ ### Footers (Context)
207
+
208
+ Slack allows you to add a small additional piece of text to your message, which
209
+ will be rendered in italics and small text. It can support both links and emoji,
210
+ and is useful for providing minor details for your message.
211
+
212
+ ```ruby
213
+ SlackMessage.build do
214
+ text "New coffee complaints have been added."
215
+ context "this complaint added by #{link('Joe Mastey', 'hello@joemastey.com')}."
216
+ end
217
+
218
+ # => [{:type=>"section",
219
+ # :text=>{:type=>"mrkdwn", :text=>"New coffee complaints have been added."}
220
+ # },
221
+ # {:type=>"context", :elements=>
222
+ # [{:type=>"mrkdwn",
223
+ # :text=>"this complaint added by <hello@joemastey.com|Joe Mastey>."
224
+ # }]
225
+ # }]
226
+ ```
227
+
228
+ Context does not belong to a section, and is per-message, not per-section.
229
+ Specifying more than one context will simply overwrite previous calls.
230
+
231
+ ### Bot Customization
232
+
233
+ By default - and with scheduled messages - Slack will use the name and icon of
234
+ the Slack app whose API key you configured. As seen before, it's
235
+ possible to override those default names and icons in configuration. However, it
236
+ can also be customized per-message.
237
+
238
+ ```ruby
239
+ SlackMessage.build do
240
+ bot_icon ":sad_robot:"
241
+ bot_name "BadNewsBuildBot"
242
+
243
+ text "The build is broken. @here"
244
+ end
245
+
246
+ # => [{:type=>"section", :text=>{:type=>"mrkdwn", :text=>"The build is broken. @here"}}]
247
+ ```
248
+
249
+ Notice that the bot details aren't shown in the output of the `build` command.
250
+ To view the changes these methods cause, use `debug` mode.
251
+
252
+ The `bot_icon` can be specified as either an emoji (`:example:`), or a URL
253
+ pointing to an image (`http://mysite.com/shipit.png`). Any other value seems to
254
+ cause an error.
255
+
256
+ ### Custom Notification Text
257
+
258
+ For users who have notifications turned on, Slack will provide a small message
259
+ preview when you send them a message. By default, this preview will take the
260
+ first several words from your message.
261
+
262
+ However, you can specify some custom notification text to be shown to the user.
263
+ This text supports basic emoji and formatting, but nothing complicated.
264
+
265
+ ```ruby
266
+ SlackMessage.build do
267
+ notification_text "Having issues with the build. :ohnoes:"
268
+
269
+ text "The build is broken. The error message was 'undefined method round for NilClass'"
270
+
271
+ # => [{:type=>"section",
272
+ # :text=>
273
+ # {:type=>"mrkdwn",
274
+ # :text=>"The build is broken. The error message was 'undefined method round for NilClass'"}}]
275
+ end
276
+ ```
277
+
278
+ Again notice that notification text is not set within the blocks themselves, so
279
+ you will need to enable debugging to see how it changes what is sent to the API.
280
+
281
+ ---
282
+
283
+ Next: [Editing Messages](https://jmmastey.github.io/slack_message/04_editing_messages)
@@ -0,0 +1,88 @@
1
+ ### Updating a Previous Message
2
+
3
+ After you've posted a message, you may want to edit it later. Interactive bots,
4
+ for instance, may want to repeatedly update a message.
5
+
6
+ Posting will always return an object representing your posted message.
7
+
8
+ ```ruby
9
+ message = SlackMessage.post_to('#general') do
10
+ text "Getting ready..."
11
+ end
12
+ ```
13
+
14
+ Then, you can use that response object to go back and rewrite the message a
15
+ little or a lot.
16
+
17
+ ```ruby
18
+ SlackMessage.update(message) do
19
+ text "Done!"
20
+ end
21
+ ```
22
+
23
+ The new message contents will be built and updated via the API. To give an
24
+ example, you could alert slack to a job status by updating your original
25
+ message.
26
+
27
+
28
+ ```ruby
29
+ class SomeWorker < ApplicationWorker
30
+ def perform
31
+ post_started_status
32
+
33
+ # ... perform work here
34
+
35
+ post_finished_status
36
+ end
37
+
38
+ private
39
+
40
+ def post_started_status
41
+ @message = SlackMessage.post_as(:job_worker) do
42
+ text "Beginning upload."
43
+ end
44
+ end
45
+
46
+ def post_finished_status
47
+ SlackMessage.update(@message) do
48
+ text "Finished upload! @here come and get it."
49
+ link_button "See Results", uploaded_data_url
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
55
+ #### Storing Response Objects for Later
56
+
57
+ Since updates are likely to occur after you've long since finished posting the
58
+ original message, you'll need to persist the message response somehow until you
59
+ need to update it later. As one option, you could serialize the response object
60
+ for later.
61
+
62
+ ```ruby
63
+ # initially
64
+ message = SlackMessage.post_to('#general') do
65
+ text "Starting..."
66
+ end
67
+ redis_connection.set(self.message_cache_key, Marshal.dump(message))
68
+
69
+
70
+ # later
71
+ message = Marshal.load(redis_connection.get(self.message_cache_key))
72
+ SlackMessage.update(message) do
73
+ text "Finished!"
74
+ end
75
+ ```
76
+
77
+ #### Updating Scheduled Messages
78
+
79
+ Sadly, there's currently no way to edit a scheduled message. You'll receive an
80
+ error if you attempt to call `update` on a scheduled message.
81
+
82
+ See the [API documentation for
83
+ chat.update](https://api.slack.com/methods/chat.update) for more information on
84
+ updating messages.
85
+
86
+ ---
87
+
88
+ Next: [Deleting Messages](https://jmmastey.github.io/slack_message/05_deleting_messages)
@@ -0,0 +1,45 @@
1
+ ### Deleting Messages
2
+
3
+ Deleting a message is much like editing a message, only simpler. Just like when
4
+ you edit a message, you'll need a reference to the message you posted.
5
+
6
+ *Important Note: It's not possible to delete a message sent directly to a user.
7
+ It's also not possible to delete a scheduled message once it's already posted.
8
+ Don't send anything you don't want your boss to read.*
9
+
10
+ ```ruby
11
+ message = SlackMessage.post_to('#general') do
12
+ text "Testing: #{SLACK_SECRET_KEY}"
13
+ end
14
+ ```
15
+
16
+ Now you can simply call the `delete` method to make up for your mistakes.
17
+
18
+ ```ruby
19
+ SlackMessage.delete(message)
20
+ ```
21
+
22
+ As with editing a message, it's possible to persist messages to redis / your
23
+ database and remove them using the timestamp and channel of your message.
24
+
25
+ ```ruby
26
+ # initially
27
+ message = SlackMessage.post_to('#general') do
28
+ text "Testing: #{SLACK_SECRET_KEY}"
29
+ end
30
+ redis_connection.set(self.message_cache_key, Marshal.dump(message))
31
+
32
+
33
+ # later
34
+ message = Marshal.load(redis_connection.get(self.message_cache_key))
35
+ SlackMessage.delete(message)
36
+ ```
37
+
38
+ See the [API documentation for
39
+ chat.delete](https://api.slack.com/methods/chat.delete) or
40
+ [chat.deleteScheduledMessage](https://api.slack.com/methods/chat.deleteScheduledMessage)
41
+ for more information on deleting messages.
42
+
43
+ ---
44
+
45
+ Next: [Mentions / Notifying Users](https://jmmastey.github.io/slack_message/06_notifying_users)
@@ -0,0 +1,62 @@
1
+ ## Mentions / Notifying Users
2
+
3
+ There are several supported ways to tag and notify users. As mentioned
4
+ initially, it's possible to DM a user by their account email.
5
+
6
+ ```ruby
7
+ SlackMessage.post_to('hello@joemastey.com') do
8
+ text "Hi there!"
9
+ end
10
+ ```
11
+
12
+ You can also mention a user by email within a channel by wrapping their name in
13
+ tags.
14
+
15
+ ```ruby
16
+ SlackMessage.post_to('#general') do
17
+ bot_name "CoffeeBot"
18
+ bot_icon ":coffee:"
19
+
20
+ text ":coffee: It's your turn to make coffee <hello@joemastey.com>."
21
+ end
22
+ ```
23
+
24
+ Emails that are not wrapped in tags will be rendered as normal email addresses.
25
+ Additionally, Slack will automatically convert a number of channel names and
26
+ tags you're probably already used to.
27
+
28
+ ```ruby
29
+ SlackMessage.post_to('#general') do
30
+ bot_name "CoffeeBot"
31
+ bot_icon ":coffee:"
32
+
33
+ text "@here There's no coffee left! Let #general know when you fix it."
34
+ end
35
+ ```
36
+
37
+ By default, the desktop notification for a message will be the text of the
38
+ message itself. However, you can customize desktop notifications if you prefer.
39
+
40
+ ```ruby
41
+ SlackMessage.post_to('hello@joemastey.com') do
42
+ bot_name "CoffeeBot"
43
+ bot_icon ":coffee:"
44
+
45
+ notification_text "It's a coffee emergency!"
46
+ text "There's no coffee left!"
47
+ end
48
+ ```
49
+
50
+ #### Using @channel or @here
51
+
52
+ Not really a feature, but Slack will respect usage of `@here` and `@channel`.
53
+
54
+ ```ruby
55
+ SlackMessage.post_to('#general') do
56
+ text "Hey @channel, don't forget to submit your drink requests."
57
+ end
58
+ ```
59
+
60
+ ---
61
+
62
+ Next: [Testing](https://jmmastey.github.io/slack_message/07_testing)
@@ -0,0 +1,49 @@
1
+ ## Testing
2
+
3
+ You can do some basic testing against SlackMessage, at least if you use RSpec!
4
+ You'll need to require and include the testing behavior in your spec_helper
5
+ file.
6
+
7
+ ```ruby
8
+ require 'slack_message/rspec'
9
+
10
+ RSpec.configure do |config|
11
+ include SlackMessage::RSpec
12
+
13
+ # your other spec_helper config
14
+ end
15
+ ```
16
+
17
+ This will prevent API calls from leaking in your tests, and will allow you
18
+ access to some custom matchers.
19
+
20
+ ```ruby
21
+ expect {
22
+ SlackMessage.post_to('#general') { text "foo" }
23
+ }.to post_slack_message_to('#general').with_content_matching(/foo/)
24
+
25
+ expect {
26
+ SlackMessage.post_as(:schmoebot) { text "foo" }
27
+ }.to post_slack_message_as(:schmoebot)
28
+
29
+ expect {
30
+ SlackMessage.post_as(:schmoebot) { text "foo" }
31
+ }.to post_slack_message_as('Schmoe Bot')
32
+
33
+ expect {
34
+ SlackMessage.post_as(:schmoebot) { text "foo" }
35
+ }.to post_slack_message_with_icon(':schmoebot:')
36
+
37
+ expect {
38
+ SlackMessage.post_as(:schmoebot) { text "foo" }
39
+ }.to post_slack_message_with_icon_matching(/gravatar/)
40
+
41
+ expect {
42
+ SlackMessage.post_to('#general') { text "foo" }
43
+ }.to post_to_slack
44
+ ```
45
+
46
+ Be forewarned, I'm frankly not that great at more complicated RSpec matchers,
47
+ so I'm guessing there are some bugs. Also, because the content of a message
48
+ gets turned into a complex JSON object, matching against content isn't capable
49
+ of very complicated regexes.
data/docs/index.md ADDED
@@ -0,0 +1,7 @@
1
+ * [Configuration](https://jmmastey.github.io/slack_message/01_configuration)
2
+ * [Posting a Message](https://jmmastey.github.io/slack_message/02_posting_a_message)
3
+ * [The SlackMessage DSL](https://jmmastey.github.io/slack_message/03_message_dsl)
4
+ * [Editing Messages](https://jmmastey.github.io/slack_message/04_editing_messages)
5
+ * [Deleting Messages](https://jmmastey.github.io/slack_message/05_deleting_messages)
6
+ * [Mentions / Notifying Users](https://jmmastey.github.io/slack_message/06_notifying_users)
7
+ * [Testing](https://jmmastey.github.io/slack_message/07_testing)
@@ -59,7 +59,7 @@ module SlackMessage::Api
59
59
  if !time.nil?
60
60
  params[:post_at] = time.to_i
61
61
 
62
- if params[:icon_url] || params[:icon_emoji]
62
+ if payload.custom_bot_name || payload.custom_bot_icon
63
63
  raise ArgumentError, "Sorry, setting an image / emoji icon for scheduled messages isn't supported."
64
64
  end
65
65
  end
@@ -72,6 +72,8 @@ module SlackMessage::Api
72
72
  body = JSON.parse(response.body)
73
73
  error = body.fetch("error", "")
74
74
 
75
+ # TODO: if a scheduled message w/ a short timer is "time_in_past", warn the user?
76
+
75
77
  # let's try to be helpful about error messages
76
78
  if ["token_revoked", "token_expired", "invalid_auth", "not_authed"].include?(error)
77
79
  raise SlackMessage::ApiError, "Couldn't send slack message because the API key for profile '#{profile[:handle]}' is wrong."
@@ -87,6 +89,51 @@ module SlackMessage::Api
87
89
  raise SlackMessage::ApiError, "Got an error back from the Slack API (HTTP #{response.code}):\n#{response.body}"
88
90
  end
89
91
 
92
+ SlackMessage::Response.new(response, profile[:handle])
93
+ end
94
+
95
+ def update(payload, message, profile)
96
+ params = {
97
+ channel: message.channel,
98
+ ts: message.timestamp,
99
+ blocks: payload.render,
100
+ text: payload.custom_notification # TODO: ???
101
+ }
102
+
103
+ if params[:blocks].length == 0
104
+ raise ArgumentError, "Tried to send an entirely empty message."
105
+ end
106
+
107
+ if SlackMessage::Configuration.debugging?
108
+ warn params.inspect
109
+ end
110
+
111
+ response = update_message(profile, params)
112
+ body = JSON.parse(response.body)
113
+ error = body.fetch("error", "")
114
+
115
+ # TODO: error messaging
116
+
117
+ SlackMessage::Response.new(response, profile[:handle])
118
+ end
119
+
120
+ def delete(message, profile)
121
+ params = if message.scheduled?
122
+ {
123
+ channel: message.channel,
124
+ scheduled_message_id: message.scheduled_message_id,
125
+ }
126
+ else
127
+ {
128
+ channel: message.channel,
129
+ ts: message.timestamp,
130
+ }
131
+ end
132
+
133
+ response = delete_message(profile, params)
134
+
135
+ # TODO error handling (incl for already scheduled-and-sent messages)
136
+
90
137
  response
91
138
  end
92
139
 
@@ -108,7 +155,7 @@ module SlackMessage::Api
108
155
  end
109
156
 
110
157
  def post_message(profile, params)
111
- uri = if params[:post_at]
158
+ uri = if params.has_key?(:post_at)
112
159
  URI("https://slack.com/api/chat.scheduleMessage")
113
160
  else
114
161
  URI("https://slack.com/api/chat.postMessage")
@@ -124,4 +171,36 @@ module SlackMessage::Api
124
171
  http.request(request)
125
172
  end
126
173
  end
174
+
175
+ def update_message(profile, params)
176
+ uri = URI("https://slack.com/api/chat.update")
177
+
178
+ request = Net::HTTP::Post.new(uri).tap do |req|
179
+ req['Authorization'] = "Bearer #{profile[:api_token]}"
180
+ req['Content-type'] = "application/json; charset=utf-8"
181
+ req.body = params.to_json
182
+ end
183
+
184
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
185
+ http.request(request)
186
+ end
187
+ end
188
+
189
+ def delete_message(profile, params)
190
+ uri = if params.has_key?(:scheduled_message_id)
191
+ URI("https://slack.com/api/chat.deleteScheduledMessage")
192
+ else
193
+ URI("https://slack.com/api/chat.delete")
194
+ end
195
+
196
+ request = Net::HTTP::Post.new(uri).tap do |req|
197
+ req['Authorization'] = "Bearer #{profile[:api_token]}"
198
+ req['Content-type'] = "application/json; charset=utf-8"
199
+ req.body = params.to_json
200
+ end
201
+
202
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
203
+ http.request(request)
204
+ end
205
+ end
127
206
  end
@@ -0,0 +1,42 @@
1
+ class SlackMessage::Response
2
+ attr_reader :channel, :timestamp, :profile_handle, :scheduled_message_id, :original_response
3
+
4
+ def initialize(api_response, profile_handle)
5
+ @original_response = JSON.parse(api_response.body)
6
+ @ok = @original_response["ok"]
7
+ @channel = @original_response["channel"]
8
+
9
+ @timestamp = @original_response["ts"]
10
+ @scheduled_message_id = @original_response["scheduled_message_id"]
11
+
12
+ @profile_handle = profile_handle
13
+ end
14
+
15
+ def marshal_dump
16
+ [ @profile_handle, @channel, @timestamp, @original_response, @ok, @original_response ]
17
+ end
18
+
19
+ def marshal_load(data)
20
+ @profile_handle, @channel, @timestamp, @original_response, @ok, @original_response = data
21
+ end
22
+
23
+ def sent_to_user?
24
+ channel =~ /^D.*/ # users are D for DM, channels start w/ C
25
+ end
26
+
27
+ def scheduled?
28
+ !!scheduled_message_id
29
+ end
30
+
31
+ def inspect
32
+ identifier = if scheduled?
33
+ "scheduled_message_id=#{scheduled_message_id}"
34
+ else
35
+ "timestamp=#{timestamp}"
36
+ end
37
+
38
+ ok_msg = @ok ? "ok" : "error"
39
+
40
+ "<SlackMessage::Response #{ok_msg} profile_handle=:#{profile_handle} channel=#{channel} #{identifier}>"
41
+ end
42
+ end
@@ -14,6 +14,8 @@ require 'rspec/mocks'
14
14
  # it can be cleaned up properly.
15
15
  #
16
16
 
17
+ # TODO: testing for scheduled messages, editing and deleting
18
+
17
19
  module SlackMessage::RSpec
18
20
  extend RSpec::Matchers::DSL
19
21
 
data/lib/slack_message.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module SlackMessage
2
+ require 'slack_message/response'
2
3
  require 'slack_message/dsl'
3
4
  require 'slack_message/api'
4
5
  require 'slack_message/configuration'
@@ -39,16 +40,46 @@ module SlackMessage
39
40
  raise ArgumentError, "Sorry, you need to specify a default_channel for profile #{profile_name} to use post_as"
40
41
  end
41
42
 
43
+ target = profile[:default_channel]
42
44
  payload = Dsl.new(block, profile).tap do |instance|
43
45
  instance.instance_eval(&block)
44
46
  end
45
47
 
46
- target = profile[:default_channel]
47
48
  target = Api::user_id_for(target, profile) if target =~ EMAIL_PATTERN
48
49
 
49
50
  Api.post(payload, target, profile, at)
50
51
  end
51
52
 
53
+ def self.update(message, &block)
54
+ unless message.is_a?(SlackMessage::Response)
55
+ raise ArgumentError, "You must pass in a SlackMessage::Response to update a message"
56
+ end
57
+
58
+ if message.scheduled?
59
+ raise ArgumentError, "Sorry, scheduled messages cannot be updated. You will need to delete the message and schedule a new one."
60
+ end
61
+
62
+ profile = Configuration.profile(message.profile_handle)
63
+ payload = Dsl.new(block, profile).tap do |instance|
64
+ instance.instance_eval(&block)
65
+ end
66
+
67
+ Api.update(payload, message, profile)
68
+ end
69
+
70
+ def self.delete(message)
71
+ unless message.is_a?(SlackMessage::Response)
72
+ raise ArgumentError, "You must pass in a SlackMessage::Response to delete a message"
73
+ end
74
+
75
+ if message.sent_to_user?
76
+ raise ArgumentError, "It's not possible to delete messages sent directly to users."
77
+ end
78
+
79
+ profile = Configuration.profile(message.profile_handle)
80
+ Api.delete(message, profile)
81
+ end
82
+
52
83
  def self.build(profile_name = :default, &block)
53
84
  profile = Configuration.profile(profile_name)
54
85
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = 'slack_message'
3
- gem.version = "2.4.0"
3
+ gem.version = "3.0.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'
@@ -157,4 +157,6 @@ RSpec.describe SlackMessage do
157
157
  }.to post_to_slack.with_content_matching(/ABC123/)
158
158
  end
159
159
  end
160
+
161
+ # tests for actual sending methods? what would actually be useful?
160
162
  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.0
4
+ version: 3.0.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-12-13 00:00:00.000000000 Z
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -66,10 +66,19 @@ files:
66
66
  - Gemfile
67
67
  - MIT-LICENSE
68
68
  - README.md
69
+ - docs/01_configuration.md
70
+ - docs/02_posting_a_message.md
71
+ - docs/03_message_dsl.md
72
+ - docs/04_editing_messages.md
73
+ - docs/05_deleting_messages.md
74
+ - docs/06_notifying_users.md
75
+ - docs/07_testing.md
76
+ - docs/index.md
69
77
  - lib/slack_message.rb
70
78
  - lib/slack_message/api.rb
71
79
  - lib/slack_message/configuration.rb
72
80
  - lib/slack_message/dsl.rb
81
+ - lib/slack_message/response.rb
73
82
  - lib/slack_message/rspec.rb
74
83
  - slack_message.gemspec
75
84
  - spec/slack_message_spec.rb