Skip to content

Commit

Permalink
Merge pull request #12 from valgaze/feat/updates
Browse files Browse the repository at this point in the history
Add $uperpowers, doc, and other fixes
  • Loading branch information
valgaze authored Nov 10, 2021
2 parents 35e69d6 + 91fde41 commit 82a8780
Show file tree
Hide file tree
Showing 18 changed files with 968 additions and 100 deletions.
127 changes: 124 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ npx speedyhelper setup

## 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 is a "toolkit" to take you from zero to a non-trivial bot as quickly as possible w/ a buttery-smooth developer experience (live-reload, intelli-sense, etc). Dive in immediately and focus on the stuff that matters-- content, workflows/integrations, processing files, 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

Even if you don't use all of speedybot's features, if nothing else there are several helper utillties that are useful for crafting a rich conversation agent-- more details **[here](https://github.com/valgaze/speedybot/blob/master/docs/util.md)** Speedybot has one required dependency
Speedybot also makes it easy to get up and running fast without worrying about tunneling, webhooks, etc.

Speedybot can also give your bot $uperpowers-- **[see here for details on $uperpowers](https://github.com/valgaze/speedybot/blob/master/docs/superpowers.md)**


## Adding a new chat handler

Expand All @@ -55,6 +58,124 @@ Example handler:
}
```

## $uperpowers

Speedybot can also give your bot $uperpowers-- **[see here for details on $uperpowers](https://github.com/valgaze/speedybot/blob/master/docs/superpowers.md)**

<details><summary>$uperpowers sample</summary>

```ts
import { $ } from 'speedybot'

export default {
keyword: ['$', '$uperpowers', '$uperpower', '$superpower'],
async handler(bot, trigger) {

// ## 0) Wrap the bot object in $ to give it $uperpowers, ex $(bot)
const $bot = $(bot)

// ## 1) Contexts: set, remove, and list
// Contexts persist between "turns" of chat
// Note: contexts can optionally store data
// If you just need to stash information attached to a user, see "$(bot).saveData" below
await $bot.saveContext('mycontext1')
await $bot.saveContext('mycontext2', { data: new Date().toISOString()})

const mycontext2 = await $bot.getContext('mycontext2')
$bot.log('# mycontext2', mycontext2) // { data: '2021-11-05T05:03:58.755Z'}

// Contexts: list active contexts
const allContexts = await $bot.getAllContexts() // ['mycontext1', 'mycontext2']
bot.say(`Contexts: ${JSON.stringify(allContexts)}`)

// Contexts: check if context is active
const isActive = await $bot.contextActive('mycontext1')
$bot.log(`mycontext1 is active, ${isActive}`) // 'mycontext1 is active, true'

// Contexts: remove context
await $bot.deleteContext('mycontext1')

const isStillActive = await $bot.contextActive('mycontext1')
$bot.log(`mycontext1 is active, ${isStillActive}`) // 'mycontext1 is active, false'

// ## 2) Helpers to add variation and rich content

// sendRandom: Sends a random string from a list
$bot.sendRandom(['Hey!','Hello!!','Hiya!'])

// sendTemplate: like sendRandom but replace $[variable_name] with a value
const utterances = ['Hey how are you $[name]?', `$[name]! How's it going?`, '$[name]']
const template = { name: 'Joey'}
$bot.sendTemplate(utterances, template)

// sendURL: Sends a URL in a clickable card
$bot.sendURL('https://www.youtube.com/watch?v=3GwjfUFyY6M', 'Go Celebrate')

// snippet: Generate a snippet that will render data in markdown-friendly format
const JSONData = {a: 1, b:2, c:3, d:4}

$bot.sendSnippet(JSONData, `**Here's some JSON, you'll love it**`) // send to room

// Snippet to a specifc room or specific email
// const snippet = $bot.snippet(JSONData)
// $bot.send({markdown: snippet, roomId:trigger.message.roomId, text: 'Your client does not render markdown :('}) // send to a specific room
// $bot.send({markdown: snippet, toPersonEmail:'[email protected]', text: 'Your client does not render markdown :('}) // send to a specific person


// ## 3) Save data between conversation "runs"

interface SpecialUserData {
specialValue: string;
userId: String;
}
const specialData:SpecialUserData = {
specialValue: Math.random().toString(36).slice(2),
userId: trigger.personId,
}

// Save the data
await $bot.saveData<SpecialUserData>('userData', specialData)

// Retrieve the data (returns null if does not exist)
const dataRes = await $bot.getData<SpecialUserData>('userData')

if (dataRes) {
// These are now "typed"
const theValue = dataRes.specialValue
const id = dataRes.userId
$bot.log(`Your specal value was ${theValue} and your id is ${id}`)

// destroy data
$bot.deleteData('userData')
}

// ## 4) Integrate with 3rd-parties: $bot.get, $bot.post, etc

// ex. get external data
// Opts are axios request config (for bearer tokens, proxies, unique config, etc)
const res = await $bot.get('https://randomuser.me/api/')
bot.say({markdown: $bot.snippet(res.data)})

// ## 4) Files & attachments

// Send a local file
// Provide a path/filename, will be attached to message
$bot.sendFile(__dirname, 'assets', 'speedybot.pdf')

// Send a publicly accessible URL file
// Supported filetypes: ['doc', 'docx' , 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'jpg', 'jpeg', 'bmp', 'gif', 'png']
$bot.sendDataFromUrl('https://drive.google.com/uc?export=download&id=1VI4I4pYVVdMnB6YOQuSejVcrSwN0cotd')

// // experimental (fileystem write): send arbitrary JSON back as a file
// $bot.sendDataAsFile(JSON.stringify({a:1,b:2}), '.json')

// For an example involving parse'able spreadsheets (.xlsx), see here: https://github.com/valgaze/speedybot-superpowers
},
helpText: 'A demo of $uperpowers'
}
```
</details>

## Special keywords

There are a few "special" keywords you can use to "listen" to special events:
Expand All @@ -74,7 +195,7 @@ There are a few "special" keywords you can use to "listen" to special events:
ex. Tell the bot "sendcard" to get a card, type into the card & tap submit, catch submission using *<@submit>* and echo back to user

```ts
import { Card } from 'speedybot'
import { SpeedyCard } from 'speedybot'
export default [{
keyword: '<@submit>',
handler(bot, trigger) {
Expand Down
Binary file added docs/assets/healthcheck.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/speedybot.pdf
Binary file not shown.
Binary file added docs/assets/speedybot_superpowers.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 15 additions & 17 deletions docs/how-to.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,25 +56,23 @@ export default handlers = [
},
{
keyword: '<@fileupload>',
handler(bot, trigger) {
const files = trigger.message.files || []

bot.say(`(**Note:** These files are not publicly accessible)\n ${files.length > 1 ? 'These files were' : 'This file was'} uploaded successfully!`)

files.forEach(async (file, idx) => {
// Note the URL here will fail for user because they require an Authorization

await bot.say(`${idx + 1}: ${file}`)
})

if (files.length === 1) {
bot.dm(trigger.person.id, `Sending a file back at ya!`)
bot.dm(trigger.person.id, { file: 'https://camo.githubusercontent.com/b846bfa57dd26af4e1526abe1173e0b332b75af5d642564b2ab1d0c12a482290/68747470733a2f2f692e696d6775722e636f6d2f56516f5866486e2e676966' })
async handler(bot, trigger) {
const [file] = trigger.message.files
const fileData = await $(bot).getFile(file) // "getFile" $uperpower
const {extension, type, fileName, data, markdownSnippet} = fileData

bot.say(`You uploaded '${fileName}'`)

if (type === 'application/json') {
bot.say({markdown: markdownSnippet})
} else if (type === 'text/plain' || type === 'text/csv') {
bot.say(data)
console.log("#", data)
} else {
bot.say(`// todo: add *.${extension} support`)
}
// ex. From here, you could download the content of the files (with an Authorization header)
// Pass onto another service for analysis/etc
},
helpText: `A special handler that fires anytime a user submits a file`
helpText: `A special handler that fires anytime a user submits data (you can only trigger this handler by tapping Submit in a card)`
}
]
```
Expand Down
4 changes: 4 additions & 0 deletions docs/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Useful reading

- speedybot $uperpowers: **[docs/superpowers.md](./superpowers.md)**

- https://developer.webex.com/blog/from-zero-to-webex-teams-chatbot-in-15-minutes

- https://developer.webex.com/blog/introducing-the-webex-teams-bot-framework-for-node-js
Expand All @@ -17,3 +19,5 @@
- https://developer.webex.com/blog/five-tips-for-well-behaved-webex-bots

- https://github.com/WebexSamples/webex-node-bot-framework/blob/master/docs/buttons-and-cards-example.md

- https://developer.webex.com/blog/uploading-local-files-to-spark
188 changes: 188 additions & 0 deletions docs/superpowers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
![sb](./assets/speedybot_superpowers.gif)

Speedybot $uperpowers: various helper utilities to give your bot $uperpowers when interacting with 3rd-party integrations, files, external resources, and adds the capability get/set/delete conversational "contexts." To give your bot instance $uperpowers, just wrap it in an ```$```


##### Samples
- **[Kitchen sink](#kitchen-sink)**
- **[Extract uploaded file](#get-uploaded-file-details)**
- **[Extract uploaded file (ex spreadsheets)](#retrieve-raw-file-data)**


## Kitchen sink

(not including file handling)

```ts
import { $ } from 'speedybot'

export default {
keyword: ['$', '$uperpowers', '$uperpower', '$superpower'],
async handler(bot, trigger) {

// ## 0) Wrap the bot object in $ to give it $uperpowers, ex $(bot)
const $bot = $(bot)

// ## 1) Contexts: set, remove, and list
// Contexts persist between "turns" of chat
// Note: contexts can optionally store data
// If you just need to stash information attached to a user, see "$(bot).saveData" below
await $bot.saveContext('mycontext1')
await $bot.saveContext('mycontext2', { data: new Date().toISOString()})

const mycontext2 = await $bot.getContext('mycontext2')
$bot.log('# mycontext2', mycontext2) // { data: '2021-11-05T05:03:58.755Z'}

// Contexts: list active contexts
const allContexts = await $bot.getAllContexts() // ['mycontext1', 'mycontext2']
bot.say(`Contexts: ${JSON.stringify(allContexts)}`)

// Contexts: check if context is active
const isActive = await $bot.contextActive('mycontext1')
$bot.log(`mycontext1 is active, ${isActive}`) // 'mycontext1 is active, true'

// Contexts: remove context
await $bot.deleteContext('mycontext1')

const isStillActive = await $bot.contextActive('mycontext1')
$bot.log(`mycontext1 is active, ${isStillActive}`) // 'mycontext1 is active, false'

// ## 2) Helpers to add variation and rich content

// sendRandom: Sends a random string from a list
$bot.sendRandom(['Hey!','Hello!!','Hiya!'])

// sendTemplate: like sendRandom but replace $[variable_name] with a value
const utterances = ['Hey how are you $[name]?', `$[name]! How's it going?`, '$[name]']
const template = { name: 'Joey'}
$bot.sendTemplate(utterances, template)

// sendURL: Sends a URL in a clickable card
$bot.sendURL('https://www.youtube.com/watch?v=3GwjfUFyY6M', 'Go Celebrate')

// snippet: Generate a snippet that will render data in markdown-friendly format
const JSONData = {a: 1, b:2, c:3, d:4}

$bot.sendSnippet(JSONData, `**Here's some JSON, you'll love it**`) // send to room

// Snippet to a specifc room or specific email
// const snippet = $bot.snippet(JSONData)
// $bot.send({markdown: snippet, roomId:trigger.message.roomId, text: 'Your client does not render markdown :('}) // send to a specific room
// $bot.send({markdown: snippet, toPersonEmail:'[email protected]', text: 'Your client does not render markdown :('}) // send to a specific person


// ## 3) Save data between conversation "runs"

interface SpecialUserData {
specialValue: string;
userId: String;
}
const specialData:SpecialUserData = {
specialValue: Math.random().toString(36).slice(2),
userId: trigger.personId,
}

// Save the data
await $bot.saveData<SpecialUserData>('userData', specialData)

// Retrieve the data (returns null if does not exist)
const dataRes = await $bot.getData<SpecialUserData>('userData')

if (dataRes) {
// These are now "typed"
const theValue = dataRes.specialValue
const id = dataRes.userId
$bot.log(`Your specal value was ${theValue} and your id is ${id}`)

// destroy data
$bot.deleteData('userData')
}

// ## 4) Integrate with 3rd-parties: $bot.get, $bot.post, etc

// ex. get external data
// Opts are axios request config (for bearer tokens, proxies, unique config, etc)
const res = await $bot.get('https://randomuser.me/api/')
bot.say({markdown: $bot.snippet(res.data)})

// ## 4) Files & attachments

// Send a local file
// Provide a path/filename, will be attached to message
$bot.sendFile(__dirname, 'assets', 'speedybot.pdf')

// Send a publically accessible URL file
// Supported filetypes: ['doc', 'docx' , 'xls', 'xlsx', 'ppt', 'pptx', 'pdf', 'jpg', 'jpeg', 'bmp', 'gif', 'png']
$bot.sendDataFromUrl('https://drive.google.com/uc?export=download&id=1VI4I4pYVVdMnB6YOQuSejVcrSwN0cotd')

// // experimental (fileystem write): send arbitrary JSON back as a file
// $bot.sendDataAsFile(JSON.stringify({a:1,b:2}), '.json')

// For an example involving parse'able spreadsheets (.xlsx), see here: https://github.com/valgaze/speedybot-superpowers
},
helpText: 'A demo of $uperpowers'
}
```

## Get uploaded file details

Important note: If you attempt to display a snippet of an uploaded file (like a user-submitted list), note that message length is limited to 7439 characters before encryptions & 10000 after encryption

```ts
import { $ } from 'speedybot'

export default {
keyword: '<@fileupload>',
async handler(bot, trigger) {
const supportedFiles = ['json', 'txt', 'csv']
// take 1st file uploaded, note this is just a URL
const [file] = trigger.message.files

// Retrieve file data
const fileData = await $(bot).getFile(file)
const { extension, type, } = fileData

if (supportedFiles.includes(extension)) {
const {data} = fileData
// bot.snippet will format json or text data into markdown format
bot.say({markdown: $(bot).snippet(data))})
} else {
bot.say(`Sorry, somebody needs to add support to handle *.${extension} (${type}) files`)
}
},
helpText: `Special handler that's fired when the user uploads a file to your bot (by default supports json/csv/txt)`
}
```

## Retrieve raw file data

(ex spreadsheets)

```ts
import { $ } from 'speedybot'

export default {
keyword: '<@fileupload>',
async handler(bot, trigger) {
const supportedFiles = ['xlsx']

// take 1st file uploaded, note this is just a URL
const [file] = trigger.message.files

// Retrieve file data (note response type)
const fileData = await $(bot).getFile(file, {responseType: 'arraybuffer'})
const { extension, type, } = fileData

if (supportedFiles.includes(extension)) {
const {data} = fileData
// Transform data with a library like SheetJS: https://www.npmjs.com/package/xlsx

// See <@fileupload> handler here: https://github.com/valgaze/speedybot-superpowers

} else {
bot.say(`Sorry, somebody needs to add support to handle *.${extension} files`)
}
},
helpText: `Special handler that's fired when the user uploads a file to your bot (by default supports json/csv/txt)`
}
```
Loading

0 comments on commit 82a8780

Please sign in to comment.