Skip to content

Commit

Permalink
[Node-SendAttachment] Updates to show different use cases
Browse files Browse the repository at this point in the history
- Inline image
- Upload image to Connector API
- External (Internet hosted) image
  • Loading branch information
pcostantini committed Jan 23, 2017
1 parent 51ed21e commit 6bb4763
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 24 deletions.
108 changes: 95 additions & 13 deletions Node/core-SendAttachment/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,115 @@ Many messaging channels provide the ability to attach richer objects. Bot Builde
* **Media and Files**: Basic files can be sent by setting [contentType](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html#contenttype) to the MIME type of the file and then passing a link to the file in [contentUrl](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html#contenturl).
* **Cards and Keyboards**: A rich set of visual cards and custom keyboards can by setting [contentType](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html#contenttype) to the cards type and then passing the JSON for the card in [content](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html#content). If you use one of the rich card builder classes like [HeroCard](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.herocard.html) the attachment will automatically filled in for you.

As a developer, you have three ways to send the attachment. The attachment can be:
- An inline file, by encoding the file as base64 and use it in the contentUrl
- A file uploaded to the channel's store via the Connection API, then using the attachmentId to create the contentUrl
- An externally hosted file, by just specifying the Url of the file (it should be publicly accessible)

#### Attaching the image inline

It consists on sending the file contents, encoded in base64, along with the message payload. This option works for small files, like icon size images.
You'll need to encode file's content, then set the attachment's `contentUrl` as follows:

````
data:image/png;base64,iVBORw0KGgo…
````

Checkout [app.js](./app.js#L64-L73) to see how to convert a file read using `fs.readFile()` and then create the message attachment.

````JavaScript
function (session) {

// Create and send attachment
var attachment = {
contentUrl: "https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png",
contentType: "image/png",
name: "BotFrameworkOverview.png"
};
fs.readFile('./images/small-image.png', (err, data) => {
var contentType = 'image/png';
var base64 = Buffer.from(data).toString('base64');

var msg = new builder.Message(session)
.addAttachment(attachment);
.addAttachment({
contentUrl: util.format('data:%s;base64,%s', contentType, base64),
contentType: contentType,
name: 'BotFrameworkLogo.png'
});

session.send(msg);
}
});
````

#### Uploading the file via the Connector API

This option should be used when the file to send is less than 256Kb in size when encoded to base64. A good scenario are images generated based on user input.
It does require a few more steps than the other methods, but leverages the channels store to store the file:

0. Read (or generate) the content file and store it in a Buffer for encoding to base64 ([relevant code](./app.js#L127))
1. Create a client to the Connector API ([relevant code](./app.js#L9-L14))
2. Inject the Bot Connector's token into the Connector API client ([relevant code](./app.js#L147))
3. Set the Connector API client service url to the Connector's ([relevant code](./app.js#L148-L151))
4. Upload the base64 encoded payload to the conversations/attachments endpoint ([relevant code](./app.js#L153-L163))
5. Use the returned attachmentId to generate the contentUrl ([relevant code](./app.js#L165-L169))

This sample provides a [helper method](./app.js#L124-L172) you can use that encapsulates most of the previous steps.

````JavaScript
// read file content and upload
fs.readFile('./images/big-image.png', (err, data) => {
if (err) {
return session.send('Oops. Error reading file.');
}

// Upload file data using helper function
uploadAttachment(
data,
'image/png',
'BotFrameworkImage.png',
connector,
connectorApiClient,
session.message.address.serviceUrl,
session.message.address.conversation.id)
.then(attachmentUrl => {
// Send Message with Attachment obj using returned Url
var msg = new builder.Message(session)
.addAttachment({
contentUrl: attachmentUrl,
contentType: 'image/png',
name: 'BotFrameworkLogo.png'
});

session.send(msg);
})
.catch(err => {
console.log('Error uploading file', err);
session.send('Oops. Error uploading file. ' + err.message);
});
});
````

#### Using an externally hosted file

This option is the simplest but requires the image to be already on the Internet and be publicly accesible.
You could also provide an Url pointing to your own site.

Checkout [app.js](./app.js#L114-L121) to see how to create a message with a single image attachment.

````JavaScript
var msg = new builder.Message(session)
.addAttachment({
contentUrl: 'https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png',
contentType: 'image/png',
name: 'BotFrameworkOverview.png'
});

session.send(msg);
````

### Outcome

You will see the following in the Bot Framework Emulator when opening and running the sample solution.
You will see the following in the Bot Framework Emulator when selecting the inline attachment. See how the image is encoded in the `contentUrl` of the attachment.

![Sample Outcome](images/outcome-emulator.png)

You will see the following in your Facebook Messenger.
You will see the following in your Facebook Messenger when selecting to upload the attachment.

![Sample Outcome](images/outcome-facebook.png)

On the other hand, you will see the following in Skype.
On the other hand, you will see the following in Skype when selecting an Internet attachment.

![Sample Outcome](images/outcome-skype.png)

Expand All @@ -57,3 +138,4 @@ To get more information about how to get started in Bot Builder for Node and Att
* [Adding Attachments to a Message](https://docs.botframework.com/en-us/core-concepts/attachments)
* [Attachments](https://docs.botframework.com/en-us/node/builder/chat-reference/interfaces/_botbuilder_d_.iattachment.html)
* [Message.addAttachment method](https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html#addattachment)
* [Connector API - UploadAttachment](https://docs.botframework.com/en-us/restapi/connector/#!/Conversations/Conversations_UploadAttachment)
160 changes: 150 additions & 10 deletions Node/core-SendAttachment/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
var builder = require('botbuilder');
var restify = require('restify');
var Swagger = require('swagger-client');
var Promise = require('bluebird');
var url = require('url');
var fs = require('fs');
var util = require('util');

// Swagger client for Bot Connector API
var connectorApiClient = new Swagger(
{
url: 'https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector/Swagger/ConnectorAPI.json',
usePromise: true
});

// Setup Restify Server
var server = restify.createServer();
Expand All @@ -16,17 +28,145 @@ var connector = new builder.ChatConnector({
// Listen for messages
server.post('/api/messages', connector.listen());

var bot = new builder.UniversalBot(connector, function (session) {

// Create and send attachment
var attachment = {
contentUrl: 'https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png',
contentType: 'image/png',
name: 'BotFrameworkOverview.png'
};
// Bot Dialogs
var bot = new builder.UniversalBot(connector, [
function (session) {
session.send('Welcome, here you can see attachment alternatives:');
builder.Prompts.choice(session, 'What sample option would you like to see?', Options, {
maxRetries: 3
});
},
function (session, results) {
var option = results.response ? results.response.entity : Inline;
switch (option) {
case Inline:
return sendInline(session, './images/small-image.png', 'image/png', 'BotFrameworkLogo.png');
case Upload:
return uploadFileAndSend(session, './images/big-image.png', 'image/png', 'BotFramework.png');
case External:
var url = 'https://docs.botframework.com/en-us/images/faq-overview/botframework_overview_july.png';
return sendInternetUrl(session, url, 'image/png', 'BotFrameworkOverview.png');
}
}]);

const Inline = 'Show inline attachment';
const Upload = 'Show uploaded attachment';
const External = 'Show Internet attachment';
const Options = [Inline, Upload, External];

// Sends attachment inline in base64
function sendInline(session, filePath, contentType, attachmentFileName) {
fs.readFile(filePath, (err, data) => {
if (err) {
return session.send('Oops. Error reading file.');
}

var base64 = Buffer.from(data).toString('base64');

var msg = new builder.Message(session)
.addAttachment({
contentUrl: util.format('data:%s;base64,%s', contentType, base64),
contentType: contentType,
name: attachmentFileName
});

session.send(msg);
});
}

// Uploads a file using the Connector API and sends attachment
function uploadFileAndSend(session, filePath, contentType, attachmentFileName) {

// read file content and upload
fs.readFile(filePath, (err, data) => {
if (err) {
return session.send('Oops. Error reading file.');
}

// Upload file data using helper function
uploadAttachment(
data,
contentType,
attachmentFileName,
connector,
connectorApiClient,
session.message.address.serviceUrl,
session.message.address.conversation.id)
.then(attachmentUrl => {
// Send Message with Attachment obj using returned Url
var msg = new builder.Message(session)
.addAttachment({
contentUrl: attachmentUrl,
contentType: contentType,
name: attachmentFileName
});

session.send(msg);
})
.catch(err => {
console.log('Error uploading file', err);
session.send('Oops. Error uploading file. ' + err.message);
});
});
}
// Sends attachment using an Internet url
function sendInternetUrl(session, url, contentType, attachmentFileName) {
var msg = new builder.Message(session)
.addAttachment(attachment);
.addAttachment({
contentUrl: url,
contentType: contentType,
name: attachmentFileName
});

session.send(msg);
});
}

// Uploads file to Connector API and returns Attachment URLs
function uploadAttachment(fileData, contentType, fileName, connector, connectorApiClient, baseServiceUrl, conversationId) {

var base64 = Buffer.from(fileData).toString('base64');

// Inject the conenctor's JWT token into to the Swagger client
function addTokenToClient(connector, clientPromise) {
// ask the connector for the token. If it expired, a new token will be requested to the API
var obtainToken = Promise.promisify(connector.addAccessToken.bind(connector));
var options = {};
return Promise.all([clientPromise, obtainToken(options)]).then((values) => {
var client = values[0];
var hasToken = !!options.headers.Authorization;
if (hasToken) {
var authHeader = options.headers.Authorization;
client.clientAuthorizations.add('AuthorizationBearer', new Swagger.ApiKeyAuthorization('Authorization', authHeader, 'header'));
}

return client;
});
}

// 1. inject the JWT from the connector to the client on every call
return addTokenToClient(connector, connectorApiClient).then((client) => {
// 2. override API client host (api.botframework.com) with channel's serviceHost (e.g.: slack.botframework.com)
var serviceUrl = url.parse(baseServiceUrl);
var serviceHost = serviceUrl.host;
client.setHost(serviceHost);

// 3. POST /v3/conversations/{conversationId}/attachments
var uploadParameters = {
conversationId: conversationId,
attachmentUpload: {
type: contentType,
name: fileName,
originalBase64: base64
}
};

return client.Conversations.Conversations_UploadAttachment(uploadParameters)
.then((res) => {
var attachmentId = res.obj.id;
var attachmentUrl = serviceUrl;

attachmentUrl.pathname = util.format('/v3/attachments/%s/views/%s', attachmentId, 'original');
return attachmentUrl.format();
});
});
}
Binary file added Node/core-SendAttachment/images/big-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Node/core-SendAttachment/images/outcome-emulator.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Node/core-SendAttachment/images/outcome-facebook.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Node/core-SendAttachment/images/outcome-skype.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Node/core-SendAttachment/images/small-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion Node/core-SendAttachment/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
"url": "https://github.com/Microsoft/BotBuilder-Samples.git"
},
"dependencies": {
"bluebird": "^3.4.7",
"botbuilder": "^3.5.1",
"restify": "^4.3.0"
"restify": "^4.3.0",
"swagger-client": "^2.1.32"
}
}

0 comments on commit 6bb4763

Please sign in to comment.