Skip to content

Commit 2bfacd5

Browse files
Virtual-Joshjohnataylor
authored andcommitted
Merging Teams in so far (#454)
* initial commit for Teams work * initial commit for Teams * adding teams activity handler, team info, and teams channel account classes * adding conversation update scenario * fixing linting issues * updating classes to use standard attrs * cleaning up PR feedback * adding line * adding another blank line * adding mentions bot and fixing bug for resource response IDs * Threading helper workaround * Corrected case of "teams" folder name in core. Corrected __init__.py in schema so TeamsChannelAccount was defined. * adding mention bot updating mention bot cleaning up linter removing readme, removing self from on_error * resolving merge conflict * adding mention bot cleaning up linter * updating linting * adding mention bot updating mention bot cleaning up linter removing readme, removing self from on_error * resolving merge conflict * adding mention bot cleaning up linter * updating linting * Added 43.complex-dialog * Pinned dependencies in all libraries * adding activity update and delete * adding list for activities * cleaning up config * Pinned dependencies in libraries (missed some setup.py) * modify echo to work out of the box w/ ARM template * Added 47.inspection (#381) * Added 47.inspection, corrected README in 45.state-management * Changed the on_error function to be unbound for consistency. * ChoiceFactory.for_channel was erroneously returning a List instead of an Activity (#383) * Refactored to unbound on_error methods when accessing outer app.py va… (#385) * Refactored to unbound on_error methods when accessing outer app.py variables. * Removed unused imports * Added 16.proactive-messages (#413) * Added 19.custom-dialogs (#411) * Fix ChoicePrompt ListStyle.none when set via PromptOptions (#373) * fix ChoicePrompt none style when set via options * black compat * Added 18.bot-authentication (#419) * Added 17.multilingual-bot * Added 23.facebook-events sample * 23.facebook-events: on_error is now an unbound function * Partial 15.handling-attachments * Removing unnecesary encoding * Added 15.handling-attachments * 17.multilingual-bot suggested corrections * 15.handling-attachments suggested corrections * pylint and black, suggested corrections. * pylint and black changes. No logic changes. (#427) * Fixes #425: Using incorrect BotState (#426) * Added send_activities and updated the logic * pylint: Added send_activities and updated the logic * pylint: Added send_activities and updated the logic * black formatter: Added send_activities and updated the logic * Added 11.qnamaker (#429) * Added 40.timex resolution (#430) * Unfinished push until recognizers-text is updated. * Added 40.timex-resolution * Added 42.scaleout (#435) * Pinned pytest version (#438) * updating linting * fixing linting * initial commit for Teams work * initial commit for Teams * adding teams activity handler, team info, and teams channel account classes * adding conversation update scenario * fixing linting issues * updating classes to use standard attrs * cleaning up PR feedback * adding line * adding another blank line * Corrected case of "teams" folder name in core. Corrected __init__.py in schema so TeamsChannelAccount was defined. * removing extension file * resovling conflict * more merge conflict resolution * fixing linting * fixing conflicts * adding updated teams activity handler * updating None check * updating activity handler and fixing spacing issue * updating activity handler and tests * updating teams activity handler * removing constant * adding tests and removing constant * moving scenarios to root * updating attr check, using .seralize(), removing return * rerunnign black * updating names * updating loop to downcast * member not memeber * adding s
1 parent e5254b6 commit 2bfacd5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1747
-1
lines changed

libraries/botbuilder-core/botbuilder/core/bot_framework_adapter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
ConversationAccount,
1313
ConversationParameters,
1414
ConversationReference,
15-
ResourceResponse,
1615
TokenResponse,
16+
ResourceResponse,
1717
)
1818
from botframework.connector import Channels, EmulatorApiClient
1919
from botframework.connector.aio import ConnectorClient
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# coding=utf-8
2+
# --------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See License.txt in the project root for
5+
# license information.
6+
# --------------------------------------------------------------------------
7+
8+
from .teams_activity_handler import TeamsActivityHandler
9+
10+
__all__ = ["TeamsActivityHandler"]

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

Lines changed: 396 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from typing import List
2+
3+
import aiounittest
4+
from botbuilder.core import BotAdapter, TurnContext
5+
from botbuilder.core.teams import TeamsActivityHandler
6+
from botbuilder.schema import (
7+
Activity,
8+
ActivityTypes,
9+
ChannelAccount,
10+
ConversationReference,
11+
MessageReaction,
12+
ResourceResponse,
13+
)
14+
15+
16+
class TestingTeamsActivityHandler(TeamsActivityHandler):
17+
def __init__(self):
18+
self.record: List[str] = []
19+
20+
async def on_message_activity(self, turn_context: TurnContext):
21+
self.record.append("on_message_activity")
22+
return await super().on_message_activity(turn_context)
23+
24+
async def on_members_added_activity(
25+
self, members_added: ChannelAccount, turn_context: TurnContext
26+
):
27+
self.record.append("on_members_added_activity")
28+
return await super().on_members_added_activity(members_added, turn_context)
29+
30+
async def on_members_removed_activity(
31+
self, members_removed: ChannelAccount, turn_context: TurnContext
32+
):
33+
self.record.append("on_members_removed_activity")
34+
return await super().on_members_removed_activity(members_removed, turn_context)
35+
36+
async def on_message_reaction_activity(self, turn_context: TurnContext):
37+
self.record.append("on_message_reaction_activity")
38+
return await super().on_message_reaction_activity(turn_context)
39+
40+
async def on_reactions_added(
41+
self, message_reactions: List[MessageReaction], turn_context: TurnContext
42+
):
43+
self.record.append("on_reactions_added")
44+
return await super().on_reactions_added(message_reactions, turn_context)
45+
46+
async def on_reactions_removed(
47+
self, message_reactions: List[MessageReaction], turn_context: TurnContext
48+
):
49+
self.record.append("on_reactions_removed")
50+
return await super().on_reactions_removed(message_reactions, turn_context)
51+
52+
async def on_token_response_event(self, turn_context: TurnContext):
53+
self.record.append("on_token_response_event")
54+
return await super().on_token_response_event(turn_context)
55+
56+
async def on_event(self, turn_context: TurnContext):
57+
self.record.append("on_event")
58+
return await super().on_event(turn_context)
59+
60+
async def on_unrecognized_activity_type(self, turn_context: TurnContext):
61+
self.record.append("on_unrecognized_activity_type")
62+
return await super().on_unrecognized_activity_type(turn_context)
63+
64+
65+
class NotImplementedAdapter(BotAdapter):
66+
async def delete_activity(
67+
self, context: TurnContext, reference: ConversationReference
68+
):
69+
raise NotImplementedError()
70+
71+
async def send_activities(
72+
self, context: TurnContext, activities: List[Activity]
73+
) -> List[ResourceResponse]:
74+
raise NotImplementedError()
75+
76+
async def update_activity(self, context: TurnContext, activity: Activity):
77+
raise NotImplementedError()
78+
79+
80+
class TestTeamsActivityHandler(aiounittest.AsyncTestCase):
81+
async def test_message_reaction(self):
82+
# Note the code supports multiple adds and removes in the same activity though
83+
# a channel may decide to send separate activities for each. For example, Teams
84+
# sends separate activities each with a single add and a single remove.
85+
86+
# Arrange
87+
activity = Activity(
88+
type=ActivityTypes.message_reaction,
89+
reactions_added=[MessageReaction(type="sad")],
90+
)
91+
turn_context = TurnContext(NotImplementedAdapter(), activity)
92+
93+
# Act
94+
bot = TestingTeamsActivityHandler()
95+
await bot.on_turn(turn_context)
96+
97+
# Assert
98+
assert len(bot.record) == 2
99+
assert bot.record[0] == "on_message_reaction_activity"
100+
assert bot.record[1] == "on_reactions_added"

libraries/botbuilder-schema/botbuilder/schema/_connector_client_enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class ActivityTypes(str, Enum):
2727
end_of_conversation = "endOfConversation"
2828
event = "event"
2929
invoke = "invoke"
30+
invoke_response = "invokeResponse"
3031
delete_user_data = "deleteUserData"
3132
message_update = "messageUpdate"
3233
message_delete = "messageDelete"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from .team_info import TeamInfo
2+
from .notification_info import NotificationInfo
3+
from .tenant_info import TenantInfo
4+
from .channel_info import ChannelInfo
5+
from .teams_channel_data import TeamsChannelData
6+
from .teams_channel_account import TeamsChannelAccount
7+
8+
__all__ = [
9+
"TeamInfo",
10+
"ChannelInfo",
11+
"TeamsChannelData",
12+
"TeamsChannelAccount",
13+
"TenantInfo",
14+
"NotificationInfo",
15+
]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License. See License.txt in the project root for
3+
# license information.
4+
#
5+
# Code generated by Microsoft (R) AutoRest Code Generator.
6+
# Changes may cause incorrect behavior and will be lost if the code is
7+
# regenerated.
8+
9+
10+
class ChannelInfo(object):
11+
def __init__(self, id="", name=""):
12+
self.id = id
13+
self.name = name
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License. See License.txt in the project root for
3+
# license information.
4+
#
5+
# Code generated by Microsoft (R) AutoRest Code Generator.
6+
# Changes may cause incorrect behavior and will be lost if the code is
7+
# regenerated.
8+
9+
10+
class NotificationInfo:
11+
def __init__(self, alert: bool = False):
12+
self.alert = alert
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License. See License.txt in the project root for
3+
# license information.
4+
#
5+
# Code generated by Microsoft (R) AutoRest Code Generator.
6+
# Changes may cause incorrect behavior and will be lost if the code is
7+
# regenerated.
8+
9+
10+
class TeamInfo:
11+
def __init__(self, id="", name="", aadGroupId=""):
12+
self.id = id
13+
self.name = name
14+
self.aad_group_id = aadGroupId
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License. See License.txt in the project root for
3+
# license information.
4+
#
5+
# Code generated by Microsoft (R) AutoRest Code Generator.
6+
# Changes may cause incorrect behavior and will be lost if the code is
7+
# regenerated.
8+
9+
from botbuilder.schema import ChannelAccount
10+
11+
12+
class TeamsChannelAccount(ChannelAccount):
13+
def __init__(
14+
self,
15+
id="",
16+
name="",
17+
aad_object_id="",
18+
role="",
19+
given_name="",
20+
surname="",
21+
email="",
22+
userPrincipalName="",
23+
):
24+
super().__init__(
25+
**{"id": id, "name": name, "aad_object_id": aad_object_id, "role": role}
26+
)
27+
self.given_name = given_name
28+
self.surname = surname
29+
self.email = email
30+
# This isn't camel_cased because the JSON that makes this object isn't camel_case
31+
self.user_principal_name = userPrincipalName
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License. See License.txt in the project root for
3+
# license information.
4+
#
5+
# Code generated by Microsoft (R) AutoRest Code Generator.
6+
# Changes may cause incorrect behavior and will be lost if the code is
7+
# regenerated.
8+
9+
from botbuilder.schema.teams import ChannelInfo, TeamInfo, NotificationInfo, TenantInfo
10+
11+
12+
class TeamsChannelData:
13+
def __init__(
14+
self,
15+
channel: ChannelInfo = None,
16+
eventType="",
17+
team: TeamInfo = None,
18+
notification: NotificationInfo = None,
19+
tenant: TenantInfo = None,
20+
):
21+
self.channel = ChannelInfo(**channel) if channel is not None else ChannelInfo()
22+
# This is not camel case because the JSON that makes this object isn't
23+
self.event_type = eventType
24+
self.team = TeamInfo(**team) if team is not None else TeamInfo()
25+
self.notification = (
26+
NotificationInfo(**notification)
27+
if notification is not None
28+
else NotificationInfo()
29+
)
30+
self.tenant = TenantInfo(**tenant) if tenant is not None else TenantInfo()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License. See License.txt in the project root for
3+
# license information.
4+
#
5+
# Code generated by Microsoft (R) AutoRest Code Generator.
6+
# Changes may cause incorrect behavior and will be lost if the code is
7+
# regenerated.
8+
9+
10+
class TenantInfo:
11+
def __init__(self, id=""):
12+
self._id = id
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# EchoBot
2+
3+
Bot Framework v4 echo bot sample.
4+
5+
This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back.
6+
7+
## Running the sample
8+
- Clone the repository
9+
```bash
10+
git clone https://github.com/Microsoft/botbuilder-python.git
11+
```
12+
- Activate your desired virtual environment
13+
- Bring up a terminal, navigate to `botbuilder-python\samples\02.echo-bot` folder
14+
- In the terminal, type `pip install -r requirements.txt`
15+
- In the terminal, type `python app.py`
16+
17+
## Testing the bot using Bot Framework Emulator
18+
[Microsoft Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel.
19+
20+
- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
21+
22+
### Connect to bot using Bot Framework Emulator
23+
- Launch Bot Framework Emulator
24+
- Paste this URL in the emulator window - http://localhost:3978/api/messages
25+
26+
## Further reading
27+
28+
- [Bot Framework Documentation](https://docs.botframework.com)
29+
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
30+
- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import asyncio
5+
import sys
6+
from datetime import datetime
7+
from types import MethodType
8+
9+
from flask import Flask, request, Response
10+
from botbuilder.core import (
11+
BotFrameworkAdapterSettings,
12+
TurnContext,
13+
BotFrameworkAdapter,
14+
)
15+
from botbuilder.schema import Activity, ActivityTypes
16+
17+
from bots import ActivitiyUpdateAndDeleteBot
18+
19+
# Create the loop and Flask app
20+
LOOP = asyncio.get_event_loop()
21+
APP = Flask(__name__, instance_relative_config=True)
22+
APP.config.from_object("config.DefaultConfig")
23+
24+
# Create adapter.
25+
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
26+
SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"])
27+
ADAPTER = BotFrameworkAdapter(SETTINGS)
28+
29+
30+
# Catch-all for errors.
31+
async def on_error( # pylint: disable=unused-argument
32+
self, context: TurnContext, error: Exception
33+
):
34+
# This check writes out errors to console log .vs. app insights.
35+
# NOTE: In production environment, you should consider logging this to Azure
36+
# application insights.
37+
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
38+
39+
# Send a message to the user
40+
await context.send_activity("The bot encountered an error or bug.")
41+
await context.send_activity(
42+
"To continue to run this bot, please fix the bot source code."
43+
)
44+
# Send a trace activity if we're talking to the Bot Framework Emulator
45+
if context.activity.channel_id == "emulator":
46+
# Create a trace activity that contains the error object
47+
trace_activity = Activity(
48+
label="TurnError",
49+
name="on_turn_error Trace",
50+
timestamp=datetime.utcnow(),
51+
type=ActivityTypes.trace,
52+
value=f"{error}",
53+
value_type="https://www.botframework.com/schemas/error",
54+
)
55+
# Send a trace activity, which will be displayed in Bot Framework Emulator
56+
await context.send_activity(trace_activity)
57+
58+
59+
ADAPTER.on_turn_error = MethodType(on_error, ADAPTER)
60+
ACTIVITY_IDS = []
61+
# Create the Bot
62+
BOT = ActivitiyUpdateAndDeleteBot(ACTIVITY_IDS)
63+
64+
# Listen for incoming requests on /api/messages.s
65+
@APP.route("/api/messages", methods=["POST"])
66+
def messages():
67+
# Main bot message handler.
68+
if "application/json" in request.headers["Content-Type"]:
69+
body = request.json
70+
else:
71+
return Response(status=415)
72+
73+
activity = Activity().deserialize(body)
74+
auth_header = (
75+
request.headers["Authorization"] if "Authorization" in request.headers else ""
76+
)
77+
78+
try:
79+
task = LOOP.create_task(
80+
ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
81+
)
82+
LOOP.run_until_complete(task)
83+
return Response(status=201)
84+
except Exception as exception:
85+
raise exception
86+
87+
88+
if __name__ == "__main__":
89+
try:
90+
APP.run(debug=False, port=APP.config["PORT"]) # nosec debug
91+
except Exception as exception:
92+
raise exception
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .activity_update_and_delete_bot import ActivitiyUpdateAndDeleteBot
5+
6+
__all__ = ["ActivitiyUpdateAndDeleteBot"]

0 commit comments

Comments
 (0)