Skip to content

Commit

Permalink
Image Caption Bot: Skype workarounds for authentication and parsing urls
Browse files Browse the repository at this point in the history
  • Loading branch information
iassal committed Sep 29, 2016
1 parent 43625dd commit 70c5850
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 12 deletions.
76 changes: 69 additions & 7 deletions CSharp/intelligence-ImageCaption/Controllers/MessagesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
{
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Connector;
Expand All @@ -28,7 +31,7 @@ public async Task<HttpResponseMessage> Post([FromBody]Activity activity)

try
{
message = await this.GetCaptionAsync(activity);
message = await this.GetCaptionAsync(activity, connector);
}
catch (ArgumentException e)
{
Expand Down Expand Up @@ -61,29 +64,88 @@ public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
/// and calling the appropriate caption service method.
/// </summary>
/// <param name="activity">The activity.</param>
/// <param name="connector">The connector.</param>
/// <returns>The caption if found</returns>
/// <exception cref="ArgumentException">The activity doesn't contain a valid image attachment or an image URL.</exception>
private async Task<string> GetCaptionAsync(Activity activity)
private async Task<string> GetCaptionAsync(Activity activity, ConnectorClient connector)
{

var imageAttachment = activity.Attachments?.FirstOrDefault(a => a.ContentType.Contains("image"));
if (imageAttachment != null)
{
var req = WebRequest.Create(imageAttachment.ContentUrl);

using (var stream = req.GetResponse().GetResponseStream())
using (var stream = await GetImageStream(connector, imageAttachment))
{
return await this.captionService.GetCaptionAsync(stream);
}
}
else if (Uri.IsWellFormedUriString(activity.Text, UriKind.Absolute))

string url;
if (TryParseAnchorTag(activity.Text, out url))
{
return await this.captionService.GetCaptionAsync(url);
}

if (Uri.IsWellFormedUriString(activity.Text, UriKind.Absolute))
{
return await this.captionService.GetCaptionAsync(activity.Text);
}

// If we reach here then the activity is neither an image attachment nor an image URL.
throw new ArgumentException("The activity doesn't contain a valid image attachment or an image URL.");
}

private static async Task<Stream> GetImageStream(ConnectorClient connector, Attachment imageAttachment)
{
using (var httpClient = new HttpClient())
{
// The Skype attachment URLs are secured by JwtToken,
// you should set the JwtToken of your bot as the authorization header for the GET request your bot initiates to fetch the image.
// https://github.com/Microsoft/BotBuilder/issues/662
var uri = new Uri(imageAttachment.ContentUrl);
if (uri.Host.EndsWith("skype.com") && uri.Scheme == "https")
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", await GetTokenAsync(connector));
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
}
else
{
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(imageAttachment.ContentType));
}

return await httpClient.GetStreamAsync(uri);
}
}

/// <summary>
/// Gets the href value in an anchor element.
/// </summary>
/// Skype transforms raw urls to html. Here we extract the href value from the url
/// <param name="text">Anchor tag html.</param>
/// <param name="url">Url if valid anchor tag, null otherwise</param>
/// <returns>True if valid anchor element</returns>
private static bool TryParseAnchorTag(string text, out string url)
{
var regex = new Regex("^<a href=\"(?<href>[^\"]*)\">[^<]*</a>$", RegexOptions.IgnoreCase);
url = regex.Matches(text).OfType<Match>().Select(m => m.Groups["href"].Value).FirstOrDefault();
return url != null;
}


/// <summary>
/// Gets the JwT token of the bot.
/// </summary>
/// <param name="connector"></param>
/// <returns>JwT token of the bot</returns>
private static async Task<string> GetTokenAsync(ConnectorClient connector)
{
var credentials = connector.Credentials as MicrosoftAppCredentials;
if (credentials != null)
{
return await credentials.GetTokenAsync();
}

return null;
}

/// <summary>
/// Handles the system activity.
/// </summary>
Expand Down
50 changes: 45 additions & 5 deletions Node/intelligence-ImageCaption/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const builder = require('botbuilder'),
captionService = require('./caption-service'),
needle = require("needle"),
restify = require('restify'),
url = require('url');
validUrl = require('valid-url');

//=========================================================
Expand All @@ -17,7 +18,7 @@ const builder = require('botbuilder'),

// Setup Restify Server
const server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, () => {
server.listen(process.env.port || process.env.PORT || 3979, () => {
console.log('%s listening to %s', server.name, server.url);
});

Expand Down Expand Up @@ -57,18 +58,17 @@ bot.on('conversationUpdate', message => {
// Gets the caption by checking the type of the image (stream vs URL) and calling the appropriate caption service method.
bot.dialog('/', session => {
if (hasImageAttachment(session)) {
var stream = needle.get(session.message.attachments[0].contentUrl);
var stream = getImageStreamFromUrl(session.message.attachments[0]);
captionService
.getCaptionFromStream(stream)
.then(caption => handleSuccessResponse(session, caption))
.catch(error => handleErrorResponse(session, error));
}
else if (validUrl.isUri(session.message.text)) {
else if(imageUrl = (parseAnchorTag(session.message.text) || (validUrl.isUri(session.message.text)? session.message.text : null))) {
captionService
.getCaptionFromUrl(session.message.text)
.getCaptionFromUrl(imageUrl)
.then(caption => handleSuccessResponse(session, caption))
.catch(error => handleErrorResponse(session, error));

}
else {
session.send("Did you upload an image? I'm more of a visual person. Try sending me an image or an image URL");
Expand All @@ -82,6 +82,46 @@ const hasImageAttachment = session => {
return ((session.message.attachments.length > 0) && (session.message.attachments[0].contentType.indexOf("image") !== -1));
}

const getImageStreamFromUrl = attachment => {
var headers = {};
if (isSkypeAttachment(attachment)) {
// The Skype attachment URLs are secured by JwtToken,
// you should set the JwtToken of your bot as the authorization header for the GET request your bot initiates to fetch the image.
// https://github.com/Microsoft/BotBuilder/issues/662
connector.getAccessToken((error, token) => {
var tok = token;
headers['Authorization'] = 'Bearer ' + token;
headers['Content-Type'] = 'application/octet-stream';

return needle.get(imageUrl, { headers: headers });
});
}

headers['Content-Type'] = attachment.contentType;
return needle.get(attachment.contentUrl, { headers: headers });
}

const isSkypeAttachment = attachment => {
if (url.parse(attachment.contentUrl).hostname.substr(-"skype.com".length) == "skype.com") {
return true;
}

return false;
}

/**
* Gets the href value in an anchor element.
* Skype transforms raw urls to html. Here we extract the href value from the url
*/
const parseAnchorTag = input => {
var match = input.match("^<a href=\"([^\"]*)\">[^<]*</a>$");
if(match && match[1]) {
return match[1];
}

return null;
}

//=========================================================
// Response Handling
//=========================================================
Expand Down
1 change: 1 addition & 0 deletions Node/intelligence-ImageCaption/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"needle": "^1.1.2",
"request": "^2.75.0",
"restify": "^4.1.1",
"url": "^0.11.0",
"valid-url": "^1.0.9"
}
}

0 comments on commit 70c5850

Please sign in to comment.