Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
TaskModuleResponse,
TabRequest,
TabSubmit,
MeetingParticipantsEventDetails,
)
from botframework.connector import Channels
from ..serializer_helper import deserializer_helper
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
TabRequest,
TabSubmit,
TabContext,
MeetingParticipantsEventDetails,
)
from botframework.connector import Channels
from simple_adapter import SimpleAdapter
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -155,4 +158,7 @@
"TabSubmitData",
"TabSuggestedActions",
"TaskModuleCardResponse",
"UserMeetingDetails",
"TeamsMeetingMember",
"MeetingParticipantsEventDetails",
]
62 changes: 62 additions & 0 deletions libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down