Skip to content

Commit

Permalink
[JS] feat: #668 Add Meetings event handlers, read receipt handler, an…
Browse files Browse the repository at this point in the history
…d Application Builder's turnStateFactory (#845)

## Linked issues

closes: #668

## Details

- Add a new class, `Meetings.ts`, for handling Teams meeting events
   - `start`
   - `end`
   - `participantsJoin`
   - `participantsLeave` 
- Add `teamsReadReceipt` handler for Teams read receipt information
- After turnStateManager rename to `turnStateFactory`, add options
configuration for Application Builder's `turnStateFactory`

## Attestation Checklist

### Unit tests TBD in upcoming PR. 
- [x] My code follows the style guidelines of this project

- I have checked for/fixed spelling, linting, and other errors
- I have commented my code for clarity
- I have made corresponding changes to the documentation (we use
[TypeDoc](https://typedoc.org/) to document our code)
- My changes generate no new warnings
- I have added tests that validates my changes, and provides sufficient
test coverage. I have tested with:
  - Local testing
  - E2E testing in Teams
- New and existing unit tests pass locally with my changes

---------

Co-authored-by: Corina Gum <>
  • Loading branch information
corinagum authored Nov 20, 2023
1 parent 6a60d0d commit 3be8a81
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
32 changes: 32 additions & 0 deletions js/packages/teams-ai/src/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
Storage,
TurnContext
} from 'botbuilder';
import { ReadReceiptInfo } from 'botframework-connector';

import { AdaptiveCards, AdaptiveCardsOptions } from './AdaptiveCards';
import { AI, AIOptions } from './AI';
import { MessageExtensions } from './MessageExtensions';
Expand Down Expand Up @@ -784,6 +786,26 @@ export class Application<TState extends TurnState = TurnState> {
return this;
}

/**
* Adds a handler for Teams' readReceiptInfo event
* @param {(context: TurnContext, state: TState, readReceiptInfo: ReadReceiptInfo) => Promise<boolean>} handler Function to call when the event is triggered.
* @returns {this} The application instance for chaining purposes.
*/
public teamsReadReceipt(handler: (context: TurnContext, state: TState, readReceiptInfo: ReadReceiptInfo) => Promise<void>): this {
const selector = (context: TurnContext): Promise<boolean> => {
return Promise.resolve(context.activity.type === ActivityTypes.Event && context.activity.channelId === 'msteams' && context.activity.name === 'application/vnd.microsoft/readReceipt');
};

const handlerWrapper = (context: TurnContext, state: TState): Promise<void> => {
const readReceiptInfo = context.activity.value as ReadReceiptInfo;
return handler(context, state, readReceiptInfo);
}

this.addRoute(selector, handlerWrapper);

return this;
}

/**
* Calls the given event handlers with the given context and state.
* @param {TurnContext} context - The context for the current turn with the user.
Expand Down Expand Up @@ -930,6 +952,16 @@ export class ApplicationBuilder<TState extends TurnState = TurnState> {
return this;
}

/**
* Configures the turn state factory for managing the bot's turn state.
* @param turnStateFactory
* @returns
*/
public withTurnStateFactory(turnStateFactory: () => TState): this {
this._options.turnStateFactory = turnStateFactory;
return this;
}

/**
* Configures the removing of mentions of the bot's name from incoming messages.
* Default state for removeRecipientMention is true
Expand Down
94 changes: 94 additions & 0 deletions js/packages/teams-ai/src/Meetings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { ActivityTypes, MeetingEndEventDetails, MeetingParticipantsEventDetails, MeetingStartEventDetails, TurnContext } from "botbuilder";
import { Application } from "./Application";
import { TurnState } from "./TurnState";

/**
* Provides a set of methods for handling Teams meeting events.
*/
export class Meetings<TState extends TurnState = TurnState> {
private readonly _app: Application<TState>;

public constructor(app: Application<TState>) {
this._app = app;
}

/**
* Handles meeting start events for Microsoft Teams.
* @param {(context: TurnContext, state: TState, meeting: MeetingStartEventDetails)} handler - Function to call when the handler is triggered.
* @returns {Application<TState>} The application for chaining purposes.
*/
public start(handler: (context: TurnContext, state: TState, meeting: MeetingStartEventDetails) => Promise<void>): Application<TState> {
const selector = (context: TurnContext): Promise<boolean> => {
return Promise.resolve(context.activity.type === ActivityTypes.Event && context.activity.channelId === 'msteams' && context.activity.name === 'application/vnd.microsoft.meetingStart');
}

const handlerWrapper = (context: TurnContext, state: TState): Promise<void> => {
const meeting = context.activity.value as MeetingStartEventDetails;
return handler(context, state, meeting);
}

this._app.addRoute(selector, handlerWrapper);

return this._app;
}

/**
* Handles meeting end events for Microsoft Teams.
* @param {(context: TurnContext, state: TState, meeting: MeetingEndEventDetails)} handler - Function to call when the handler is triggered.
* @returns {Application<TState>} The application for chaining purposes.
*/
public end(handler: (context: TurnContext, state: TState, meeting: MeetingEndEventDetails) => Promise<void>): Application<TState> {
const selector = (context: TurnContext): Promise<boolean> => {
return Promise.resolve(context.activity.type === ActivityTypes.Event && context.activity.channelId === 'msteams' && context.activity.name === 'application/vnd.microsoft.meetingEnd');
}

const handlerWrapper = (context: TurnContext, state: TState): Promise<void> => {
const meeting = context.activity.value as MeetingEndEventDetails;
return handler(context, state, meeting);
}

this._app.addRoute(selector, handlerWrapper);

return this._app;
}

/**
* Handles meeting participant join events for Microsoft Teams.
* @param {(context: TurnContext, state: TState, meeting: MeetingParticipantsEventDetails)} handler - Function to call when the handler is triggered.
* @returns {Application<TState>} The application for chaining purposes.
*/
public participantsJoin(handler: (context: TurnContext, state: TState, meeting: MeetingParticipantsEventDetails) => Promise<void>): Application<TState> {
const selector = (context: TurnContext): Promise<boolean> => {
return Promise.resolve(context.activity.type === ActivityTypes.Event && context.activity.channelId === 'msteams' && context.activity.name === 'application/vnd.microsoft.meetingParticipantsJoin');
}

const handlerWrapper = (context: TurnContext, state: TState): Promise<void> => {
const meeting = context.activity.value as MeetingParticipantsEventDetails;
return handler(context, state, meeting);
}

this._app.addRoute(selector, handlerWrapper);

return this._app;
}

/**
* Handles meeting participant leave events for Microsoft Teams.
* @param {(context: TurnContext, state: TState, meeting: MeetingParticipantsEventDetails)} handler - Function to call when the handler is triggered.
* @returns {Application<TState>} The application for chaining purposes.
*/
public participantsLeave(handler: (context: TurnContext, state: TState, meeting: MeetingParticipantsEventDetails) => Promise<void>): Application<TState> {
const selector = (context: TurnContext): Promise<boolean> => {
return Promise.resolve(context.activity.type === ActivityTypes.Event && context.activity.channelId === 'msteams' && context.activity.name === 'application/vnd.microsoft.meetingParticipantsLeave');
}

const handlerWrapper = (context: TurnContext, state: TState): Promise<void> => {
const meeting = context.activity.value as MeetingParticipantsEventDetails;
return handler(context, state, meeting);
}

this._app.addRoute(selector, handlerWrapper);

return this._app;
}
}

0 comments on commit 3be8a81

Please sign in to comment.