Skip to content

Commit

Permalink
Merge pull request #9 from valgaze/ez/webhooks
Browse files Browse the repository at this point in the history
Add webhoook support, slim assets
  • Loading branch information
valgaze authored Oct 3, 2021
2 parents 2e7b71f + 2933160 commit f5a5521
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 24 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
tl:dr; The speedy & easy way to launch a bot
```

![j5](https://i.imgur.com/VQoXfHn.gif)
[![sb](https://i.imgur.com/VQoXfHn.gif)](https://share.descript.com/view/ds3UA1kUb9z)


---

Expand All @@ -17,12 +18,19 @@ npx speedyhelper setup
```
---

## Video instructions

- 101: https://share.descript.com/view/ds3UA1kUb9z

- Webhooks/3rd-party integrations: https://share.descript.com/view/bnyupJvNJcx

## In a nutshell

Speedybot is a "toolkit" to take you from zero to a non-trivial bot as quickly as possible. Dive in immediately and focus on the stuff that matters-- features, workflows/integrations, content, & interactivity, etc

Speedybot instruments on top of the incredibly useful **[webex-node-bot-framework](https://github.com/WebexSamples/webex-node-bot-framework)** and steps through the fastest path to a working bot and provides some convenience features


## Adding a new chat handler

With Speedybot, all you need to worry about is the **[settings directory](https://github.com/valgaze/speedybot/tree/master/settings)** directory with two files:
Expand Down Expand Up @@ -126,11 +134,6 @@ export default [{

</details>

## Video instructions

```
// todo
```

## CLI Instructions

Expand Down
Binary file removed docs/assets/chip_example.gif
Binary file not shown.
Binary file removed docs/assets/framework_success.png
Binary file not shown.
Binary file removed docs/assets/healthcheck.gif
Binary file not shown.
Binary file removed docs/assets/tunnel.png
Binary file not shown.
58 changes: 54 additions & 4 deletions docs/how-to.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ There are a few "special" keyword words you can use which have a special meaning

- *<@despawn>*: Opposite of spawn, see **[here](https://github.com/WebexSamples/webex-node-bot-framework/#despawn)** for details


- *<@webhook>*: Put your webhook handlers alongside your handlers, see the **["Handling Webhooks"](#handling-webhooks)** section below for an example or video instruction here: https://share.descript.com/view/bnyupJvNJcx
ex.

```ts
Expand Down Expand Up @@ -84,7 +86,7 @@ export default handlers = [

- Start a 1-1 & ask your bot "healthcheck"-- if all works well you should see something like this:

![image](https://raw.githubusercontent.com/valgaze/speedybot/master/docs/assets/healthcheck.gif)
![image](https://raw.githubusercontent.com/valgaze/speedybot-starter/master/docs/assets/healthcheck.gif)


You can also add a bot to a group space, but note that you or any other human members of the space will need to explicitly "@"-mention the bot to get functionality
Expand All @@ -95,7 +97,7 @@ A suggestion "chip" is a button which, when clicked, is the equivalent of the us

ex.

![image](./assets/chip_example.gif)
![image](https://raw.githubusercontent.com/valgaze/speedybot-starter/master/docs/assets/chip_example.gif)


Ex. If a button/chip has the label "bongo", when the user taps it, the phrase "bongo" will be processed by chat handlers as if the user typed "bongo" on their own
Expand Down Expand Up @@ -244,7 +246,7 @@ const config: SpeedybotConfig = {
webhookUrl: 'https://123-45-678-910-987.ngrok.io/speedybotwebhook'
}

app.post('/speedybotwebhook', SpeedybotWebhook(config, handlers))
app.post('/speedybotwebhook', SpeedybotWebhook(config, handlers, app))

app.listen(port, () => {
console.log(`Listening + tunneled on port ${port}`)
Expand All @@ -264,7 +266,7 @@ npx speedyhelper tunnel

If all went well it should look like this:

![image](./assets/tunnel.png)
![image](https://raw.githubusercontent.com/valgaze/speedybot-starter/master/docs/assets/tunnel.png)

### 2. Set value in config

Expand All @@ -276,3 +278,51 @@ Set the value under ```webhookUrl``` in config and append ```/speedybotwebhook``
"webhookUrl":"http://9044-47-144-173-232.ngrok.io/speedybotwebhook"
}
```


## Handling Webhooks

If you deploy your agent using a server (rather than websockets, ie webhookUrl is set blank in **[settings/config.json](./../settings/config.json)**), you can write your webhook handlers using the <@webhook> keyword. Note that the bot & trigger parameters are not available, however,

Ex. Process incoming webhooks that post to your agent:

```ts
{
keyword: '<@webhook>',
route: '/my_test_webhook',
handler(req, res) {
// Note this is a special handler unlike all the others
// Webhook/alert handlers don't have
const { body } = req
const msg = `Webhook alert for /my_test_route, data received: ${JSON.stringify(body)}`

const cardJson = {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.0",
"body": [
{
"type": "TextBlock",
"size": "Medium",
"weight": "Bolder",
"text": "Data"
},
{
"type": "RichTextBlock",
"inlines": [
{
"type": "TextRun",
"text": JSON.stringify(body, null, 2)
}
]
}
],
}

this.send({toPersonEmail: '[email protected]', text: msg})
this.sendCardToPerson('[email protected]', cardJson)

res.send('Thanks') // optional, in case server needs acknowledgement
}
}
```
13 changes: 9 additions & 4 deletions quickstart.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
## Quickstart

Note: The steps below assume you have a working WebEx account & **[Nodejs](https://nodejs.org/en/download/)** 12+
Note: The steps below assume you have a function WebEx account & **[Nodejs](https://nodejs.org/en/download/)** 12+ available on your system

Steps below can be automated using the cli

Steps below can be automated using the cli

```sh
npx speedyhelper setup
```

Video instructions available here: https://share.descript.com/view/ds3UA1kUb9z

-----

## 1. Fetch repo & install dependencies

```
Expand All @@ -31,13 +36,13 @@ npm start
```

If all went well, it should look something like this:
![image](https://raw.githubusercontent.com/valgaze/speedybot/master/docs/assets/framework_success.png)
![image](https://raw.githubusercontent.com/valgaze/speedybot-starter/master/docs/assets/framework_success.png)

## 4. Run a "healthcheck" with the bot

To make sure all is well, add your bot from Step 1 in a 1-1 chat session and tell it "healthcheck"-- if everything is configured properly you should see something like this:

![image](https://raw.githubusercontent.com/valgaze/speedybot/master/docs/assets/healthcheck.gif)
![image](https://raw.githubusercontent.com/valgaze/speedybot-starter/master/docs/assets/healthcheck.gif)

## 5. Extend

Expand Down
26 changes: 24 additions & 2 deletions src/framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/**
* Framework instance: https://github.com/WebexSamples/webex-node-bot-framework/blob/master/lib/framework.js#L25-L34
*
*/
*/
export interface FrameworkInst {
options: FrameworkOptions;
id: string;
Expand Down Expand Up @@ -233,4 +233,26 @@ export interface BotHandler {
handler: handlerFunc;
helpText: string; // Used by built-in help generator any handlers you write this way will list out their help data
preference?: number; // defaults to 0, specifies preference of phrase when overlapping handlers match, lower number >> higher match priority
}
}

export type AlertFunc = (req: any, res: any) => void;

export type ValidMethods = 'POST' | 'GET' | 'PUT' | 'PATCH' | 'DELETE'
export interface WebhookHandler {
keyword: '<@webhook>';
route: string;
handler: AlertFunc;
method?: ValidMethods; // default to post
}
/**
* const alerter = {
* keyword: '<@webhook>'
* route: '/my_webhook_route'
* handler(req, res) {
* const {body} = req
* this.send({toPersonEmail: '[email protected]', text:`Webhook alert!, `})
* }
* }
*
*
*/
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { Speedybot, SpeedybotWebhook, Speedytunnel, Launch } from './speedybot'
export { SpeedybotConfig } from './speedybot'

// Types: framework
export { FrameworkInst, BotHandler, Message, ToMessage, BotInst, Trigger } from './framework'
export { FrameworkInst, BotHandler,WebhookHandler, Message, ToMessage, BotInst, Trigger } from './framework'
export { bad, help, ascii_art, log, good, askQuestion, loud } from './logger'
// helpers
export { fillTemplate, pickRandom } from './helpers'
Expand Down
68 changes: 61 additions & 7 deletions src/speedybot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FrameworkInst, BotHandler, ToMessage, BotInst, Trigger } from './framework'
import { FrameworkInst, BotHandler, ToMessage, BotInst, Trigger, WebhookHandler } from './framework'
import { ValidatewebhookUrl, pickRandom } from './helpers'
import { placeholder, ascii_art } from './'
// TODO: make peer dependency
Expand Down Expand Up @@ -26,6 +26,7 @@ export class Speedybot {
Magickeywords = {
'<@help>': ['help'],
'<@catchall>': /(.*?)/,

}

MagicFrameworkkeywords = {
Expand All @@ -35,17 +36,40 @@ export class Speedybot {
'<@fileupload>': 'files',
}

WebhookKeyword = '<@webhook>'

constructor(config: SpeedybotConfig) {
const inst:FrameworkInst = new Botframework(config);
this.frameworkRef = inst;
}

sendMessage(payload: ToMessage) {
send(payload: ToMessage) {
this.frameworkRef.webex.messages.create(payload)
}

_send(payload: ToMessage) {
this.frameworkRef.webex.messages.create(payload)
// Send card is a bit tricky in the <@webhook> case since we don't have any
// existing room binding
// For now, just make 2 different methods
sendCardToRoom(roomId, cardPayload: any, fallbackText= 'Your client does not appear to support rendering adaptive cards') {
const card = {
roomId,
markdown: fallbackText,
attachments: [{
contentType: "application/vnd.microsoft.card.adaptive",
content: cardPayload}]
}
this.frameworkRef.webex.messages.create(card)
}

sendCardToPerson(email, cardPayload: any, fallbackText= 'Your client does not appear to support rendering adaptive cards') {
const card = {
toPersonEmail: email,
markdown: fallbackText,
attachments: [{
contentType: "application/vnd.microsoft.card.adaptive",
content: cardPayload}]
}
this.frameworkRef.webex.messages.create(card)
}

async start():Promise<FrameworkInst> {
Expand Down Expand Up @@ -238,15 +262,43 @@ export class Speedybot {
* @param handlers: Bothandler[]
* @returns Promise<unknown>
*/
export const SpeedybotWebhook = (config: SpeedybotConfig, handlers: BotHandler[]) => {
export const SpeedybotWebhook = (config: SpeedybotConfig, handlers: (BotHandler | WebhookHandler)[], app?:any) => {
const { webhookUrl = ''} = config;
ValidatewebhookUrl(webhookUrl)
if (config.token === placeholder) {
throw new Error(`Placeholder detected under 'token' in config.json! See here for instructions: https://github.com/valgaze/speedybot-starter/blob/master/quickstart.md Exiting...`)
}
const speedybot = new Speedybot(config)
speedybot.loadHandlers(handlers)
const webhookHandlers: WebhookHandler[] = []
const standardHandlers = handlers.filter((handler) => {
const { keyword } = handler
if (typeof keyword === 'string') {
const isWebhook = keyword.includes(speedybot.WebhookKeyword)
if (isWebhook) {
webhookHandlers.push(handler as WebhookHandler)
return false
} else {
return true
}
} else {
return true // regex | (string|regex)[]
}
})

speedybot.loadHandlers(standardHandlers)
speedybot.start()

// TODO: support other route handler schemas-- koa, hapi, sails, etc
if (app) {
webhookHandlers.forEach(webhook => {
const { method = 'post', route, handler } = webhook
app[method](route, async (req, res) => {
return handler.call(speedybot, req, res)
})
})
}

// Main webhook specifically for incoming chat webhooks
return async (req, res) => {
return speedybot.webhook()(req)
}
Expand Down Expand Up @@ -308,7 +360,9 @@ export const Launch = async (config: SpeedybotConfig, handlerList: BotHandler[])
}
ascii_art()
const speedybot = new Speedybot(config);
speedybot.loadHandlers(handlerList)
// Filterout webhooks
const tidyHandlerList = handlerList.filter(({keyword}) => !(typeof keyword === 'string' && keyword === speedybot.WebhookKeyword))
speedybot.loadHandlers(tidyHandlerList)
const frameworkRef = await speedybot.start()
return frameworkRef
}
Expand Down

0 comments on commit f5a5521

Please sign in to comment.