memo about Botkit v4 (4.10.0)
So I inherited a bot that used run on an ancient version of Botkit. There were problems and we needed to upgrade the whole thing to Botkit v4. I found the Botkit documentation to be utterly lacking and confusing, so I decided to leave my notes here so I don’t have to look them up again.
(Note: this is a work in progress, as I’m still in the middle of work)
Botkit Mock
It only works with newer versions of Botkit. Upgrade if you were in the same boat as I was.
storage (Redis)
At least as of this writing, there is no Redis storage backend for v4. Write your own. Use MemoryStorage
until you are ready.
Writing the event handlers in Typescript
If you are using Typescript to write the event handlers, you are going to need to somehow give the handler object parameters some type information. That is, this won’t work:
controller.hears("foo", "message", async(bot, message) => {
...
});
You need to give bot
and message
some type. This information is not immediately obvious from the Botkit docs. Upon further inspection, you will find the definition for these in botkit/src/core.ts
, then you can write:
import { Botkit, BotWorker, BotkitMessage } from 'botkit';controller.hears("foo", "message", async(bot :BotWorker, message :BotkitMessage) => {
...
});
The asymmetry in naming between the various BotkitXXXXX
types and BotWorker
is just so-o-o-o-o deliciously dangerous.
controller.on and controller.hears
controller.on
responds to event type names. controller.hears
responds to the message text that was received.
The doc is lying when they say controller.on('event', ...)
because this does not match any event. Instead of 'event'
you need to specify the event name. So if you want to match a message, you need to say controller.on('message', ...)
.
Also, if you have controllers that match the same event, the first one wins.
controller.hears('^hello', 'message', ....);
controller.on('message', ...);
In the above case, any message starting with the text “hello” will be intercepted by the controller.hears
handler, and everything else will fallthrough to controller.on
.
Matching against ‘app_mention’ events
You can’t. Typing @bot foo
in Slack generates two events, one for an app_mention
event type and one for message
. The message
type can be matched using controller.hears('foo', 'message', ...)
but notice that the first string (the string that matches against the message text) is not anchored to the beginning of the string. This is because the message payload actually contains the bot’s user ID in the form of "<@U********> foo"
and therefore you cannot safely anchor the pattern to the beginning of the line.
On the other hand, using the type app_mention
in either controller.hears()
or controller.on()
does absolutely nothing, because the message.type
field is populated with the value "event"
and not "app_mention"
.
The only way to safely match against events of type "app_mention"
is to use controller.on('event', ...)
then filter out for the "app_metion"
type, but this needs more introspection, because the fields in the message
object just aren’t populated correctly.
You are going to have to write something that resembles the following:
controller.on('event', async(bot :BotWorker, message :BotkitMessage) => {
// message.incoming_message.channelData contains the raw
// data from the incoming message
let chData = message.incoming_message.channelData;
if (chData.type == 'app_mention') {
// Inspecting the message object reveals that when
// "@bot foo" is issued in Slack, this hook receives a
// message with message.text == null
//
// So we extract the raw text from and remove the
// bot's user ID from the beginning of the text so
// it's easier to match against the text pattern
let botID = message.incoming_message.recipient.id;
let text = chData.text;
if (text.startsWith('<@' + botID + '> ')) {
text = text.slice(botID.length + 4);
}
message.text = text; // Overwrite // now match against message.text, do whatever
}
});
Starting Slack Thread Conversations
As of this writing, no document properly explains this.
First, do not believe the documentation that you can find by searching for startConversationInThread()
because they are all lies. Well, they are about older versions of the library.
The way to do this as of this writing is by importing the SlackBotWorker
type, and casting the bot
parameter to it.
import { Botkit, BotWorker, BotkitMessage } from 'botkit';
import { SlackBotWorker } from 'botbuilder-adapter-slack';controller.on(..., (bot :BotWorker, message :BotkitMessage) => {
let sbot = bot as SlackBotWorker;
await sbot.startConversationInThread(...);
await sbot.reply(...)
});
To be continued…