1 | [![Sponsored by Beep Boop](https://img.shields.io/badge/%E2%9D%A4%EF%B8%8F_sponsored_by-%E2%9C%A8_Robots%20%26%20Pencils_%E2%9C%A8-FB6CBE.svg)](https://beepboophq.com)
|
2 | [![Build Status](https://travis-ci.org/BeepBoopHQ/slapp.svg)](https://travis-ci.org/BeepBoopHQ/slapp)
|
3 | [![Coverage Status](https://coveralls.io/repos/github/BeepBoopHQ/slapp/badge.svg)](https://coveralls.io/github/BeepBoopHQ/slapp)
|
4 |
|
5 | # Slapp
|
6 | Slapp is a node.js module for creating Slack integrations from simple slash commands to complex bots. It is specifically for Slack --not a generic bot framework-- because we believe the best restaurants in the world are not buffets. 🍴😉
|
7 |
|
8 | Slapp heavily favors the new HTTP based [Slack Events API](https://api.slack.com/events-api) over [Realtime Messaging API](https://api.slack.com/rtm) websockets for creating more scalable and manageable bots. It supports simple conversation flows with state managed out of process to survive restarts and horizontal scaling. Conversation flows aren't just message based but may include any Slack event, [interactive buttons](https://api.slack.com/docs/message-buttons), [slash commands](https://api.slack.com/slash-commands), etc.
|
9 |
|
10 | Slapp is built on a strong foundation with a test suite with 100% test coverage and depends on the [smallwins/slack](https://github.com/smallwins/slack) client.
|
11 |
|
12 | Here is a basic example:
|
13 | ```js
|
14 | const Slapp = require('slapp')
|
15 | const BeepBoopContext = require('slapp-context-beepboop')
|
16 | if (!process.env.PORT) throw Error('PORT missing but required')
|
17 |
|
18 | var slapp = Slapp({ context: BeepBoopContext() })
|
19 |
|
20 | slapp.message('^(hi|hello|hey).*', ['direct_mention', 'direct_message'], (msg, text, greeting) => {
|
21 | msg
|
22 | .say(`${greeting}, how are you?`)
|
23 | .route('handleHowAreYou') // where to route the next msg in the conversation
|
24 | })
|
25 |
|
26 | // register a route handler
|
27 | slapp.route('handleHowAreYou', (msg) => {
|
28 | // respond with a random entry from array
|
29 | msg.say(['Me too', 'Noted', 'That is interesting'])
|
30 | })
|
31 |
|
32 | // attach handlers to an Express app
|
33 | slapp.attachToExpress(require('express')()).listen(process.env.PORT)
|
34 | ```
|
35 |
|
36 | ## Install
|
37 |
|
38 | ```
|
39 | npm install --save slapp
|
40 | ```
|
41 |
|
42 | ## Getting Started
|
43 | We recommend you watch this [quick tutorial](https://www.youtube.com/watch?v=q9iMeRbrgpw) on how to get started with Slapp on BeepBoop! It'll talk you through some of these key points:
|
44 |
|
45 | * Creating your first Slapp application
|
46 | * Adding your application to [Beep Boop](https://beepboophq.com)
|
47 | * Setting up a Slack App ready to work with Slapp / Beep Boop
|
48 |
|
49 | Even if you're not using Beep Boop the video should help you understand how to get your Slack App setup properly so you can make the most of Slapp.
|
50 |
|
51 | ## Setup
|
52 | You can call the Slapp function with the following options:
|
53 | ```js
|
54 | const Slapp = require('slapp')
|
55 | const ConvoStore = require('slapp-convo-beepboop')
|
56 | const BeepBoopContext = require('slapp-context-beepboop')
|
57 |
|
58 | var slapp = Slapp({
|
59 | verify_token: process.env.SLACK_VERIFY_TOKEN,
|
60 | convo_store: ConvoStore(),
|
61 | context: BeepBoopContext(),
|
62 | log: true,
|
63 | colors: true
|
64 | })
|
65 | ```
|
66 |
|
67 | ### Context Lookup
|
68 | One of the challenges with writing a multi-team Slack app is that you need to make sure you have the appropriate tokens and meta-data for a team when you get a message from them. This lets you make api calls on behalf of that team in response to incoming messages from Slack. You typically collect and store this meta-data during the **Add to Slack** OAuth flow. If you're running on [Beep Boop][beepboop], this data is saved for you automatically. Slapp has a required `context` option that gives you a convenient hook to load that team-specific meta-data and enrich the message with it. While you can add whatever meta-data you have about a team in this function, there are a few required properties that need to be set on `req.slapp.meta` for Slapp to process requests:
|
69 |
|
70 | + `app_token` - **required** OAuth `access_token` property
|
71 | + `bot_token` - **required if you have a bot user** OAuth `bot.bot_access_token` property
|
72 | + `bot_user_id` - **required if you have a bot user** OAuth `bot.bot_user_id` property
|
73 | + `app_bot_id` - **required if you have a bot user and use ignoreSelf option** Profile call with bot token, `users.profile.bot_id` property
|
74 |
|
75 | The incoming request from Slack has been parsed and normalized by the time the `context` function runs, and is available via `req.slapp`. You can rely on this data in your `context` function to assist you in looking up the necessary tokens and meta-data.
|
76 |
|
77 | `req.slapp` has the following structure:
|
78 | ```js
|
79 | {
|
80 | type: 'event|command|action',
|
81 | body: {}, // original payload from Slack
|
82 | meta: {
|
83 | user_id: '<USER_ID>',
|
84 | channel_id: '<CHANNEL_ID>',
|
85 | team_id: '<TEAM_ID>'
|
86 | }
|
87 | }
|
88 | ```
|
89 |
|
90 | If you're running on [Beep Boop][beepboop], these values are stored and added automatically for you, otherwise you'll need to set these properties on `req.slapp.meta` with data retreived from wherever you're storing your OAuth data. That might look something like this:
|
91 | ```js
|
92 | // your database module...
|
93 | var myDB = require('./my-db')
|
94 |
|
95 | var slapp = Slapp({
|
96 | context (req, res, next) {
|
97 | var meta = req.slapp.meta
|
98 |
|
99 | myDB.getTeamData(meta.team_id, (err, data) => {
|
100 | if (err) {
|
101 | console.error('Error loading team data: ', err)
|
102 | return res.send(err)
|
103 | }
|
104 |
|
105 | // mixin necessary team meta-data
|
106 | req.slapp.meta = Object.assign(req.slapp.meta, {
|
107 | app_token: data.app_token,
|
108 | bot_token: data.bot_token,
|
109 | bot_user_id: data.bot_user_id,
|
110 | // you can add your own team meta-data as well
|
111 | other_value: data.other_value
|
112 | })
|
113 | })
|
114 | }
|
115 | })
|
116 | ```
|
117 |
|
118 | ### Message Middleware
|
119 | Slapp supports middleware for incoming events, allowing you to stop the propagation
|
120 | of the event by not calling `next()`, passively observing, or appending metadata
|
121 | to the message by adding properties to `msg.meta`. Middleware is processed in the
|
122 | order it is added.
|
123 |
|
124 | Register new middleware with `use`:
|
125 |
|
126 | ```
|
127 | slapp.use(fn(msg, next))
|
128 | ```
|
129 |
|
130 | For example, simple middleware that logs all incoming messages:
|
131 |
|
132 | ```
|
133 | slapp.use((msg, next) => {
|
134 | console.log(msg)
|
135 | next()
|
136 | })
|
137 | ```
|
138 |
|
139 | Or that does some validation:
|
140 |
|
141 | ```
|
142 | slapp.use((msg, next) => {
|
143 | if (valid) {
|
144 | next()
|
145 | } else {
|
146 | console.error('uh oh')
|
147 | }
|
148 | })
|
149 | ```
|
150 |
|
151 | ## Slack Events
|
152 | Listen for any Slack event with `slapp.event(event_name, (msg) => {})`.
|
153 |
|
154 | ```js
|
155 | // add a smile reaction by the bot for any message reacted to
|
156 | slapp.event('reaction_added', (msg) => {
|
157 | let token = msg.meta.bot_token
|
158 | let timestamp = msg.body.event.item.ts
|
159 | let channel = msg.body.event.item.channel
|
160 | slapp.client.reactions.add({token, name: 'smile', channel, timestamp}, (err) => {
|
161 | if (err) console.log('Error adding reaction', err)
|
162 | })
|
163 | })
|
164 | ```
|
165 |
|
166 | ![Slack Events Demo](https://storage.googleapis.com/beepboophq/_assets/slackapp/demo-event.gif)
|
167 |
|
168 | ## Slack Event Messages
|
169 | A message is just a subtype of Slack event but has a special convenience method `slapp.message(regex, [types], (msg) => {})`:
|
170 |
|
171 | ```js
|
172 | slapp.message('goodnight', 'mention', (msg) => {
|
173 | msg.say('sweet dreams :crescent_moon: ')
|
174 | })
|
175 | ```
|
176 | ![Slack Message Demo](https://storage.googleapis.com/beepboophq/_assets/slackapp/demo-message.gif)
|
177 |
|
178 |
|
179 | ## Interactive Messages
|
180 | `msg.say()` may be passed text, an array of text values (one is chosen randomly), or an object to be sent to [`chat.postMessage`](https://api.slack.com/methods/chat.postMessage). It defaults to the current channel and the bot user token (or app token if there is not bot user). Here's an example of using `msg.say()` to send an interactive message and registering a handler to receive the button action:
|
181 |
|
182 | ```js
|
183 | slapp.message('yesno', (msg) => {
|
184 | msg.say({
|
185 | text: '',
|
186 | attachments: [
|
187 | {
|
188 | text: '',
|
189 | fallback: 'Yes or No?',
|
190 | callback_id: 'yesno_callback',
|
191 | actions: [
|
192 | { name: 'answer', text: 'Yes', type: 'button', value: 'yes' },
|
193 | { name: 'answer', text: 'No', type: 'button', value: 'no' }
|
194 | ]
|
195 | }]
|
196 | })
|
197 | })
|
198 |
|
199 | slapp.action('yesno_callback', 'answer', (msg, value) => {
|
200 | msg.respond(msg.body.response_url, `${value} is a good choice!`)
|
201 | })
|
202 | ```
|
203 |
|
204 | ![Interactive Message Demo](https://storage.googleapis.com/beepboophq/_assets/slackapp/demo-interactive.gif)
|
205 |
|
206 | ## Slash Commands
|
207 |
|
208 | ```js
|
209 | slapp.command('/inorout', /^in/, (msg) => {
|
210 | // `respond` is used for actions or commands and uses the `response_url` provided by the
|
211 | // incoming request from Slack
|
212 | msg.respond(`Glad you are in ${match}!`)
|
213 | })
|
214 | ```
|
215 |
|
216 | ![Slash Command Demo](https://storage.googleapis.com/beepboophq/_assets/slackapp/demo-slash.gif)
|
217 |
|
218 |
|
219 | You can also match on text after the command similar to messages like this:
|
220 | ```js
|
221 | slapp.command('/inorout', 'create (.*)', (msg, text, question) => {
|
222 | // if "/inorout create Who is in?" is received:
|
223 | // text = create Who is in?
|
224 | // question = Who is in?
|
225 | })
|
226 | ```
|
227 |
|
228 | ## Conversations and Bots
|
229 | With Slapp you can use the Slack Events API to create bots much like you would with a
|
230 | a Realtime Messaging API socket. Events over HTTP may be not necessarily be received by
|
231 | the same process if you are running multiple instances of your app behind a load balancer;
|
232 | therefore your Slapp process should be stateless. And thus conversation state should be
|
233 | stored out of process.
|
234 |
|
235 | You can pass a conversation store implementation into the Slapp factory with the `convo_store` option. If you are using [Beep Boop](https://beepboophq.com), you should use `require('slapp-convo-beepboop')()` and it will be handled for you. Otherwise, a conversation store needs to implement these three functions:
|
236 |
|
237 | ```js
|
238 | set (id, params, callback) {} // callback(err)
|
239 | get (id, callback) // callback(err, val)
|
240 | del (id, callback) {} // callback(err)
|
241 | ```
|
242 |
|
243 | The [in memory implementation](https://github.com/BeepBoopHQ/slapp/blob/master/src/conversation_store/memory.js) can be used for testing and as an example when creating your own implementation.
|
244 |
|
245 | ### What is a conversation?
|
246 | A conversation is scoped by the combination of Slack Team, Channel, and User. When
|
247 | you register a new route handler (see below), it will only be invoked when receiving
|
248 | a message from the same team in the same channel by the same user.
|
249 |
|
250 | ### Conversation Routing
|
251 | Conversations use a very simple routing mechanism. Within any msg handler you may
|
252 | call `msg.route` to designate a handler for the next msg received in a conversation.
|
253 | The handler must be preregistered with the same key through `slapp.route`.
|
254 |
|
255 | For example, if we register a route handler under the key `handleGoodDay`:
|
256 |
|
257 | ```js
|
258 | slapp.route('handleGoodDay', (msg) => {
|
259 | msg.say(':expressionless:')
|
260 | })
|
261 | ```
|
262 |
|
263 | We can route to that in a `msg` handler like this:
|
264 |
|
265 | ```js
|
266 | slapp.message('^hi', 'direct_message', (msg) => {
|
267 | msg.say('Are you having a good day?').route('handleGoodDay')
|
268 | })
|
269 | ```
|
270 |
|
271 | The route handler will get called for this conversation no matter what type of event
|
272 | it is. This means you can use any slack events, slash commands interactive message actions,
|
273 | and the like in your conversation flows. If a route handler is registered, it will
|
274 | supercede any other matcher.
|
275 |
|
276 | ### Conversation State and Expiration
|
277 | When specifying a route handler with `msg.route` you can optionally pass an arbitrary
|
278 | object and expiration time in seconds.
|
279 |
|
280 | Consider the example below. If a user says "do it" in a direct message then ask
|
281 | for confirmation using an interactive message. If they do something other than
|
282 | answer by pressing a button, redirect them to choose one of the options, yes or no.
|
283 | When they choose, handle the response accordingly.
|
284 |
|
285 | Notice the `state` object that is passed to `msg.route` and into `slapp.route`. Each time `msg.route` is called an expiration time of 60 seconds is set. If
|
286 | there is not activity by the user for 60 seconds, we expire the conversation flow.
|
287 |
|
288 |
|
289 | ```js
|
290 | // if a user says "do it" in a DM
|
291 | slapp.message('do it', 'direct_message', (msg) => {
|
292 | var state = { requested: Date.now() }
|
293 | // respond with an interactive message with buttons Yes and No
|
294 | msg
|
295 | .say({
|
296 | text: '',
|
297 | attachments: [
|
298 | {
|
299 | text: 'Are you sure?',
|
300 | fallback: 'Are you sure?',
|
301 | callback_id: 'doit_confirm_callback',
|
302 | actions: [
|
303 | { name: 'answer', text: 'Yes', type: 'button', value: 'yes' },
|
304 | { name: 'answer', text: 'No', type: 'button', value: 'no' }
|
305 | ]
|
306 | }]
|
307 | })
|
308 | // handle the response with this route passing state
|
309 | // and expiring the conversation after 60 seconds
|
310 | .route('handleDoitConfirmation', state, 60)
|
311 | })
|
312 |
|
313 | slapp.route('handleDoitConfirmation', (msg, state) => {
|
314 | // if they respond with anything other than a button selection,
|
315 | // get them back on track
|
316 | if (msg.type !== 'action') {
|
317 | msg
|
318 | .say('Please choose a Yes or No button :wink:')
|
319 | // notice we have to declare the next route to handle the response
|
320 | // every time. Pass along the state and expire the conversation
|
321 | // 60 seconds from now.
|
322 | .route('handleDoitConfirmation', state, 60)
|
323 | return
|
324 | }
|
325 |
|
326 | let answer = msg.body.actions[0].value
|
327 | if (answer !== 'yes') {
|
328 | // the answer was not affirmative
|
329 | msg.respond(msg.body.response_url, {
|
330 | text: `OK, not doing it. Whew that was close :cold_sweat:`,
|
331 | delete_original: true
|
332 | })
|
333 | // notice we did NOT specify a route because the conversation is over
|
334 | return
|
335 | }
|
336 |
|
337 | // use the state that's been passed through the flow to figure out the
|
338 | // elapsed time
|
339 | var elapsed = (Date.now() - state.requested)/1000
|
340 | msg.respond(msg.body.response_url, {
|
341 | text: `You requested me to do it ${elapsed} seconds ago`,
|
342 | delete_original: true
|
343 | })
|
344 |
|
345 | // simulate doing some work and send a confirmation.
|
346 | setTimeout(() => {
|
347 | msg.say('I "did it"')
|
348 | }, 3000)
|
349 | })
|
350 | ```
|
351 |
|
352 | ![Conversation Demo](https://storage.googleapis.com/beepboophq/_assets/slackapp/demo-doit.gif)
|
353 |
|
354 | ## Custom Logging
|
355 | You can pass in your own custom logger instead of using the built-in logger. A custom logger would implement:
|
356 |
|
357 | ```js
|
358 | (app, opts) => {
|
359 | app
|
360 | .on('info', (msg) => {
|
361 | ...
|
362 | })
|
363 | .on('error', (err) => {
|
364 | ...
|
365 | })
|
366 | }
|
367 | ```
|
368 | The `msg` is the same as the Message type. `opts` includes the `opts.colors` passed into Slapp initially.
|
369 |
|
370 | # API
|
371 |
|
372 | # slapp
|
373 |
|
374 | - [slapp()](#slappoptsobject)
|
375 |
|
376 | ## slapp(opts:Object)
|
377 |
|
378 | Create a new Slapp, accepts an options object
|
379 |
|
380 | Parameters
|
381 | - `opts.verify_token` Slack Veryify token to validate authenticity of requests coming from Slack
|
382 | - `opts.signing_secret` Slack signing secret to check/verify the signature of requests coming from Slack
|
383 | - `opts.signing_version` Slack signing version string, defaults to 'v0'
|
384 | - `opts.convo_store` Implementation of ConversationStore, defaults to memory
|
385 | - `opts.context` `Function (req, res, next)` HTTP Middleware function to enrich incoming request with context
|
386 | - `opts.log` defaults to `true`, `false` to disable logging
|
387 | - `opts.logger` Implementation of a logger, defaults to built-in Slapp command line logger.
|
388 | - `opts.colors` defaults to `process.stdout.isTTY`, `true` to enable colors in logging
|
389 | - `opts.ignoreSelf` defaults to `true`, `true` to automatically ignore any messages from yourself. This flag requires the context to set `meta.app_bot_id` with the Slack App's users.profile.bot_id.
|
390 | - `opts.ignoreBots` defaults to `false`, `true` to ignore any messages from bot users automatically
|
391 |
|
392 | Example
|
393 |
|
394 |
|
395 | ```js
|
396 | var Slapp = require('slapp')
|
397 | var BeepBoopConvoStore = require('slapp-convo-beepboop')
|
398 | var BeepBoopContext = require('slapp-context-beepboop')
|
399 | var slapp = Slapp({
|
400 | record: 'out.jsonl',
|
401 | context: BeepBoopContext(),
|
402 | convo_store: BeepBoopConvoStore({ debug: true })
|
403 | })
|
404 | ```
|
405 |
|
406 |
|
407 |
|
408 | # Slapp
|
409 |
|
410 | - [Slapp.use()](#slappusefnfunction)
|
411 | - [Slapp.attachToExpress()](#slappattachtoexpressappobjectoptsobject)
|
412 | - [Slapp.route()](#slapproutefnkeystringfnfunction)
|
413 | - [Slapp.getRoute()](#slappgetroutefnkeystring)
|
414 | - [Slapp.match()](#slappmatchfnfunction)
|
415 | - [Slapp.message()](#slappmessagecriteriastringtypefilterstringarray)
|
416 | - [Slapp.event()](#slappeventcriteriastringregexpcallbackfunction)
|
417 | - [Slapp.action()](#slappactioncallbackidstringactionnamecriteriastringregexpactionvaluecriteriastringregexpcallbackfunction)
|
418 | - [Slapp.messageAction()](#slappmessageactioncallbackidstringcallbackfunction)
|
419 | - [Slapp.options()](#slappoptionscallbackidstringactionnamecriteriastringregexpactionvaluecriteriastringregexpcallbackfunction)
|
420 | - [Slapp.command()](#slappcommandcommandstringcriteriastringregexpcallbackfunction)
|
421 | - [Slapp.dialog()](#slappdialogcallbackidstringcallbackfunction)
|
422 |
|
423 | ## Slapp.use(fn:function)
|
424 |
|
425 | Register a new middleware, processed in the order registered.
|
426 |
|
427 | #### Parameters
|
428 | - `fn`: middleware function `(msg, next) => { }`
|
429 |
|
430 |
|
431 | #### Returns
|
432 | - `this` (chainable)
|
433 |
|
434 | ## Slapp.attachToExpress(app:Object, opts:Object)
|
435 |
|
436 | Attach HTTP routes to an Express app
|
437 |
|
438 | Routes are:
|
439 | - POST `/slack/event`
|
440 | - POST `/slack/command`
|
441 | - POST `/slack/action`
|
442 |
|
443 | #### Parameters
|
444 | - `app` instance of Express app or Express.Router
|
445 | - `opts.event` `boolean|string` - event route (defaults to `/slack/event`) [optional]
|
446 | - `opts.command` `boolean|string` - command route (defaults to `/slack/command`) [optional]
|
447 | - `opts.action` `boolean|string` - action route (defaults to `/slack/action`) [optional]
|
448 |
|
449 |
|
450 | #### Returns
|
451 | - `app` reference to Express app or Express.Router passed in
|
452 |
|
453 |
|
454 | Examples:
|
455 |
|
456 | ```js
|
457 | // would attach all routes w/ default paths
|
458 | slapp.attachToExpress(app)
|
459 | ```
|
460 |
|
461 |
|
462 | ```js
|
463 | // with options
|
464 | slapp.attachToExpress(app, {
|
465 | event: true, // would register event route with default of /slack/event
|
466 | command: false, // would not register a route for commands
|
467 | action: '/slack-action' // custom route for actions
|
468 | })
|
469 | ```
|
470 |
|
471 |
|
472 | ```js
|
473 | // would only attach a route for events w/ default path
|
474 | slapp.attachToExpress(app, {
|
475 | event: true
|
476 | })
|
477 | ```
|
478 |
|
479 | ## Slapp.route(fnKey:string, fn:function)
|
480 |
|
481 | Register a new function route
|
482 |
|
483 | #### Parameters
|
484 | - `fnKey` unique key to refer to function
|
485 | - `fn` `(msg, state) => {}`
|
486 |
|
487 |
|
488 | #### Returns
|
489 | - `this` (chainable)
|
490 |
|
491 | ## Slapp.getRoute(fnKey:string)
|
492 |
|
493 | Return a registered route
|
494 |
|
495 | #### Parameters
|
496 | - `fnKey` string - unique key to refer to function
|
497 |
|
498 |
|
499 | #### Returns
|
500 | - `(msg, state) => {}`
|
501 |
|
502 | ## Slapp.match(fn:function)
|
503 |
|
504 | Register a custom Match function (fn)
|
505 |
|
506 | #### Returns `true` if there is a match AND you handled the msg.
|
507 | Return `false` if there is not a match and you pass on the message.
|
508 |
|
509 | All of the higher level matching convenience functions
|
510 | generate a match function and call `match` to register it.
|
511 |
|
512 | Only one matcher can return true, and they are executed in the order they are
|
513 | defined. Match functions should return as fast as possible because it's important
|
514 | that they are efficient. However you may do asyncronous tasks within to
|
515 | your hearts content.
|
516 |
|
517 | #### Parameters
|
518 | - `fn` function - match function `(msg) => { return bool }`
|
519 |
|
520 |
|
521 | #### Returns
|
522 | - `this` (chainable)
|
523 |
|
524 | ## Slapp.message(criteria:string, typeFilter:string|Array)
|
525 |
|
526 | Register a new message handler function for the criteria
|
527 |
|
528 | #### Parameters
|
529 | - `criteria` text that message contains or regex (e.g. "^hi")
|
530 | - `typeFilter` [optional] Array for multiple values or string for one value. Valid values are `direct_message`, `direct_mention`, `mention`, `ambient`
|
531 | - `callback` function - `(msg, text, [match1], [match2]...) => {}`
|
532 |
|
533 |
|
534 | #### Returns
|
535 | - `this` (chainable)
|
536 |
|
537 | Example with regex matchers:
|
538 |
|
539 | ```js
|
540 | slapp.message('^play (song|artist) <([^>]+)>', (msg, text, type, toplay) => {
|
541 | // text = 'play artist spotify:track:1yJiE307EBIzOB9kqH1deb'
|
542 | // type = 'artist'
|
543 | // toplay = 'spotify:track:1yJiE307EBIzOB9kqH1deb'
|
544 | }
|
545 | ```
|
546 |
|
547 |
|
548 | Example without matchers:
|
549 |
|
550 | ```js
|
551 | slapp.message('play', (msg, text) => {
|
552 | // text = 'play'
|
553 | }
|
554 | ```
|
555 |
|
556 |
|
557 | Example `msg.body`:
|
558 |
|
559 | ```js
|
560 | {
|
561 | "token":"dxxxxxxxxxxxxxxxxxxxx",
|
562 | "team_id":"TXXXXXXXX",
|
563 | "api_app_id":"AXXXXXXXX",
|
564 | "event":{
|
565 | "type":"message",
|
566 | "user":"UXXXXXXXX",
|
567 | "text":"hello!",
|
568 | "ts":"1469130107.000088",
|
569 | "channel":"DXXXXXXXX"
|
570 | },
|
571 | "event_ts":"1469130107.000088",
|
572 | "type":"event_callback",
|
573 | "authed_users":[
|
574 | "UXXXXXXXX"
|
575 | ]
|
576 | }
|
577 | ```
|
578 |
|
579 | ## Slapp.event(criteria:string|RegExp, callback:function)
|
580 |
|
581 | Register a new event handler for an actionName
|
582 |
|
583 | #### Parameters
|
584 | - `criteria` the type of event
|
585 | - `callback` `(msg) => {}`
|
586 |
|
587 |
|
588 | #### Returns
|
589 | - `this` (chainable)
|
590 |
|
591 |
|
592 | Example `msg` object:
|
593 |
|
594 | ```js
|
595 | {
|
596 | "token":"dxxxxxxxxxxxxxxxxxxxx",
|
597 | "team_id":"TXXXXXXXX",
|
598 | "api_app_id":"AXXXXXXXX",
|
599 | "event":{
|
600 | "type":"reaction_added",
|
601 | "user":"UXXXXXXXX",
|
602 | "item":{
|
603 | "type":"message",
|
604 | "channel":"DXXXXXXXX",
|
605 | "ts":"1469130181.000096"
|
606 | },
|
607 | "reaction":"grinning"
|
608 | },
|
609 | "event_ts":"1469131201.822817",
|
610 | "type":"event_callback",
|
611 | "authed_users":[
|
612 | "UXXXXXXXX"
|
613 | ]
|
614 | }
|
615 | ```
|
616 |
|
617 | ## Slapp.action(callbackId:string, actionNameCriteria:string|RegExp, actionValueCriteria:string|RegExp, callback:function)
|
618 |
|
619 | Register a new handler for button or menu actions. The actionValueCriteria
|
620 | (optional) for menu options will successfully match if any one of the values
|
621 | match the criteria.
|
622 |
|
623 | The `callbackId` can optionally accept a URL path like pattern matcher that can be
|
624 | used to match as well as extract values. For example if `callbackId` is `/myaction/:type/:id`,
|
625 | it _will_ match on `/myaction/a-great-action/abcd1234`. And the resulting `Message` object will
|
626 | include a `meta.params` object that contains the extracted variables. For example,
|
627 | `msg.meta.params.type` ==> `a-great-action` and `msg.meta.params.id` ==> `abcd1234`. This allows
|
628 | you to match on dynamic callbackIds while passing data.
|
629 |
|
630 | Note, `callback_id` values must be properly encoded. We suggest you use `encodeURIComponent` and `decodeURIComponent`.
|
631 |
|
632 | The underlying module used for matching
|
633 | is [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) where there are a lot of examples.
|
634 |
|
635 |
|
636 | #### Parameters
|
637 | - `callbackIdPath` string - may be a simple string or a URL path matcher
|
638 | - `actionNameCriteria` string or RegExp - the name of the action [optional]
|
639 | - `actionValueCriteria` string or RegExp - the value of the action [optional]
|
640 | - `callback` function - `(msg, value) => {}` - value may be a string or array of strings
|
641 |
|
642 |
|
643 | #### Returns
|
644 | - `this` (chainable)
|
645 |
|
646 | Example:
|
647 |
|
648 | ```js
|
649 | // match name and value
|
650 | slapp.action('dinner_callback', 'drink', 'beer', (msg, val) => {}
|
651 | // match name and value either beer or wine
|
652 | slapp.action('dinner_callback', 'drink', '(beer|wine)', (msg, val) => {}
|
653 | // match name drink, any value
|
654 | slapp.action('dinner_callback', 'drink', (msg, val) => {}
|
655 | // match dinner_callback, any name or value
|
656 | slapp.action('dinner_callback', 'drink', (msg, val) => {}
|
657 | // match with regex
|
658 | slapp.action('dinner_callback', /^drink$/, /^b[e]{2}r$/, (msg, val) => {}
|
659 | // callback_id matcher
|
660 | slapp.action('/dinner_callback/:drink', (msg, val) => {}
|
661 | ```
|
662 |
|
663 |
|
664 | Example button action `msg.body` object:
|
665 |
|
666 | ```js
|
667 | {
|
668 | "actions":[
|
669 | {
|
670 | "name":"answer",
|
671 | "value":":wine_glass:"
|
672 | }
|
673 | ],
|
674 | "callback_id":"in_or_out_callback",
|
675 | "team":{
|
676 | "id":"TXXXXXXXX",
|
677 | "domain":"companydomain"
|
678 | },
|
679 | "channel":{
|
680 | "id":"DXXXXXXXX",
|
681 | "name":"directmessage"
|
682 | },
|
683 | "user":{
|
684 | "id":"UXXXXXXXX",
|
685 | "name":"mike.brevoort"
|
686 | },
|
687 | "action_ts":"1469129995.067370",
|
688 | "message_ts":"1469129988.000084",
|
689 | "attachment_id":"1",
|
690 | "token":"dxxxxxxxxxxxxxxxxxxxx",
|
691 | "original_message":{
|
692 | "text":"What?",
|
693 | "username":"In or Out",
|
694 | "bot_id":"BXXXXXXXX",
|
695 | "attachments":[
|
696 | {
|
697 | "callback_id":"in_or_out_callback",
|
698 | "fallback":"Pick one",
|
699 | "id":1,
|
700 | "actions":[
|
701 | {
|
702 | "id":"1",
|
703 | "name":"answer",
|
704 | "text":":beer:",
|
705 | "type":"button",
|
706 | "value":":beer:",
|
707 | "style":""
|
708 | },
|
709 | {
|
710 | "id":"2",
|
711 | "name":"answer",
|
712 | "text":":beers:",
|
713 | "type":"button",
|
714 | "value":":wine:",
|
715 | "style":""
|
716 | },
|
717 | ]
|
718 | },
|
719 | {
|
720 | "text":":beers: • mike.brevoort",
|
721 | "id":2,
|
722 | "fallback":"who picked beers"
|
723 | }
|
724 | ],
|
725 | "type":"message",
|
726 | "subtype":"bot_message",
|
727 | "ts":"1469129988.000084"
|
728 | },
|
729 | "response_url":"https://hooks.slack.com/actions/TXXXXXXXX/111111111111/txxxxxxxxxxxxxxxxxxxx"
|
730 | }
|
731 | ```
|
732 |
|
733 |
|
734 |
|
735 | Example menu action `msg.body` object:
|
736 |
|
737 | ```js
|
738 | {
|
739 | "actions": [
|
740 | {
|
741 | "name": "winners_list",
|
742 | "selected_options": [
|
743 | {
|
744 | "value": "U061F1ZUR"
|
745 | }
|
746 | ]
|
747 | }
|
748 | ],
|
749 | "callback_id": "select_simple_1234",
|
750 | "team": {
|
751 | "id": "T012AB0A1",
|
752 | "domain": "pocket-calculator"
|
753 | },
|
754 | "channel": {
|
755 | "id": "C012AB3CD",
|
756 | "name": "general"
|
757 | },
|
758 | "user": {
|
759 | "id": "U012A1BCD",
|
760 | "name": "musik"
|
761 | },
|
762 | "action_ts": "1481579588.685999",
|
763 | "message_ts": "1481579582.000003",
|
764 | "attachment_id": "1",
|
765 | "token": "verification_token_string",
|
766 | "original_message": {
|
767 | "text": "It's time to nominate the channel of the week",
|
768 | "bot_id": "B08BCU62D",
|
769 | "attachments": [
|
770 | {
|
771 | "callback_id": "select_simple_1234",
|
772 | "fallback": "Upgrade your Slack client to use messages like these.",
|
773 | "id": 1,
|
774 | "color": "3AA3E3",
|
775 | "actions": [
|
776 | {
|
777 | "id": "1",
|
778 | "name": "channels_list",
|
779 | "text": "Which channel changed your life this week?",
|
780 | "type": "select",
|
781 | "data_source": "channels"
|
782 | }
|
783 | ]
|
784 | }
|
785 | ],
|
786 | "type": "message",
|
787 | "subtype": "bot_message",
|
788 | "ts": "1481579582.000003"
|
789 | },
|
790 | "response_url": "https://hooks.slack.com/actions/T012AB0A1/1234567890/JpmK0yzoZ5eRiqfeduTBYXWQ"
|
791 | }
|
792 | ```
|
793 |
|
794 | ## Slapp.messageAction(callbackId:string, callback:function)
|
795 |
|
796 | Register a new handler for a [message action](https://api.slack.com/actions).
|
797 |
|
798 | The `callbackId` should match the "Callback ID" registered in the message action.
|
799 |
|
800 |
|
801 | #### Parameters
|
802 | - `callbackId` string
|
803 | - `callback` function - `(msg, message) => {}` - message
|
804 |
|
805 |
|
806 | #### Returns
|
807 | - `this` (chainable)
|
808 |
|
809 | Example:
|
810 |
|
811 | ```js
|
812 | // match on callback_id
|
813 | slapp.messageAction('launch_message_action', (msg, message) => {}
|
814 | ```
|
815 |
|
816 |
|
817 |
|
818 | Example message action `msg.body` object:
|
819 |
|
820 | ```js
|
821 | {
|
822 | "token": "Nj2rfC2hU8mAfgaJLemZgO7H",
|
823 | "callback_id": "chirp_message",
|
824 | "type": "message_action",
|
825 | "trigger_id": "13345224609.8534564800.6f8ab1f53e13d0cd15f96106292d5536",
|
826 | "response_url": "https://hooks.slack.com/app-actions/T0MJR11A4/21974584944/yk1S9ndf35Q1flupVG5JbpM6",
|
827 | "team": {
|
828 | "id": "T0MJRM1A7",
|
829 | "domain": "pandamonium",
|
830 | },
|
831 | "channel": {
|
832 | "id": "D0LFFBKLZ",
|
833 | "name": "cats"
|
834 | },
|
835 | "user": {
|
836 | "id": "U0D15K92L",
|
837 | "name": "dr_maomao"
|
838 | },
|
839 | "message": {
|
840 | "type": "message",
|
841 | "user": "U0MJRG1AL",
|
842 | "ts": "1516229207.000133",
|
843 | "text": "World's smallest big cat! <https://youtube.com/watch?v=W86cTIoMv2U>"
|
844 | }
|
845 | }
|
846 | ```
|
847 |
|
848 | ## Slapp.options(callbackId:string, actionNameCriteria:string|RegExp, actionValueCriteria:string|RegExp, callback:function)
|
849 |
|
850 | Register a new interactive message options handler
|
851 |
|
852 | `options` accepts a `callbackIdPath` like `action`. See `action` for details.
|
853 |
|
854 | #### Parameters
|
855 | - `callbackIdPath` string - may be a simple string or a URL path matcher
|
856 | - `actionNameCriteria` string or RegExp - the name of the action [optional]
|
857 | - `actionValueCriteria` string or RegExp - the value of the action [optional]
|
858 | - `callback` function - `(msg, value) => {}` - value is the current value of the option (e.g. partially typed)
|
859 |
|
860 |
|
861 | #### Returns
|
862 | - `this` (chainable)
|
863 |
|
864 | Example matching callback only
|
865 |
|
866 | ```js
|
867 | slapp.options('my_callback', (msg, value) => {}
|
868 | ```
|
869 |
|
870 |
|
871 |
|
872 | Example with name matcher
|
873 |
|
874 | ```js
|
875 | slapp.options('my_callback', 'my_name', (msg, value) => {}
|
876 | ```
|
877 |
|
878 |
|
879 |
|
880 | Example with RegExp matcher criteria:
|
881 |
|
882 | ```js
|
883 | slapp.options('my_callback', /my_n.+/, (msg, value) => {}
|
884 | ```
|
885 |
|
886 |
|
887 | Example with callback_id path criteria:
|
888 |
|
889 | ```js
|
890 | slapp.options('/my_callback/:id', (msg, value) => {}
|
891 | ```
|
892 |
|
893 |
|
894 |
|
895 |
|
896 | Example `msg.body` object:
|
897 |
|
898 | ```js
|
899 | {
|
900 | "name": "musik",
|
901 | "value": "",
|
902 | "callback_id": "select_remote_1234",
|
903 | "team": {
|
904 | "id": "T012AB0A1",
|
905 | "domain": "pocket-calculator"
|
906 | },
|
907 | "channel": {
|
908 | "id": "C012AB3CD",
|
909 | "name": "general"
|
910 | },
|
911 | "user": {
|
912 | "id": "U012A1BCD",
|
913 | "name": "musik"
|
914 | },
|
915 | "action_ts": "1481670445.010908",
|
916 | "message_ts": "1481670439.000007",
|
917 | "attachment_id": "1",
|
918 | "token": "verification_token_string"
|
919 | }
|
920 | ```
|
921 |
|
922 | *
|
923 |
|
924 | ## Slapp.command(command:string, criteria:string|RegExp, callback:function)
|
925 |
|
926 | Register a new slash command handler
|
927 |
|
928 | #### Parameters
|
929 | - `command` string - the slash command (e.g. "/doit")
|
930 | - `criteria` string or RegExp (e.g "/^create.+$/") [optional]
|
931 | - `callback` function - `(msg) => {}`
|
932 |
|
933 |
|
934 | #### Returns
|
935 | - `this` (chainable)
|
936 |
|
937 | Example without parameters:
|
938 |
|
939 | ```js
|
940 | // "/acommand"
|
941 | slapp.command('acommand', (msg) => {
|
942 | }
|
943 | ```
|
944 |
|
945 |
|
946 |
|
947 | Example with RegExp matcher criteria:
|
948 |
|
949 | ```js
|
950 | // "/acommand create flipper"
|
951 | slapp.command('acommand', 'create (.*)'(msg, text, name) => {
|
952 | // text = 'create flipper'
|
953 | // name = 'flipper'
|
954 | }
|
955 | ```
|
956 |
|
957 |
|
958 |
|
959 | Example `msg` object:
|
960 |
|
961 | ```js
|
962 | {
|
963 | "type":"command",
|
964 | "body":{
|
965 | "token":"xxxxxxxxxxxxxxxxxxx",
|
966 | "team_id":"TXXXXXXXX",
|
967 | "team_domain":"teamxxxxxxx",
|
968 | "channel_id":"Dxxxxxxxx",
|
969 | "channel_name":"directmessage",
|
970 | "user_id":"Uxxxxxxxx",
|
971 | "user_name":"xxxx.xxxxxxxx",
|
972 | "command":"/doit",
|
973 | "text":"whatever was typed after command",
|
974 | "response_url":"https://hooks.slack.com/commands/TXXXXXXXX/111111111111111111111111111"
|
975 | },
|
976 | "resource":{
|
977 | "app_token":"xoxp-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX-XXXXXXXXXX",
|
978 | "app_user_id":"UXXXXXXXX",
|
979 | "bot_token":"xoxb-XXXXXXXXXX-XXXXXXXXXXXXXXXXXXXX",
|
980 | "bot_user_id":"UXXXXXXXX"
|
981 | },
|
982 | "meta":{
|
983 | "user_id":"UXXXXXXXX",
|
984 | "channel_id":"DXXXXXXXX",
|
985 | "team_id":"TXXXXXXXX"
|
986 | },
|
987 | }
|
988 | ```
|
989 |
|
990 | ## Slapp.dialog(callbackId:string, callback:function)
|
991 |
|
992 | Register a dialog submission handler for the given callback_id
|
993 |
|
994 | #### Parameters
|
995 | - `callbackId` string - the callback_id of the form
|
996 | - `callback` function - `(msg, submission) => {}`
|
997 |
|
998 |
|
999 | #### Returns
|
1000 | - `this` (chainable)
|
1001 |
|
1002 | Example;
|
1003 |
|
1004 | ```js
|
1005 | // "/acommand"
|
1006 | slapp.command('my_callback_id', (msg, submission) => {
|
1007 | submission.prop_name_1
|
1008 | }
|
1009 | ```
|
1010 |
|
1011 |
|
1012 |
|
1013 | Example `msg` object:
|
1014 |
|
1015 | ```js
|
1016 | {
|
1017 | "type":"action",
|
1018 | "body":{
|
1019 | "type": "dialog_submission",
|
1020 | "submission": {
|
1021 | "answer": "two",
|
1022 | "feedback": "test"
|
1023 | },
|
1024 | "callback_id": "xyz",
|
1025 | "team": {
|
1026 | "id": "T1PR9DEFS",
|
1027 | "domain": "aslackdomain"
|
1028 | },
|
1029 | "user": {
|
1030 | "id": "U1ABCDEF",
|
1031 | "name": "mikebrevoort"
|
1032 | },
|
1033 | "channel": {
|
1034 | "id": "C1PR520RRR",
|
1035 | "name": "random"
|
1036 | },
|
1037 | "action_ts": "1503445940.478855"
|
1038 | },
|
1039 | }
|
1040 | ```
|
1041 |
|
1042 |
|
1043 |
|
1044 | # Message
|
1045 |
|
1046 |
|
1047 | A Message object is created for every incoming Slack event, slash command, and interactive message action.
|
1048 | It is generally always passed as `msg`.
|
1049 |
|
1050 | `msg` has three main top level properties
|
1051 | - `type` - one of `event`, `command`, `action`
|
1052 | - `body` - the unmodified payload of the original event
|
1053 | - `meta` - derived or normalized properties and anything appended by middleware.
|
1054 |
|
1055 | `meta` should at least have these properties
|
1056 | - `app_token` - token for the user for the app
|
1057 | - `app_user_id` - userID for the user who install ed the app
|
1058 | - `bot_token` - token for a bot user of the app
|
1059 | - `bot_user_id` - userID of the bot user of the app
|
1060 |
|
1061 |
|
1062 | - [Message.constructor()](#messageconstructortypestringbodyobjectmetaobject)
|
1063 | - [Message.hasResponse()](#messagehasresponse)
|
1064 | - [Message.route()](#messageroutefnkeystringstateobjectsecondstoexpirenumber)
|
1065 | - [Message.cancel()](#messagecancel)
|
1066 | - [Message.say()](#messagesayinputstringobjectarraycallbackfunction)
|
1067 | - [Message.respond()](#messagerespondresponseurlstringinputstringobjectarraycallbackfunction)
|
1068 | - [Message.thread()](#messagethread)
|
1069 | - [Message.unthread()](#messageunthread)
|
1070 | - [Message._request()](#message_request)
|
1071 | - [Message.isBot()](#messageisbot)
|
1072 | - [Message.isBaseMessage()](#messageisbasemessage)
|
1073 | - [Message.isThreaded()](#messageisthreaded)
|
1074 | - [Message.isDirectMention()](#messageisdirectmention)
|
1075 | - [Message.isDirectMessage()](#messageisdirectmessage)
|
1076 | - [Message.isMention()](#messageismention)
|
1077 | - [Message.isAmbient()](#messageisambient)
|
1078 | - [Message.isAnyOf()](#messageisanyofofarray)
|
1079 | - [Message.isAuthedTeam()](#messageisauthedteam)
|
1080 | - [Message.usersMentioned()](#messageusersmentioned)
|
1081 | - [Message.channelsMentioned()](#messagechannelsmentioned)
|
1082 | - [Message.subteamGroupsMentioned()](#messagesubteamgroupsmentioned)
|
1083 | - [Message.everyoneMentioned()](#messageeveryonementioned)
|
1084 | - [Message.channelMentioned()](#messagechannelmentioned)
|
1085 | - [Message.hereMentioned()](#messageherementioned)
|
1086 | - [Message.linksMentioned()](#messagelinksmentioned)
|
1087 | - [Message.stripDirectMention()](#messagestripdirectmention)
|
1088 |
|
1089 | ## Message.constructor(type:string, body:Object, meta:Object)
|
1090 |
|
1091 | Construct a new Message
|
1092 |
|
1093 | #### Parameters
|
1094 | - `type` the type of message (event, command, action, etc.)
|
1095 |
|
1096 | ## Message.hasResponse()
|
1097 |
|
1098 | May this message be responded to with `msg.respond` because the originating
|
1099 | event included a `response_url`. If `hasResponse` returns false, you may
|
1100 | still call `msg.respond` while explicitly passing a `response_url`.
|
1101 |
|
1102 | #### Returns `true` if `msg.respond` may be called on this message, implicitly.
|
1103 |
|
1104 | ## Message.route(fnKey:string, state:Object, secondsToExpire:number)
|
1105 |
|
1106 | Register the next function to route to in a conversation.
|
1107 |
|
1108 | The route should be registered already through `slapp.route`
|
1109 |
|
1110 | #### Parameters
|
1111 | - `fnKey` `string`
|
1112 | - `state` `object` arbitrary data to be passed back to your function [optional]
|
1113 | - `secondsToExpire` `number` - number of seconds to wait for the next message in the conversation before giving up. Default 60 minutes [optional]
|
1114 |
|
1115 |
|
1116 | #### Returns
|
1117 | - `this` (chainable)
|
1118 |
|
1119 | ## Message.cancel()
|
1120 |
|
1121 | Explicity cancel pending `route` registration.
|
1122 |
|
1123 | ## Message.say(input:string|Object|Array, callback:function)
|
1124 |
|
1125 | Send a message through [`chat.postmessage`](https://api.slack.com/methods/chat.postMessage).
|
1126 |
|
1127 | The current channel and inferred tokens are used as defaults. `input` maybe a
|
1128 | `string`, `Object` or mixed `Array` of `strings` and `Objects`. If a string,
|
1129 | the value will be set to `text` of the `chat.postmessage` object. Otherwise pass
|
1130 | a [`chat.postmessage`](https://api.slack.com/methods/chat.postMessage) `Object`.
|
1131 | If the current message is part of a thread, the new message will remain
|
1132 | in the thread. To control if a message is threaded or not you can use the
|
1133 | `msg.thread()` and `msg.unthread()` functions.
|
1134 |
|
1135 | If `input` is an `Array`, a random value in the array will be selected.
|
1136 |
|
1137 | #### Parameters
|
1138 | - `input` the payload to send, maybe a string, Object or Array.
|
1139 | - `callback` (err, data) => {}
|
1140 |
|
1141 |
|
1142 | #### Returns
|
1143 | - `this` (chainable)
|
1144 |
|
1145 | ## Message.respond([responseUrl]:string, input:string|Object|Array, callback:function)
|
1146 |
|
1147 | Respond to a Slash command, interactive message action, or interactive message options request.
|
1148 |
|
1149 | Slash commands and message actions responses should be passed a [`chat.postmessage`](https://api.slack.com/methods/chat.postMessage)
|
1150 | payload. If `respond` is called within 3000ms (2500ms actually with a 500ms buffer) of the original request,
|
1151 | the original request will be responded to instead or using the `response_url`. This will keep the
|
1152 | action button spinner in sync with an awaiting update and is about 25% more responsive when tested.
|
1153 |
|
1154 | `input` options are the same as [`say`](#messagesay)
|
1155 |
|
1156 |
|
1157 | If a response to an interactive message options request then an array of options should be passed
|
1158 | like:
|
1159 |
|
1160 | ```js
|
1161 | {
|
1162 | "options": [
|
1163 | { "text": "value" },
|
1164 | { "text": "value" }
|
1165 | ]
|
1166 | }
|
1167 | ```
|
1168 |
|
1169 |
|
1170 |
|
1171 | #### Parameters
|
1172 | - `responseUrl` string - URL provided by a Slack interactive message action or slash command [optional]
|
1173 | - `input` the payload to send, maybe a string, Object or Array.
|
1174 | - `callback` (err, data) => {}
|
1175 |
|
1176 | Example:
|
1177 |
|
1178 | ```js
|
1179 | // responseUrl implied from body.response_url if this is an action or command
|
1180 | msg.respond('thanks!', (err) => {})
|
1181 | ```
|
1182 |
|
1183 |
|
1184 | ```js
|
1185 | // responseUrl explicitly provided
|
1186 | msg.respond(responseUrl, 'thanks!', (err) => {})
|
1187 | ```
|
1188 |
|
1189 |
|
1190 | ```js
|
1191 | // input provided as object
|
1192 | msg.respond({ text: 'thanks!' }, (err) => {})
|
1193 | ```
|
1194 |
|
1195 |
|
1196 | ```js
|
1197 | // input provided as Array
|
1198 | msg.respond(['thanks!', 'I :heart: u'], (err) => {})
|
1199 | ```
|
1200 |
|
1201 |
|
1202 |
|
1203 | #### Returns
|
1204 | - `this` (chainable)
|
1205 |
|
1206 | ## Message.thread()
|
1207 |
|
1208 | Ensures all subsequent messages created are under a thread of the current message
|
1209 |
|
1210 | Example:
|
1211 |
|
1212 | ```js
|
1213 | // current msg is not part of a thread (i.e. does not have thread_ts set)
|
1214 | msg.
|
1215 | .say('This message will not be part of the thread and will be in the channel')
|
1216 | .thread()
|
1217 | .say('This message will remain in the thread')
|
1218 | .say('This will also be in the thread')
|
1219 | ```
|
1220 |
|
1221 |
|
1222 | #### Returns
|
1223 | - `this` (chainable)
|
1224 |
|
1225 | ## Message.unthread()
|
1226 |
|
1227 | Ensures all subsequent messages created are not part of a thread
|
1228 |
|
1229 | Example:
|
1230 |
|
1231 | ```js
|
1232 | // current msg is part of a thread (i.e. has thread_ts set)
|
1233 | msg.
|
1234 | .say('This message will remain in the thread')
|
1235 | .unthread()
|
1236 | .say('This message will not be part of the thread and will be in the channel')
|
1237 | .say('This will also not be part of the thread')
|
1238 | ```
|
1239 |
|
1240 |
|
1241 |
|
1242 | #### Returns
|
1243 | - `this` (chainable)
|
1244 |
|
1245 | ## Message._request()
|
1246 |
|
1247 | istanbul ignore next
|
1248 |
|
1249 | ## Message.isBot()
|
1250 |
|
1251 | Is this from a bot user?
|
1252 |
|
1253 | #### Returns `bool` true if `this` is a message from a bot user
|
1254 |
|
1255 | ## Message.isBaseMessage()
|
1256 |
|
1257 | Is this an `event` of type `message` without any [subtype](https://api.slack.com/events/message)?
|
1258 |
|
1259 |
|
1260 | #### Returns `bool` true if `this` is a message event type with no subtype
|
1261 |
|
1262 | ## Message.isThreaded()
|
1263 |
|
1264 | Is this an `event` of type `message` without any [subtype](https://api.slack.com/events/message)?
|
1265 |
|
1266 |
|
1267 | #### Returns `bool` true if `this` is an event that is part of a thread
|
1268 |
|
1269 | ## Message.isDirectMention()
|
1270 |
|
1271 | Is this a message that is a direct mention ("@botusername: hi there", "@botusername goodbye!")
|
1272 |
|
1273 |
|
1274 | #### Returns `bool` true if `this` is a direct mention
|
1275 |
|
1276 | ## Message.isDirectMessage()
|
1277 |
|
1278 | Is this a message in a direct message channel (one on one)
|
1279 |
|
1280 |
|
1281 | #### Returns `bool` true if `this` is a direct message
|
1282 |
|
1283 | ## Message.isMention()
|
1284 |
|
1285 | Is this a message where the bot user mentioned anywhere in the message.
|
1286 | Only checks for mentions of the bot user and does not consider any other users.
|
1287 |
|
1288 |
|
1289 | #### Returns `bool` true if `this` mentions the bot user
|
1290 |
|
1291 | ## Message.isAmbient()
|
1292 |
|
1293 | Is this a message that's not a direct message or that mentions that bot at
|
1294 | all (other users could be mentioned)
|
1295 |
|
1296 |
|
1297 | #### Returns `bool` true if `this` is an ambient message
|
1298 |
|
1299 | ## Message.isAnyOf(of:Array)
|
1300 |
|
1301 | Is this a message that matches any one of the filters
|
1302 |
|
1303 | #### Parameters
|
1304 | - `messageFilters` Array - any of `direct_message`, `direct_mention`, `mention` and `ambient`
|
1305 |
|
1306 |
|
1307 | #### Returns `bool` true if `this` is a message that matches any of the filters
|
1308 |
|
1309 | ## Message.isAuthedTeam()
|
1310 |
|
1311 | Return true if the event "team_id" is included in the "authed_teams" array.
|
1312 | In other words, this event originated from a team who has installed your app
|
1313 | versus a team who is sharing a channel with a team who has installed the app
|
1314 | but in fact hasn't installed the app into that team explicitly.
|
1315 | There are some events that do not include an "authed_teams" property. In these
|
1316 | cases, error on the side of claiming this IS from an authed team.
|
1317 |
|
1318 | #### Returns an Array of user IDs
|
1319 |
|
1320 | ## Message.usersMentioned()
|
1321 |
|
1322 | Return the user IDs of any users mentioned in the message
|
1323 |
|
1324 | #### Returns an Array of user IDs
|
1325 |
|
1326 | ## Message.channelsMentioned()
|
1327 |
|
1328 | Return the channel IDs of any channels mentioned in the message
|
1329 |
|
1330 | #### Returns an Array of channel IDs
|
1331 |
|
1332 | ## Message.subteamGroupsMentioned()
|
1333 |
|
1334 | Return the IDs of any subteams (groups) mentioned in the message
|
1335 |
|
1336 | #### Returns an Array of subteam IDs
|
1337 |
|
1338 | ## Message.everyoneMentioned()
|
1339 |
|
1340 | Was "@everyone" mentioned in the message
|
1341 |
|
1342 | #### Returns `bool` true if `@everyone` was mentioned
|
1343 |
|
1344 | ## Message.channelMentioned()
|
1345 |
|
1346 | Was the current "@channel" mentioned in the message
|
1347 |
|
1348 | #### Returns `bool` true if `@channel` was mentioned
|
1349 |
|
1350 | ## Message.hereMentioned()
|
1351 |
|
1352 | Was the "@here" mentioned in the message
|
1353 |
|
1354 | #### Returns `bool` true if `@here` was mentioned
|
1355 |
|
1356 | ## Message.linksMentioned()
|
1357 |
|
1358 | Return the URLs of any links mentioned in the message
|
1359 |
|
1360 | #### Returns `Array:string` of URLs of links mentioned in the message
|
1361 |
|
1362 | ## Message.stripDirectMention()
|
1363 |
|
1364 | Strip the direct mention prefix from the message text and return it. The
|
1365 | original text is not modified
|
1366 |
|
1367 |
|
1368 | #### Returns `string` original `text` of message with a direct mention of the bot
|
1369 | user removed. For example, `@botuser hi` or `@botuser: hi` would produce `hi`.
|
1370 | `@notbotuser hi` would produce `@notbotuser hi`
|
1371 |
|
1372 |
|
1373 | # Contributing
|
1374 |
|
1375 | We adore contributions. Please include the details of the proposed changes in a Pull Request and ensure `npm test` passes. 👻
|
1376 |
|
1377 | ### Scripts
|
1378 | - `npm test` - runs linter and tests with coverage
|
1379 | - `npm run unit` - runs unit tests without coverage
|
1380 | - `npm run lint` - just runs JS standard linter
|
1381 | - `npm run coverage` - runs tests with coverage
|
1382 | - `npm run lcov` - runs tests with coverage and output lcov report
|
1383 | - `npm run docs` - regenerates API docs in this README.md
|
1384 |
|
1385 | # License
|
1386 | MIT Copyright (c) 2016 Beep Boop, Robots & Pencils
|
1387 |
|
1388 | [beepboop]: https://beepboophq.com
|