Skip to content
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 @@ -2,7 +2,22 @@
# Licensed under the MIT License.

from botbuilder.schema import Activity
from botbuilder.schema.teams import NotificationInfo, TeamsChannelData, TeamInfo
from botbuilder.schema.teams import (
NotificationInfo,
TeamsChannelData,
TeamInfo,
TeamsMeetingInfo,
)


def teams_get_channel_data(activity: Activity) -> TeamsChannelData:
if not activity:
return None

if activity.channel_data:
return TeamsChannelData().deserialize(activity.channel_data)

return None


def teams_get_channel_id(activity: Activity) -> str:
Expand Down Expand Up @@ -41,3 +56,14 @@ def teams_notify_user(
channel_data.notification.alert_in_meeting = alert_in_meeting
channel_data.notification.external_resource_url = external_resource_url
activity.channel_data = channel_data


def teams_get_meeting_info(activity: Activity) -> TeamsMeetingInfo:
if not activity:
return None

if activity.channel_data:
channel_data = TeamsChannelData().deserialize(activity.channel_data)
return channel_data.meeting

return None
52 changes: 50 additions & 2 deletions libraries/botbuilder-core/botbuilder/core/teams/teams_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
# Licensed under the MIT License.

from typing import List, Tuple

from botframework.connector.aio import ConnectorClient
from botframework.connector.teams.teams_connector_client import TeamsConnectorClient
from botbuilder.schema import ConversationParameters, ConversationReference
from botbuilder.core.teams.teams_activity_extensions import (
teams_get_meeting_info,
teams_get_channel_data,
)
from botbuilder.core.turn_context import Activity, TurnContext
from botbuilder.schema.teams import (
ChannelInfo,
TeamDetails,
TeamsChannelData,
TeamsChannelAccount,
TeamsPagedMembersResult,
TeamsMeetingParticipant,
)
from botframework.connector.aio import ConnectorClient
from botframework.connector.teams.teams_connector_client import TeamsConnectorClient


class TeamsInfo:
Expand Down Expand Up @@ -177,6 +183,48 @@ async def get_member(

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

@staticmethod
async def get_meeting_participant(
turn_context: TurnContext,
meeting_id: str = None,
participant_id: str = None,
tenant_id: str = None,
) -> TeamsMeetingParticipant:
meeting_id = (
meeting_id
if meeting_id
else teams_get_meeting_info(turn_context.activity).id
)
if meeting_id is None:
raise TypeError(
"TeamsInfo._get_meeting_participant: method requires a meeting_id"
)

participant_id = (
participant_id
if participant_id
else turn_context.activity.from_property.aad_object_id
)
if participant_id is None:
raise TypeError(
"TeamsInfo._get_meeting_participant: method requires a participant_id"
)

tenant_id = (
tenant_id
if tenant_id
else teams_get_channel_data(turn_context.activity).tenant.id
)
if tenant_id is None:
raise TypeError(
"TeamsInfo._get_meeting_participant: method requires a tenant_id"
)

connector_client = await TeamsInfo.get_teams_connector_client(turn_context)
return connector_client.teams.fetch_participant(
meeting_id, participant_id, tenant_id
)

@staticmethod
async def get_teams_connector_client(
turn_context: TurnContext,
Expand Down
11 changes: 11 additions & 0 deletions libraries/botbuilder-core/tests/teams/test_teams_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
teams_get_team_info,
teams_notify_user,
)
from botbuilder.core.teams.teams_activity_extensions import teams_get_meeting_info


class TestTeamsActivityHandler(aiounittest.AsyncTestCase):
Expand Down Expand Up @@ -149,3 +150,13 @@ def test_teams_notify_user_with_no_channel_data(self):
# Assert
assert activity.channel_data.notification.alert
assert activity.id == "id123"

def test_teams_meeting_info(self):
# Arrange
activity = Activity(channel_data={"meeting": {"id": "meeting123"}})

# Act
meeting_id = teams_get_meeting_info(activity).id

# Assert
assert meeting_id == "meeting123"
21 changes: 20 additions & 1 deletion libraries/botbuilder-core/tests/teams/test_teams_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed under the MIT License.

import aiounittest

from botframework.connector import Channels

from botbuilder.core import TurnContext, MessageFactory
from botbuilder.core.teams import TeamsInfo, TeamsActivityHandler
Expand Down Expand Up @@ -199,6 +199,25 @@ def create_conversation():
else:
assert False, "should have raise TypeError"

async def test_get_participant(self):
adapter = SimpleAdapterWithCreateConversation()

activity = Activity(
type="message",
text="Test-get_participant",
channel_id=Channels.ms_teams,
from_property=ChannelAccount(aad_object_id="participantId-1"),
channel_data={
"meeting": {"id": "meetingId-1"},
"tenant": {"id": "tenantId-1"},
},
service_url="https://test.coffee",
)

turn_context = TurnContext(adapter, activity)
handler = TeamsActivityHandler()
await handler.on_turn(turn_context)


class TestTeamsActivityHandler(TeamsActivityHandler):
async def on_turn(self, turn_context: TurnContext):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@
from ._models_py3 import TeamsChannelData
from ._models_py3 import TeamsPagedMembersResult
from ._models_py3 import TenantInfo
from ._models_py3 import TeamsMeetingInfo
from ._models_py3 import TeamsMeetingParticipant
from ._models_py3 import MeetingParticipantInfo

__all__ = [
"AppBasedLinkQuery",
Expand Down Expand Up @@ -117,4 +120,7 @@
"TeamsChannelData",
"TeamsPagedMembersResult",
"TenantInfo",
"TeamsMeetingInfo",
"TeamsMeetingParticipant",
"MeetingParticipantInfo",
]
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
# Licensed under the MIT License.

from msrest.serialization import Model
from botbuilder.schema import Activity, Attachment, ChannelAccount, PagedMembersResult
from botbuilder.schema import (
Attachment,
ChannelAccount,
PagedMembersResult,
ConversationAccount,
)


class TaskModuleRequest(Model):
Expand Down Expand Up @@ -1898,6 +1903,8 @@ class TeamsChannelData(Model):
:type notification: ~botframework.connector.teams.models.NotificationInfo
:param tenant: Information about the tenant in which the message was sent
:type tenant: ~botframework.connector.teams.models.TenantInfo
:param meeting: Information about the meeting in which the message was sent
:type meeting: ~botframework.connector.teams.models.TeamsMeetingInfo
"""

_attribute_map = {
Expand All @@ -1906,6 +1913,7 @@ class TeamsChannelData(Model):
"team": {"key": "team", "type": "TeamInfo"},
"notification": {"key": "notification", "type": "NotificationInfo"},
"tenant": {"key": "tenant", "type": "TenantInfo"},
"meeting": {"key": "meeting", "type": "TeamsMeetingInfo"},
}

def __init__(
Expand All @@ -1916,6 +1924,7 @@ def __init__(
team=None,
notification=None,
tenant=None,
meeting=None,
**kwargs
) -> None:
super(TeamsChannelData, self).__init__(**kwargs)
Expand All @@ -1925,6 +1934,7 @@ def __init__(
self.team = team
self.notification = notification
self.tenant = tenant
self.meeting = meeting


class TenantInfo(Model):
Expand All @@ -1941,3 +1951,70 @@ class TenantInfo(Model):
def __init__(self, *, id: str = None, **kwargs) -> None:
super(TenantInfo, self).__init__(**kwargs)
self.id = id


class TeamsMeetingInfo(Model):
"""Describes a Teams Meeting.

:param id: Unique identifier representing a meeting
:type id: str
"""

_attribute_map = {
"id": {"key": "id", "type": "str"},
}

def __init__(self, *, id: str = None, **kwargs) -> None:
super(TeamsMeetingInfo, self).__init__(**kwargs)
self.id = id


class MeetingParticipantInfo(Model):
"""Teams meeting participant details.

: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(MeetingParticipantInfo, self).__init__(**kwargs)
self.role = role
self.in_meeting = in_meeting


class TeamsMeetingParticipant(Model):
"""Teams participant channel account detailing user Azure Active Directory and meeting participant details.

:param user: Teams Channel Account information for this meeting participant
:type user: TeamsChannelAccount
:param meeting: >Information specific to this participant in the specific meeting.
:type meeting: MeetingParticipantInfo
:param conversation: Conversation Account for the meeting.
:type conversation: ConversationAccount
"""

_attribute_map = {
"user": {"key": "user", "type": "TeamsChannelAccount"},
"meeting": {"key": "meeting", "type": "MeetingParticipantInfo"},
"conversation": {"key": "conversation", "type": "ConversationAccount"},
}

def __init__(
self,
*,
user: TeamsChannelAccount = None,
meeting: MeetingParticipantInfo = None,
conversation: ConversationAccount = None,
**kwargs
) -> None:
super(TeamsMeetingParticipant, self).__init__(**kwargs)
self.user = user
self.meeting = meeting
self.conversation = conversation
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,74 @@ def get_team_details(
return deserialized

get_team_details.metadata = {"url": "/v3/teams/{teamId}"}

def fetch_participant(
self,
meeting_id: str,
participant_id: str,
tenant_id: str,
custom_headers=None,
raw=False,
**operation_config
):
"""Fetches Teams meeting participant details.

:param meeting_id: Teams meeting id
:type meeting_id: str
:param participant_id: Teams meeting participant id
:type participant_id: str
:param tenant_id: Teams meeting tenant id
:type tenant_id: str
:param dict custom_headers: headers that will be added to the request
:param bool raw: returns the direct response alongside the
deserialized response
:param operation_config: :ref:`Operation configuration
overrides<msrest:optionsforoperations>`.
:return: TeamsMeetingParticipant or ClientRawResponse if raw=true
:rtype: ~botframework.connector.teams.models.TeamsParticipantChannelAccount or
~msrest.pipeline.ClientRawResponse
:raises:
:class:`HttpOperationError<msrest.exceptions.HttpOperationError>`
"""

# Construct URL
url = self.fetch_participant.metadata["url"]
path_format_arguments = {
"meetingId": self._serialize.url("meeting_id", meeting_id, "str"),
"participantId": self._serialize.url(
"participant_id", participant_id, "str"
),
"tenantId": self._serialize.url("tenant_id", tenant_id, "str"),
}
url = self._client.format_url(url, **path_format_arguments)

# Construct parameters
query_parameters = {}

# Construct headers
header_parameters = {}
header_parameters["Accept"] = "application/json"
if custom_headers:
header_parameters.update(custom_headers)

# Construct and send request
request = self._client.get(url, query_parameters, header_parameters)
response = self._client.send(request, stream=False, **operation_config)

if response.status_code not in [200]:
raise HttpOperationError(self._deserialize, response)

deserialized = None

if response.status_code == 200:
deserialized = self._deserialize("TeamsMeetingParticipant", response)

if raw:
client_raw_response = ClientRawResponse(deserialized, response)
return client_raw_response

return deserialized

fetch_participant.metadata = {
"url": "/v1/meetings/{meetingId}/participants/{participantId}?tenantId={tenantId}"
}