8000 Added 15.handling-attachments · snifhex/botbuilder-python@0b17bde · GitHub
[go: up one dir, main page]

Skip to content

Commit 0b17bde

Browse files
author
Tracy Boehrer
committed
Added 15.handling-attachments
1 parent 6e69cb6 commit 0b17bde

File tree

2 files changed

+102
-28
lines changed

2 files changed

+102
-28
lines changed
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
# EchoBot
1+
# Handling Attachments
22

3-
Bot Framework v4 echo bot sample.
3+
Bot Framework v4 handling attachments bot sample
44

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.
5+
This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to send outgoing attachments and how to save attachments to disk.
66

77
## Running the sample
88
- Clone the repository
99
```bash
1010
git clone https://github.com/Microsoft/botbuilder-python.git
1111
```
1212
- Activate your desired virtual environment
13-
- Bring up a terminal, navigate to `botbuilder-python\samples\02.echo-bot` folder
13+
- Bring up a terminal, navigate to `botbuilder-python\samples\15.handling-attachments` folder
1414
- In the terminal, type `pip install -r requirements.txt`
1515
- In the terminal, type `python app.py`
1616

@@ -21,10 +21,18 @@ git clone https://github.com/Microsoft/botbuilder-python.git
2121

2222
### Connect to bot using Bot Framework Emulator
2323
- Launch Bot Framework Emulator
24+
- File -> Open Bot
2425
- Paste this URL in the emulator window - http://localhost:3978/api/messages
2526

27+
## Attachments
28+
29+
A message exchange between user and bot may contain cards and media attachments, such as images, video, audio, and files.
30+
The types of attachments that may be sent and received varies by channel. Additionally, a bot may also receive file attachments.
31+
2632
## Further reading
2733

2834
- [Bot Framework Documentation](https://docs.botframework.com)
2935
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
3036
- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)
37+
- [Attachments](https://docs.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-send-receive-attachments?view=azure-bot-service-4.0)
38+
- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0)

samples/15.handling-attachments/bots/attachments_bot.py

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,32 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
3+
34
import os
45
import urllib.parse
56
import urllib.request
67
import base64
8+
import json
79

810
from botbuilder.core import ActivityHandler, MessageFactory, TurnContext, CardFactory
9-
from botbuilder.schema import ChannelAccount, HeroCard, CardAction, ActivityTypes, Attachment, AttachmentData, Activity, \
11+
from botbuilder.schema import (
12+
ChannelAccount,
13+
HeroCard,
14+
CardAction,
15+
ActivityTypes,
16+
Attachment,
17+
AttachmentData,
18+
Activity,
1019
ActionTypes
11-
import json
20+
)
21+
22+
"""
23+
Represents a bot that processes incoming activities.
24+
For each user interaction, an instance of this class is created and the OnTurnAsync method is called.
25+
This is a Transient lifetime service. Transient lifetime services are created
26+
each time they're requested. For each Activity received, a new instance of this
27+
class is created. Objects that are expensive to construct, or have a lifetime
28+
beyond the single turn, should be carefully managed.
29+
"""
1230

1331

1432
class AttachmentsBot(ActivityHandler):
@@ -24,39 +42,62 @@ async def on_message_activity(self, turn_context: TurnContext):
2442
await self._display_options(turn_context)
2543

2644
async def _send_welcome_message(self, turn_context: TurnContext):
45+
"""
46+
Greet the user and give them instructions on how to interact with the bot.
47+
:param turn_context:
48+
:return:
49+
"""
2750
for member in turn_context.activity.members_added:
2851
if member.id != turn_context.activity.recipient.id:
2952
await turn_context.send_activity(f"Welcome to AttachmentsBot {member.name}. This bot will introduce "
3053
f"you to Attachments. Please select an option")
3154
await self._display_options(turn_context)
3255

3356
async def _handle_incoming_attachment(self, turn_context: TurnContext):
57+
"""
58+
Handle attachments uploaded by users. The bot receives an Attachment in an Activity.
59+
The activity has a List of attachments.
60+
Not all channels allow users to upload files. Some channels have restrictions
61+
on file type, size, and other attributes. Consult the documentation for the channel for
62+
more information. For example Skype's limits are here
63+
<see ref="https://support.skype.com/en/faq/FA34644/skype-file-sharing-file-types-size-and-time-limits"/>.
64+
:param turn_context:
65+
:return:
66+
"""
3467
for attachment in turn_context.activity.attachments:
3568
attachment_info = await self._download_attachment_and_write(attachment)
36-
await turn_context.send_activity(
37-
f"Attachment {attachment_info['filename']} has been received to {attachment_info['local_path']}")
69+
if "filename" in attachment_info:
70+
await turn_context.send_activity(
71+
f"Attachment {attachment_info['filename']} has been received to {attachment_info['local_path']}")
3872

3973
async def _download_attachment_and_write(self, attachment: Attachment) -> dict:
40-
url = attachment.content_url
41-
42-
local_filename = os.path.join(os.getcwd(), attachment.name)
43-
74+
"""
75+
Retrieve the attachment via the attachment's contentUrl.
76+
:param attachment:
77+
:return: Dict: keys "filename", "local_path"
78+
"""
4479
try:
45-
response = urllib.request.urlopen("http://www.python.org")
80+
response = urllib.request.urlopen(attachment.content_url)
4681
headers = response.info()
82+
83+
# If user uploads JSON file, this prevents it from being written as
84+
# "{"type":"Buffer","data":[123,13,10,32,32,34,108..."
4785
if headers["content-type"] == "application/json":
48-
data = json.load(response.data)
49-
with open(local_filename, "w") as out_file:
50-
out_file.write(data)
51-
52-
return {
53-
"filename": attachment.name,
54-
"local_path": local_filename
55-
}
86+
data = bytes(json.load(response)["data"])
5687
else:
57-
return None
58-
except:
59-
return None
88+
data = response.read()
89+
90+
local_filename = os.path.join(os.getcwd(), attachment.name)
91+
with open(local_filename, "wb") as out_file:
92+
out_file.write(data)
93+
94+
return {
95+
"filename": attachment.name,
96+
"local_path": local_filename
97+
}
98+
except Exception as e:
99+
print(e)
100+
return {}
60101

61102
async def _handle_outgoing_attachment(self, turn_context: TurnContext):
62103
reply = Activity(
@@ -79,6 +120,15 @@ async def _handle_outgoing_attachment(self, turn_context: TurnContext):
79120
await turn_context.send_activity(reply)
80121

81122
async def _display_options(self, turn_context: TurnContext):
123+
"""
124+
Create a HeroCard with options for the user to interact with the bot.
125+
:param turn_context:
126+
:return:
127+
"""
128+
129+
# Note that some channels require different values to be used in order to get buttons to display text.
130+
# In this code the emulator is accounted for with the 'title' parameter, but in other channels you may
131+
# need to provide a value for other parameters like 'text' or 'displayText'.
82132
card = HeroCard(
83133
text="You can upload an image or select one of the following choices",
84134
buttons=[
@@ -104,9 +154,17 @@ async def _display_options(self, turn_context: TurnContext):
104154
await turn_context.send_activity(reply)
105155

106156
def _get_inline_attachment(self) -> Attachment:
157+
"""
158+
Creates an inline attachment sent from the bot to the user using a base64 string.
159+
Using a base64 string to send an attachment will not work on all channels.
160+
Additionally, some channels will only allow certain file types to be sent this way.
161+
For example a .png file may work but a .pdf file may not on some channels.
162+
Please consult the channel documentation for specifics.
163+
:return: Attachment
164+
"""
107165
file_path = os.path.join(os.getcwd(), "resources/architecture-resize.png")
108166
with open(file_path, "rb") as in_file:
109-
base64_image = base64.b64encode(in_file.read())
167+
base64_image = base64.b64encode(in_file.read()).decode()
110168

111169
return Attachment(
112170
name="architecture-resize.png",
@@ -115,6 +173,11 @@ def _get_inline_attachment(self) -> Attachment:
115173
)
116174

117175
async def _get_upload_attachment(self, turn_context: TurnContext) -> Attachment:
176+
"""
177+
Creates an "Attachment" to be sent from the bot to the user from an uploaded file.
178+
:param turn_context:
179+
:return: Attachment
180+
"""
118181
with open(os.path.join(os.getcwd(), "resources/architecture-resize.png"), "rb") as in_file:
119182
image_data = in_file.read()
120183

@@ -125,7 +188,6 @@ async def _get_upload_attachment(self, turn_context: TurnContext) -> Attachment:
125188
AttachmentData(
126189
name="architecture-resize.png",
127190
original_base64=image_data,
128-
thumbnail_base64=image_data,
129191
type="image/png"
130192
)
131193
)
@@ -134,7 +196,7 @@ async def _get_upload_attachment(self, turn_context: TurnContext) -> Attachment:
134196
attachment_uri = \
135197
base_uri \
136198
+ ("" if base_uri.endswith("/") else "/") \
137-
+ f"v3/attachments/${urllib.parse.urlencode(response.id)}/views/original"
199+
+ f"v3/attachments/{response.id}/views/original"
138200

139201
return Attachment(
140202
name="architecture-resize.png",
@@ -143,8 +205,12 @@ async def _get_upload_attachment(self, turn_context: TurnContext) -> Attachment:
143205
)
144206

145207
def _get_internet_attachment(self) -> Attachment:
208+
"""
209+
Creates an Attachment to be sent from the bot to the user from a HTTP URL.
210+
:return: Attachment
211+
"""
146212
return Attachment(
147-
name="Resources\architecture-resize.png",
213+
name="architecture-resize.png",
148214
content_type="image/png",
149215
content_url="https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png"
150216
)

0 commit comments

Comments
 (0)
0