Skip to content
This repository was archived by the owner on Jan 5, 2026. It is now read-only.

Commit ae2a56e

Browse files
authored
Merge pull request #1411 from microsoft/trboehre/teamsmeeting
Support for meeting APIs
2 parents 8a27c69 + 896b8b9 commit ae2a56e

7 files changed

Lines changed: 263 additions & 5 deletions

File tree

libraries/botbuilder-core/botbuilder/core/teams/teams_activity_extensions.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,22 @@
22
# Licensed under the MIT License.
33

44
from botbuilder.schema import Activity
5-
from botbuilder.schema.teams import NotificationInfo, TeamsChannelData, TeamInfo
5+
from botbuilder.schema.teams import (
6+
NotificationInfo,
7+
TeamsChannelData,
8+
TeamInfo,
9+
TeamsMeetingInfo,
10+
)
11+
12+
13+
def teams_get_channel_data(activity: Activity) -> TeamsChannelData:
14+
if not activity:
15+
return None
16+
17+
if activity.channel_data:
18+
return TeamsChannelData().deserialize(activity.channel_data)
19+
20+
return None
621

722

823
def teams_get_channel_id(activity: Activity) -> str:
@@ -41,3 +56,14 @@ def teams_notify_user(
4156
channel_data.notification.alert_in_meeting = alert_in_meeting
4257
channel_data.notification.external_resource_url = external_resource_url
4358
activity.channel_data = channel_data
59+
60+
61+
def teams_get_meeting_info(activity: Activity) -> TeamsMeetingInfo:
62+
if not activity:
63+
return None
64+
65+
if activity.channel_data:
66+
channel_data = TeamsChannelData().deserialize(activity.channel_data)
67+
return channel_data.meeting
68+
69+
return None

libraries/botbuilder-core/botbuilder/core/teams/teams_info.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,23 @@
22
# Licensed under the MIT License.
33

44
from typing import List, Tuple
5+
6+
from botframework.connector.aio import ConnectorClient
7+
from botframework.connector.teams.teams_connector_client import TeamsConnectorClient
58
from botbuilder.schema import ConversationParameters, ConversationReference
9+
from botbuilder.core.teams.teams_activity_extensions import (
10+
teams_get_meeting_info,
11+
teams_get_channel_data,
12+
)
613
from botbuilder.core.turn_context import Activity, TurnContext
714
from botbuilder.schema.teams import (
815
ChannelInfo,
916
TeamDetails,
1017
TeamsChannelData,
1118
TeamsChannelAccount,
1219
TeamsPagedMembersResult,
20+
TeamsMeetingParticipant,
1321
)
14-
from botframework.connector.aio import ConnectorClient
15-
from botframework.connector.teams.teams_connector_client import TeamsConnectorClient
1622

1723

1824
class TeamsInfo:
@@ -177,6 +183,48 @@ async def get_member(
177183

178184
return await TeamsInfo.get_team_member(turn_context, team_id, member_id)
179185

186+
@staticmethod
187+
async def get_meeting_participant(
188+
turn_context: TurnContext,
189+
meeting_id: str = None,
190+
participant_id: str = None,
191+
tenant_id: str = None,
192+
) -> TeamsMeetingParticipant:
193+
meeting_id = (
194+
meeting_id
195+
if meeting_id
196+
else teams_get_meeting_info(turn_context.activity).id
197+
)
198+
if meeting_id is None:
199+
raise TypeError(
200+
"TeamsInfo._get_meeting_participant: method requires a meeting_id"
201+
)
202+
203+
participant_id = (
204+
participant_id
205+
if participant_id
206+
else turn_context.activity.from_property.aad_object_id
207+
)
208+
if participant_id is None:
209+
raise TypeError(
210+
"TeamsInfo._get_meeting_participant: method requires a participant_id"
211+
)
212+
213+
tenant_id = (
214+
tenant_id
215+
if tenant_id
216+
else teams_get_channel_data(turn_context.activity).tenant.id
217+
)
218+
if tenant_id is None:
219+
raise TypeError(
220+
"TeamsInfo._get_meeting_participant: method requires a tenant_id"
221+
)
222+
223+
connector_client = await TeamsInfo.get_teams_connector_client(turn_context)
224+
return connector_client.teams.fetch_participant(
225+
meeting_id, participant_id, tenant_id
226+
)
227+
180228
@staticmethod
181229
async def get_teams_connector_client(
182230
turn_context: TurnContext,

libraries/botbuilder-core/tests/teams/test_teams_extension.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
teams_get_team_info,
1111
teams_notify_user,
1212
)
13+
from botbuilder.core.teams.teams_activity_extensions import teams_get_meeting_info
1314

1415

1516
class TestTeamsActivityHandler(aiounittest.AsyncTestCase):
@@ -149,3 +150,13 @@ def test_teams_notify_user_with_no_channel_data(self):
149150
# Assert
150151
assert activity.channel_data.notification.alert
151152
assert activity.id == "id123"
153+
154+
def test_teams_meeting_info(self):
155+
# Arrange
156+
activity = Activity(channel_data={"meeting": {"id": "meeting123"}})
157+
158+
# Act
159+
meeting_id = teams_get_meeting_info(activity).id
160+
161+
# Assert
162+
assert meeting_id == "meeting123"

libraries/botbuilder-core/tests/teams/test_teams_info.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Licensed under the MIT License.
33

44
import aiounittest
5-
5+
from botframework.connector import Channels
66

77
from botbuilder.core import TurnContext, MessageFactory
88
from botbuilder.core.teams import TeamsInfo, TeamsActivityHandler
@@ -199,6 +199,25 @@ def create_conversation():
199199
else:
200200
assert False, "should have raise TypeError"
201201

202+
async def test_get_participant(self):
203+
adapter = SimpleAdapterWithCreateConversation()
204+
205+
activity = Activity(
206+
type="message",
207+
text="Test-get_participant",
208+
channel_id=Channels.ms_teams,
209+
from_property=ChannelAccount(aad_object_id="participantId-1"),
210+
channel_data={
211+
"meeting": {"id": "meetingId-1"},
212+
"tenant": {"id": "tenantId-1"},
213+
},
214+
service_url="https://test.coffee",
215+
)
216+
217+
turn_context = TurnContext(adapter, activity)
218+
handler = TeamsActivityHandler()
219+
await handler.on_turn(turn_context)
220+
202221

203222
class TestTeamsActivityHandler(TeamsActivityHandler):
204223
async def on_turn(self, turn_context: TurnContext):

libraries/botbuilder-schema/botbuilder/schema/teams/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
from ._models_py3 import TeamsChannelData
5959
from ._models_py3 import TeamsPagedMembersResult
6060
from ._models_py3 import TenantInfo
61+
from ._models_py3 import TeamsMeetingInfo
62+
from ._models_py3 import TeamsMeetingParticipant
63+
from ._models_py3 import MeetingParticipantInfo
6164

6265
__all__ = [
6366
"AppBasedLinkQuery",
@@ -117,4 +120,7 @@
117120
"TeamsChannelData",
118121
"TeamsPagedMembersResult",
119122
"TenantInfo",
123+
"TeamsMeetingInfo",
124+
"TeamsMeetingParticipant",
125+
"MeetingParticipantInfo",
120126
]

libraries/botbuilder-schema/botbuilder/schema/teams/_models_py3.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
# Licensed under the MIT License.
33

44
from msrest.serialization import Model
5-
from botbuilder.schema import Activity, Attachment, ChannelAccount, PagedMembersResult
5+
from botbuilder.schema import (
6+
Attachment,
7+
ChannelAccount,
8+
PagedMembersResult,
9+
ConversationAccount,
10+
)
611

712

813
class TaskModuleRequest(Model):
@@ -1898,6 +1903,8 @@ class TeamsChannelData(Model):
18981903
:type notification: ~botframework.connector.teams.models.NotificationInfo
18991904
:param tenant: Information about the tenant in which the message was sent
19001905
:type tenant: ~botframework.connector.teams.models.TenantInfo
1906+
:param meeting: Information about the meeting in which the message was sent
1907+
:type meeting: ~botframework.connector.teams.models.TeamsMeetingInfo
19011908
"""
19021909

19031910
_attribute_map = {
@@ -1906,6 +1913,7 @@ class TeamsChannelData(Model):
19061913
"team": {"key": "team", "type": "TeamInfo"},
19071914
"notification": {"key": "notification", "type": "NotificationInfo"},
19081915
"tenant": {"key": "tenant", "type": "TenantInfo"},
1916+
"meeting": {"key": "meeting", "type": "TeamsMeetingInfo"},
19091917
}
19101918

19111919
def __init__(
@@ -1916,6 +1924,7 @@ def __init__(
19161924
team=None,
19171925
notification=None,
19181926
tenant=None,
1927+
meeting=None,
19191928
**kwargs
19201929
) -> None:
19211930
super(TeamsChannelData, self).__init__(**kwargs)
@@ -1925,6 +1934,7 @@ def __init__(
19251934
self.team = team
19261935
self.notification = notification
19271936
self.tenant = tenant
1937+
self.meeting = meeting
19281938

19291939

19301940
class TenantInfo(Model):
@@ -1941,3 +1951,70 @@ class TenantInfo(Model):
19411951
def __init__(self, *, id: str = None, **kwargs) -> None:
19421952
super(TenantInfo, self).__init__(**kwargs)
19431953
self.id = id
1954+
1955+
1956+
class TeamsMeetingInfo(Model):
1957+
"""Describes a Teams Meeting.
1958+
1959+
:param id: Unique identifier representing a meeting
1960+
:type id: str
1961+
"""
1962+
1963+
_attribute_map = {
1964+
"id": {"key": "id", "type": "str"},
1965+
}
1966+
1967+
def __init__(self, *, id: str = None, **kwargs) -> None:
1968+
super(TeamsMeetingInfo, self).__init__(**kwargs)
1969+
self.id = id
1970+
1971+
1972+
class MeetingParticipantInfo(Model):
1973+
"""Teams meeting participant details.
1974+
1975+
:param role: Role of the participant in the current meeting.
1976+
:type role: str
1977+
:param in_meeting: True, if the participant is in the meeting.
1978+
:type in_meeting: bool
1979+
"""
1980+
1981+
_attribute_map = {
1982+
"role": {"key": "role", "type": "str"},
1983+
"in_meeting": {"key": "inMeeting", "type": "bool"},
1984+
}
1985+
1986+
def __init__(self, *, role: str = None, in_meeting: bool = None, **kwargs) -> None:
1987+
super(MeetingParticipantInfo, self).__init__(**kwargs)
1988+
self.role = role
1989+
self.in_meeting = in_meeting
1990+
1991+
1992+
class TeamsMeetingParticipant(Model):
1993+
"""Teams participant channel account detailing user Azure Active Directory and meeting participant details.
1994+
1995+
:param user: Teams Channel Account information for this meeting participant
1996+
:type user: TeamsChannelAccount
1997+
:param meeting: >Information specific to this participant in the specific meeting.
1998+
:type meeting: MeetingParticipantInfo
1999+
:param conversation: Conversation Account for the meeting.
2000+
:type conversation: ConversationAccount
2001+
"""
2002+
2003+
_attribute_map = {
2004+
"user": {"key": "user", "type": "TeamsChannelAccount"},
2005+
"meeting": {"key": "meeting", "type": "MeetingParticipantInfo"},
2006+
"conversation": {"key": "conversation", "type": "ConversationAccount"},
2007+
}
2008+
2009+
def __init__(
2010+
self,
2011+
*,
2012+
user: TeamsChannelAccount = None,
2013+
meeting: MeetingParticipantInfo = None,
2014+
conversation: ConversationAccount = None,
2015+
**kwargs
2016+
) -> None:
2017+
super(TeamsMeetingParticipant, self).__init__(**kwargs)
2018+
self.user = user
2019+
self.meeting = meeting
2020+
self.conversation = conversation

libraries/botframework-connector/botframework/connector/teams/operations/teams_operations.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,74 @@ def get_team_details(
145145
return deserialized
146146

147147
get_team_details.metadata = {"url": "/v3/teams/{teamId}"}
148+
149+
def fetch_participant(
150+
self,
151+
meeting_id: str,
152+
participant_id: str,
153+
tenant_id: str,
154+
custom_headers=None,
155+
raw=False,
156+
**operation_config
157+
):
158+
"""Fetches Teams meeting participant details.
159+
160+
:param meeting_id: Teams meeting id
161+
:type meeting_id: str
162+
:param participant_id: Teams meeting participant id
163+
:type participant_id: str
164+
:param tenant_id: Teams meeting tenant id
165+
:type tenant_id: str
166+
:param dict custom_headers: headers that will be added to the request
167+
:param bool raw: returns the direct response alongside the
168+
deserialized response
169+
:param operation_config: :ref:`Operation configuration
170+
overrides<msrest:optionsforoperations>`.
171+
:return: TeamsMeetingParticipant or ClientRawResponse if raw=true
172+
:rtype: ~botframework.connector.teams.models.TeamsParticipantChannelAccount or
173+
~msrest.pipeline.ClientRawResponse
174+
:raises:
175+
:class:`HttpOperationError<msrest.exceptions.HttpOperationError>`
176+
"""
177+
178+
# Construct URL
179+
url = self.fetch_participant.metadata["url"]
180+
path_format_arguments = {
181+
"meetingId": self._serialize.url("meeting_id", meeting_id, "str"),
182+
"participantId": self._serialize.url(
183+
"participant_id", participant_id, "str"
184+
),
185+
"tenantId": self._serialize.url("tenant_id", tenant_id, "str"),
186+
}
187+
url = self._client.format_url(url, **path_format_arguments)
188+
189+
# Construct parameters
190+
query_parameters = {}
191+
192+
# Construct headers
193+
header_parameters = {}
194+
header_parameters["Accept"] = "application/json"
195+
if custom_headers:
196+
header_parameters.update(custom_headers)
197+
198+
# Construct and send request
199+
request = self._client.get(url, query_parameters, header_parameters)
200+
response = self._client.send(request, stream=False, **operation_config)
201+
202+
if response.status_code not in [200]:
203+
raise HttpOperationError(self._deserialize, response)
204+
205+
deserialized = None
206+
207+
if response.status_code == 200:
208+
deserialized = self._deserialize("TeamsMeetingParticipant", response)
209+
210+
if raw:
211+
client_raw_response = ClientRawResponse(deserialized, response)
212+
return client_raw_response
213+
214+
return deserialized
215+
216+
fetch_participant.metadata = {
217+
"url": "/v1/meetings/{meetingId}/participants/{participantId}?tenantId={tenantId}"
218+
}

0 commit comments

Comments
 (0)