-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
29 changed files
with
830 additions
and
366 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
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 |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import {BotConfig, ChatStep} from './types' | ||
|
||
export async function addStep(config: BotConfig, name: string, step: ChatStep) { | ||
if (!name) { | ||
throw `The chat step name should not be empty` | ||
} | ||
if (config.steps[name]) { | ||
throw `The chat step '${name}' is already defined` | ||
} | ||
config.steps[name] = step | ||
} |
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,62 @@ | ||
import {WebhookEvent, EventEntry} from '../events' | ||
import { MessagingItem } from '../events/types' | ||
import {Message, MessageReply, MessageBuilder} from '../messages' | ||
import {BotConfig, ChatState} from './types' | ||
|
||
export async function handle(config: BotConfig, event: WebhookEvent) { | ||
if (event.object !== 'page') { | ||
throw `Unknown event.object value '${event.object}', it should be 'page'` | ||
} | ||
await Promise.all(event.entry.map(e => handleEntry(config, e))) | ||
} | ||
|
||
async function handleEntry(config: BotConfig, entry: EventEntry) { | ||
const messaging = entry.messaging[0] | ||
const sender = messaging.sender | ||
const state = await getState(config, sender.id) | ||
const step = await listen(config, state, messaging) | ||
const reply = makeReply(sender, await step.send(state.context)) | ||
await Promise.all([config.send(config.accessToken, reply), setState(config, sender.id, state)]) | ||
} | ||
|
||
async function listen(config: BotConfig, state: ChatState, messaging: MessagingItem) { | ||
if (!state.step) { | ||
state.step = 'start' | ||
return config.steps['start'] | ||
} | ||
const {sender, message, postback} = messaging as any | ||
const step = config.steps[state.step] | ||
const setContext = async (value: any) => { | ||
state.context = value | ||
return setState(config, sender.id, state) | ||
} | ||
const nextStep = await step.listen({message, postback, context: state.context, setContext}) | ||
if (!nextStep) { | ||
return step | ||
} | ||
if (!config.steps[nextStep]) { | ||
throw `Unknown chat step '${nextStep}'` | ||
} | ||
state.step = nextStep | ||
return config.steps[nextStep] | ||
} | ||
|
||
async function getState(config: BotConfig, key: string): Promise<ChatState> { | ||
const value = await config.storage.get(key) | ||
if (value == undefined) { | ||
return {step: '', context: {...config.initialContext}} | ||
} | ||
return JSON.parse(value) | ||
} | ||
|
||
async function setState(config: BotConfig, key: string, state: ChatState): Promise<void> { | ||
await config.storage.set(key, JSON.stringify(state)) | ||
} | ||
|
||
function makeReply(recipient: {id: string}, message: MessageBuilder<Message>): MessageReply { | ||
return { | ||
recipient, | ||
messaging_type: 'RESPONSE', | ||
message: message.get(), | ||
} | ||
} |
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,21 @@ | ||
import {send} from './send' | ||
import {handle} from './handle' | ||
import {verify} from './verify' | ||
import {addStep} from './addStep' | ||
import {Bot, BotConfig} from './types' | ||
import {memoryStorage} from '../storages' | ||
|
||
export function bot(config: Partial<BotConfig>): Bot { | ||
if (!config.accessToken) throw `The accessToken is missing on the bot configuration!` | ||
if (!config.verifyToken) throw `The verifyToken is missing on the bot configuration!` | ||
if (!config.send) config.send = send | ||
if (!config.storage) config.storage = memoryStorage() | ||
if (!config.initialContext) config.initialContext = {} | ||
if (!config.steps) config.steps = {} | ||
|
||
return { | ||
on: (name, step) => addStep(config as BotConfig, name, step), | ||
verify: query => verify(config as BotConfig, query), | ||
handle: event => handle(config as BotConfig, event), | ||
} | ||
} |
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,19 @@ | ||
import https from 'https' | ||
|
||
export async function send(accessToken: string, data: any) { | ||
data = JSON.stringify(data) | ||
const req = https.request({ | ||
hostname: 'graph.facebook.com', | ||
port: 443, | ||
path: '/v11.0/me/messages?access_token=' + accessToken, | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Content-Length': data.length, | ||
}, | ||
}) | ||
return new Promise<void>((resolve, reject) => { | ||
req.on('error', err => reject(err)) | ||
req.end(data, () => resolve()) | ||
}) | ||
} |
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,41 @@ | ||
import {WebhookEvent, PostbackEvent, MessageEvent} from '../events' | ||
import {Message, MessageBuilder} from '../messages' | ||
import {ContextStorage} from '../storages' | ||
|
||
export type BotConfig = { | ||
accessToken: string | ||
verifyToken: string | ||
initialContext: any | ||
steps: Record<string, ChatStep> | ||
storage: ContextStorage | ||
send: (accessToken: string, data: any) => Promise<void> | ||
} | ||
|
||
export interface Bot { | ||
on(name: string, step: ChatStep): void | ||
verify(query: VerificationQuery): string | ||
handle(event: WebhookEvent): Promise<void> | ||
} | ||
|
||
export type VerificationQuery = { | ||
'hub.mode': string | ||
'hub.challenge': string | ||
'hub.verify_token': string | ||
} | ||
|
||
export type ChatStep = { | ||
send: (context: any) => Promise<MessageBuilder<Message>> | ||
listen: (entry: ChatEntry) => Promise<string | void> | ||
} | ||
|
||
export type ChatEntry = { | ||
context: any | ||
setContext: (value: Record<string, any>) => Promise<void> | ||
message?: MessageEvent | ||
postback?: PostbackEvent | ||
} | ||
|
||
export type ChatState = { | ||
step: string | ||
context: any | ||
} |
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 {BotConfig, VerificationQuery} from './types' | ||
|
||
export function verify({verifyToken}: BotConfig, query: VerificationQuery) { | ||
if (query['hub.mode'] !== 'subscribe' || query['hub.verify_token'] !== verifyToken) { | ||
throw `The query parameters are incorrect` | ||
} | ||
return query['hub.challenge'] | ||
} |
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 |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import {EventBuilder, EventBuilderData} from './EventBuilder' | ||
|
||
export {WebhookEvent, EventEntry, PostbackEvent, MessageEvent} from './types' | ||
|
||
export function event(data?: EventBuilderData) { | ||
return new EventBuilder(data || {timestamp: Date.now(), items: []}) | ||
} |
Oops, something went wrong.