diff --git a/examples/viber-demo-bot/src/bot.ts b/examples/viber-demo-bot/src/bot.ts index 0db50e6..54c7fb9 100644 --- a/examples/viber-demo-bot/src/bot.ts +++ b/examples/viber-demo-bot/src/bot.ts @@ -3,9 +3,25 @@ import { ViberAdapter } from '@ebenos/viber-adapter'; import { viberConfig } from './secret'; import getStartedModule from './modules/getStarted'; +import { + IViberConversationStartedEvent, + IViberSubscribedEvent, + IViberUnsubscribedEvent +} from '@ebenos/viber-adapter'; export const adapter = new ViberAdapter({ - authToken: viberConfig.auth_token + authToken: viberConfig.auth_token, + webhookHandlers: { + conversationStartedWebhook: async (e: IViberConversationStartedEvent) => { + console.log(e); + }, + subscribeWebhook: async (e: IViberSubscribedEvent) => { + console.log(e); + }, + unsubscribeWebhook: async (e: IViberUnsubscribedEvent) => { + console.log(e); + } + } }); export const bot = new Bot(adapter, {}); diff --git a/examples/viber-demo-bot/src/modules/getStarted/actions.ts b/examples/viber-demo-bot/src/modules/getStarted/actions.ts index 9418616..3d86fa6 100644 --- a/examples/viber-demo-bot/src/modules/getStarted/actions.ts +++ b/examples/viber-demo-bot/src/modules/getStarted/actions.ts @@ -1,14 +1,7 @@ import { bot } from '../../bot'; import { RichMedia, Button, Message, Keyboard } from '@ebenos/viber-elements'; import { Carousel } from '@ebenos/viber-elements'; -import { - addAction, - addPostbackRule, - addTextRule, - InMemoryUser, - addBaseLocationRule, - IPayload -} from '@ebenos/framework'; +import { addAction, addPostbackRule, addTextRule, InMemoryUser, IPayload } from '@ebenos/framework'; import getStartedModule from '.'; const testArray = [ @@ -199,7 +192,7 @@ async function getTest12(user: InMemoryUser) { } addAction(getStartedModule, location); -addBaseLocationRule(getStartedModule, location); + async function location( user: InMemoryUser, payload?: { text: string; location?: { lat: number; lon: number }; somethingNew?: string } @@ -249,9 +242,38 @@ async function location1(user: InMemoryUser) { .end(); } -addAction(getStartedModule, location2); -addTextRule(getStartedModule, location2, /PAME/); -async function location2(user: InMemoryUser, payload?: IPayload) { +addAction(getStartedModule, phone); +addTextRule(getStartedModule, phone, /PHONE_TEST/); +async function phone(user: InMemoryUser) { + await bot + .scenario(user) + .send( + new Message({ + sender: { + name: 'Giorgos' + }, + text: 'Send Phone', + keyboard: new Keyboard({ + ButtonsGroupColumns: 3, + ButtonsGroupRows: 3, + Buttons: [ + new Button({ + Rows: 3, + Columns: 3, + ActionBody: 'pame_phone', + Text: 'tel', + ActionType: 'share-phone' + }) + ] + }) + }) + ) + .end(); +} + +addAction(getStartedModule, phone2); +addTextRule(getStartedModule, phone2, /PAME_PHONE/); +async function phone2(user: InMemoryUser, payload: IPayload) { await bot .scenario(user) .send( @@ -259,7 +281,7 @@ async function location2(user: InMemoryUser, payload?: IPayload) { sender: { name: 'Giorgos' }, - text: 'PAMEEE The location has lat ' + payload?.location?.lat, + text: 'Your phone number is ' + payload?.contact?.phone_number, tracking_data: { age: 18 } diff --git a/packages/framework/lib/adapter.ts b/packages/framework/lib/adapter.ts index 4071b44..438a234 100644 --- a/packages/framework/lib/adapter.ts +++ b/packages/framework/lib/adapter.ts @@ -20,6 +20,11 @@ export interface EbonyHandlers { text: string; tracking_data?: ITrackingData; location?: { lon: number; lat: number }; + contact?: { + name?: string; + phone_number: string; + avatar?: string; + }; }, nlp: WitNLP | undefined, user: U @@ -96,6 +101,11 @@ interface InitOptionsHandlers { text: string; tracking_data?: ITrackingData; location?: { lon: number; lat: number }; + contact?: { + name?: string; + phone_number: string; + avatar?: string; + }; }, nlp: WitNLP | undefined, user: U diff --git a/packages/framework/lib/handlers/text.ts b/packages/framework/lib/handlers/text.ts index f0542e8..20b4ef3 100644 --- a/packages/framework/lib/handlers/text.ts +++ b/packages/framework/lib/handlers/text.ts @@ -40,6 +40,11 @@ export default function textHandlerFactory>( text: string; tracking_data?: ITrackingData; location?: { lon: number; lat: number }; + contact?: { + name?: string; + phone_number: string; + avatar?: string; + }; }, nlp: WitNLP | undefined, user: U diff --git a/packages/framework/lib/index.ts b/packages/framework/lib/index.ts index 2fcbc3f..b53b50c 100644 --- a/packages/framework/lib/index.ts +++ b/packages/framework/lib/index.ts @@ -12,6 +12,7 @@ import Bot from './bot'; import User from './models/User'; import DatabaseUser, { userLoader as databaseUserLoader } from './models/DatabaseUser'; import InMemoryUser, { userLoader as inMemoryUserLoader } from './models/InMemoryUser'; +import { IUser } from './models/UserSchema'; import GenericAdapter from './adapter'; import TestAdapter from './tests/test-adapter'; export { Module, BotOptions, Scenario } from './interfaces/bot'; @@ -33,5 +34,6 @@ export { databaseUserLoader, InMemoryUser, inMemoryUserLoader, - TestAdapter + TestAdapter, + IUser }; diff --git a/packages/framework/lib/interfaces/nlp.ts b/packages/framework/lib/interfaces/nlp.ts index 34b8747..c9d4d74 100644 --- a/packages/framework/lib/interfaces/nlp.ts +++ b/packages/framework/lib/interfaces/nlp.ts @@ -1,11 +1,16 @@ export interface WitNLP { - entities: { - intent?: WitEntityNLP[]; - sentiment?: WitEntityNLP[]; - }; + text: string; + intents: WitIntentNLP[]; + entities: any; } export interface WitEntityNLP { value: string; confidence: number; } + +export interface WitIntentNLP { + id: string; + name: string; + confidence: number; +} diff --git a/packages/framework/lib/interfaces/payload.ts b/packages/framework/lib/interfaces/payload.ts index f653b79..fc7e2c2 100644 --- a/packages/framework/lib/interfaces/payload.ts +++ b/packages/framework/lib/interfaces/payload.ts @@ -3,5 +3,10 @@ export interface IPayload extends Record { text: string; tracking_data?: ITrackingData; location?: { lon: number; lat: number }; + contact?: { + name?: string; + phone_number: string; + avatar?: string; + }; type?: string; } diff --git a/packages/framework/lib/interfaces/trackingData.ts b/packages/framework/lib/interfaces/trackingData.ts index 86910fa..dcbc9f4 100644 --- a/packages/framework/lib/interfaces/trackingData.ts +++ b/packages/framework/lib/interfaces/trackingData.ts @@ -1,5 +1 @@ -export type TrackingDataPrimitives = null | string | number | boolean; -export type ITrackingData = - | TrackingDataPrimitives - | Array - | { [key: string]: ITrackingData }; +export type ITrackingData = Record; diff --git a/packages/framework/lib/modules/index.ts b/packages/framework/lib/modules/index.ts index 8ce5da4..504ed87 100644 --- a/packages/framework/lib/modules/index.ts +++ b/packages/framework/lib/modules/index.ts @@ -70,16 +70,9 @@ export function addPostbackRule>( return { type: actionName }; } -export function addBaseLocationRule>( - module: Module, - action: (user: U, payload?: IPayload, ...args: any[]) => Promise -): void { - return addTextRule(module, action, /USER_SEND_LOCATION/); -} - export function addTextRule>( module: Module, - action: (user: U, payload?: IPayload, ...args: any[]) => Promise, + action: (user: U, payload: IPayload, ...args: any[]) => Promise, rule: RegExp | RegExp[] ): void { const actionName = module.name + '/' + action.name; diff --git a/packages/viber-adapter/lib/adapter.ts b/packages/viber-adapter/lib/adapter.ts index bb9a221..10f6f8d 100644 --- a/packages/viber-adapter/lib/adapter.ts +++ b/packages/viber-adapter/lib/adapter.ts @@ -1,26 +1,49 @@ -import { EbonyHandlers, GenericAdapter, GenericAttachment, IRouters } from '@ebenos/framework'; +import { + EbonyHandlers, + GenericAdapter, + GenericAttachment, + IRouters, + ITrackingData, + IUser +} from '@ebenos/framework'; import express, { Request, Response } from 'express'; import { json as bodyParser } from 'body-parser'; import senderFactory from './sender'; -import { IViberMessageEvent, IViberSender, WebhookIncomingViberEvent } from './interfaces/webhook'; +import { + IViberMessageEvent, + IViberSender, + WebhookIncomingViberEvent, + IViberUnsubscribedEvent, + IViberSubscribedEvent, + IViberConversationStartedEvent +} from './interfaces/webhook'; import { setWebhook } from './api/requests'; import { IViberSetWebhookResult } from './interfaces/api'; -import { IUser } from '@ebenos/framework/lib/models/UserSchema'; import { isMediaMessage, + IViberContactMessage, IViberLocationMessage, IViberTextMessage } from './interfaces/message_types'; import { isPostbackTrackingData } from './interfaces/tracking_data'; -import { ITrackingData } from '@ebenos/framework'; -export interface IViberOptions { +export interface IViberOptions { route?: string; authToken: string; welcomeMessage?: Record; + userLoader?: (userData: IViberSender) => Promise; + webhookHandlers?: IViberWebhookHandlers; +} + +export interface IViberWebhookHandlers { + unsubscribeWebhook?: (e: IViberUnsubscribedEvent) => Promise; + subscribeWebhook?: (e: IViberSubscribedEvent) => Promise; + conversationStartedWebhook?: ( + e: IViberConversationStartedEvent + ) => Promise | void>; } -export default class ViberAdapter extends GenericAdapter { +export default class ViberAdapter extends GenericAdapter { public operations = { handover: (): Promise => { console.log('Not implemented!'); @@ -31,25 +54,33 @@ export default class ViberAdapter extends GenericAdapter { public sender; private welcomeMessage?: Record; + private webhookHandlers?: IViberWebhookHandlers; private route: string; private authToken: string; public webhook = express(); + private userLoader?: (userData: IViberSender) => Promise; - constructor(options: IViberOptions) { + constructor(options: IViberOptions) { super(); - const { route = '/viber/webhook', authToken, welcomeMessage } = options; + const { route = '/viber/webhook', authToken, userLoader, webhookHandlers } = options; this.route = route; this.authToken = authToken; this.sender = senderFactory(this.authToken); - this.welcomeMessage = welcomeMessage; + this.userLoader = userLoader; + this.webhookHandlers = webhookHandlers; } - public initialization(): void { + public async initialization(): Promise { this.webhook.use(bodyParser()); this.webhook.post( this.route, - viberWebhookFactory(this.routers, this.handlers, this.welcomeMessage) + await viberWebhookFactory( + this.routers, + this.handlers, + this.userLoader, + this.webhookHandlers + ) ); } @@ -73,7 +104,7 @@ function convertViberSenderToUser(sender: IViberSender): IUser { } function handleTextMessage( - m: IViberTextMessage | IViberLocationMessage, + m: IViberTextMessage | IViberLocationMessage | IViberContactMessage, user: IUser, textHandler: EbonyHandlers['text'], routers: IRouters @@ -82,16 +113,16 @@ function handleTextMessage( console.log('No text handler'); return; } - const text = m.text ? m.text : 'user_send_location'; + const text = m.text; const location = m.type === 'location' ? m.location : undefined; + const contact = m.type === 'contact' ? m.contact : undefined; let tracking_data: ITrackingData; try { // Tracking Data is an object tracking_data = JSON.parse(m.tracking_data) as ITrackingData; } catch { - // Tracking Data is a string - tracking_data = m.tracking_data; + tracking_data = { data: m.tracking_data }; } if (isPostbackTrackingData(tracking_data)) { @@ -100,29 +131,38 @@ function handleTextMessage( type: tracking_data.type, text, location, + contact, tracking_data }); routerExists(routers.PostbackRouter).objectPayloadHandler(payload, user); return; } - textHandler({ text, tracking_data, location }, undefined, user); + textHandler({ text, tracking_data, location, contact }, undefined, user); return; } -function viberWebhookFactory( +async function viberWebhookFactory( routers: IRouters, handlers: EbonyHandlers, - welcomeMessage?: Record + userLoader?: (userData: IViberSender) => Promise, + webhooks?: { + unsubscribeWebhook?: (e: IViberUnsubscribedEvent) => Promise; + subscribeWebhook?: (e: IViberSubscribedEvent) => Promise; + conversationStartedWebhook?: ( + e: IViberConversationStartedEvent + ) => Promise | void>; + } ) { - function messageWebhook(e: IViberMessageEvent): void { - const user = convertViberSenderToUser(e.sender); + async function messageWebhook(e: IViberMessageEvent): Promise { + const user = userLoader ? await userLoader(e.sender) : convertViberSenderToUser(e.sender); + switch (e.message.type) { case 'text': case 'location': + case 'contact': handleTextMessage(e.message, user, handlers.text, routers); return; - default: if (isMediaMessage(e.message)) { if (handlers.attachment !== undefined) { @@ -137,50 +177,50 @@ function viberWebhookFactory( } } - return (req: Request, res: Response) => { + return async (req: Request, res: Response) => { const body = req.body as WebhookIncomingViberEvent; - - if (body.event !== 'conversation_started') { - res.status(200).send(); - } - switch (body.event) { case 'message': messageWebhook(body); - return; - case 'seen': - console.log('seen'); - return; + break; case 'conversation_started': console.log('conversation_started'); - if (welcomeMessage !== undefined) { - res.json(welcomeMessage); - } else { - res.status(200).send(); + if (webhooks?.conversationStartedWebhook) { + const welcomeMessage = await webhooks.conversationStartedWebhook(body); + if (welcomeMessage !== undefined) { + console.log(welcomeMessage); + res.json(welcomeMessage); + } } - return; - case 'delivered': - console.log('delivered'); - return; + break; case 'subscribed': + if (webhooks?.subscribeWebhook) await webhooks.subscribeWebhook(body); console.log('subscribed'); - return; + break; case 'unsubscribed': + if (webhooks?.unsubscribeWebhook) await webhooks.unsubscribeWebhook(body); console.log('unsubscribed'); - return; + break; + case 'seen': + console.log('seen'); + break; + case 'delivered': + console.log('delivered'); + break; case 'failed': console.log(`Failed: ${body.desc}`); - return; + break; case 'client_status': console.log('client_status'); - return; + break; case 'webhook': console.log('Webhook connected'); - return; + break; default: console.log('Unknown event type: ' + body); - return; + break; } + res.status(200).send(); }; } diff --git a/packages/viber-adapter/lib/index.ts b/packages/viber-adapter/lib/index.ts index 0a59a9a..e93eca3 100644 --- a/packages/viber-adapter/lib/index.ts +++ b/packages/viber-adapter/lib/index.ts @@ -1,3 +1,8 @@ import ViberAdapter from './adapter'; +export * from './interfaces/message_types'; +export * from './interfaces/tracking_data'; +export * from './interfaces/api'; +export * from './interfaces/webhook'; +export * from './adapter'; export { ViberAdapter }; diff --git a/packages/viber-adapter/lib/interfaces/message_types.ts b/packages/viber-adapter/lib/interfaces/message_types.ts index 9d249a9..bcdd66b 100644 --- a/packages/viber-adapter/lib/interfaces/message_types.ts +++ b/packages/viber-adapter/lib/interfaces/message_types.ts @@ -45,6 +45,7 @@ export interface IViberStickerMessage extends IViberMessage { export interface IViberContactMessage extends IViberMessage { type: 'contact'; + text: string; contact: { name?: string; phone_number: string; @@ -54,7 +55,7 @@ export interface IViberContactMessage extends IViberMessage { export interface IViberLocationMessage extends IViberMessage { type: 'location'; - text?: string; + text: string; location: { lat: number; lon: number;