├── .gitignore ├── .github └── PULL_REQUEST_TEMPLATE.md ├── CODE_OF_CONDUCT.md ├── README.md ├── examples ├── inbound-call │ ├── scripts │ │ ├── utils.js │ │ ├── deconfigure.js │ │ └── configure.js │ ├── package.json │ ├── cloudformation.yaml │ ├── index.js │ ├── sample_contact_flow │ └── README.md └── private-bot │ ├── scripts │ ├── utils.js │ ├── deconfigure.js │ └── configure.js │ ├── package.json │ ├── simple-proxy-api.yaml │ ├── cloudformation.yaml │ ├── index.js │ ├── README.md │ └── bot_command_handler.js ├── LICENSE └── CONTRIBUTING.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | package-lock.json 4 | packaged-sam.yaml 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Amazon Chime Private Bot Demo 2 | 3 | This repository shows you how to build Amazon Chime Bot and integrates with Amazon Connect, Amazon Lex, and AWS Lambda. 4 | 5 | ## Getting started 6 | 7 | * [Part 1:](examples/private-bot/README.md) Create Chime private bot 8 | * [Part 2:](examples/inbound-call/README.md) Integrate Chime private bot with Amazon Connect and Amazon Lex. 9 | 10 | ## License Summary 11 | 12 | This sample code is made available under the MIT-0 license. See the LICENSE file. 13 | -------------------------------------------------------------------------------- /examples/inbound-call/scripts/utils.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const fs = require('fs') 5 | 6 | module.exports.modifyFiles = function modifyFiles (files, replacements) { 7 | files.forEach((file) => { 8 | let fileContentModified = fs.readFileSync(file, 'utf8') 9 | 10 | replacements.forEach((v) => { 11 | fileContentModified = fileContentModified.replace(v.regexp, v.replacement) 12 | }) 13 | 14 | fs.writeFileSync(file, fileContentModified, 'utf8') 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /examples/private-bot/scripts/utils.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const fs = require('fs') 5 | 6 | module.exports.modifyFiles = function modifyFiles (files, replacements) { 7 | files.forEach((file) => { 8 | let fileContentModified = fs.readFileSync(file, 'utf8') 9 | 10 | replacements.forEach((v) => { 11 | fileContentModified = fileContentModified.replace(v.regexp, v.replacement) 12 | }) 13 | 14 | fs.writeFileSync(file, fileContentModified, 'utf8') 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /examples/private-bot/scripts/deconfigure.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const modifyFiles = require('./utils').modifyFiles 5 | const packageJson = require('../package.json') 6 | const config = packageJson.config 7 | 8 | modifyFiles(['./package.json', './cloudformation.yaml', './simple-proxy-api.yaml'], [{ 9 | regexp: new RegExp(config.accountId, 'g'), 10 | replacement: 'YOUR_ACCOUNT_ID' 11 | }, { 12 | regexp: new RegExp(config.region, 'g'), 13 | replacement: 'YOUR_AWS_REGION' 14 | }, { 15 | regexp: new RegExp(config.s3BucketName, 'g'), 16 | replacement: 'AMAZON_CHIME_PRIVATE_BOT_LAMBDA_DEPLOY_BUCKET_NAME' 17 | }, { 18 | regexp: new RegExp(config.functionName, 'g'), 19 | replacement: 'AMAZON_CHIME_PRIVATE_BOT_LAMBDA_FUNCTION_NAME' 20 | }, { 21 | regexp: new RegExp(config.cloudFormationStackName, 'g'), 22 | replacement: 'AMAZON_CHIME_PRIVATE_BOT_LAMBDA_STACK_NAME' 23 | }]) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /examples/inbound-call/scripts/deconfigure.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const modifyFiles = require('./utils').modifyFiles 5 | const packageJson = require('../package.json') 6 | const config = packageJson.config 7 | 8 | config.botIncomingWebhook = config.botIncomingWebhook.replace('?', '\\?'); 9 | 10 | modifyFiles(['./package.json', './cloudformation.yaml'], [ 11 | { 12 | regexp: new RegExp(config.connectArn, 'g'), 13 | replacement: 'AMAZON_CHIME_INBOUND_CALL_CONNECT_ARN' 14 | }, { 15 | regexp: new RegExp(config.accountId, 'g'), 16 | replacement: 'YOUR_ACCOUNT_ID' 17 | }, { 18 | regexp: new RegExp(config.region, 'g'), 19 | replacement: 'YOUR_AWS_REGION' 20 | }, { 21 | regexp: new RegExp(config.s3BucketName, 'g'), 22 | replacement: 'AMAZON_CHIME_INBOUND_CALL_LAMBDA_DEPLOY_BUCKET_NAME' 23 | }, { 24 | regexp: new RegExp(config.functionName, 'g'), 25 | replacement: 'AMAZON_CHIME_INBOUND_CALL_LAMBDA_FUNCTION_NAME' 26 | }, { 27 | regexp: new RegExp(config.botIncomingWebhook, 'g'), 28 | replacement: 'AMAZON_CHIME_INBOUND_CALL_LAMBDA_BOT_INCOMING_WEBHOOK' 29 | }, { 30 | regexp: new RegExp(config.userEmail, 'g'), 31 | replacement: 'AMAZON_CHIME_INBOUND_CALL_LAMBDA_USER_EMAIL' 32 | }, { 33 | regexp: new RegExp(config.cloudFormationStackName, 'g'), 34 | replacement: 'AMAZON_CHIME_INBOUND_CALL_LAMBDA_STACK_NAME' 35 | }]) 36 | -------------------------------------------------------------------------------- /examples/private-bot/scripts/configure.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const modifyFiles = require('./utils').modifyFiles 5 | const yargs = require('yargs') 6 | const argv = yargs.options({ 7 | accountId: { 8 | demand: true, 9 | alias: 'account-id', 10 | describe: 'aws account id', 11 | string: true 12 | }, 13 | bucketName: { 14 | demand: true, 15 | alias: 'bucket-name', 16 | describe: 'S3 bucket used to deploy lambda', 17 | string: true 18 | }, 19 | functionName: { 20 | alias: 'function-name', 21 | describe: 'Lambda function name', 22 | string: true, 23 | default: 'AmazonChimePrivateBot' 24 | }, 25 | cloudFormationStackName: { 26 | alias: 'cloudformation-stack-name', 27 | describe: 'CloudFormation stack name', 28 | string: true, 29 | default: 'AmazonChimePrivateBotLambdaStack' 30 | }, 31 | region: { 32 | alias: 'region', 33 | describe: 'AWS Region demo uses', 34 | string: true, 35 | default: 'us-east-1' 36 | } 37 | }).argv; 38 | 39 | modifyFiles(['./package.json', './cloudformation.yaml', './simple-proxy-api.yaml'], [{ 40 | regexp: /YOUR_ACCOUNT_ID/g, 41 | replacement: argv.accountId 42 | }, { 43 | regexp: /YOUR_AWS_REGION/g, 44 | replacement: argv.region 45 | }, { 46 | regexp: /AMAZON_CHIME_PRIVATE_BOT_LAMBDA_DEPLOY_BUCKET_NAME/g, 47 | replacement: argv.bucketName 48 | }, { 49 | regexp: /AMAZON_CHIME_PRIVATE_BOT_LAMBDA_FUNCTION_NAME/g, 50 | replacement: argv.functionName 51 | }, { 52 | regexp: /AMAZON_CHIME_PRIVATE_BOT_LAMBDA_STACK_NAME/g, 53 | replacement: argv.cloudFormationStackName 54 | }]) 55 | -------------------------------------------------------------------------------- /examples/private-bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazon-chime-private-bot-demo", 3 | "version": "1.0.0", 4 | "description": "this is for amazon chime private bot demo", 5 | "main": "index.js", 6 | "config": { 7 | "s3BucketName": "AMAZON_CHIME_PRIVATE_BOT_LAMBDA_DEPLOY_BUCKET_NAME", 8 | "region": "YOUR_AWS_REGION", 9 | "cloudFormationStackName": "AMAZON_CHIME_PRIVATE_BOT_LAMBDA_STACK_NAME", 10 | "functionName": "AMAZON_CHIME_PRIVATE_BOT_LAMBDA_FUNCTION_NAME", 11 | "accountId": "YOUR_ACCOUNT_ID" 12 | }, 13 | "scripts": { 14 | "start": "node index.js", 15 | "config": "npm install && node ./scripts/configure.js", 16 | "deconfig": "node ./scripts/deconfigure.js", 17 | "local": "node scripts/local", 18 | "create-bucket": "aws s3 mb s3://$npm_package_config_s3BucketName --region $npm_package_config_region", 19 | "delete-bucket": "aws s3 rb s3://$npm_package_config_s3BucketName --region $npm_package_config_region", 20 | "package": "aws cloudformation package --template ./cloudformation.yaml --s3-bucket $npm_package_config_s3BucketName --output-template packaged-sam.yaml --region $npm_package_config_region", 21 | "deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name $npm_package_config_cloudFormationStackName --capabilities CAPABILITY_IAM --region $npm_package_config_region", 22 | "package-deploy": "npm run package && npm run deploy", 23 | "delete-stack": "aws cloudformation delete-stack --stack-name $npm_package_config_cloudFormationStackName --region $npm_package_config_region", 24 | "setup": "npm install && (aws s3api get-bucket-location --bucket $npm_package_config_s3BucketName --region $npm_package_config_region || npm run create-bucket) && npm run package-deploy" 25 | }, 26 | "license": "MIT-0", 27 | "dependencies": { 28 | "aws-sdk": "^2.445.0", 29 | "request": "^2.88.0", 30 | "request-promise": "^4.2.4", 31 | "yargs": "^13.2.4" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/inbound-call/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazon-chime-private-bot-demo", 3 | "version": "1.0.0", 4 | "description": "this is for amazon chime private bot demo", 5 | "main": "index.js", 6 | "config": { 7 | "s3BucketName": "AMAZON_CHIME_INBOUND_CALL_LAMBDA_DEPLOY_BUCKET_NAME", 8 | "region": "YOUR_AWS_REGION", 9 | "cloudFormationStackName": "AMAZON_CHIME_INBOUND_CALL_LAMBDA_STACK_NAME", 10 | "functionName": "AMAZON_CHIME_INBOUND_CALL_LAMBDA_FUNCTION_NAME", 11 | "accountId": "YOUR_ACCOUNT_ID", 12 | "botIncomingWebhook": "AMAZON_CHIME_INBOUND_CALL_LAMBDA_BOT_INCOMING_WEBHOOK", 13 | "userEmail": "AMAZON_CHIME_INBOUND_CALL_LAMBDA_USER_EMAIL", 14 | "connectArn": "AMAZON_CHIME_INBOUND_CALL_CONNECT_ARN" 15 | }, 16 | "scripts": { 17 | "start": "node index.js", 18 | "config": "npm install && node ./scripts/configure.js", 19 | "deconfig": "node ./scripts/deconfigure.js", 20 | "local": "node scripts/local", 21 | "create-bucket": "aws s3 mb s3://$npm_package_config_s3BucketName --region $npm_package_config_region", 22 | "delete-bucket": "aws s3 rb s3://$npm_package_config_s3BucketName --region $npm_package_config_region", 23 | "package": "aws cloudformation package --template ./cloudformation.yaml --s3-bucket $npm_package_config_s3BucketName --output-template packaged-sam.yaml --region $npm_package_config_region", 24 | "deploy": "aws cloudformation deploy --template-file packaged-sam.yaml --stack-name $npm_package_config_cloudFormationStackName --capabilities CAPABILITY_IAM --region $npm_package_config_region", 25 | "package-deploy": "npm run package && npm run deploy", 26 | "delete-stack": "aws cloudformation delete-stack --stack-name $npm_package_config_cloudFormationStackName --region $npm_package_config_region", 27 | "setup": "npm install && (aws s3api get-bucket-location --bucket $npm_package_config_s3BucketName --region $npm_package_config_region || npm run create-bucket) && npm run package-deploy" 28 | }, 29 | "license": "MIT-0", 30 | "dependencies": { 31 | "aws-sdk": "^2.445.0", 32 | "request": "^2.88.0", 33 | "request-promise": "^4.2.4", 34 | "yargs": "^13.2.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/private-bot/simple-proxy-api.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | --- 4 | swagger: 2.0 5 | info: 6 | title: PrivateBotDemo 7 | basePath: /Demo 8 | schemes: 9 | - https 10 | paths: 11 | /: 12 | x-amazon-apigateway-any-method: 13 | produces: 14 | - application/json 15 | responses: 16 | 200: 17 | description: 200 response 18 | schema: 19 | $ref: "#/definitions/Empty" 20 | x-amazon-apigateway-integration: 21 | responses: 22 | default: 23 | statusCode: 200 24 | uri: arn:aws:apigateway:YOUR_AWS_REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:YOUR_AWS_REGION:YOUR_ACCOUNT_ID:function:AMAZON_CHIME_PRIVATE_BOT_LAMBDA_FUNCTION_NAME/invocations 25 | passthroughBehavior: when_no_match 26 | httpMethod: POST 27 | type: aws_proxy 28 | options: 29 | consumes: 30 | - application/json 31 | produces: 32 | - application/json 33 | responses: 34 | 200: 35 | description: 200 response 36 | schema: 37 | $ref: "#/definitions/Empty" 38 | headers: 39 | Access-Control-Allow-Origin: 40 | type: string 41 | Access-Control-Allow-Methods: 42 | type: string 43 | Access-Control-Allow-Headers: 44 | type: string 45 | x-amazon-apigateway-integration: 46 | contentHandling: CONVERT_TO_TEXT 47 | responses: 48 | default: 49 | statusCode: 200 50 | responseParameters: 51 | method.response.header.Access-Control-Allow-Methods: "'POST'" 52 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'" 53 | method.response.header.Access-Control-Allow-Origin: "'https://example.com'" 54 | passthroughBehavior: when_no_match 55 | requestTemplates: 56 | application/json: "{\"statusCode\": 200}" 57 | type: mock 58 | x-amazon-apigateway-binary-media-types: 59 | - '*/*' 60 | definitions: 61 | Empty: 62 | type: object 63 | -------------------------------------------------------------------------------- /examples/private-bot/cloudformation.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | --- 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | Transform: AWS::Serverless-2016-10-31 6 | Description: Chime private bot demo process bot logic 7 | Resources: 8 | PrivateBotApiGatewayApi: 9 | Type: AWS::Serverless::Api 10 | Properties: 11 | DefinitionUri: ./simple-proxy-api.yaml 12 | StageName: Demo 13 | Variables: 14 | PrivateBotLambdaFuncionName: !Ref AMAZON_CHIME_PRIVATE_BOT_LAMBDA_FUNCTION_NAME 15 | 16 | PrivateBotLambdaExecutionRole: 17 | Type: AWS::IAM::Role 18 | Properties: 19 | AssumeRolePolicyDocument: 20 | Version: '2012-10-17' 21 | Statement: 22 | Effect: Allow 23 | Principal: 24 | Service: lambda.amazonaws.com 25 | Action: sts:AssumeRole 26 | Path: "/" 27 | Policies: 28 | - PolicyName: root 29 | PolicyDocument: 30 | Version: '2012-10-17' 31 | Statement: 32 | - Effect: Allow 33 | Action: 34 | - logs:CreateLogGroup 35 | - logs:CreateLogStream 36 | - logs:PutLogEvents 37 | Resource: arn:aws:logs:*:*:* 38 | 39 | PrivateBotLambdaApiGatewayExecutionPermission: 40 | Type: AWS::Lambda::Permission 41 | Properties: 42 | Action: lambda:InvokeFunction 43 | FunctionName: !GetAtt AMAZON_CHIME_PRIVATE_BOT_LAMBDA_FUNCTION_NAME.Arn 44 | Principal: apigateway.amazonaws.com 45 | SourceArn: !Join 46 | - '' 47 | - - 'arn:aws:execute-api:' 48 | - !Ref AWS::Region 49 | - ":" 50 | - !Ref AWS::AccountId 51 | - ":" 52 | - !Ref PrivateBotApiGatewayApi 53 | - "/*/*" 54 | 55 | AMAZON_CHIME_PRIVATE_BOT_LAMBDA_FUNCTION_NAME: 56 | Type: AWS::Serverless::Function 57 | Properties: 58 | FunctionName: AMAZON_CHIME_PRIVATE_BOT_LAMBDA_FUNCTION_NAME 59 | Description: Example lambda to handle private bot operation 60 | CodeUri: ./ 61 | Handler: index.handler 62 | MemorySize: 256 63 | Role: !GetAtt PrivateBotLambdaExecutionRole.Arn 64 | Runtime: nodejs8.10 65 | Timeout: 30 66 | -------------------------------------------------------------------------------- /examples/inbound-call/cloudformation.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | --- 4 | AWSTemplateFormatVersion: '2010-09-09' 5 | Transform: AWS::Serverless-2016-10-31 6 | Description: Chime private bot demo to forward Amazon connect call to Chime 7 | Resources: 8 | InboundLambdaExecutionRole: 9 | Type: AWS::IAM::Role 10 | Properties: 11 | AssumeRolePolicyDocument: 12 | Version: '2012-10-17' 13 | Statement: 14 | Effect: Allow 15 | Principal: 16 | Service: lambda.amazonaws.com 17 | Action: sts:AssumeRole 18 | Path: "/" 19 | Policies: 20 | - PolicyName: root 21 | PolicyDocument: 22 | Version: '2012-10-17' 23 | Statement: 24 | - Effect: Allow 25 | Action: 26 | - logs:CreateLogGroup 27 | - logs:CreateLogStream 28 | - logs:PutLogEvents 29 | Resource: arn:aws:logs:*:*:* 30 | - PolicyName: ChimeReadOnly 31 | PolicyDocument: 32 | Version: '2012-10-17' 33 | Statement: 34 | - Effect: Allow 35 | Action: 36 | - chime:ListAccounts 37 | - chime:ListUsers 38 | Resource: "*" 39 | 40 | AMAZON_CHIME_INBOUND_CALL_LAMBDA_FUNCTION_NAME: 41 | Type: AWS::Serverless::Function 42 | Properties: 43 | FunctionName: AMAZON_CHIME_INBOUND_CALL_LAMBDA_FUNCTION_NAME 44 | Description: Example lambda to forward inbound call 45 | CodeUri: ./ 46 | Handler: index.handler 47 | MemorySize: 256 48 | Role: !GetAtt InboundLambdaExecutionRole.Arn 49 | Runtime: nodejs8.10 50 | Timeout: 30 51 | Environment: 52 | Variables: 53 | IncomingWebhook: AMAZON_CHIME_INBOUND_CALL_LAMBDA_BOT_INCOMING_WEBHOOK 54 | Email: AMAZON_CHIME_INBOUND_CALL_LAMBDA_USER_EMAIL 55 | 56 | AllowInvokeByConnect: 57 | Type: AWS::Lambda::Permission 58 | Properties: 59 | Action: 'lambda:InvokeFunction' 60 | FunctionName: 61 | Ref: AMAZON_CHIME_INBOUND_CALL_LAMBDA_FUNCTION_NAME 62 | Principal: 'connect.amazonaws.com' 63 | SourceAccount: 'YOUR_ACCOUNT_ID' 64 | SourceArn: AMAZON_CHIME_INBOUND_CALL_CONNECT_ARN 65 | -------------------------------------------------------------------------------- /examples/inbound-call/scripts/configure.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | const modifyFiles = require('./utils').modifyFiles 5 | const yargs = require('yargs') 6 | const argv = yargs.options({ 7 | accountId: { 8 | demand: true, 9 | alias: 'account-id', 10 | describe: 'aws account id', 11 | string: true 12 | }, 13 | bucketName: { 14 | demand: true, 15 | alias: 'bucket-name', 16 | describe: 'S3 bucket used to deploy lambda', 17 | string: true 18 | }, 19 | functionName: { 20 | alias: 'function-name', 21 | describe: 'Lambda function name', 22 | string: true, 23 | default: 'AmazonChimeDemoInboundCall' 24 | }, 25 | botIncomingWebhook: { 26 | demand: true, 27 | alias: 'bot-incoming-webhook', 28 | describe: 'Incoming webhook url of bot', 29 | string: true 30 | }, 31 | userEmail: { 32 | demand: true, 33 | alias: 'user-email', 34 | describe: 'Chime user email Lambda use to look up business calling number', 35 | string: true, 36 | }, 37 | connectArn: { 38 | demand: true, 39 | alias: 'connect-arn', 40 | describe: 'Amazon Connect arn', 41 | string: true 42 | }, 43 | cloudFormationStackName: { 44 | alias: 'cloudformation-stack-name', 45 | describe: 'CloudFormation stack name', 46 | string: true, 47 | default: 'AmazonChimeInboundCallLambdaStack' 48 | }, 49 | connectArn: { 50 | demand: true, 51 | alias: 'connect-arn', 52 | describe: 'Amazon Connect arn', 53 | string: true 54 | }, 55 | region: { 56 | alias: 'region', 57 | describe: 'AWS Region demo uses', 58 | string: true, 59 | default: 'us-east-1' 60 | } 61 | }).argv; 62 | 63 | modifyFiles(['./package.json', './cloudformation.yaml'], [{ 64 | regexp: /YOUR_ACCOUNT_ID/g, 65 | replacement: argv.accountId 66 | }, { 67 | regexp: /YOUR_AWS_REGION/g, 68 | replacement: argv.region 69 | }, { 70 | regexp: /AMAZON_CHIME_INBOUND_CALL_LAMBDA_DEPLOY_BUCKET_NAME/g, 71 | replacement: argv.bucketName 72 | }, { 73 | regexp: /AMAZON_CHIME_INBOUND_CALL_LAMBDA_FUNCTION_NAME/g, 74 | replacement: argv.functionName 75 | }, { 76 | regexp: /AMAZON_CHIME_INBOUND_CALL_LAMBDA_BOT_INCOMING_WEBHOOK/g, 77 | replacement: argv.botIncomingWebhook 78 | }, { 79 | regexp: /AMAZON_CHIME_INBOUND_CALL_LAMBDA_USER_EMAIL/g, 80 | replacement: argv.userEmail 81 | }, { 82 | regexp: /AMAZON_CHIME_INBOUND_CALL_CONNECT_ARN/g, 83 | replacement: argv.connectArn 84 | }, { 85 | regexp: /AMAZON_CHIME_INBOUND_CALL_LAMBDA_STACK_NAME/g, 86 | replacement: argv.cloudFormationStackName 87 | }]) 88 | -------------------------------------------------------------------------------- /examples/private-bot/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | const util = require('util'); 5 | const BotCommandHanlder = require('./bot_command_handler'); 6 | 7 | const EVENT_TYPE_CHIME_BOT_OUTBOUND_ENDPOINT_VERIFICATION = "HTTPSEndpointVerification"; 8 | const EVENT_TYPE_MENTION = "Mention"; 9 | const EVENT_TYPE_INVITE = "Invite"; 10 | 11 | const handle = async (event, context) => { 12 | try { 13 | const payload = getRequestBody(event); 14 | if (!payload) { 15 | const error = new Error("Payload is empty"); 16 | error.statusCode = 400; 17 | throw error; 18 | } 19 | switch (payload.EventType) { 20 | case EVENT_TYPE_CHIME_BOT_OUTBOUND_ENDPOINT_VERIFICATION: 21 | return handleBotVerification(payload.Challenge); 22 | break; 23 | case EVENT_TYPE_MENTION: 24 | await handleMention(payload); 25 | break; 26 | case EVENT_TYPE_INVITE: 27 | await handleInvite(payload); 28 | break; 29 | default: 30 | const error = new Error("Invalid event type"); 31 | error.statusCode = 400; 32 | throw error; 33 | } 34 | return { 35 | statusCode: 200 36 | } 37 | } catch (error) { 38 | console.log(`Failed with error = ${error}, event = ${JSON.stringify(event)}`); 39 | return { 40 | statusCode: error.statusCode || 500, 41 | error: `${error}` 42 | }; 43 | } 44 | }; 45 | 46 | const getRequestBody = (event) => { 47 | try { 48 | if (!event || !event.body) { 49 | return event; 50 | } 51 | const payload = Buffer.from(event.body, event.isBase64Encoded ? 'base64' : null).toString(); 52 | return JSON.parse(payload); 53 | } catch (error) { 54 | console.log(`Failed to parse body, error = ${error}, event = ${util.inspect(event)}`); 55 | return null; 56 | } 57 | }; 58 | 59 | const handleBotVerification = (securityToken) => { 60 | return { 61 | statusCode: 200, 62 | body: JSON.stringify({ 63 | Challenge: securityToken 64 | }), 65 | headers: { 66 | 'Content-Type': 'application/json', 67 | } 68 | }; 69 | }; 70 | 71 | const handleInvite = async (event) => { 72 | if (event && event.InboundHttpsEndpoint && event.InboundHttpsEndpoint.Url) { 73 | await BotCommandHanlder.notifyRoom(event.InboundHttpsEndpoint.Url, `Hello, welcome to the room, my webhook is ${event.InboundHttpsEndpoint.Url}`); 74 | } 75 | } 76 | 77 | const handleMention = async (event) => { 78 | await BotCommandHanlder.handle(event); 79 | }; 80 | 81 | exports.handler = handle; 82 | -------------------------------------------------------------------------------- /examples/inbound-call/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | const requestPromise = require('request-promise'); 5 | const AWS = require('aws-sdk'); 6 | 7 | const CUSTOMER_NAME_KEY = 'CustomerName'; 8 | const PRODUCT_NAME_KEY = 'ProductName'; 9 | const ZIP_CODE_KEY = 'CustomerZipCode'; 10 | 11 | const chime = new AWS.Chime({endpoint: 'service.chime.aws.amazon.com.'}); 12 | 13 | const handleInboundCall = async (event, context) => { 14 | try { 15 | const email = process.env.Email; 16 | const incomingwebhook = process.env.IncomingWebhook; 17 | const user = await getUser(email); 18 | const notificationMessage = buildNotification(event, user); 19 | await notifyRoom(incomingwebhook, notificationMessage); 20 | return { 21 | 'OutboundNumber': user.PrimaryProvisionedNumber 22 | }; 23 | } catch (err) { 24 | console.log(`Cannot forward call to business calling number, error = ${err}`); 25 | throw err; 26 | } 27 | }; 28 | 29 | const buildNotification = (event, user) => { 30 | let customerName = getAttribute(event, CUSTOMER_NAME_KEY); 31 | const productName = getAttribute(event, PRODUCT_NAME_KEY); 32 | const zipCode = getAttribute(event, ZIP_CODE_KEY); 33 | const customerPhoneNumber = getContactData(event).CustomerEndpoint.Address; 34 | return ` 35 | Received customer call, redirecting to <@${user.UserId}|${user.DisplayName}> 36 | Name: ${capitalize(customerName)} 37 | Product interested: ${capitalize(productName)} 38 | Zip code: ${zipCode} 39 | Phone Number: ${customerPhoneNumber}` 40 | } 41 | 42 | const capitalize = (input) => { 43 | return input.charAt(0).toUpperCase() + input.slice(1); 44 | } 45 | 46 | const getContactData = (event) => event.Details.ContactData; 47 | 48 | const getAttribute = (event, attributeName) => { 49 | try { 50 | const contactData = getContactData(event); 51 | return contactData.Attributes[attributeName]; 52 | } catch (error) { 53 | console.log(`Cannot find ${attributeName}, error = ${error}`); 54 | throw new Error(`Cannot find ${attributeName}`); 55 | } 56 | }; 57 | 58 | const getUserAccountId = async (email) => { 59 | if (!email) { 60 | throw new Error('User email cannot be empty'); 61 | } 62 | const response = await chime.listAccounts({UserEmail: email}).promise(); 63 | if (!response || !response.Accounts || response.Accounts.length < 1) { 64 | throw new Error(`Cannnot find account for email ${email}`); 65 | } 66 | return response.Accounts[0].AccountId; 67 | }; 68 | 69 | const getUser = async (email) => { 70 | const accountId = await getUserAccountId(email); 71 | if (!accountId) { 72 | throw new Error('User account id cannot be empty'); 73 | } 74 | const params = { 75 | AccountId: accountId, /* required */ 76 | UserEmail: email /* required */ 77 | }; 78 | const response = await chime.listUsers(params).promise(); 79 | if (!response || !response.Users || response.Users.length < 1) { 80 | throw new Error(`Cannnot find user for email ${email}, accountId = ${accountId}`); 81 | } 82 | return response.Users[0]; 83 | } 84 | 85 | const notifyRoom = (webhook, messageBody) => { 86 | return requestPromise({ 87 | method: 'POST', 88 | uri: webhook, 89 | body: { 90 | Content: messageBody 91 | }, 92 | json: true 93 | }); 94 | }; 95 | 96 | exports.handler = handleInboundCall; 97 | -------------------------------------------------------------------------------- /examples/private-bot/README.md: -------------------------------------------------------------------------------- 1 | ## Example 2 | 3 | The `example` directory includes a [Swagger file](http://swagger.io/specification/), [CloudFormation template](https://aws.amazon.com/cloudformation/aws-cloudformation-templates/) with [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model), and helper scripts to help you set up and manage your application. 4 | 5 | ### Steps for running the example 6 | This guide assumes you have already [set up an AWS account](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/AboutAWSAccounts.html) and have the latest version of the [AWS CLI](https://aws.amazon.com/cli/) installed. 7 | 8 | Configure AWS CLI with AWS credentials. 9 | 10 | #### Create Lambda function to handle bot event 11 | 1. From your preferred project directory: `git clone https://github.com/aws-samples/amazon-chime-private-bot-demo.git && cd amazon-chime-private-bot-demo/examples/private-bot`. 12 | 2. Run `npm run config -- --account-id= --bucket-name= --function-name= --cloudformation-stack-name=` to configure the example. 13 | 14 | eg. `npm run config -- --account-id="123456789012" --bucket-name="my-unique-bucket" --function-name="myFunction" --cloudformation-stack-name="AmazonChimePrivateBotLambdaStack"` 15 | 16 | This modifies `package.json` and `cloudformation.yaml` with your account ID, bucket, region and function name (region defaults to `us-east-1` and function name defaults to `AmazonChimePrivateBot`). If the bucket you specify does not yet exist, the next step will create it for you. This step modifies the existing files in-place; if you wish to make changes to these settings, you will need to modify `package.json` and `cloudformation.yaml` manually. 17 | 3. Run `npm run setup` - this installs the node dependencies, creates an S3 bucket (if it does not already exist), packages and deploys example lambda to handle bot event and create API Gateway to provides an HTTP endpoint for Chime to invoke Lambda function. 18 | 4. Copy http url from [API Gateway AWS console](https://console.aws.amazon.com/apigateway/home?region=us-east-1#/apis//stages/), http url will be used when setup bot. 19 | 20 | #### Setup Chime Bot 21 | 1. Create Chime Bot through AWS CLI 22 | eg. `aws chime create-bot --account-id "" --display-name "" --domain ""`` 23 | 24 | botId from Response is used in the next step to put events configuration 25 | bot email is used to invite bot to chat room. 26 | 27 | e.g. 28 | ` "Bot": { 29 | "CreatedTimestamp": "", 30 | "DisplayName": "ExampleBot", 31 | "Disabled": false, 32 | "UserId": "", 33 | "BotId": "", 34 | "UpdatedTimestamp": "", 35 | "BotType": "ChatBot", 36 | "SecurityToken": "", 37 | "BotEmail": "" 38 | ` 39 | 40 | 2. Setup Chime bot event configuration to use http endpoint created by Chime private bot, see private bot example. 41 | eg. `aws chime put-events-configuration --account-id "" --bot-id "" --outbound-events-https-endpoint ""` 42 | 3. Invite Chime bot to a chat room, bot will show incoming webhook url to use later. 43 | 44 | #### Interact with Bot via @mention message 45 | - Help: `@ help` 46 | - check-inventory: `@ check-inventory help` 47 | - find-in-store: `@ find-in-store help` 48 | 49 | #### Clean up: 50 | 1. Run `npm run deconfig`, it resets project parameters. 51 | 52 | ## Node.js version 53 | 54 | This example was written against Node.js version 8.10 55 | -------------------------------------------------------------------------------- /examples/inbound-call/sample_contact_flow: -------------------------------------------------------------------------------- 1 | {"modules":[{"id":"edcfabf8-8eba-4a99-80a6-b01d65f8a605","type":"GetUserInput","branches":[{"condition":"NoMatch","transition":"f899654a-d9e7-49f4-98d8-d4011c9ceffb"},{"condition":"Error","transition":"b9b933ca-6ed2-4d8b-b9e1-8d7a4f81a011"}],"parameters":[{"name":"Text","value":"Hello, this is Outdoor Store. How can I help you?","namespace":null},{"name":"TextToSpeechType","value":"text"},{"name":"BotName","value":"OutdoorStore"},{"name":"BotAlias","value":"$LATEST"},{"name":"BotRegion","value":"us-east-1"}],"metadata":{"position":{"x":220,"y":20},"conditionMetadata":[],"useDynamic":false,"dynamicMetadata":{}},"target":"Lex"},{"id":"b9b933ca-6ed2-4d8b-b9e1-8d7a4f81a011","type":"PlayPrompt","branches":[{"condition":"Success","transition":"a450fc22-d853-4a81-9396-23f3e290808e"}],"parameters":[{"name":"Text","value":"Sorry, I can not understand you, bye bye.","namespace":null},{"name":"TextToSpeechType","value":"text"}],"metadata":{"position":{"x":140,"y":700},"useDynamic":false}},{"id":"a450fc22-d853-4a81-9396-23f3e290808e","type":"Disconnect","branches":[],"parameters":[],"metadata":{"position":{"x":820,"y":400}}},{"id":"39d21dda-d024-4ea6-ad02-9d3f48078653","type":"PlayPrompt","branches":[{"condition":"Success","transition":"a450fc22-d853-4a81-9396-23f3e290808e"}],"parameters":[{"name":"Text","value":"Sorry, the store is busy, please try again later. Bye Bye","namespace":null},{"name":"TextToSpeechType","value":"text"}],"metadata":{"position":{"x":500,"y":720},"useDynamic":false}},{"id":"de9eda1b-23fc-4653-80de-c47170012157","type":"Transfer","branches":[{"condition":"Success","transition":"a450fc22-d853-4a81-9396-23f3e290808e"},{"condition":"CallFailure","transition":"39d21dda-d024-4ea6-ad02-9d3f48078653"},{"condition":"Timeout","transition":"39d21dda-d024-4ea6-ad02-9d3f48078653"},{"condition":"Error","transition":"39d21dda-d024-4ea6-ad02-9d3f48078653"}],"parameters":[{"name":"TimeLimit","value":"30"},{"name":"BlindTransfer","value":false},{"name":"PhoneNumber","value":"OutboundNumber","namespace":"External"}],"metadata":{"position":{"x":500,"y":400}},"target":"PhoneNumber"},{"id":"53e4e001-fc27-4f80-bcfe-1e404f0cede7","type":"PlayPrompt","branches":[{"condition":"Success","transition":"de9eda1b-23fc-4653-80de-c47170012157"}],"parameters":[{"name":"Text","value":"Thank you $.Attributes.CustomerName. directed to the camping department at your local store $.Attributes.LocalStoreAddress","namespace":null},{"name":"TextToSpeechType","value":"text"}],"metadata":{"position":{"x":220,"y":400},"useDynamic":false}},{"id":"f899654a-d9e7-49f4-98d8-d4011c9ceffb","type":"SetAttributes","branches":[{"condition":"Success","transition":"4222485a-1320-4337-91ca-32d079890d0d"},{"condition":"Error","transition":"b9b933ca-6ed2-4d8b-b9e1-8d7a4f81a011"}],"parameters":[{"name":"Attribute","value":"Product","key":"ProductName","namespace":"Lex.Slots"},{"name":"Attribute","value":"Name","key":"CustomerName","namespace":"Lex.Slots"},{"name":"Attribute","value":"ZipCode","key":"CustomerZipCode","namespace":"Lex.Slots"}],"metadata":{"position":{"x":500,"y":20}}},{"id":"4222485a-1320-4337-91ca-32d079890d0d","type":"InvokeExternalResource","branches":[{"condition":"Success","transition":"53e4e001-fc27-4f80-bcfe-1e404f0cede7"},{"condition":"Error","transition":"b9b933ca-6ed2-4d8b-b9e1-8d7a4f81a011"}],"parameters":[{"name":"FunctionArn","value":"arn:aws:lambda:us-east-1:849454618968:function:DemoInboundCallLambda","namespace":null},{"name":"TimeLimit","value":"3"}],"metadata":{"position":{"x":740,"y":20},"dynamicMetadata":{},"useDynamic":false},"target":"Lambda"}],"version":"1","type":"contactFlow","start":"edcfabf8-8eba-4a99-80a6-b01d65f8a605","metadata":{"entryPointPosition":{"x":20,"y":20},"snapToGrid":true,"name":"Outdoor Store","description":null,"type":"contactFlow","status":"published","hash":"0b024064f4d0e43d9dea5920281541a01e6e7d85a481b40b814f56536b8902a5"}} 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws-samples/amazon-chime-private-bot-demo/issues), or [recently closed](https://github.com/aws-samples/amazon-chime-private-bot-demo/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws-samples/amazon-chime-private-bot-demo/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws-samples/amazon-chime-private-bot-demo/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /examples/inbound-call/README.md: -------------------------------------------------------------------------------- 1 | ## Example 2 | 3 | This example creates a serverless application for an outdoor store contact center. Amazon Connect contact flow integrates Lex bot to collect basic customer information. Amazon Connect then invoke a lambda function look up sales associate's business calling number. At the end Amazon Connect forwards the call to corresponding sales associate. 4 | 5 | The `example` directory includes a [Swagger file](http://swagger.io/specification/), [CloudFormation template](https://aws.amazon.com/cloudformation/aws-cloudformation-templates/) with [Serverless Application Model (SAM)](https://github.com/awslabs/serverless-application-model), and helper scripts to help you set up and manage your application. 6 | 7 | ### Steps for running the example 8 | This guide assumes you have already [set up an AWS account](http://docs.aws.amazon.com/AmazonSimpleDB/latest/DeveloperGuide/AboutAWSAccounts.html) and have the latest version of the [AWS CLI](https://aws.amazon.com/cli/) installed. 9 | 10 | Configure AWS CLI with AWS credentials. 11 | 12 | #### Setup Chime Bot 13 | 1. Create Chime Bot through AWS CLI 14 | eg. `aws chime create-bot --account-id "" --display-name "" --domain ""`` 15 | 16 | botId from Response is used in the next step to put events configuration. BotEmail is used to invite bot to chat room. 17 | 18 | e.g. 19 | ` "Bot": { 20 | "CreatedTimestamp": "", 21 | "DisplayName": "ExampleBot", 22 | "Disabled": false, 23 | "UserId": "", 24 | "BotId": "", 25 | "UpdatedTimestamp": "", 26 | "BotType": "ChatBot", 27 | "SecurityToken": "", 28 | "BotEmail": "" 29 | ` 30 | 31 | 2. Setup Chime bot event configuration to use http endpoint created by Chime private bot, see private bot example. 32 | eg. `aws chime put-events-configuration --account-id "" --bot-id "" --outbound-events-https-endpoint ""` 33 | 3. Invite Chime bot to a chat room, bot will show incoming webhook url to use later. 34 | 35 | #### Setup Amazon Connect and Lex bot 36 | 1. Create Amazon Lex bot to collect customer basic information, [How to Create a Custom Amazon Lex Bot](https://docs.aws.amazon.com/lex/latest/dg/getting-started-ex2.html) 37 | - Product name customer wants to buy 38 | - Customer name 39 | - Customer zip code 40 | 2. Create Amazon Connect instance and provision a phone number. 41 | 3. Create Contact flows including Lex bot, invoke Lambda and transfer to Chime business calling number 42 | 43 | Note: Import sample Amazon Connect contact flow by using sample file provided sample_contact_flow [How to import Amazon Connect contact flow](https://docs.aws.amazon.com/connect/latest/userguide/contactflow.html#contact-flow-import-export). 44 | 45 | #### Create Lambda function 46 | 1. From your preferred project directory: `git clone https://github.com/aws-samples/amazon-chime-private-bot-demo.git && cd amazon-chime-private-bot-demo/examples/inbound-call`. 47 | 2. Run `npm run config -- --account-id= --bucket-name= --function-name= --user-email= --bot-incoming-webhook="" --connect-arn="" --cloudformation-stack-name=""` to configure the example. 48 | 49 | eg. `npm run config -- --account-id="123456789012" --bucket-name="my-unique-bucket" --function-name="myFunction" --user-email="example@email.com" --bot-incoming-webhook="http://example.incomingwebhook" --connect-arn="arn:aws:lambda:us-east-1:accountId:arn" --cloudformation-stack-name="AmazonChimeInboundCallLambdaStack"` 50 | 51 | This modifies `package.json` and `cloudformation.yaml` with your account ID, bucket, region and function name (region defaults to `us-east-1` and function name defaults to `AmazonChimeDemoInboundCall`). If the bucket you specify does not yet exist, the next step will create it for you. This step modifies the existing files in-place; if you wish to make changes to these settings, you will need to modify `package.json` and `cloudformation.yaml` manually. 52 | 53 | 3. Run `npm run setup` - this installs the node dependencies, creates an S3 bucket (if it does not already exist), packages and deploys example inbound call forwarding application to AWS Lambda and allow Amazon Connect to invoke lambda. 54 | 55 | #### Update Amazon Connect contact flow Lambda 56 | 1. Update Amazon Connect with Lambda function created in previous step. 57 | 58 | #### Clean up: 59 | 1. Run `npm run deconfig`, it resets project parameters. 60 | 61 | ## Node.js version 62 | 63 | This example was written against Node.js version 8.10 64 | -------------------------------------------------------------------------------- /examples/private-bot/bot_command_handler.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: MIT-0 3 | 4 | const requestPromise = require('request-promise'); 5 | 6 | const COMMAND_MESSAGE_REG_EXP = /^@\S+?\s+(\S+)\s*(.*)/i; 7 | 8 | const HELP_MESSAGE = `Private bot available commands: 9 | - check-inventory: example @ check-inventory product:Funtainer brand:Thermos 10 | - find-in-store: @ find-in-store sku:therfun034dl` 11 | 12 | const PRODUCT_CATALOGUE_ATTRIBUTES = [ 13 | 'product', 'brand', 'name', 'price', 'color', 'sku', 'availability', 'link' 14 | ]; 15 | 16 | const PRODUCT_CATALOGUE = [ 17 | { 18 | product: 'Funtainer', 19 | brand: 'Thermos', 20 | name: 'Thermos Funtainer 10 ounce', 21 | price: '$10.99', 22 | color: 'Yellow', 23 | sku: 'therfun034ds', 24 | availability: 'In-stock', 25 | link: '[https://example.com/products/thermos-funtainer-10oz](https://www.amazon.com/Thermos-Funtainer-Ounce-Food-Blue/dp/B00CBFAE6W/ref=sr_1_3?keywords=Thermos+Funtainer+10+ounce&qid=1557203036&s=gateway&sr=8-3)' 26 | }, 27 | { 28 | product: 'Funtainer', 29 | brand: 'Thermos', 30 | name: 'Thermos Funtainer 18 ounce', 31 | price: '$19.99', 32 | color: 'Purple', 33 | sku: 'therfun034dl', 34 | availability: 'Out-of-stock', 35 | link: '[https://example.com/products/thermos-funtainer-18oz](https://www.amazon.com/dp/B002PY7AYS/ref=sspa_dk_detail_0?psc=1&pd_rd_i=B002PY7AYS&pd_rd_w=oRGGo&pf_rd_p=8a8f3917-7900-4ce8-ad90-adf0d53c0985&pd_rd_wg=aKexj&pf_rd_r=BH2S0AP2S6KF4JY3KKQ7&pd_rd_r=691dbf90-7081-11e9-bb1d-335c9152e117)' 36 | } 37 | ]; 38 | 39 | const INVENTORYS_ATTRIBUTES = [ 40 | 'sku', 'store', 'address', 'hours', 'phone', 'zipcode' 41 | ] 42 | 43 | const INVENTORYS = [ 44 | { 45 | sku: 'therfun034dl', 46 | store: 'Outdoor Store', 47 | address: '2111 7th Ave, Seattle, WA 98121', 48 | hours: '10:00AM-9:00PM', 49 | phone: '+1(206)555-1234', 50 | zipcode: '98121' 51 | }, 52 | { 53 | sku: 'therfun034dl', 54 | store: 'Outdoor Store', 55 | address: '399 4th St, Edmunds, WA 98020', 56 | hours: '11:00AM-10:00PM', 57 | phone: '+1(425)618-0066', 58 | zipcode: '98020' 59 | } 60 | ] 61 | 62 | const handle = async (event) => { 63 | const incomingWebhook = event.InboundHttpsEndpoint.Url; 64 | const message = replyMessage(event.Message); 65 | return await notifyRoom(incomingWebhook, message); 66 | } 67 | 68 | const replyMessage = (event) => { 69 | const commandParts = COMMAND_MESSAGE_REG_EXP.exec(event); 70 | if (!commandParts) { 71 | return HELP_MESSAGE; 72 | } 73 | const commandName = commandParts[1]; 74 | const commandPayload = commandParts[2]; 75 | return processCommand(commandName, commandPayload); 76 | } 77 | 78 | const checkInventory = (options) => { 79 | if (options == 'help') { 80 | return `Example: @ check-inventory product:Funtainer brand:Thermos\nSupported attributes: ${JSON.stringify(PRODUCT_CATALOGUE_ATTRIBUTES)}`; 81 | } 82 | const result = query(options, PRODUCT_CATALOGUE, PRODUCT_CATALOGUE_ATTRIBUTES); 83 | return result.length > 0 ? `/md\n## I recommend these products based on criteria: ${options}\n${prettify(result, PRODUCT_CATALOGUE_ATTRIBUTES)}` : `Requested product cannot be found in cataloge`; 84 | } 85 | 86 | const findInStore = (options) => { 87 | if (options == 'help') { 88 | return `Example: @ find-in-store sku:therfun034dl\nSupported attributes: ${JSON.stringify(PRODUCT_CATALOGUE_ATTRIBUTES)}`; 89 | } 90 | const result = query(options, INVENTORYS, INVENTORYS_ATTRIBUTES); 91 | return result.length > 0 ? `/md\n## I found inventory in these stores based on criteria: ${options}\n${prettify(result, INVENTORYS_ATTRIBUTES)}` : `Requested product cannot be found in any stores`; 92 | } 93 | 94 | const prettify = (items, attributes) => { 95 | let output = attributes.join('|') + '\n'; 96 | output += Array(attributes.length).fill('---').join('|') + '\n'; 97 | for (item of items) { 98 | const row = []; 99 | for (attribute of attributes) { 100 | row.push(item[attribute]); 101 | } 102 | output += row.join('|') + '\n'; 103 | } 104 | return output; 105 | } 106 | 107 | const processCommand = (commandName, inputJson) => { 108 | switch (commandName) { 109 | case 'check-inventory': 110 | return checkInventory(inputJson); 111 | case 'find-in-store': 112 | return findInStore(inputJson); 113 | default: 114 | return `Command ${commandName}, not exist.\n` + HELP_MESSAGE; 115 | } 116 | } 117 | 118 | const getAttributeFromQueryExpression = (queryExpression, attribute) => { 119 | const regExp = new RegExp(`.*${attribute}:\\s*(\\S+)`, 'i'); 120 | const parts = regExp.exec(queryExpression); 121 | return parts ? parts[1] : null; 122 | } 123 | 124 | const query = (queryExpression, table, attributes) => { 125 | return table.filter((item) => { 126 | let match = 0; 127 | for (attribute of attributes) { 128 | const queryValue = getAttributeFromQueryExpression(queryExpression, attribute); 129 | if (queryValue && (queryValue != item[attribute])) { 130 | return false; 131 | } 132 | match += queryValue ? 1: 0; 133 | } 134 | return match != 0; 135 | }); 136 | } 137 | 138 | const notifyRoom = (webhook, messageBody) => { 139 | return requestPromise({ 140 | method: 'POST', 141 | uri: webhook, 142 | body: { 143 | Content: messageBody 144 | }, 145 | json: true 146 | }); 147 | }; 148 | 149 | module.exports = { 150 | handle: handle, 151 | notifyRoom: notifyRoom 152 | }; 153 | --------------------------------------------------------------------------------