Skip to content
Merged
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

- **MSAL Token Credential**: Added `MsalTokenCredential`, an Azure Core-compatible asynchronous token credential backed by MSAL, for authenticating Azure SDK clients that accept an `AsyncTokenCredential`.
- **Authorization Token Credentials**: Added `AsyncTokenCredential` access through `AccessTokenProviderBase` and `Authorization`, including user-token refresh and exchange flows.
- **Channel Feature Support Helpers**: Added `Channels` helper methods (e.g. `supports_card_actions`, `supports_suggested_actions`, `supports_video_card`, `supports_receipt_card`, `supports_thumbnail_card`, `supports_audio_card`, `supports_animation_card`, `supports_create_conversation`, `supports_update_activity`, `supports_delete_activity`) for checking per-channel feature support, accepting `Channels`, `ChannelId`, and plain string inputs (#580)

## Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from __future__ import annotations

from enum import Enum
from typing_extensions import Self

from .channel_id import ChannelId


class Channels(str, Enum):
"""
Ids of channels supported by ABS.
"""

"""Agents channel."""
agents = "agents"
"""Agents channel."""

alexa = "alexa"
"""Alexa channel."""

console = "console"
"""Console channel."""
Expand Down Expand Up @@ -43,7 +49,7 @@ class Channels(str, Enum):
line = "line"
"""Line channel."""

ms_teams = "msteams"
msteams = "msteams"
"""MS Teams channel."""

skype = "skype"
Expand All @@ -58,6 +64,9 @@ class Channels(str, Enum):
sms = "sms"
"""SMS (Twilio) channel."""

twilio = "twilio-sms"
"""Twilio channel."""

telegram = "telegram"
"""Telegram channel."""

Expand All @@ -70,20 +79,35 @@ class Channels(str, Enum):
copilot_studio = "pva-studio"
"""Microsoft Copilot Studio channel."""

# TODO: validate the need of Self annotations in the following methods
ms_teams = "msteams"
"""Deprecated alias for :attr:`msteams`. Kept for backwards compatibility."""

@staticmethod
def supports_suggested_actions(channel_id: Self, button_cnt: int = 100) -> bool:
"""Determine if a number of Suggested Actions are supported by a Channel.
def _normalize_channel_id(channel_id: str | Channels | ChannelId) -> str:
"""Normalize a channel ID to obtain the parent channel.

Args:
channel_id (str): The Channel to check the if Suggested Actions are supported in.
button_cnt (int, Optional): Defaults to 100. The number of Suggested Actions to check for the Channel.
:param channel_id: The channel ID to normalize.
:returns: The canonical string representation of the channel ID.
"""
return ChannelId(channel_id).channel

Returns:
bool: True if the Channel supports the button_cnt total Suggested Actions, False if the Channel does not
support that number of Suggested Actions.
@staticmethod
def supports_suggested_actions(
channel_id: str, button_cnt: int = 100, conversation_type: str | None = None
) -> bool:
"""Determine if a number of Suggested Actions are supported by a Channel.

:param channel_id: The ID of the channel to check for support of Suggested Actions.
:param button_cnt: The number of Suggested Actions to check for the Channel.
:param conversation_type: The type of conversation, if applicable.
:returns: True if the Channel supports the button_cnt total Suggested Actions, False if the Channel does not support that number of Suggested Actions.
"""

channel_id = Channels._normalize_channel_id(channel_id)

if channel_id == Channels.msteams:
return conversation_type == "personal" and button_cnt <= 3

max_actions = {
# https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies
Channels.facebook: 10,
Expand All @@ -105,60 +129,167 @@ def supports_suggested_actions(channel_id: Self, button_cnt: int = 100) -> bool:
)

@staticmethod
def supports_card_actions(channel_id: Self, button_cnt: int = 100) -> bool:
def supports_card_actions(channel_id: str, button_cnt: int = 100) -> bool:
"""Determine if a number of Card Actions are supported by a Channel.

Args:
channel_id (str): The Channel to check if the Card Actions are supported in.
button_cnt (int, Optional): Defaults to 100. The number of Card Actions to check for the Channel.

Returns:
bool: True if the Channel supports the button_cnt total Card Actions, False if the Channel does not support
that number of Card Actions.
:param button_cnt: The number of Card Actions to check for the Channel.
:returns: True if the Channel supports the button_cnt total Card Actions, False if the Channel does not support that number of Card Actions.
"""

channel = Channels._normalize_channel_id(channel_id)

max_actions = {
Channels.facebook: 3,
Channels.skype: 3,
Channels.ms_teams: 3,
Channels.msteams: 50,
Channels.line: 99,
Channels.slack: 100,
Channels.telegram: 100,
Channels.emulator: 100,
Channels.direct_line: 100,
Channels.direct_line_speech: 100,
Channels.webchat: 100,
Channels.cortana: 100,
}
return (
button_cnt <= max_actions[channel_id]
if channel_id in max_actions
else False
return button_cnt <= max_actions[channel] if channel in max_actions else False

@staticmethod
def supports_video_card(channel_id: str) -> bool:
"""Determine if a Channel supports Video Cards.

:param channel_id: The Channel to check for Video Card support.
:returns: True if the Channel supports Video Cards, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel not in (
Channels.alexa,
Channels.msteams,
Channels.twilio,
)

@staticmethod
def has_message_feed(_: str) -> bool:
"""Determine if a Channel has a Message Feed.
def supports_receipt_card(channel_id: str) -> bool:
"""Determine if a Channel supports Receipt Cards.

:param channel_id: The Channel to check for Receipt Card support.
:returns: True if the Channel supports Receipt Cards, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel not in (
Channels.alexa,
Channels.groupme,
Channels.msteams,
Channels.twilio,
)

Args:
channel_id (str): The Channel to check for Message Feed.
@staticmethod
def supports_thumbnail_card(channel_id: str) -> bool:
"""Determine if a Channel supports Thumbnail Cards.

Returns:
bool: True if the Channel has a Message Feed, False if it does not.
:param channel_id: The Channel to check for Thumbnail Card support.
:returns: True if the Channel supports Thumbnail Cards, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel not in (
Channels.alexa,
Channels.groupme,
Channels.line,
Channels.slack,
Channels.twilio,
)

@staticmethod
def supports_audio_card(channel_id: str) -> bool:
"""Determine if a Channel supports Audio Cards.

return True
:param channel_id: The Channel to check for Audio Card support.
:returns: True if the Channel supports Audio Cards, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel not in (
Channels.alexa,
Channels.msteams,
Channels.twilio,
Channels.email,
Channels.groupme,
Channels.line,
Channels.slack,
Channels.telegram,
)

@staticmethod
def max_action_title_length( # pylint: disable=unused-argument
channel_id: Self,
) -> int:
"""Maximum length allowed for Action Titles.
def supports_animation_card(channel_id: str) -> bool:
"""Determine if a Channel supports Animation Cards.

Args:
channel_id (str): The Channel to determine Maximum Action Title Length.
:param channel_id: The Channel to check for Animation Card support.
:returns: True if the Channel supports Animation Cards, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel not in (
Channels.alexa,
Channels.msteams,
Channels.email,
Channels.groupme,
Channels.twilio,
)

Returns:
int: The total number of characters allowed for an Action Title on a specific Channel.
@staticmethod
def has_message_feed(channel_id: str) -> bool:
"""Determine if a Channel has a Message Feed.
:param channel_id: The Channel to check for Message Feed support.
:returns: True if the Channel has a Message Feed, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel != Channels.cortana

@staticmethod
def max_action_title_length(channel_id: str) -> int:
"""Maximum length allowed for Action Titles.
:param channel_id: The Channel to check for maximum Action Title length.
:returns: The maximum number of characters allowed for an Action Title.
"""
return 20

@staticmethod
def supports_create_conversation(channel_id: str) -> bool:
"""Determine if a Channel supports creating a conversation.

:param channel_id: The Channel to check for create conversation support.
:returns: True if the Channel supports creating a conversation, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel in (
Channels.email,
Channels.facebook,
Channels.groupme,
Channels.kik,
Channels.line,
Channels.msteams,
Channels.slack,
Channels.sms,
Channels.telegram,
)

@staticmethod
def supports_update_activity(channel_id: str) -> bool:
"""Determine if a Channel supports updating activities.

:param channel_id: The Channel to check for update activity support.
:returns: True if the Channel supports updating activities, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel == Channels.msteams

@staticmethod
def supports_delete_activity(channel_id: str) -> bool:
"""Determine if a Channel supports deleting activities.

:param channel_id: The Channel to check for delete activity support.
:returns: True if the Channel supports deleting activities, False if it does not.
"""
channel = Channels._normalize_channel_id(channel_id)
return channel in (
Channels.msteams,
Channels.slack,
Channels.telegram,
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,7 @@ def supports_suggested_actions(channel_id: str, button_cnt: int = 100) -> bool:
bool: True if the Channel supports the button_cnt total Suggested Actions, False if the Channel does not
support that number of Suggested Actions.
"""
if isinstance(channel_id, Channels):
channel_id = channel_id.value

max_actions = {
# https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies
Channels.facebook.value: 10,
Channels.skype.value: 10,
# https://developers.line.biz/en/reference/messaging-api/#items-object
Channels.line.value: 13,
# https://dev.kik.com/#/docs/messaging#text-response-object
Channels.kik.value: 20,
Channels.telegram.value: 100,
Channels.emulator.value: 100,
Channels.direct_line.value: 100,
Channels.direct_line_speech.value: 100,
Channels.webchat.value: 100,
}
return (
button_cnt <= max_actions[channel_id]
if channel_id in max_actions
else False
)
return Channels.supports_suggested_actions(channel_id, button_cnt)

@staticmethod
def supports_card_actions(channel_id: str, button_cnt: int = 100) -> bool:
Expand All @@ -57,29 +36,10 @@ def supports_card_actions(channel_id: str, button_cnt: int = 100) -> bool:
bool: True if the Channel supports the button_cnt total Card Actions, False if the Channel does not support
that number of Card Actions.
"""
if isinstance(channel_id, Channels):
channel_id = channel_id.value

max_actions = {
Channels.facebook.value: 3,
Channels.skype.value: 3,
Channels.ms_teams.value: 3,
Channels.line.value: 99,
Channels.slack.value: 100,
Channels.telegram.value: 100,
Channels.emulator.value: 100,
Channels.direct_line.value: 100,
Channels.direct_line_speech.value: 100,
Channels.webchat.value: 100,
}
return (
button_cnt <= max_actions[channel_id]
if channel_id in max_actions
else False
)
return Channels.supports_card_actions(channel_id, button_cnt)
Comment thread
rodrigobr-msft marked this conversation as resolved.
Resolved

@staticmethod
def has_message_feed(_: str) -> bool:
def has_message_feed(channel_id: str) -> bool:
"""Determine if a Channel has a Message Feed.

Args:
Expand All @@ -88,8 +48,7 @@ def has_message_feed(_: str) -> bool:
Returns:
bool: True if the Channel has a Message Feed, False if it does not.
"""

return True
return Channels.has_message_feed(channel_id)

@staticmethod
def get_channel_id(turn_context: TurnContext) -> str:
Expand All @@ -106,7 +65,7 @@ def get_channel_id(turn_context: TurnContext) -> str:
return ""

@staticmethod
def max_action_title_length( # pylint: disable=unused-argument
def max_action_title_length(
channel_id: str,
) -> int:
"""Maximum length allowed for Action Titles.
Expand All @@ -117,5 +76,4 @@ def max_action_title_length( # pylint: disable=unused-argument
Returns:
int: The total number of characters allowed for an Action Title on a specific Channel.
"""

return 20
return Channels.max_action_title_length(channel_id)
Loading