Skip to content

Commit

Permalink
feat(channels): channels package (#270)
Browse files Browse the repository at this point in the history
* feat(channels): conversation.started

* fix

* feat(channels): channels package

* fix

* config file

* schwag

* more schwag

* twilio

* twilio validation

* smooch

* change config

* abstract

* abstract telegram

* abstract twilio

* abstract app

* fix

* teams

* fix

* bring changes

* bring changes

* bring changes

* add base renderers

* refact streams

* telegram all content types

* abstract

* twilio all content types

* slack all content types

* vonage all content types

* messenger all content

* teams all content

* smooch all content

* destroy

* initialize

* bring changes

* chore(channels): delete channels (#271)

* delete channel code

* chore(channels): use channels from channels package (#272)

* use telegram from channel package

* use package twilio

* use teams

* use smooch

* example change

* config change

* slack

* messenger

* vonage

* integrate other channels

* telegram +

* fix tests

* fix docker

* initialize

* stop when server closes

* dispatch stop

* webhookRouter

* ChannelTemplate

* prettier

* fix

* any -> void

* remove || '*' in server

* remove ChannelStreamRenderers

* initialize messenger

* messenger map scope

* fix

* put back twilio testing

* vonage receive content types

* restore index responses

* fix

* fix

* logger

* logger

* printWebhook

* kvs interface

* fix
  • Loading branch information
samuelmasse committed Dec 10, 2021
1 parent b05bb69 commit 2dadfee
Show file tree
Hide file tree
Showing 194 changed files with 2,242 additions and 2,049 deletions.
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ WORKDIR /messaging
COPY --from=build /messaging/packages/server/dist packages/server/src
COPY --from=build /messaging/packages/server/package.json packages/server/package.json

COPY --from=build /messaging/packages/channels/dist packages/channels/dist
COPY --from=build /messaging/packages/channels/package.json packages/channels/package.json

COPY --from=build /messaging/packages/engine/dist packages/engine/dist
COPY --from=build /messaging/packages/engine/package.json packages/engine/package.json

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"start": "yarn workspace @botpress/messaging-server start",
"dev": "yarn workspace @botpress/messaging-server dev",
"board": "yarn workspace @botpress/messaging-board dev",
"channels": "yarn workspace @botpress/messaging-channels example",
"test": "yarn test:unit && yarn test:func && yarn test:e2e",
"test:unit": "jest -c jest.unit.config.ts",
"test:func": "jest -c jest.functional.config.ts",
Expand Down
60 changes: 60 additions & 0 deletions packages/channels/example/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import clc from 'cli-color'
import { Router } from 'express'
import { Channel } from '../src/base/channel'
import { MessengerChannel } from '../src/messenger/channel'
import { SlackChannel } from '../src/slack/channel'
import { SmoochChannel } from '../src/smooch/channel'
import { TeamsChannel } from '../src/teams/channel'
import { TelegramChannel } from '../src/telegram/channel'
import { TwilioChannel } from '../src/twilio/channel'
import { VonageChannel } from '../src/vonage/channel'
import payloads from './payloads.json'

export class App {
constructor(private router: Router, private config: any) {}

async setup() {
await this.setupChannel('telegram', new TelegramChannel())
await this.setupChannel('twilio', new TwilioChannel())
await this.setupChannel('smooch', new SmoochChannel())
await this.setupChannel('teams', new TeamsChannel())
await this.setupChannel('slack', new SlackChannel())
await this.setupChannel('messenger', new MessengerChannel())
await this.setupChannel('vonage', new VonageChannel())
}

async setupChannel(name: string, channel: Channel) {
await channel.setup(this.router)

channel.on('message', async ({ scope, endpoint, content }) => {
this.log('message', name, scope, { endpoint, content })

const respond = async () => {
try {
for (const payload of payloads) {
await channel.send(scope, endpoint, payload)
}
} catch (e) {
console.error('Error occurred sending message', e)
}
}

void respond()
})

channel.makeUrl(async (scope: string) => {
return `${this.config.externalUrl}/webhooks/${scope}/${channel.meta.name}`
})

for (const [key, val] of Object.entries<any>(this.config.scopes)) {
if (val[name]) {
await channel.start(key, val[name])
this.log('conf', name, key, val[name])
}
}
}

private log(type: string, channel: string, context: string, obj: any) {
console.info(clc.blue(type), clc.bold(channel), context, obj)
}
}
22 changes: 22 additions & 0 deletions packages/channels/example/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"externalUrl": "",
"scopes": {
"CLIENT_ID": {
"telegram": {
"botToken": ""
},
"twilio": {
"accountSID": "",
"authToken": ""
},
"smooch": {
"keyId": "",
"secret": ""
},
"teams": {
"appId": "",
"appPassword": ""
}
}
}
}
26 changes: 26 additions & 0 deletions packages/channels/example/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import clc from 'cli-color'
import express, { Router } from 'express'
import { App } from './app'
import config from './config.json'

const setup = async () => {
console.info('====================\n' + ` ${clc.magentaBright('channels example')}\n` + '====================')

const exp = express()
exp.get('/', (req, res) => {
res.sendStatus(200)
})

const router = Router()
const app = new App(router, config)
await app.setup()

const port = 3100
exp.use('/webhooks', router)
exp.listen(port)

console.info(`${clc.cyan('url')} ${config.externalUrl}`)
console.info(`${clc.cyan('port')} ${port}`)
}

void setup()
91 changes: 91 additions & 0 deletions packages/channels/example/payloads.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
[
{ "type": "text", "text": "yoyo", "typing": true },
{
"type": "image",
"title": "ducky duck duck",
"image": "https://upload.wikimedia.org/wikipedia/commons/a/a1/Mallard2.jpg",
"typing": true
},
{
"type": "card",
"title": "A banana card",
"subtitle": "This is a banana",
"image": "https://upload.wikimedia.org/wikipedia/commons/8/8a/Banana-Single.jpg",
"actions": [
{
"action": "Open URL",
"title": "Learn more",
"url": "https://en.wikipedia.org/wiki/Banana"
},
{
"action": "Say something",
"title": "Say Banana!",
"text": "Banana!!!"
},
{
"action": "Postback",
"title": "Post banana",
"payload": "BANANA"
}
]
},
{
"type": "carousel",
"items": [
{
"title": "Dinosaurs",
"subtitle": "A bunch of dinosaurs",
"image": "https://upload.wikimedia.org/wikipedia/commons/2/28/Macronaria_scrubbed_enh.jpg",
"actions": [
{
"action": "Open URL",
"title": "Learn more",
"url": "https://en.wikipedia.org/wiki/Dinosaur"
},
{
"action": "Postback",
"title": "Cool",
"payload": "COOL"
}
]
},
{
"title": "T-Rex",
"subtitle": "A big dinosaur",
"image": "https://d.newsweek.com/en/full/986534/gettyimages-967254666.jpg",
"actions": [
{
"action": "Say something",
"title": "Roar",
"text": "Woof woof"
}
]
},
{
"title": "T-Bone",
"subtitle": "A big steak",
"image": "https://upload.wikimedia.org/wikipedia/commons/9/91/T-bone-raw-MCB.jpg",
"actions": [
{
"action": "Open URL",
"title": "Learn more",
"url": "https://en.wikipedia.org/wiki/T-bone_steak"
},
{
"action": "Say something",
"title": "Miam",
"text": "Miam Miam"
}
]
}
]
},
{
"type": "single-choice",
"text": "Make a choice :",
"choices": [
{ "title": "Yay", "value": "YES" },
{ "title": "Nay", "value": "NO" }
]
}
]
7 changes: 7 additions & 0 deletions packages/channels/example/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../../tsconfig.packages.json",
"compilerOptions": {
"rootDir": ".."
},
"include": ["../test/**/*", "../src/**/*"]
}
15 changes: 15 additions & 0 deletions packages/channels/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "@botpress/messaging-channels",
"version": "0.0.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"source": "src/index.ts",
"license": "AGPL-3.0",
"scripts": {
"build": "yarn --silent && tsc --build",
"watch": "yarn --silent && tsc --build --watch",
"example": "yarn --silent && ts-node-dev --debounce 500 --transpile-only example/index.ts"
},
"devDependencies": {},
"dependencies": {}
}
77 changes: 77 additions & 0 deletions packages/channels/src/base/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import clc from 'cli-color'
import { Request, Router, Response, RequestHandler, NextFunction } from 'express'
import yn from 'yn'
import { Logger } from './logger'
import { ChannelService } from './service'

export class ChannelApi<TService extends ChannelService<any, any>> {
protected urlCallback?: (scope: string) => Promise<string>

constructor(protected readonly service: TService) {}

async setup(router: ChannelApiManager) {}

makeUrl(callback: (scope: string) => Promise<string>) {
this.urlCallback = callback
}

protected async printWebhook(scope: string, name: string, path?: string) {
// TODO: remove this dependency on server env vars
if (yn(process.env.SPINNED)) {
const externalUrl = await this.urlCallback!(scope)

this.service.logger?.info(
`[${scope}] ${clc.bold(name.charAt(0).toUpperCase() + name.slice(1))}${
path ? ' ' + path : ''
} webhook ${clc.blackBright(`${externalUrl}/${name}${path ? `/${path}` : ''}`)}`
)
}
}
}

export type Middleware<T> = (req: T, res: Response, next: NextFunction) => Promise<void>

export class ChannelApiManager {
constructor(private service: ChannelService<any, any>, private router: Router, private logger?: Logger) {}

post(path: string, fn: Middleware<ChannelApiRequest>) {
this.wrap('post', path, fn)
}

get(path: string, fn: Middleware<ChannelApiRequest>) {
this.wrap('get', path, fn)
}

delete(path: string, fn: Middleware<ChannelApiRequest>) {
this.wrap('delete', path, fn)
}

use(path: string, fn: RequestHandler) {
this.router.use(`/:scope${path}`, fn)
}

protected wrap(type: 'post' | 'get' | 'delete' | 'use', path: string, fn: Middleware<ChannelApiRequest>) {
this.router[type](
`/:scope${path}`,
this.asyncMiddleware(async (req, res, next) => {
const nreq = req as ChannelApiRequest
nreq.scope = req.params.scope
await this.service.require(nreq.scope)
await fn(nreq, res, next)
})
)
}

protected asyncMiddleware(fn: Middleware<Request>) {
return (req: Request, res: Response, next: NextFunction) => {
fn(req, res, next).catch((e) => {
this.logger?.error(`Error occurred calling route ${req.originalUrl}`, e)
return res.sendStatus(500)
})
}
}
}

export interface ChannelApiRequest extends Request {
scope: string
}

0 comments on commit 2dadfee

Please sign in to comment.