-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* start * text receive send * token -> botToken * auth * fix * readme * better text * image * readme * choices * fix * dropdown * better dropdown * carousel * refact * refact * refact * audio file location video * fix * fix * add to server * fix flaky test * pr comments
- Loading branch information
1 parent
6717e0a
commit bf3c5f5
Showing
27 changed files
with
768 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
export * from './base/channel' | ||
export * from './base/endpoint' | ||
export * from './messenger/channel' | ||
export * from './slack/channel' | ||
export * from './twilio/channel' | ||
export * from './telegram/channel' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,29 @@ | ||
### Sending | ||
|
||
| Channels | Twilio | | ||
| -------- | :----: | | ||
| Text | ✅ | | ||
| Image | ✅ | | ||
| Choice | ✅ | | ||
| Dropdown | ✅ | | ||
| Card | ✅ | | ||
| Carousel | ✅ | | ||
| File | ✅ | | ||
| Audio | ✅ | | ||
| Video | ✅ | | ||
| Location | ✅ | | ||
| Channels | Messenger | | ||
| -------- | :-------: | | ||
| Text | ✅ | | ||
| Image | ✅ | | ||
| Choice | ✅ | | ||
| Dropdown | ✅ | | ||
| Card | ✅ | | ||
| Carousel | ✅ | | ||
| File | ✅ | | ||
| Audio | ✅ | | ||
| Video | ✅ | | ||
| Location | ✅ | | ||
|
||
### Receiving | ||
|
||
| Channels | Twilio | | ||
| ------------- | :----: | | ||
| Text | ✅ | | ||
| Quick Reply | ✅ | | ||
| Postback | ✅ | | ||
| Say Something | ✅ | | ||
| Voice | ❌ | | ||
| Image | ❌ | | ||
| File | ❌ | | ||
| Audio | ❌ | | ||
| Video | ❌ | | ||
| Location | ❌ | | ||
| Channels | Messenger | | ||
| ------------- | :-------: | | ||
| Text | ✅ | | ||
| Quick Reply | ✅ | | ||
| Postback | ✅ | | ||
| Say Something | ✅ | | ||
| Voice | ❌ | | ||
| Image | ❌ | | ||
| File | ❌ | | ||
| Audio | ❌ | | ||
| Video | ❌ | | ||
| Location | ❌ | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
### Sending | ||
|
||
| Channels | Slack | | ||
| -------- | :---: | | ||
| Text | ✅ | | ||
| Image | ✅ | | ||
| Choice | ✅ | | ||
| Dropdown | ✅ | | ||
| Card | ✅ | | ||
| Carousel | ✅ | | ||
| File | ✅ | | ||
| Audio | ✅ | | ||
| Video | ✅ | | ||
| Location | ✅ | | ||
|
||
### Receiving | ||
|
||
| Channels | Slack | | ||
| ------------- | :---: | | ||
| Text | ✅ | | ||
| Quick Reply | ✅ | | ||
| Postback | ✅ | | ||
| Say Something | ✅ | | ||
| Voice | ❌ | | ||
| Image | ❌ | | ||
| File | ❌ | | ||
| Audio | ❌ | | ||
| Video | ❌ | | ||
| Location | ❌ | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import { | ||
AckFn, | ||
ButtonAction, | ||
ReceiverEvent, | ||
RespondFn, | ||
SlackAction, | ||
SlackActionMiddlewareArgs, | ||
SlackEventMiddlewareArgs, | ||
StaticSelectAction | ||
} from '@slack/bolt' | ||
import crypto from 'crypto' | ||
import { NextFunction, Response } from 'express' | ||
import rawBody from 'raw-body' | ||
import tsscmp from 'tsscmp' | ||
import { URLSearchParams } from 'url' | ||
import { Endpoint } from '..' | ||
import { ChannelApi, ChannelApiManager, ChannelApiRequest } from '../base/api' | ||
import { POSTBACK_PREFIX, SAY_PREFIX } from '../messenger/renderers/carousel' | ||
import { QUICK_REPLY_PREFIX } from './renderers/choices' | ||
import { SlackService } from './service' | ||
|
||
export class SlackApi extends ChannelApi<SlackService> { | ||
async setup(router: ChannelApiManager) { | ||
router.post('/slack', this.verifyRequestSignature.bind(this)) | ||
router.post('/slack', this.handleRequest.bind(this)) | ||
|
||
this.service.on('start', this.handleStart.bind(this)) | ||
} | ||
|
||
private async verifyRequestSignature(req: ChannelApiRequest, res: Response, next: NextFunction) { | ||
// The verification code is mostly copy pasted from the ExpressReceiver code | ||
// see : https://github.com/slackapi/bolt-js/blob/main/src/receivers/ExpressReceiver.ts | ||
|
||
const signature = req.headers['x-slack-signature'] as string | undefined | ||
const requestTimestamp = req.headers['x-slack-request-timestamp'] as string | undefined | ||
const contentType = req.headers['content-type'] as string | undefined | ||
|
||
if (!signature || !requestTimestamp) { | ||
return res.sendStatus(401) | ||
} | ||
|
||
const ts = Number(requestTimestamp) | ||
if (isNaN(ts)) { | ||
return res.sendStatus(401) | ||
} | ||
|
||
// Divide current date to match Slack ts format | ||
// Subtract 5 minutes from current time | ||
const fiveMinutesAgo = Math.floor(Date.now() / 1000) - 60 * 5 | ||
|
||
if (ts < fiveMinutesAgo) { | ||
// timestamp is too old | ||
return res.sendStatus(403) | ||
} | ||
|
||
const { config } = this.service.get(req.scope) | ||
const stringBody = (await rawBody(req)).toString() | ||
|
||
const hmac = crypto.createHmac('sha256', config.signingSecret) | ||
const [version, hash] = signature.split('=') | ||
hmac.update(`${version}:${ts}:${stringBody}`) | ||
|
||
if (!tsscmp(hash, hmac.digest('hex'))) { | ||
return res.sendStatus(403) | ||
} | ||
|
||
// interactive api works with url encoded and events api with json... | ||
if (contentType === 'application/x-www-form-urlencoded') { | ||
const parsedBody = new URLSearchParams(stringBody) | ||
// when we click a button, the payload is actually there in json string | ||
req.body = JSON.parse(parsedBody.get('payload')!) | ||
} else { | ||
req.body = JSON.parse(stringBody) | ||
} | ||
|
||
next() | ||
} | ||
|
||
private async handleRequest(req: ChannelApiRequest, res: Response) { | ||
if (req.body?.ssl_check) { | ||
return res.send() | ||
} else if (req.body?.type === 'url_verification') { | ||
return res.json({ challenge: req.body.challenge }) | ||
} else { | ||
const event: ReceiverEvent = { | ||
body: req.body, | ||
ack: async (response) => { | ||
if (!response) { | ||
res.send('') | ||
} else if (typeof response === 'string') { | ||
res.send(response) | ||
} else { | ||
res.json(response) | ||
} | ||
} | ||
} | ||
|
||
const { app } = this.service.get(req.scope) | ||
await app.processEvent(event) | ||
} | ||
} | ||
|
||
private async handleStart({ scope }: { scope: string }) { | ||
const { app } = this.service.get(scope) | ||
app.message(async (e) => this.handleMessage(scope, e)) | ||
app.action({}, async (e) => this.handleAction(scope, e)) | ||
} | ||
|
||
private async handleMessage(scope: string, { message }: SlackEventMiddlewareArgs<'message'>) { | ||
if ('bot_id' in message) { | ||
return | ||
} | ||
|
||
if ('user' in message) { | ||
await this.service.receive( | ||
scope, | ||
{ identity: '*', sender: message.user, thread: message.channel }, | ||
{ type: 'text', text: message.text } | ||
) | ||
} | ||
} | ||
|
||
private async handleAction(scope: string, e: SlackActionMiddlewareArgs<SlackAction>) { | ||
const { body, action, respond, ack } = e | ||
const endpoint = { identity: '*', sender: body.user.id, thread: body.channel?.id || '*' } | ||
|
||
if (action.type === 'button' && 'text' in action) { | ||
await this.handleButtonAction(scope, { endpoint, action, respond }) | ||
} else if (action.type === 'static_select') { | ||
await this.handleSelectAction(scope, { endpoint, action, respond }) | ||
} | ||
|
||
await ack() | ||
} | ||
|
||
private async handleButtonAction(scope: string, e: SlackActionHandler<ButtonAction>) { | ||
const { respond, endpoint, action } = e | ||
|
||
if (action.action_id.startsWith(QUICK_REPLY_PREFIX)) { | ||
await respond({ text: `*${action.text.text}*` }) | ||
|
||
await this.service.receive(scope, endpoint, { | ||
type: 'quick_reply', | ||
text: action.text.text, | ||
payload: action.value | ||
}) | ||
} else { | ||
if (action.action_id.startsWith(SAY_PREFIX)) { | ||
await this.service.receive(scope, endpoint, { type: 'say_something', text: action.value }) | ||
} else if (action.action_id.startsWith(POSTBACK_PREFIX)) { | ||
await this.service.receive(scope, endpoint, { type: 'postback', payload: action.value }) | ||
} | ||
} | ||
} | ||
|
||
private async handleSelectAction(scope: string, e: SlackActionHandler<StaticSelectAction>) { | ||
const { respond, action, endpoint } = e | ||
|
||
await respond(`*${action.selected_option.text.text}*`) | ||
|
||
await this.service.receive(scope, endpoint, { | ||
type: 'quick_reply', | ||
text: action.selected_option.text.text, | ||
payload: action.selected_option.value | ||
}) | ||
} | ||
} | ||
|
||
interface SlackActionHandler<T> { | ||
endpoint: Endpoint | ||
action: T | ||
respond: RespondFn | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { ChannelTemplate } from '../base/channel' | ||
import { SlackApi } from './api' | ||
import { SlackConfig, SlackConfigSchema } from './config' | ||
import { SlackService } from './service' | ||
import { SlackStream } from './stream' | ||
|
||
export class SlackChannel extends ChannelTemplate<SlackConfig, SlackService, SlackApi, SlackStream> { | ||
get meta() { | ||
return { | ||
id: 'a3551758-a03f-4a68-97f2-4536a8805b52', | ||
name: 'slack', | ||
version: '1.0.0', | ||
schema: SlackConfigSchema, | ||
initiable: false, | ||
lazy: true | ||
} | ||
} | ||
|
||
constructor() { | ||
const service = new SlackService() | ||
super(service, new SlackApi(service), new SlackStream(service)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import Joi from 'joi' | ||
import { ChannelConfig } from '../base/config' | ||
|
||
export interface SlackConfig extends ChannelConfig { | ||
botToken: string | ||
signingSecret: string | ||
} | ||
|
||
export const SlackConfigSchema = { | ||
botToken: Joi.string().required(), | ||
signingSecret: Joi.string().required() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Block, KnownBlock } from '@slack/bolt' | ||
import { ChatPostMessageArguments } from '@slack/web-api' | ||
import { ChannelContext } from '../base/context' | ||
import { SlackState } from './service' | ||
|
||
export type SlackContext = ChannelContext<SlackState> & { | ||
message: ChatPostMessageArguments & { blocks: (KnownBlock | Block)[] } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { AudioRenderer } from '../../base/renderers/audio' | ||
import { AudioContent } from '../../content/types' | ||
import { SlackContext } from '../context' | ||
|
||
export class SlackAudioRenderer extends AudioRenderer { | ||
renderAudio(context: SlackContext, payload: AudioContent) { | ||
context.message.blocks.push({ | ||
type: 'section', | ||
text: { type: 'mrkdwn', text: `<${payload.audio}|${payload.title || payload.audio}>` } | ||
}) | ||
|
||
context.message.text = payload.title || payload.audio | ||
} | ||
} |
Oops, something went wrong.