8000 Merge pull request #431 from microsoft/josh/ActivityHandler · yosshy/botbuilder-python@a590910 · GitHub
[go: up one dir, main page]

Skip to content

Commit a590910

Browse files
authored
Merge pull request microsoft#431 from microsoft/josh/ActivityHandler
Conversation Update Scenario
2 parents 1b60528 + f8f3ea5 commit a590910

File tree

20 files changed

+570
-2
lines changed

20 files changed

+570
-2
lines changed
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"]
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from http import HTTPStatus
5+
from botbuilder.schema import ActivityTypes, ChannelAccount
6+
from botbuilder.core.turn_context import TurnContext
7+
from botbuilder.core import ActivityHandler, MessageFactory, InvokeResponse
8+
from botbuilder.schema.teams import (
9+
TeamInfo,
10+
ChannelInfo,
11+
TeamsChannelData,
12+
TeamsChannelAccount,
13+
)
14+
from botframework.connector import Channels
15+
16+
class TeamsActivityHandler(ActivityHandler):
17+
async def on_turn(self, turn_context: TurnContext):
18+
if turn_context is None:
19+
raise TypeError("ActivityHandler.on_turn(): turn_context cannot be None.")
20+
21+
if hasattr(turn_context, "activity") and turn_context.activity is None:
22+
raise TypeError(
23+
"ActivityHandler.on_turn(): turn_context must have a non-None activity."
24+
)
25+
26+
if (
27+
hasattr(turn_context.activity, "type")
28+
and turn_context.activity.type is None
29+
):
30+
raise TypeError(
31+
"ActivityHandler.on_turn(): turn_context activity must have a non-None type."
32+
)
33+
34+
if turn_context.activity.type == ActivityTypes.invoke:
35+
invoke_response = await self.on_invoke_activity(turn_context)
36+
else:
37+
await super().on_turn(turn_context)
38+
39+
async def on_invoke_activity(self, turn_context: TurnContext):
40+
try:
41+
if (
42+
not turn_context.activity.name
43+
and turn_context.activity.channel_id == Channels.Msteams
44+
):
45+
return # await on_teams_card_action_invoke_activity(turn_context)
46+
47+
turn_context.send_activity(MessageFactory.text("working"))
48+
except:
49+
return
50+
51+
async def on_conversation_update_activity(self, turn_context: TurnContext):
52+
if turn_context.activity.channel_id == Channels.ms_teams:
53+
channel_data = TeamsChannelData(**turn_context.activity.channel_data)
54+
55+
if turn_context.activity.members_added:
56+
return await self.on_teams_members_added_dispatch_activity(
57+
turn_context.activity.members_added, channel_data.team, turn_context
58+
)
59+
60+
if turn_context.activity.members_removed:
61+
return await self.on_teams_members_removed_dispatch_activity(
62+
turn_context.activity.members_removed,
63+
channel_data.team,
64+
turn_context,
65+
)
66+
67+
if channel_data:
68+
if channel_data.event_type == "channelCreated":
69+
return await self.on_teams_channel_created_activity(
70+
channel_data.channel, channel_data.team, turn_context
71+
)
72+
if channel_data.event_type == "channelDeleted":
73+
return await self.on_teams_channel_deleted_activity(
74+
channel_data.channel, channel_data.team, turn_context
75+
)
76+
if channel_data.event_type == "channelRenamed":
77+
return await self.on_teams_channel_renamed_activity(
78+
channel_data.channel, channel_data.team, turn_context)
79+
if channel_data.event_type == "teamRenamed":
80+
return await self.on_teams_team_renamed_activity(
81+
channel_data.team, turn_context
82+
)
83+
return await super().on_conversation_update_activity(turn_context)
84+
85+
return await super().on_conversation_update_activity(turn_context)
86+
87+
async def on_teams_channel_created_activity( # pylint: disable=unused-argument
88+
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
89+
):
90+
return
91+
92+
async def on_teams_team_renamed_activity( # pylint: disable=unused-argument
93+
self, team_info: TeamInfo, turn_context: TurnContext
94+
):
95+
return
96+
97+
async def on_teams_members_added_dispatch_activity( # pylint: disable=unused-argument
98+
self,
99+
members_added: [ChannelAccount],
100+
team_info: TeamInfo,
101+
turn_context: TurnContext,
102+
):
103+
"""
104+
team_members = {}
105+
team_members_added = []
106+
for member in members_added:
107+
if member.additional_properties != {}:
108+
team_members_added.append(TeamsChannelAccount(member))
109+
else:
110+
if team_members == {}:
111+
result = await TeamsInfo.get_members_async(turn_context)
112+
team_members = { i.id : i for i in result }
113+
114+
if member.id in team_members:
115+
team_members_added.append(member)
116+
else:
117+
newTeamsChannelAccount = TeamsChannelAccount(
118+
id=member.id,
119+
name = member.name,
120+
aad_object_id = member.aad_object_id,
121+
role = member.role
122+
)
123+
team_members_added.append(newTeamsChannelAccount)
124+
125+
return await self.on_teams_members_added_activity(teams_members_added, team_info, turn_context)
126+
"""
127+
for member in members_added:
128+
new_account_json = member.__dict__
129+
del new_account_json["additional_properties"]
130+
member = TeamsChannelAccount(**new_account_json)
131+
return await self.on_teams_members_added_activity(members_added, turn_context)
132+
133+
async def on_teams_members_added_activity(
134+
self, teams_members_added: [TeamsChannelAccount], turn_context: TurnContext
135+
):
136+
for member in teams_members_added:
137+
member = ChannelAccount(member)
138+
return super().on_members_added_activity(teams_members_added, turn_context)
139+
140+
async def on_teams_members_removed_dispatch_activity( # pylint: disable=unused-argument
141+
self,
142+
members_removed: [ChannelAccount],
143+
team_info: TeamInfo,
144+
turn_context: TurnContext,
145+
):
146+
teams_members_removed = []
147+
for member in members_removed:
148+
new_account_json = member.__dict__
149+
del new_account_json["additional_properties"]
150+
teams_members_removed.append(TeamsChannelAccount(**new_account_json))
151+
152+
return await self.on_teams_members_removed_activity(
153+
teams_members_removed, turn_context
154+
)
155+
156+
async def on_teams_members_removed_activity(
157+
self, teams_members_removed: [TeamsChannelAccount], turn_context: TurnContext
158+
):
159+
members_removed = [ChannelAccount(i) for i in teams_members_removed]
160+
return super().on_members_removed_activity(members_removed, turn_context)
161+
162+
async def on_teams_channel_deleted_activity( # pylint: disable=unused-argument
163+
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
164+
):
165+
return # Task.CompleteTask
166+
167+
async def on_teams_channel_renamed_activity( # pylint: disable=unused-argument
168+
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
169+
):
170+
return # Task.CompleteTask
171+
172+
async def on_teams_team_reanamed_async( # pylint: disable=unused-argument
173+
self, team_info: TeamInfo, turn_context: TurnContext
174+
):
175+
return # Task.CompleteTask
176+
177+
@staticmethod
178+
def _create_invoke_response(body: object = None) -> InvokeResponse:
179+
return InvokeResponse(status=int(HTTPStatus.OK), body=body)
180+
181+
class _InvokeResponseException(Exception):
182+
def __init__(self, status_code: HTTPStatus, body: object = None):
183+
super().__init__()
184+
self._status_code = status_code
185+
self._body = body
186+
187+
def create_invoke_response(self) -> InvokeResponse:
188+
return InvokeResponse(status=int(self._status_code), body=self._body)

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

Whitespace-only changes.
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 ConversationUpdateBot
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+
61+
# Create the Bot
62+
BOT = ConversationUpdateBot()
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 .conversation_update_bot import ConversationUpdateBot
5+
6+
__all__ = ["ConversationUpdateBot"]
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.core import MessageFactory, TurnContext
5+
from botbuilder.core.teams import TeamsActivityHandler
6+
from botbuilder.schema.teams import ChannelInfo, TeamInfo, TeamsChannelAccount
7+
8+
9+
class ConversationUpdateBot(TeamsActivityHandler):
10+
async def on_teams_channel_created_activity(
11+
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
12+
):
13+
return await turn_context.send_activity(
14+
MessageFactory.text(
15+
f"The new channel is {channel_info.name}. The channel id is {channel_info.id}"
16+
)
17+
)
18+
19+
async def on_teams_channel_deleted_activity(
20+
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
21+
):
22+
return await turn_context.send_activity(
23+
MessageFactory.text(f"The deleted channel is {channel_info.name}")
24+
)
25+
26+
async def on_teams_channel_renamed_activity(
27+
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
28+
):
29+
return await turn_context.send_activity(
30+
MessageFactory.text(f"The new channel name is {channel_info.name}")
31+
)
32+
33+
async def on_teams_team_renamed_activity(
34+
self, team_info: TeamInfo, turn_context: TurnContext
35+
):
36+
return await turn_context.send_activity(
37+
MessageFactory.text(f"The new team name is {team_info.name}")
38+
)
39+
40+
async def on_teams_members_added_activity(
41+
self, teams_members_added: [TeamsChannelAccount], turn_context: TurnContext
42+
):
43+
for member in teams_members_added:
44+
await turn_context.send_activity(
45+
MessageFactory.text(f"Welcome your new team member {member.id}")
46+
)
47+
return
48+
49+
async def on_teams_members_removed_activity(
50+
self, teams_members_removed: [TeamsChannelAccount], turn_context: TurnContext
51+
):
52+
for member in teams_members_removed:
53+
await turn_context.send_activity(
54+
MessageFactory.text(f"Say goodbye to your team member {member.id}")
55+
)
56+
return

0 commit comments

Comments
 (0)
0