forked from microsoft/BotBuilder-Samples
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding CSharp SimilarProducts bot sample.
- Loading branch information
kgrashad
authored and
Khaled Rashad
committed
Oct 7, 2016
1 parent
fee146b
commit f8ce953
Showing
19 changed files
with
1,063 additions
and
0 deletions.
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
CSharp/intelligence-SimilarProducts/App_Start/WebApiConfig.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System.Web.Http; | ||
using Newtonsoft.Json; | ||
using Newtonsoft.Json.Serialization; | ||
|
||
namespace SimilarProducts | ||
{ | ||
public static class WebApiConfig | ||
{ | ||
public static void Register(HttpConfiguration config) | ||
{ | ||
// Json settings | ||
config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = NullValueHandling.Ignore; | ||
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); | ||
config.Formatters.JsonFormatter.SerializerSettings.Formatting = Formatting.Indented; | ||
JsonConvert.DefaultSettings = () => new JsonSerializerSettings | ||
{ | ||
ContractResolver = new CamelCasePropertyNamesContractResolver(), | ||
Formatting = Formatting.Indented, | ||
NullValueHandling = NullValueHandling.Ignore, | ||
}; | ||
|
||
// Web API configuration and services | ||
|
||
// Web API routes | ||
config.MapHttpAttributeRoutes(); | ||
|
||
config.Routes.MapHttpRoute( | ||
name: "DefaultApi", | ||
routeTemplate: "api/{controller}/{id}", | ||
defaults: new { id = RouteParameter.Optional }); | ||
} | ||
} | ||
} |
258 changes: 258 additions & 0 deletions
258
CSharp/intelligence-SimilarProducts/Controllers/MessagesController.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,258 @@ | ||
namespace SimilarProducts.Controllers | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
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; | ||
using Services; | ||
|
||
[BotAuthentication] | ||
public class MessagesController : ApiController | ||
{ | ||
private readonly IImageSearchService imageService = new BingImageSearchService(); | ||
|
||
/// <summary> | ||
/// POST: api/Messages | ||
/// Receive a message from a user and reply to it | ||
/// </summary> | ||
public async Task<HttpResponseMessage> Post([FromBody]Activity activity) | ||
{ | ||
if (activity.Type == ActivityTypes.Message) | ||
{ | ||
var connector = new ConnectorClient(new Uri(activity.ServiceUrl)); | ||
string message = null; | ||
bool replied = false; | ||
|
||
try | ||
{ | ||
var images = await this.GetSimilarImagesAsync(activity, connector); | ||
|
||
if (images != null && images.Any()) | ||
{ | ||
Activity reply = activity.CreateReply("Here are some visually similar products I found"); | ||
reply.Type = ActivityTypes.Message; | ||
reply.AttachmentLayout = "carousel"; | ||
reply.Attachments = this.BuildImageAttachments(images.Take(10)); | ||
await connector.Conversations.ReplyToActivityAsync(reply); | ||
replied = true; | ||
} | ||
else | ||
{ | ||
message = "Couldn't find similar products images for this one"; | ||
} | ||
} | ||
catch (ArgumentException e) | ||
{ | ||
message = "Did you upload an image? I'm more of a visual person. " + | ||
"Try sending me an image or an image URL"; | ||
|
||
Trace.TraceError(e.ToString()); | ||
} | ||
catch (Exception e) | ||
{ | ||
message = "Oops! Something went wrong. Try again later."; | ||
|
||
Trace.TraceError(e.ToString()); | ||
} | ||
|
||
if (!replied) | ||
{ | ||
Activity reply = activity.CreateReply(message); | ||
await connector.Conversations.ReplyToActivityAsync(reply); | ||
} | ||
} | ||
else | ||
{ | ||
await this.HandleSystemMessage(activity); | ||
} | ||
|
||
var response = this.Request.CreateResponse(HttpStatusCode.OK); | ||
return response; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the caption asynchronously by checking the type of the image (stream vs URL) | ||
/// 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<IList<ImageResult>> GetSimilarImagesAsync(Activity activity, ConnectorClient connector) | ||
{ | ||
var imageAttachment = activity.Attachments?.FirstOrDefault(a => a.ContentType.Contains("image")); | ||
if (imageAttachment != null) | ||
{ | ||
using (var stream = await GetImageStream(connector, imageAttachment)) | ||
{ | ||
return await this.imageService.GetSimilarProductImagesAsync(stream); | ||
} | ||
} | ||
|
||
string url; | ||
if (TryParseAnchorTag(activity.Text, out url)) | ||
{ | ||
return await this.imageService.GetSimilarProductImagesAsync(url); | ||
} | ||
|
||
if (Uri.IsWellFormedUriString(activity.Text, UriKind.Absolute)) | ||
{ | ||
return await this.imageService.GetSimilarProductImagesAsync(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 IList<Attachment> BuildImageAttachments(IEnumerable<ImageResult> images) | ||
{ | ||
var attachments = new List<Attachment>(); | ||
|
||
foreach (var image in images) | ||
{ | ||
var plAttachment = new Attachment { ContentType = "application/vnd.microsoft.card.hero" }; | ||
|
||
//Construct Card | ||
var plCard = new HeroCard | ||
{ | ||
Title = image.Name, | ||
Subtitle = image.HostPageDisplayUrl, | ||
Images = new List<CardImage>() | ||
}; | ||
|
||
//Add Card Image | ||
var img = new CardImage { Url = image.ThumbnailUrl }; | ||
plCard.Images.Add(img); | ||
|
||
//Add Card Buttons | ||
plCard.Buttons = new List<CardAction>(); | ||
var plButtonBuy = new CardAction(); | ||
var plButtonSearch = new CardAction(); | ||
|
||
//Buy Button | ||
plButtonBuy.Title = "Buy from merchant"; | ||
plButtonBuy.Type = "openUrl"; | ||
plButtonBuy.Value = image.HostPageUrl; | ||
|
||
//Search More button | ||
plButtonSearch.Title = "Find more in Bing"; | ||
plButtonSearch.Type = "openUrl"; | ||
plButtonSearch.Value = image.WebSearchUrl; | ||
|
||
plCard.Buttons.Add(plButtonBuy); | ||
plCard.Buttons.Add(plButtonSearch); | ||
plAttachment.Content = plCard; | ||
|
||
attachments.Add(plAttachment); | ||
} | ||
|
||
return attachments; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the image stream. | ||
/// </summary> | ||
/// <param name="connector">The connector.</param> | ||
/// <param name="imageAttachment">The image attachment.</param> | ||
/// <returns></returns> | ||
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> | ||
/// <param name="activity">The activity.</param> | ||
/// <returns>Activity</returns> | ||
private async Task<Activity> HandleSystemMessage(Activity activity) | ||
{ | ||
switch (activity.Type) | ||
{ | ||
case ActivityTypes.DeleteUserData: | ||
// Implement user deletion here | ||
// If we handle user deletion, return a real message | ||
break; | ||
case ActivityTypes.ConversationUpdate: | ||
// Greet the user the first time the bot is added to a conversation. | ||
if (activity.MembersAdded.Any(m => m.Id == activity.Recipient.Id)) | ||
{ | ||
var connector = new ConnectorClient(new Uri(activity.ServiceUrl)); | ||
|
||
var response = activity.CreateReply(); | ||
response.Text = "Hi! I am SimilarProducts Bot. I can find you similar products" + | ||
" Try sending me an image or an image URL."; | ||
|
||
await connector.Conversations.ReplyToActivityAsync(response); | ||
} | ||
break; | ||
case ActivityTypes.ContactRelationUpdate: | ||
// Handle add/remove from contact lists | ||
break; | ||
case ActivityTypes.Typing: | ||
// Handle knowing that the user is typing | ||
break; | ||
case ActivityTypes.Ping: | ||
break; | ||
} | ||
|
||
return null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
<%@ Application Codebehind="Global.asax.cs" Inherits="SimilarProducts.WebApiApplication" Language="C#" %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using System.Web.Http; | ||
|
||
namespace SimilarProducts | ||
{ | ||
public class WebApiApplication : System.Web.HttpApplication | ||
{ | ||
protected void Application_Start() | ||
{ | ||
GlobalConfiguration.Configure(WebApiConfig.Register); | ||
} | ||
} | ||
} |
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
BIN
+106 KB
CSharp/intelligence-SimilarProducts/Images/outcome-emulator-stream.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions
34
CSharp/intelligence-SimilarProducts/Properties/AssemblyInfo.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System.Reflection; | ||
using System.Runtime.InteropServices; | ||
|
||
// General Information about an assembly is controlled through the following | ||
// set of attributes. Change these attribute values to modify the information | ||
// associated with an assembly. | ||
[assembly: AssemblyTitle("SimilarProducts")] | ||
[assembly: AssemblyDescription("")] | ||
[assembly: AssemblyConfiguration("")] | ||
[assembly: AssemblyCompany("")] | ||
[assembly: AssemblyProduct("SimilarProducts")] | ||
[assembly: AssemblyCopyright("Copyright © 2016")] | ||
[assembly: AssemblyTrademark("")] | ||
[assembly: AssemblyCulture("")] | ||
|
||
// Setting ComVisible to false makes the types in this assembly not visible | ||
// to COM components. If you need to access a type in this assembly from | ||
// COM, set the ComVisible attribute to true on that type. | ||
[assembly: ComVisible(false)] | ||
|
||
// The following GUID is for the ID of the typelib if this project is exposed to COM | ||
[assembly: Guid("a8ba1066-5695-4d71-abb4-65e5a5e0c3d4")] | ||
|
||
// Version information for an assembly consists of the following four values: | ||
// | ||
// Major Version | ||
// Minor Version | ||
// Build Number | ||
// Revision | ||
// | ||
// You can specify all the values or you can default the Revision and Build Numbers | ||
// by using the '*' as shown below: | ||
[assembly: AssemblyVersion("1.0.0.0")] | ||
[assembly: AssemblyFileVersion("1.0.0.0")] |
Oops, something went wrong.