Skip to content

Commit

Permalink
add Activityfactory (#1401)
Browse files Browse the repository at this point in the history
* add activityfactory

* retrigger CI

* fix comments

* remove unused code

* modify text

* add some comments

* fix param type
  • Loading branch information
Danieladu committed Dec 4, 2019
1 parent 32d8059 commit ae20476
Show file tree
Hide file tree
Showing 5 changed files with 1,071 additions and 1 deletion.
3 changes: 2 additions & 1 deletion libraries/botbuilder-lg/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"antlr4ts": "0.5.0-alpha.1",
"botframework-expressions": "4.1.6",
"lodash": "^4.17.11",
"uuid": "^3.3.3"
"uuid": "^3.3.3",
"botbuilder-core": "4.1.6"
},
"devDependencies": {
"@types/mocha": "^5.2.5",
Expand Down
357 changes: 357 additions & 0 deletions libraries/botbuilder-lg/src/activityFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,357 @@
/**
* @module botbuilder-lg
*/
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/

import { Activity, SuggestedActions, Attachment, ActivityTypes, ActionTypes, CardAction } from 'botframework-schema';
import { MessageFactory, CardFactory } from 'botbuilder-core';

/**
* The ActivityFactory
* to generate text and then uses simple markdown semantics like chatdown to create Activity.
*/
export class ActivityFactory {
private static genericCardTypeMapping: Map<string, string> = new Map<string, string>
([
[ 'herocard', CardFactory.contentTypes.heroCard ],
[ 'thumbnailcard', CardFactory.contentTypes.thumbnailCard ],
[ 'audiocard', CardFactory.contentTypes.audioCard ],
[ 'videocard', CardFactory.contentTypes.videoCard ],
[ 'animationcard', CardFactory.contentTypes.animationCard ],
[ 'signincard', CardFactory.contentTypes.signinCard ],
[ 'oauthcard', CardFactory.contentTypes.oauthCard ],
]);

/**
* Generate the activity.
* @param lgResult string result from languageGenerator.
*/
public static CreateActivity(lgResult: any): Partial<Activity> {
if (typeof lgResult === 'string') {
return this.buildActivityFromText(lgResult.trim());
}

return this.buildActivityFromLGStructuredResult(lgResult);
}

/**
* Given a lg result, create a text activity. This method will create a MessageActivity from text.
* @param text lg text output.
*/
private static buildActivityFromText(text: string): Partial<Activity> {
return MessageFactory.text(text, text);
}

/**
* Given a structured lg result, create an activity. This method will create an MessageActivity from object
* @param lgValue lg output.
*/
private static buildActivityFromLGStructuredResult(lgValue: any): Partial<Activity> {
let activity: Partial<Activity> = {};

const type: string = this.getStructureType(lgValue);
if (ActivityFactory.genericCardTypeMapping.has(type)) {
const attachment: Attachment = this.getAttachment(lgValue);
if (attachment !== undefined) {
activity = MessageFactory.attachment(attachment);
} else {
throw new Error(`'${ lgValue }' is not an attachment format.`);
}
} else if (type === 'activity') {
activity = this.buildActivityFromObject(lgValue);
} else {
throw new Error(`type ${ type } is not support currently.`);
}

return activity;
}

private static buildActivityFromObject(activityValue: any): Partial<Activity> {
let activity: Partial<Activity> = {};

if ('type' in activityValue && activityValue.type === 'event') {
activity = this.buildEventActivity(activityValue);
} else {
activity = this.buildMessageActivity(activityValue);
}

return activity;
}

private static buildEventActivity(eventValue: any): Partial<Activity> {
let activity: Partial<Activity> = { type: ActivityTypes.Event };
for (const item of Object.keys(eventValue)) {
const property: string = item.trim();
const value: any = eventValue[item];
switch (property.toLowerCase()) {
case 'name':
activity.name = value.toString();
break;
case 'value':
activity.value = value.toString();
break;
default:
activity[property] = value;
break;
}
}

return activity;
}

private static buildMessageActivity(messageValue: any): Partial<Activity> {
let activity: Partial<Activity> = { type: ActivityTypes.Message };
for (const key of Object.keys(messageValue)) {
const property: string = key.trim();
const value: any = messageValue[key];

switch (property.toLowerCase()) {
case 'text':
activity.text = value.toString();
break;
case 'speak':
activity.speak = value.toString();
break;
case 'inputhint':
activity.inputHint = value.toString();
break;
case 'attachments':
activity.attachments = this.getAttachments(value);
break;
case 'suggestedactions':
activity.suggestedActions = this.getSuggestions(value);
break;
case 'attachmentlayout':
activity.attachmentLayout = value.toString();
default:
activity[property] = value;
break;
}
}

return activity;
}

private static getSuggestions(suggestionsValue: any): SuggestedActions {
let actions: any[] = this.normalizedToList(suggestionsValue);

let suggestedActions: SuggestedActions = {
actions : this.getCardActions(actions),
to: []
};

return suggestedActions;
}

private static getButtons(buttonsValue: any): CardAction[] {
let actions: any[] = this.normalizedToList(buttonsValue);
return this.getCardActions(actions);
}

private static getCardActions(actions: any[]): CardAction[] {
let cardActions: (string|CardAction)[] = [];
for (const action of actions) {
if (typeof action === 'string') {
cardActions.push(action.trim());
} else {
const cardAction: CardAction = this.getCardAction(action);
if (cardAction !== undefined) {
cardActions.push(cardAction);
}
}
}

return CardFactory.actions(cardActions);
}

private static getCardAction(cardActionValue: any): CardAction {
const type: string = this.getStructureType(cardActionValue);
let cardAction: CardAction = {
type: ActionTypes.ImBack,
title: '',
value: ''
};

if(type !== 'cardaction') {
return undefined;
}

for (const key of Object.keys(cardActionValue)) {
const property: string = key.trim();
const value: any = cardActionValue[key];

switch (property.toLowerCase()) {
case 'type':
cardAction.type = value.toString();
break;
case 'title':
cardAction.title = value.toString();
break;
case 'value':
cardAction.value = value.toString();
break;
case 'displaytext':
cardAction.displayText = value.toString();
break;
case 'text':
cardAction.text = value.toString();
break;
case 'image':
cardAction.image = value.toString();
break;
default:
cardAction[property] = value;
break;
}
}

return cardAction;
}

private static getStructureType(input: any): string {
let result = '';

if (input !== undefined) {
if ('$type' in input) {
result = input['$type'].toString();
} else if ('type' in input) {
result = input['type'].toString();
}
}

return result.trim().toLowerCase();
}

private static getAttachments(input: any): Attachment[] {
let attachments: Attachment[] = [];
let attachmentsJsonList: any[] = this.normalizedToList(input);

for (const attachmentsJson of attachmentsJsonList) {
if (typeof attachmentsJson === 'object') {
const attachment = this.getAttachment(attachmentsJson);
if (attachment !== undefined) {
attachments.push(attachment);
} else {
throw new Error(`'${ attachmentsJson }' is not an attachment format.`);
}
} else {
throw new Error(`'${ attachmentsJson }' is not an attachment format.`);
}
}

return attachments;
}

private static getAttachment(input: any): Attachment {
let attachment: Attachment = {
contentType: ''
};
const type: string = this.getStructureType(input);
if (ActivityFactory.genericCardTypeMapping.has(type)) {
attachment = this.getCardAttachment(ActivityFactory.genericCardTypeMapping.get(type), input);
} else if(type === 'adaptivecard') {
attachment = CardFactory.adaptiveCard(input);
} else {
attachment = undefined;
}

return attachment;
}

private static getCardAttachment(type: string, input: any): Attachment {
let card: any = {};

for (const key in input) {
const property: string = key.trim().toLowerCase();
const value: any = input[key];

switch (property) {
case 'title':
case 'subtitle':
case 'text':
case 'aspect':
case 'value':
card[property] = value;
break;
case 'connectionname':
card['connectionName'] = value;
break;

case 'image':
case 'images':
if (type === CardFactory.contentTypes.heroCard || type === CardFactory.contentTypes.thumbnailCard) {
if (!('images' in card)) {
card['images'] = [];
}

let imageList: string[] = this.normalizedToList(value).map((u): string => u.toString());
imageList.forEach( (u): any => card['images'].push({url : u}));
} else {
card['image'] = {url: value.toString()};
}
break;
case 'media':
if (!('media' in card)) {
card['media'] = [];
}

let mediaList: string[] = this.normalizedToList(value).map((u): string => u.toString());
mediaList.forEach( (u): any => card['media'].push({url : u}));
break;
case 'buttons':
if (!('buttons' in card)) {
card['buttons'] = [];
}

let buttons: any[] = this.getButtons(value);
buttons.forEach( (u): any => card[property].push(u));
break;
case 'autostart':
case 'shareable':
case 'autoloop':
const boolValue: boolean = this.getValidBooleanValue(value.toString());
if (boolValue !== undefined) {
card[property] = boolValue;
}
break;
default:
card[property] = value;
break;
}
}

const attachment: Attachment = {
contentType: type,
content: card
};

return attachment;
}

private static getValidBooleanValue(boolValue: string): boolean{
if (boolValue.toLowerCase() == 'true')
{
return true;
}
else if (boolValue.toLowerCase() == 'false')
{
return false;
}

return undefined;
}

private static normalizedToList(item: any): any[] {
if (item === undefined) {
return [];
} else if (Array.isArray(item)){
return item;
} else {
return [item];
}
}

}
1 change: 1 addition & 0 deletions libraries/botbuilder-lg/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from './lgImport';
export * from './range';
export * from './position';
export * from './evaluationTarget';
export * from './activityFactory';
Loading

0 comments on commit ae20476

Please sign in to comment.