I'm trying to build a Dialog using the Microsoft Bot Framework which helps users consult purchase order status (currently, just a mock). I am using a LuisDialog which, when it detects the "ConsultPO" intent, it's supposed to ask for the user's 'customer id' and wait a follow up message from the user. However, it keeps going back to the start of the Luis Dialog and processing the intent instead of resuming from the waited method. This is the intent's code, which runs correctly:
[LuisIntent("ConsultPO")]
public async Task POIntent(IDialogContext context, LuisResult result)
{
string PO = "";
foreach (var entity in result.Entities)
{
if (entity.Type == "purchaseOrder")
PO = entity.Entity;
}
if (PO.Length != 0)
{
po_query = PO;
}
await context.PostAsync("Ok, can you confirm your customer id and I'll check for you?");
context.Wait(confirmCustomer_getPO);
}
This is the code I would expect to be executed after the user responds with a follow up message:
public async Task confirmCustomer_getPO(IDialogContext context, IAwaitable<object> argument)
{
await context.PostAsync("DEBUG TEST");
IMessageActivity activity = (IMessageActivity)await argument;
customer_query = activity.Text;
if (po_query.Length > 0)
{
PurchaseOrder po = POservice.findPO(po_query, customer_query);
await buildSendResponse(po, context);
//more non relevant code
When I answer to the bot's inquiry after context.Wait(confirmCustomer_getPO) is executed, it just goes into LUIS then runs the code respective to "None" intent. The message "DEBUG TEST" is never sent.
Why is "confirmCustomer_getPO" never getting called?
EDIT:
I added a debug message in the StartAsync method. I'm not sure whether this is supposed to happen but it pops up every time I send a message to the bot, which makes me believe the Dialog is simply restarting every time I message the bot:
public class EchoDialog : LuisDialog<object>
{
public EchoDialog() : base(new LuisService(new LuisModelAttribute(
ConfigurationManager.AppSettings["LuisAppId"],
ConfigurationManager.AppSettings["LuisAPIKey"],
domain: ConfigurationManager.AppSettings["LuisAPIHostName"])))
{
}
public override Task StartAsync(IDialogContext context)
{
context.PostAsync("I'm in startAsync");
return base.StartAsync(context);
}
Local debugging shows no exceptions are occurring and that any breakpoint in the waited method is never reached, although the context.Wait call does happen.
I figured out the issue myself after fighting with it for a while. The issue was with the bot store. I was using an InMemoryDataStore which was not working - switching to TableBotDataStore fixed the problem. The issue with the DataStore meant that states weren't being saved so my "waits" and "forwards" were not being saved into the dialog stack - any new incoming message was sent to the RootDialog.
Broken - not working while this was in global.asax.cs:
Conversation.UpdateContainer(
builder =>
{
builder.RegisterModule(new AzureModule(Assembly.GetExecutingAssembly()));
var store = new InMemoryDataStore(); // volatile in-memory store
builder.Register(c => store)
.Keyed<IBotDataStore<BotData>>(AzureModule.Key_DataStore)
.AsSelf()
.SingleInstance();
});
GlobalConfiguration.Configure(WebApiConfig.Register);
As soon as I updated store to:
var store = new TableBotDataStore(ConfigurationManager.AppSettings["AzureWebJobsStorage"]);
Having a valid "AzureWebJobsStorage" setting in web.config from my application settings in Azure, the problem was fixed without any other changes in the code.
Related
I'm trying to find a way to check if a user has X role and performing something if they don't, similar on how it logs it to the console if you use [RequireUserPermission(GuildPermission.Administrator)], just I don't want it to log to the console, EX:
if (has role)
{
// do stuff
} else
{
// do stuff
}
The command I'm trying to implement it into
[Command("clear")]
[RequireUserPermission(GuildPermission.ManageRoles)]
public async Task Clear(int amount)
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await ((ITextChannel)Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
As akac pointed out the precondition you mentioned [RequireUserPermission(...)] checks if any of the roles assigned to a user gives them the permission to do a specific task. Consider the following example. You create a role called "Moderators" and enable the permission "Manage Messages". You then add the precondition [RequireUserPermission(ChannelPermission.ManageMessages)] to your Clear() method. Your Clear() method will now work for anybody in the "Moderators" role because they have permission to manage messages. It will also allow anybody in any other roles with the same permission to use it.
However, if you later decide you don't want "Moderators" to be able to manage messages and remove the permission from that role, your precondition will then automatically stop anyone in that role from using the Clear command.
If you check the users roles instead of their permissions, the users with the "Moderators" role would still be able to use the Clear command to delete messages even though you've removed the permission to manage messages from that role.
If the only reason you want to check the role instead of using the precondition to check their permissions is because you don't want it to log to the console, then this is probably the wrong approach for you. Instead you should consider sticking with the precondition and look at how you're handling the logging to prevent that message from being logged to the console.
If you would still like to check the user's roles, then here is an example of how you could do that in the Clear() method you provided. You will need to add using System.Linq; to the top of the file and replace "RoleName" in the second if statement with the name of the role you want to check.
public async Task Clear(int amount)
{
// Get the user that executed the command cast it to SocketGuildUser
// so we can access the Roles property
if (Context.User is SocketGuildUser user)
{
// Check if the user has the requried role
if (user.Roles.Any(r => r.Name == "RoleName"))
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await((ITextChannel) Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
else
{
await Context.Channel.SendMessageAsync("Sorry, you don't have permission to do that.");
}
}
}
I encountered this issue a couple of days ago, my solution was to create a custom attribute that inherited from Discord.Commands.PreconditionAttribute
I discovered this as I use dotPeek on my Visual Studio install to decompile and read how the command service actually works, as I wanted to know how their attributes worked as well. As part of the command service's execution of the command it checks all preconditions for each command, and only when each are satisfied does it execute the function.
This class has a function called CheckPermissionsAsync
Here is a sample that should work for you:
public class RequiresRoleAttribute : PreconditionAttribute
{
private string m_role;
public RequiresRoleAttribute (string role)
{
m_role = role;
}
public override async Task<PreconditionResult> CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services)
{
if (context.User is SocketGuildUser sgu)
{
if (sgu.Roles.Select(x => x.Name == m_name).Contains(m_name)
{
return PreconditionResult.FromSuccess();
}
}
return PreconditionResult.FromError($"The user doesn't have the role '{m_name}'");
}
}
And the use:
[Command("clear")]
[RequiresRole("RoleName")]
public async Task Clear(int amount)
{
IEnumerable<IMessage> messages = await Context.Channel.GetMessagesAsync(amount + 1).FlattenAsync();
await ((ITextChannel)Context.Channel).DeleteMessagesAsync(messages);
const int delay = 3000;
IUserMessage m = await ReplyAsync($"I have deleted {amount} messages");
await Task.Delay(delay);
await m.DeleteAsync();
Console.Write(amount + " was cleared in a channel");
}
Here is what you do:
You use the RequireRoles attribute.
It has 4 various ways: All, any, specified only, none
Let's say you want to make a ban command, but only want admin and mods to be able to use it. You would do this:
public class Moderation : BaseCommandModule
{
[Command("ban")]
[RequireRoles(RoleCheckMode.Any, "admin", "mod")]
public async Task BanCommand(CommandContext ctx, DiscordMember name, [RemainingText] string reason)
{
}
}
What are you doing here is basically using a precondition to check if any of the user's roles have the ManageRoles permission, the description is confusing.
As far as I know, Discord.net doesn't log such errors, only puts the default error message into the Result of the command, which is then usually sent to the channel as a response. There clearly has to be some place where your code logs such errors.
I am using this tutorial in order to connect a xamarin.forms app with easy tables. I cannot add data to the database in Azure as i get
System.InvalidOperationException
The error message is the following
An insert operation on the item is already in the queue.
The exception happends in the following line of code.
await usersTable.InsertAsync(data);
In order to add a user
var user = new User { Username = "username", Password = "password" };
bool x = await AddUser(user);
AddUser
public async Task<bool> AddUser(User user)
{
try
{
await usersTable.InsertAsync(user);
await SyncUsers();
return true;
}
catch (Exception x)
{
await new MessageDialog(x.Message.ToString()).ShowAsync();
return false;
}
}
SyncUsers()
public async Task SyncUsers()
{
await usersTable.PullAsync("users", usersTable.CreateQuery());
await client.SyncContext.PushAsync();
}
where
IMobileServiceSyncTable<User> usersTable;
MobileServiceClient client = new MobileServiceClient("url");
Initialize
var path = Path.Combine(MobileServiceClient.DefaultDatabasePath, "DBNAME.db");
var store = new MobileServiceSQLiteStore(path);
store.DefineTable<User>();
await client.SyncContext.InitializeAsync(store, new MobileServiceSyncHandler());
usersTable = client.GetSyncTable<User>();
Please check your table. You probably have added the item already. Also, I would suggest that you don't set the Id property for your entity, because you might be inserting a same ID that's already existing in your table. It's probably the reason why the exception is appearing.
Hope it helps!
Some debugging you can do:
1) Turn on diagnostic logging in the backend and debug the backend: https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter8/developing/#debugging-your-cloud-mobile-backend
2) Add a logging delegating handler in your MobileServiceClient setup: https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter3/server/#turning-on-diagnostic-logs
The MobileServicePushFailedException contains an inner exception that contains the actual error. Normally, it is one of the 409/412 HTTP errors, which indicates a conflict. However, it can also be a 404 (which means there is a mismatch between what your client is asking for and the table name in Easy Tables) or 500 (which means the server crashed, in which case the server-side diagnostic logs indicate why).
Easy Tables is just a Node.js service underneath the covers.
I'm using the C# SDK for Bot Framework to develop a bot and i want my bot send a satisfaction message after a time without activity from the client.
I tried with Task but at first Task are not serializable so i can't store the task to Dispose it if i recieve a new message, and second PrompChoice throw a 'Microsoft.Bot.Builder.Internals.Fibers.InvalidNeedException' with the message : " invalid need: expected Call, have Wait"
I tried to replace my PromptChoice with just a context.sendAsync("Hi"), no exception here but message never be sent to the emulator.
If somebody have an idea on the way to do this, i would be grateful.
EDIT:
i use this sample to do the job:
using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
{
var botData = scope.Resolve<IBotData>();
await botData.LoadAsync(CancellationToken.None);
IDialogTask task = scope.Resolve<IDialogTask>();
//interrupt the stack
var dialog = new InactivityDialog();
task.Call(dialog.Void<object, IMessageActivity>(), null);
//stack.Post<InactivityDialog>(dialog, (c, item) => { return Task.CompletedTask; });
await task.PollAsync(CancellationToken.None);
//flush dialog stack
await botData.FlushAsync(CancellationToken.None);
}
and this in my static messageController:
var builder = new ContainerBuilder();
builder.Register(c => ((ClaimsIdentity)HttpContext.Current.User.Identity).GetCredentialsFromClaims())
.AsSelf()
.InstancePerLifetimeScope();
builder.Update(Conversation.Container);
But the HttpContext.Current is null when it try to resolve IBotData from the scope
That functionality isn't in the bot framework right now. You would have to create your own custom code where you save the conversation information & update your own timer if there's been activity.
https://github.com/Microsoft/BotBuilder/issues/837
I started learn the Microsoft Bot Framework recently, so I started make a Chatbot and I guess I making it wrong
I making the chatbot this way:
--> I get the message's user
--> send to LUIS
--> get the intent and the entities
--> select my answer and send it.
it's ok, but get the following situation:
USER: I wanna change my email. --> intent : ChangeInfo entities:
email/value:email
CHATBOT: Tell me your new Email please. --> intent: noIntent
entities: noEntities
USER: email#email.com --> intent: IDon'tKnow entities:
email/value:email#email.com
I take this situation, when the USER send his email, I send to LUIs, but a email dont have a intent, just have a entity, but a email can be used in a lot Different situations, My question is, How My bot know the context of conversation to understand this email is for change email and not send a email, or update this email or another thing.
my code on gitHub here, its a ugly code, i know, but i make this just to understand the bot framework, after I will let this code more beautiful
This should be as simple as using a LuisDialog and a set of Prompts to manage the users flow. Below you will find some quick code I put together to show you how this could be done. You don't need extra steps or adding extra entities, or going to Luis with the email provided by the user.
I would recommend you to read a bit more about LuisDialog and Dialogs in general as the way you are using Luis in your controller I don't think is the way to go.
Here is a good Luis Sample and here a good one around multi-dialogs.
Sample Code
namespace MyNamespace
{
using System;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Internals.Fibers;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using Microsoft.Bot.Connector;
[Serializable]
[LuisModel("YourModelId", "YourSubscriptionKey")]
public class MyLuisDialog : LuisDialog<object>
{
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, LuisResult result)
{
string message = "Não entendi, me diga com outras palavras!";
await context.PostAsync(message);
context.Wait(this.MessageReceived);
}
[LuisIntent("ChangeInfo")]
public async Task ChangeInfo(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
// no need to go to luis again..
PromptDialog.Text(context, AfterEmailProvided, "Tell me your new email please?");
}
private async Task AfterEmailProvided(IDialogContext context, IAwaitable<string> result)
{
try
{
var email = await result;
// logic to store your email...
}
catch
{
// here handle your errors in case the user doesn't not provide an email
}
context.Wait(this.MessageReceived);
}
[LuisIntent("PaymentInfo")]
public async Task Payment(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
// logic to retrieve the current payment info..
var email = "test#email.com";
PromptDialog.Confirm(context, AfterEmailConfirmation, $"Is it {email} your current email?");
}
private async Task AfterEmailConfirmation(IDialogContext context, IAwaitable<bool> result)
{
try
{
var response = await result;
// if the way to store the payment email is the same as the one used to store the email when going through the ChangeInfo intent, then you can use the same After... method; otherwise create a new one
PromptDialog.Text(context, AfterEmailProvided, "What's your current email?");
}
catch
{
// here handle your errors in case the user doesn't not provide an email
}
context.Wait(this.MessageReceived);
}
}
}
In my bot flow, I'm using a step variable that I change from the front-end. And another step variable that I change from the bot. This helps me identify which step I am in the conversation. You can do the same to identify what your bot is asking the user.
var data = {step: "asked_email"};
var msg = builder.Message(session).addEntity(data).text("Your message.");
session.send(msg);
If you don't want to send a specific step to LUIS for recognition, you can handle that in the onBeginDialog handler:
intents.onBegin(function (session, args, next) {
if (session.message.step !== "email") {
next();
} else {
//Do something else and not go to LUIS.
session.endDialog();
}
});
You can find the reference to LUIS onBeginDialog here:
https://docs.botframework.com/en-us/node/builder/chat/IntentDialog/#onbegin--ondefault-handlers
Details about message data can be found here:
https://docs.botframework.com/en-us/node/builder/chat-reference/classes/_botbuilder_d_.message.html#entities
I am trying to detect In-App-Purchases made by a client app.
I am using the following code
public async Task InitializeInAppPurchase()
{
CurrentApp.LicenseInformation.LicenseChanged += LicenseInformation_LicenseChanged;
var listingInformationTask = CurrentApp.LoadListingInformationAsync();
var listingInformation = await listingInformationTask;
PurchaseProduct(listingInformation.ProductListings.First().Value.ProductId);
}
private void LicenseInformation_LicenseChanged()
{
var receipt = CurrentApp.GetAppReceiptAsync().AsTask().Result;
Console.Writeline(receipt);
}
async void PurchaseProduct(string productId)
{
try
{
// Kick off purchase; don't ask for a receipt when it returns
var result = await CurrentApp.RequestProductPurchaseAsync(productId);
// Now that purchase is done, give the user the goods they paid for
// (DoFulfillment is defined later)
await DoFulfillment(result);
}
catch (Exception ex)
{
// When the user does not complete the purchase (e.g. cancels or navigates back from the Purchase Page), an exception with an HRESULT of E_FAIL is expected.
}
}
//
// Fulfillment of consumable in-app products
public async Task DoFulfillment(PurchaseResults result)
{
var productLicenses = CurrentApp.LicenseInformation.ProductLicenses;
// Check fulfillment for consumable products with hard-coded asset counts
await MaybeGiveMeGold(productLicenses["Test1"], 50, result);
}
// Count is passed in as a parameter
async Task MaybeGiveMeGold(ProductLicense license, int goldCount, PurchaseResults result)
{
if (license.IsConsumable && license.IsActive)
{
await CurrentApp.ReportConsumableFulfillmentAsync(license.ProductId, result.TransactionId);
}
}
When the event LicenseChanged is raised, I am surprised to see that the receipt does not include the transaction which just occurred. I get this
<Receipt Version="1.0" ReceiptDate="2015-06-18T04:41:31.867Z" ReceiptDeviceId="4e362949-acc3-fe3a-e71b-89893eb4f528" CertificateId="FB3D3A6455095D2C4A841AA8B8E20661B10A6112" xmlns="http://schemas.microsoft.com/windows/2012/store/receipt">
<AppReceipt Id="8ffa256d-eca8-712a-7cf8-cbf5522df24b" AppId="01e34c43-fdd8-47a6-a8ba-36ad5b880de9" PurchaseDate="2015-06-18T04:41:31.867Z" LicenseType="Full" />
</Receipt>
whereas the receipt should include a element as documented here
I am using the emulator and I am also using a mock server hosted locally in IIS to return my fake ProductListings from here
Can anyone tell me if there is something that I am doing wrong or if this simply what has been designed by Microsoft?
I know that In App Purchases behave differently on emulators, does anyone know if I would have had the expected behavior if I were using a real device?
Thank you
That behavior was normal because the Marketplace mock hosted locally could not return receipts for any transactions.
To test my In-App-Purchases, I had to
Publish my app in beta mode
Create test IAPs at 0.00$
The code above worked perfectly and the LicenseChanged events is raised almost instantaneously depending on network conditions.