diff --git a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py index 2e5774cc2..33b4e419c 100644 --- a/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py +++ b/libraries/botbuilder-core/botbuilder/core/teams/teams_activity_handler.py @@ -27,6 +27,7 @@ TaskModuleResponse, TabRequest, TabSubmit, + MeetingParticipantsEventDetails, ) from botframework.connector import Channels from ..serializer_helper import deserializer_helper @@ -913,6 +914,20 @@ async def on_event_activity(self, turn_context: TurnContext): return await self.on_teams_meeting_end_event( turn_context.activity.value, turn_context ) + if ( + turn_context.activity.name + == "application/vnd.microsoft.meetingParticipantJoin" + ): + return await self.on_teams_meeting_participants_join_event( + turn_context.activity.value, turn_context + ) + if ( + turn_context.activity.name + == "application/vnd.microsoft.meetingParticipantLeave" + ): + return await self.on_teams_meeting_participants_leave_event( + turn_context.activity.value, turn_context + ) return await super().on_event_activity(turn_context) @@ -941,3 +956,29 @@ async def on_teams_meeting_end_event( :returns: A task that represents the work queued to execute. """ return + + async def on_teams_meeting_participants_join_event( + self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext + ): # pylint: disable=unused-argument + """ + Override this in a derived class to provide logic for when meeting participants are added. + + :param meeting: The details of the meeting. + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + return + + async def on_teams_meeting_participants_leave_event( + self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext + ): # pylint: disable=unused-argument + """ + Override this in a derived class to provide logic for when meeting participants are removed. + + :param meeting: The details of the meeting. + :param turn_context: A context object for this turn. + + :returns: A task that represents the work queued to execute. + """ + return diff --git a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py index 32e9f2edb..390df6191 100644 --- a/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py +++ b/libraries/botbuilder-core/tests/teams/test_teams_activity_handler.py @@ -32,6 +32,7 @@ TabRequest, TabSubmit, TabContext, + MeetingParticipantsEventDetails, ) from botframework.connector import Channels from simple_adapter import SimpleAdapter @@ -333,6 +334,22 @@ async def on_teams_meeting_end_event( turn_context.activity.value, turn_context ) + async def on_teams_meeting_participants_join_event( + self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext + ): + self.record.append("on_teams_meeting_participants_join_event") + return await super().on_teams_meeting_participants_join_event( + turn_context.activity.value, turn_context + ) + + async def on_teams_meeting_participants_leave_event( + self, meeting: MeetingParticipantsEventDetails, turn_context: TurnContext + ): + self.record.append("on_teams_meeting_participants_leave_event") + return await super().on_teams_meeting_participants_leave_event( + turn_context.activity.value, turn_context + ) + class NotImplementedAdapter(BotAdapter): async def delete_activity( @@ -1157,3 +1174,57 @@ async def test_on_teams_meeting_end_event(self): assert len(bot.record) == 2 assert bot.record[0] == "on_event_activity" assert bot.record[1] == "on_teams_meeting_end_event" + + async def test_on_teams_meeting_participants_join_event(self): + # arrange + activity = Activity( + type=ActivityTypes.event, + channel_id=Channels.ms_teams, + name="application/vnd.microsoft.meetingParticipantJoin", + value={ + "members": [ + { + "user": {"id": "123", "name": "name"}, + "meeting": {"role": "role", "in_meeting": True}, + } + ], + }, + ) + + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + assert len(bot.record) == 2 + assert bot.record[0] == "on_event_activity" + assert bot.record[1] == "on_teams_meeting_participants_join_event" + + async def test_on_teams_meeting_participants_leave_event(self): + # arrange + activity = Activity( + type=ActivityTypes.event, + channel_id=Channels.ms_teams, + name="application/vnd.microsoft.meetingParticipantLeave", + value={ + "members": [ + { + "user": {"id": "id", "name": "name"}, + "meeting": {"role": "role", "in_meeting": True}, + } + ], + }, + ) + + turn_context = TurnContext(SimpleAdapter(), activity) + + # Act + bot = TestingTeamsActivityHandler() + await bot.on_turn(turn_context) + + # Assert + assert len(bot.record) == 2 + assert bot.record[0] == "on_event_activity" + assert bot.record[1] == "on_teams_meeting_participants_leave_event" diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py index 7824e4571..55901f7a4 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py @@ -77,6 +77,9 @@ from ._models_py3 import TabSubmitData from ._models_py3 import TabSuggestedActions from ._models_py3 import TaskModuleCardResponse +from ._models_py3 import UserMeetingDetails +from ._models_py3 import TeamsMeetingMember +from ._models_py3 import MeetingParticipantsEventDetails __all__ = [ "AppBasedLinkQuery", @@ -155,4 +158,7 @@ "TabSubmitData", "TabSuggestedActions", "TaskModuleCardResponse", + "UserMeetingDetails", + "TeamsMeetingMember", + "MeetingParticipantsEventDetails", ] diff --git a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py index bb860c8da..260442cf8 100644 --- a/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py +++ b/libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py @@ -2506,3 +2506,65 @@ class MeetingEndEventDetails(MeetingDetailsBase): def __init__(self, *, end_time: str = None, **kwargs): super(MeetingEndEventDetails, self).__init__(**kwargs) self.end_time = end_time + + +class UserMeetingDetails(Model): + """Specific details of a user in a Teams meeting. + + :param role: Role of the participant in the current meeting. + :type role: str + :param in_meeting: True, if the participant is in the meeting. + :type in_meeting: bool + """ + + _attribute_map = { + "role": {"key": "role", "type": "str"}, + "in_meeting": {"key": "inMeeting", "type": "bool"}, + } + + def __init__(self, *, role: str = None, in_meeting: bool = None, **kwargs) -> None: + super(UserMeetingDetails, self).__init__(**kwargs) + self.in_meeting = in_meeting + self.role = role + + +class TeamsMeetingMember(Model): + """Data about the meeting participants. + + :param user: The channel user data. + :type user: TeamsChannelAccount + :param meeting: The user meeting details. + :type meeting: UserMeetingDetails + """ + + _attribute_map = { + "user": {"key": "user", "type": "TeamsChannelAccount"}, + "meeting": {"key": "meeting", "type": "UserMeetingDetails"}, + } + + def __init__( + self, + *, + user: TeamsChannelAccount = None, + meeting: UserMeetingDetails = None, + **kwargs + ) -> None: + super(TeamsMeetingMember, self).__init__(**kwargs) + self.user = user + self.meeting = meeting + + +class MeetingParticipantsEventDetails(Model): + """Data about the meeting participants. + + :param members: The members involved in the meeting event. + :type members: list[~botframework.connector.models.TeamsMeetingMember] + """ + + _attribute_map = { + "conversations": {"key": "members", "type": "[TeamsMeetingMember]"}, + } + + def __init__(self, *, members: List[TeamsMeetingMember] = None, **kwargs) -> None: + super(MeetingParticipantsEventDetails, self).__init__(**kwargs) + self.members = members diff --git a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py index 6056fec5e..523977b08 100644 --- a/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py +++ b/libraries/botframework-connector/botframework/connector/auth/microsoft_app_credentials.py @@ -54,10 +54,15 @@ def get_access_token(self, force_refresh: bool = False) -> str: auth_token = self.__get_msal_app().acquire_token_for_client(scopes=scopes) if "access_token" in auth_token: return auth_token["access_token"] - else: - error = auth_token["error"] if "error" in auth_token else "Unknown error" - error_description = auth_token["error_description"] if "error_description" in auth_token else "Unknown error description" - raise PermissionError(f"Failed to get access token with error: {error}, error_description: {error_description}") + error = auth_token["error"] if "error" in auth_token else "Unknown error" + error_description = ( + auth_token["error_description"] + if "error_description" in auth_token + else "Unknown error description" + ) + raise PermissionError( + f"Failed to get access token with error: {error}, error_description: {error_description}" + ) def __get_msal_app(self): if not self.app: