Microsoft Bot Framework DirectLine Can't Access Conversations - c#

I am trying to use the Microsoft Bot Framework DirectLine API to read and add messages to existing conversations between other users and my bot. From what I've read I believe this should be possible when using the master-secret but it's just not working for me. I'm using a WebAPI to try and access two of my existing conversations (on Facebook & Skype) as follows:
[HttpPost]
[Route("remind")]
public string Remind()
{
var secret = System.Configuration.ConfigurationManager.AppSettings["secret"];
var uri = new Uri("https://directline.botframework.com/");
var creds = new DirectLineClientCredentials(secret);
DirectLineClient client = new DirectLineClient(uri, creds);
Conversations convs = new Conversations(client);
var conversationIDs = new string[] { "0000000000000000-0000000000000000", "00:0123456789abcdefghijklmnopqrstuvwxyz0123456789-A-_0123456798ABCDEF" }; // Existing Facebook & Skype conversations
// Send a message to each conversation:
foreach (var conversationID in conversationIDs)
{
Message message = new Message(conversationId: conversationID, fromProperty: "My Bot", text: "Hey dude, remember that thing!");
Console.WriteLine(message.Text);
convs.PostMessage(conversationID, message); // FAILS - This executes but doesn't do anything.
}
// Try reading the messages from a conversation (just to test if it's working):
string waterMark = null;
var set = convs.GetMessages(conversationIDs[0], waterMark); // FAILS - This fails with a 404 not found.
waterMark = set.Watermark;
return "Done :-)";
}
It fails silently calling PostMessage() and fails with a 404 for the GetMessages(). I seem to be doing the right thing, the bot is live etc and works very well in Facebook & Skype separately from the DirectLine API. It only works if I create a new conversation using the DirectLine API, I can then access its messages and post new messages to it.
This question sort of helps but doesn't quite tell me what to do to fix it:
Difficulty accessing messages in an existing conversation in Microsoft Bot Framework
Any help would be much appreciated.
Thanks

For security reasons, you can't use DirectLine to spy on messages from another conversation. For the scenario you describe (escalating to a human) there a number of different ways to approach this. One is to have your bot broker conversations between the accounts (i.e. Facebook End User <-> Your Bot <-> Facebook Support Person). Each is talking to the bot, and the bot passes the message through to the other user. (Could also be Facebook User <-> Your Bot <-> Skype User) Your bot would have to store last n messages to provide context. Alternatively, I've seen folks build their own customer support chat interface using direct line that sits on the far side. Hope this helps

Related

Manage a long-running operation in MS Teams Bot

I am using the following sample / article to Manage a Long-running operation in MS Teams Bot.
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-long-operations-guidance?view=azure-bot-service-4.0
In step 5, a DirectLineClient is being created and an Event Activity is sent to Bot using PostActivityAsync.
var responseActivity = new Activity("event");
responseActivity.Value = originalActivity;
responseActivity.Name = "LongOperationResponse";
responseActivity.From = new ChannelAccount("GenerateReport", "AzureFunction");
var directLineSecret = Environment.GetEnvironmentVariable("DirectLineSecret");
using(DirectLineClient client = new DirectLineClient(directLineSecret))
{
var conversation = await client.Conversations.StartConversationAsync();
await client.Conversations.PostActivityAsync(conversation.ConversationId, responseActivity);
}
However, I need the above sample to work for MS Teams Bot and not the DirectLineClient.
I used Microsoft.Bot.Connector.ConnectorClient but StartconversationAsync and PostActivityAsync methods are not available.
I tried the methods available in Microsoft.Bot.Connector.ConnectorClient
connectorClient.Conversations.CreateConversationAsync(conversationparameters)
connectorClient.ConversationsCreateDirectConversationAsync(botAccount, userAccount, (Activity)newActivity);
connectorClient.Conversations.SendToConversationAsync(conversationid, (Activity)newActivity);
But all the methods failed with Bad Requestwith the error as seen in the Response:
{"error":{"code":"BadArgument","message":"Unknown activity type"}}
The newActivity is created as below:
var messagnewActivity = new Activity("event");
newActivity.Value = originalActivity;
newActivity.From = new ChannelAccount("GenerateReport", "AzureFunction");
newActivity.Type = "event";
newActivity.Conversation = new ConversationAccount { Id = originalActivity.Conversation.Id };
newActivity.ChannelId = originalActivity.ChannelId;
Can someone please suggest how do I pass the Activity (Event Activity type) to MS Teams Bot.
Thanks
Gagan
I'm not really familiar with Direct Line, but I think it's effectively an -alternative- type of bot to Teams, so if you're trying to do this inside Teams, it explains the issue. In principle, the basic idea is quite simple though:
you store state somehow (e.g. in memory or in a database) to indicate that the long running operation is in progress for the user
when the long-running process is complete, your code (which could live OUTSIDE your bot, e.g. in an Azure Function) can send the user a message AS IF IT WAS the bot - this is called Pro-Active Messaging and you can read more about it at https://learn.microsoft.com/en-us/graph/teams-proactive-messaging.
This is to inform you that I was facing the same issue sometime before then I found a tweak in the code while debugging. when it calls twice recursively then the Activity Id is the same as the previous one. you can check if the activity id is the same then return the request else go with it.

conversation reference (concurrent dictionary become blank) after IIS pool recycle

We have a bot design in Bot framework-4 using .Net c# sdk. This bot is hosted on IIS and available
on different channel such as Directline, MS Teams etc. We want to send proactive messages to all the user in MS teams to notify them irrespective of if they communicated with bot or not. The Proactive messages will be 1:1 message.
After doing lot of R&D we found that we will be only able to send Proactive message to user only when there conversation reference is present. (let me know if other way is also possible.)
Using below link and Sample to send Proactive message to user:
Proactive Message Sample
Document Referred
We are using cosmos DB container and auto save middleware for bot conversation state and user state management.
Code in ConfigureServices method of Startup.cs file:
var blobDbService = botConfig.Services.FirstOrDefault(s => s.Type == ServiceTypes.BlobStorage) ?? throw new Exception("Please configure your Blob service in your .bot file.");
var BlobDb = blobDbService as BlobStorageService;
var dataStore = new AzureBlobStorage(BlobDb.ConnectionString, BlobDb.Container);
var userState = new UserState(dataStore);
var conversationState = new ConversationState(dataStore);
services.AddSingleton(dataStore);
services.AddSingleton(userState);
services.AddSingleton(conversationState);
services.AddSingleton<ConcurrentDictionary<string, ConversationReference>>();
services.AddSingleton(new BotStateSet(userState, conversationState));
services.AddBot<EnterpriseTiBOT>(options =>
{
// Autosave State Middleware (saves bot state after each turn)
options.Middleware.Add(new AutoSaveStateMiddleware(userState, conversationState));
}
Code to Store Conversation Reference for each user:
private void AddConversationReference(Activity activity)
{
var conversationReference = activity.GetConversationReference();
_conversationReferences.AddOrUpdate(conversationReference.User.Id, conversationReference, (key, newValue) => conversationReference);
}
protected override async Task OnStartAsync(DialogContext dc, CancellationToken cancellationToken = default(CancellationToken))
{
AddConversationReference(dc.Context, cancellationToken);
}
Code in notifyContoller is same as the code from GitHub Sample. There are 2 issues we are facing :
The concurrent dictionary having conversation reference become blank when the IIS pool is recycled and we are not able to send the proactive message to the user, how to store it in Blob storage and access the same in Notify controller?
We want to send proactive message to all the user whether they have communicated with bot or not, any way to achieve this? Tried 3rd approach from this article. But the challenge is, we are not able to send message to user based on User ID or user principle name.
There are multiple ways to store the conversation and user info. You should store these details in more persistent place rather than in memory. Here is a sample app code which stores user detail and along with the conversation Id in cosmos DB at the time of installation of the app. You could look into the implementation part. It can be any storage (blob, SQL).
For sending proactive message, User must have access to your app. You could make your app install for everyone in the tenant from Teams admin portal. Here is a reference documentation for managing the app from admin portal.
You need to have the conversation ID (between a bot and user) for sending a proactive message. And the conversation Id is created when the bot is installed for an user Or in team/group where user is part of.
I want to add comment to the answer but I do not have enough reputation to do so. Just to eleborate point 1 , if you want to ask where can we get and save the conversation refences, you can get it via method named: OnConversationUpdateActivityAsync.
It took me some time to get to this, so I think it is useful to share.
You can get a lot of information eg user ID, channel ID from the activity, here is some sample code:
public class ProactiveBot : ActivityHandler
{
...
...
protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var conversationReferences = turnContext.Activity.GetConversationReference();
//this is your user's ID
string userId = conversationReference.User.Id;
//this is the bot's ID and will be the same for all activities under same bot.
string botId = conversationReference.Bot.Id;
...
...
...
}
}

MS Bot Framework 4.x SDK. How can I send raw JSON to a facebook messenger channel using C#

You really need to be able to send raw json at facebook to take advantage of all of the facebook messenger features but I see no way to do that in the MS bot SDK. (version 4). Here is what I think should work, but no luck. If I set the Text property of a reply activity it just shows up as text on messenger (as expected). If I set the ChannelData property with a string no exceptions are thrown but facebook messenger shows no response message. Given what a huge platform FB Messenger is you would think this was a priority in the SDK but I see nothing about it.
if (turnContext.Activity.Type == ActivityTypes.Message)
{
try
{
if((turnContext.Activity.ChannelId == Channel.Channels.Facebook)|| (turnContext.Activity.ChannelId == Channel.Channels.Emulator))
{
string rsp = "{\"attachment\":{\"type\":\"template\",\"payload\":{\"template_type\":\"button\",\"text\":\"What do you want to do next?\",\"buttons\":[" +
"{\"type\":\"web_url\",\"url\":\"https://www.messenger.com\",\"title\":\"Visit Messenger\"}]}}}";
Activity reply = turnContext.Activity.CreateReply();
_logger.LogInformation(rsp);
//reply.Text = rsp; // display message as actual message to messenger
// reply.ChannelData = rsp; // fails request finishes on the MS bot service side, but nothing at all shows on messenger
reply.ChannelData = JsonConvert.DeserializeObject(rsp); // WORKS!
await turnContext.SendActivityAsync(reply);
.... // IN MY ORIGINAL POST THERE WAS AN ERROR IN THE JSON STRING and I got the full answer a minute later... see answer below.
My bad on this. The original post had an error in the rsp string but I also took a look at the sdk source and realized they wanted an object at ChannelData, not a string.... so I deserialized it and stuffed the object to channeldata and it started working. Few posts on this in SDK version 4 so hoping this helps someone. My wall needs some repair after me pounding my head against it all day. – Fred Covely just now

c# bot is responding differently on one or more channel

i have deployed my bot on azure,which is running on two channel "Skype" and "Web chat " for testing purpose i have integrated both channel in sample html file screen shot with problem description
i have used this code in ConservationUpdate , my problem domain is that webchat greet the user "welcome" whenever user has fresh start of conservation while skype bot does not greet the user
if (message.MembersAdded.Any(o => o.Id == message.Recipient.Id))
{
// var reply = message.CreateReply(Resources.RootDialog_Welcome_Message);
var reply = message.CreateReply("hello welcome to zygote");
ConnectorClient connector = new ConnectorClient(new Uri(message.ServiceUrl));
await connector.Conversations.ReplyToActivityAsync(reply);
}
can anyone explain me two different behavior on same action of bot
....suggestion and advice will be appreciated thanks ......
In web chat whenever you refresh page its create new Userid and not storing any session for user,that's why its showing welcome message. while in skype channel session store for user so its not showing welcome message, and you have to give welcome message at a time of bot registration. As per my knowledge.

How to read inbox Outlook.com mails from WinForms?

SCENARIO
I would like to develop a very simple app written in C# or Vb.Net using WinForms tech., that will help me to automate a simple task that consist in access to my Outlook.com account to read my emails received from Youtube then extract the video urls.
PROBLEM
My networking related knowledges are not good, I'm stuck at the most important point trying to find the easiest way to acchieve that task (I mean official Microsoft APIs for .Net or 3rd party APIs or other way to be able do this), trying to apply the required OAuth2 autorizathion to access the email account.
I know that the following code is not focused in the right direction because the lack of authorization, but I don't know how to implement that neither how to read the emails, so this is what I tried:
string url = "https://outlook.office.com/api/v2.0/me/messages";
string result = "";
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "GET";
try {
using (WebClient wc = new WebClient()) {
wc.Encoding = Encoding.UTF8;
result = wc.DownloadString(url);
}
} catch (Exception ex) {
MessageBox.Show(ex.Message);
}
QUESTION
How I could access to my Outlook.com account to read the title and the content of the inbox emails that I have?. And, additionally but optionally to respond (only if possible and is not too much ask), how I could delete a email?.
Note that this question reffers to Outlook.com online service, not to Outlook's desktop client neither the usage of their COM libraries or Office365.
ANSWER REQUISITES
I know that I'm no one to ask for help and put some requisites, all kind of help is appreciated for me, but this time I need to put a special requisite, because my head got crazy trying to understand, use and adapt OAuth2 solutions that were made from scratch, it generates very long codes that I don't understand at all, it's too much for me.
For that reason, I will accept an answer in this question only if the provided solution is based in the usage of a 3rd pary library that will facilitate all this task because it will serve as a complete abstraction of the OAuth2 implementation, like RestSharp, CodeScales or DotNetOpenAuth, or whichever other (free)lib that will handle the required things for me instead of the need to develop the OAuth2 algorithms by myself from scratch.
RESEARCH
My investigation started reading this Microsoft's Outlook dev. page, following to this Mail API reference, this REST API documentation, this Outlook.com API, this kind of getting started article, and ending in this fully illustrative example using ASP.Net.
What I have taken in clear from the Microsoft articles is just... nothing of nothing, I've registered the app and created the client id and the secret, but Microsoft does not provide any example for Winforms so I tried to translate their official ASP.NET example to WinForms without success.
After that waste of time, I found this OAuth documentation page which provides some libraries for .NET that I imagine will facilitate the OAuth2 authorization, then I discovered the DotNetOpenAuth library which seems very complete, and also I found this code sample for Outlook using DotNetOpenAuth but again it is for ASP.NET, and also these generic and official DotNetOpenAuth's code samples but I can't find any thing that could help me for what I want to do.
The general idea is to follow the tutorial here: Get Started with Mail, Calendar, and Contacts REST APIs but I will try to simplify it and demonstrate it with a Winforms sample.
Register App
First things first, you need to create and register an application to the Application Registration Portal (you should only have to do this once for a given application of course):
create an app
generate a new password
add a platform, and choose mobile ("mobile" here means "any device", or "not for a browser"...)
don't forget to click on save!
Authentication
Now, in your "any device" code (including winforms), you'll need to authenticate. The simplest way is to use ADAL ("Active Directory Authentication Library"). The source is available here https://github.com/AzureAD/azure-activedirectory-library-for-dotnet and the binary is available as a nuget.
Unfortunately, the latest version which you can get on nuget today named Microsoft.IdentityModel.Clients.ActiveDirectory does not work for me (It had a bug I've reported here https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/issues/412 that's already been fixed, but now the server complains about some app vs server incompatibility).
So you must use the old "Experimental" one (keep that in mind as someday in the future, we'll have to switch): Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory
You want to make sure you use the proper scopes when you do acquire an auth token. Scopes are defined here: Outlook mail, calendar, and contacts scopes and represent an area of permission. Without specifying scope, you can do nothing else but authenticate.
So, if you want to read mails, use the "https://outlook.office.com/mail.read" scope.
When you try the application, after the authentication dialog, it should display to the user the consent screen (here we see we asked for mail scope: "Read Your Mail"):
Office/Outlook/OData API
Once authentication works, you can use the REST api directly which is not that easy, or be lazy and use another package: Microsoft.Office365.OutlookServices-V2.0 that will do all underlying REST/OData magic for you. The good news is this API is quite complete so it should allow you to do other things like, message creation, delete, etc.
There is an important remark for the outlook.com case: not all accounts are enabled for this whole REST API (check the "REST API availability" chapter here: Outlook Mail), so you might want to create a new one for testing.
Here is the winforms code for a sample app that will query 10 messages and add them to a listbox.
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Office365.OutlookServices;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private const string authority = "https://login.microsoftonline.com/common";
private const string clientId = "blablabl-abla-blab-abla-blablablab"; // TODO: put your application id here
private const string redirectUri = "urn:ietf:wg:oauth:2.0:oob"; // put your redirect uri here (should be the same)
// we cache the token for the duration of this form
// you could/should use the FileCache class provided in the sample here https://dev.outlook.com/restapi/tutorial/dotnet
private TokenCache _cache = new TokenCache();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// since all packages force async,
// we have to avoid threading issues
BeginInvoke((Action)(() => GetMessages()));
}
private async void GetMessages()
{
// use the Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory nuget package for auth
var authContext = new AuthenticationContext(authority, _cache);
var result = await authContext.AcquireTokenAsync(
new[] { "https://outlook.office.com/mail.read" },
null,
clientId,
new Uri(redirectUri),
new PlatformParameters(PromptBehavior.Always, this));
// use the Microsoft.Office365.OutlookServices-V2.0 nuget package from now on
var client = new OutlookServicesClient(new Uri("https://outlook.office.com/api/v2.0"), () => Task.FromResult(result.Token));
var messages = await client.Me.Messages
.Take(10) // get only 10 messages
.ExecuteAsync();
// fill some list box
// (beware, some messages have a null subject)
foreach (var msg in messages.CurrentPage)
{
listBox1.Items.Add(msg.Subject);
}
}
}
}
I just would like to share the (almost)final solution, I extended a little bit the solution provided by #Simon Mourier, to iterate all the emails of a specific folder and, if the email is from Youtube, scrap the urls inside, then recytle the email.
When to apply this?, well, just adapt it for any case on which you need to parse the emails, my case is very specific, I have around 500 channel subscriptions so I accumulate around 200 emais from Youtube in a month, most of the emails are from music channels that I just read the email to copy the url to download it with JDownloader, so this is usefull as a savetimer because it will do all the task for me.
Private Const Authority As String = "https://login.microsoftonline.com/common"
Private Const ClientId As String = "OUR API ID"
' Put your redirect uri here (should be the same).
Private Const RedirectUri As String = "urn:ietf:wg:oauth:2.0:oob"
' We cache the token for the duration of this Form.
' You could/should use the FileCache class provided in the sample here:
' https://dev.outlook.com/restapi/tutorial/dotnet
Private cache As New TokenCache()
Private Sub Form1_Shown() Handles MyBase.Shown
' Since all packages force async, we have to avoid threading issues.
Me.BeginInvoke(Sub() GetMessages())
End Sub
Private Async Sub GetMessages()
' Use the 'Microsoft.Experimental.IdentityModel.Clients.ActiveDirectory' Nuget package for auth.
Dim authContext As New AuthenticationContext(Authority, cache)
Dim result As AuthenticationResult =
Await authContext.AcquireTokenAsync({"https://outlook.office.com/mail.readwrite"},
Nothing, ClientId, New Uri(RedirectUri),
New PlatformParameters(PromptBehavior.Auto, Me))
' Use the 'Microsoft.Office365.OutlookServices-V2.0' Nuget package from now on.
Dim client As New OutlookServicesClient(New Uri("https://outlook.office.com/api/v2.0"),
Function() Task.FromResult(result.Token))
' I have a rule set to automatically move all emails received from Youtube to a folder with name "Youtube".
Dim folder As IMailFolder =
Await client.[Me].MailFolders.Where(Function(f As IMailFolder) f.DisplayName = "Youtube").ExecuteSingleAsync()
Dim messages As IPagedCollection(Of IMessage) =
Await client.[Me].MailFolders.GetById(folder.Id).Messages.ExecuteAsync()
Do While True
Me.ParseYoutubeMessages(messages.CurrentPage)
If messages.MorePagesAvailable Then
messages = Await messages.GetNextPageAsync
Else
Exit Do
End If
Loop
End Sub
Private Async Sub ParseYoutubeMessages(ByVal messageList As IReadOnlyList(Of IMessage))
Dim urlRegex As New Regex("""http://www.youtube.com/.+watch.+uploademail""", RegexOptions.IgnoreCase)
For Each msg As IMessage In messageList
If (msg.From.EmailAddress.Name.Equals("YouTube", StringComparison.OrdinalIgnoreCase)) Then
Dim body As String = msg.Body.Content
Dim isMatch As Boolean = urlRegex.IsMatch(body)
If Not (isMatch) Then
Throw New InvalidOperationException("Youtube url regex doesn't match.")
Else
Dim urlMatches As MatchCollection = urlRegex.Matches(body)
Dim urls As String() =
(From m As Match In urlMatches.Cast(Of Match)
Select Environment.NewLine & m.Value).Distinct().ToArray()
File.AppendAllText("C:\Youtube Urls.txt", String.Join("", urls))
msg.IsRead = True
Await msg.MoveAsync("DeletedItems")
End If
End If
Next msg
End Sub
Updating this for 2018 -- Microsoft Graph API now has control over granting access to objects in the 365 world, including email. See this link for the 4-step overview: https://developer.microsoft.com/en-us/graph/docs/concepts/auth_v2_user

Categories

Resources