Hi I am working on Bot in that I have a requirement like when an user click the button I want to display the related content in next card like this below.
Can anyone please tell me how to implement the above scenario in bots by using any cards like adaptive cards, rich cards or Thumbnail cards?
With Adaptive Cards, you can use AdaptiveSubmitAction for that:
new AdaptiveSubmitAction()
{
Data = "show me the next card"
};
This produces a new incoming message with message.Text being the value of Data and you can process it the same way as a regular message from user.
You can achieve the same with other rich cards / Thumbnail cards, using ImBack and PostBack actions:
new CardAction()
{
Type = ActionTypes.ImBack,
Value = "show me the next card"
}
The AdaptiveSubmitAction also has DataJson property that you can use instead of Data (the DataJson has no effect if you use both). You can put a json structure there, that will end up in message.Value of the incoming message, while message.Text will be null in that case.
This can be handy when you need to pass more details, e.g. DataJson = "{ \"CardName\": \"City\", \"Name\": \"New York\" }" could mean you want to open some City card for New York. Then you can retrieve the structure like this:
protected override async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> item)
{
var message = await item;
if (string.IsNullOrEmpty(message.Text))
{
dynamic value = message.Value;
if (value == null)
{
// empty message - show help, error etc.
}
dynamic cardName = value.CardName;
// check the name, respond with the wanted card ...
}
else
{
// process as usual
await base.MessageReceived(context, item);
}
}
Here is a sample project that uses the json approach.
Related
I made a MEME command of Discord Bot that posts a list of memes from phpMyAdmin DB. But it is not about that. I want to add specific emojis on that bot message that people could press on those emojis and kinda vote.
It is how it posts:
It is what I want to do:
[Command("meme")]
public async Task meme(CommandContext commandInfo, int id = -1, SocketUserMessage userMsg, string emoteName)
{
var description = "";
MySqlClient.Connect();
if (id == -1)
{
var query = "SELECT * FROM memes;";
var memeReader = MySqlClient.GetDataReader(query);
if (memeReader == null) return;
while (memeReader.Read())
{
var memeid = memeReader.GetUInt64("id");
var title = memeReader.GetString("title");
description += $"**{memeid}** {title}\n";
}
}
var memeEmbed = new DiscordEmbedBuilder()
{
Color = DiscordColor.Gold,
Title = "**The Meme of The Year**",
Description = description
};
var msg = await commandInfo.Channel.SendMessageAsync(embed: memeEmbed);
}
Adding Reactions
Assuming you are using Discord.Net, you need to react to your own message using the AddReactionAsync method.
Adding Emojis
Emojis are unicode characters. To use them, pass the utf-code (e.g. "\uD83D\uDC4C") as an argument to the constructor of the Emoji` object.
await userMsg.AddReactionAsync(new Emoji("\U0001f643"));
Adding Emotes
An Emote is a 'custom Emoji' which can be set by server admins/mods.
As an argument you need to pass a custom Emote string, like this "<:thonkang:282745590985523200>".
A safe wrapper to catch non-existing emotes could look like that (taken from the docs).
public async Task ReactWithEmoteAsync(SocketUserMessage userMsg, string escapedEmote)
{
if (Emote.TryParse(escapedEmote, out var emote))
{
await userMsg.AddReactionAsync(emote);
}
}
https://docs.stillu.cc/guides/emoji/emoji.html
Counting Reactions
As a second step you need to listen to user-events, when they react to your post. This should be done in a separate event handler, as your poll will likely be up for several hours/days.
Here is an example on how to implement an event handler
You then need to keep track of the reactions and need to associate it with the matching database entry.
We have a Microsoft Teams message extension app. If I search in a group chat and select one item in the result the chat input disappears and the "New conversation" button is shown again. This was working correctly earlier when there was no "New conversation" button in group chats only the chat input directly. This was working in earlier version of Teams, when there was no "New conversation" button.
This happens only if the result ThumbnailCards have "invoke" Tap CardAction. If I remove the "invoke" Tap CardAction, then the selected item in the result is inserted into the chat input correctly.
In private chat it is working correctly with the "invoke" Tap CardAction.
This is the ThumbnailCard for each search result item:
...
var card = new ThumbnailCard
{
Title = title,
Text = cardContent,
Images = new System.Collections.Generic.List<CardImage> { new CardImage(iconUrl) },
Buttons = new List<CardAction> { new CardAction(ActionTypes.OpenUrl, downloadText, null, itemLink, downloadText, itemLink) },
Tap = new CardAction
{
Type = "invoke",
Value = new JObject
{
["Id"] = GetItemPermanentId(item["Link"].ToString()),
["Title"] = title,
["Text"] = cardContent,
["IconUrl"] = iconUrl,
["DownloadText"] = downloadText,
["DownloadLink"] = itemLink
}
}
};
var attachment = card
.ToAttachment()
.ToMessagingExtensionAttachment();
return attachment;
...
The OnTeamsMessagingExtensionSelectItemAsync method is called correctly after I click on a result, but as described above, the chat input disappears and the "New conversation" button is shown again.
Target framework: .NET Core 2.1,
AdaptiveCards: 2.1.0,
Microsoft.Bot.Builder.Azure: 4.9.2,
Microsoft.Bot.Builder.Integration.AspNet.Core: 4.7.0.
Is this a Microsoft Teams bug or I need to change something on the code?
We are able to repro the issue at our end raised a bug. We don't have ETA to share when it will be fixed.
Teams update fixed this issue.
The idea here is pretty simple. I have written a code to display an adaptive card (with the command - new SendActivity("${AdaptiveCard()}")) asking for user's name and email. After the user submit those inputs I just want to retrieve those for future implementations, but I'm not being able to do so.
I have found several implementations of this using Waterfall dialog, but none using adaptive dialogs.
Here, it can be found a stack overflow post How to retrieve user entered input in adaptive card to the c# code and how to call next intent on submit button click - about How to retrieve user entered input in adaptive card to the c# code and how to call next intent on submit button click. However, if you check the anwser the user says it is for Waterfall Dialogs, perhaps the code is alike, but I wasn't able to convert it to Adaptive Dialogs implementation.
There is another reference, that may be useful. https://blog.botframework.com/2019/07/02/using-adaptive-cards-with-the-microsoft-bot-framework/. Although this tutorial shows how to implement adaptive cards using waterfall dialog, the code might look similar. More specifically, the implementation in this tutorial to retrieve user input, can be seen below. It is crucial to note that this implementation uses turnContext.Activity.Text to access user response from the Adaptive Card, however how can I access such a variable turnContext in Adaptive Dialogs?
var txt = turnContext.Activity.Text;
dynamic val = turnContext.Activity.Value;
// Check if the activity came from a submit action
if (string.IsNullOrEmpty(txt) && val != null)
{
// Retrieve the data from the id_number field
var num = double.Parse(val.id_number);
// . . .
}
Here, we can see the code for my RootDialog class from Adaptive Dialog implementation. Where in my code can I retrieve the user information as in the example above?
public class RootDialog : ComponentDialog
{
private readonly IConfiguration configuration;
public RootDialog(IConfiguration configuration)
: base(nameof(RootDialog))
{
this.configuration = configuration;
string[] paths = { ".", "Dialogs", $"{nameof(RootDialog)}.lg" };
string fullPath = Path.Combine(paths);
// Create instance of adaptive dialog.
var rootDialog = new AdaptiveDialog(nameof(AdaptiveDialog))
{
// These steps are executed when this Adaptive Dialog begins
Triggers = new List<OnCondition>()
{
// Add a rule to welcome user
new OnConversationUpdateActivity()
{
Actions = WelcomeUserSteps()
},
// Respond to user on message activity
new OnUnknownIntent()
{
Actions = OnBeginDialogSteps()
}
},
Generator = new TemplateEngineLanguageGenerator(Templates.ParseFile(fullPath))
};
// Add named dialogs to the DialogSet. These names are saved in the dialog state.
AddDialog(rootDialog);
// The initial child Dialog to run.
InitialDialogId = nameof(AdaptiveDialog);
}
private static List<Dialog> WelcomeUserSteps()
{
return new List<Dialog>()
{
// Iterate through membersAdded list and greet user added to the conversation.
new Foreach()
{
ItemsProperty = "turn.activity.membersAdded",
Actions = new List<Dialog>()
{
// Note: Some channels send two conversation update events - one for the Bot added to the conversation and another for user.
// Filter cases where the bot itself is the recipient of the message.
new IfCondition()
{
Condition = "$foreach.value.name != turn.activity.recipient.name",
Actions = new List<Dialog>()
{
new SendActivity("Hi there, I'm here to help you!")
}
}
}
}
};
}
private static List<Dialog> OnBeginDialogSteps()
{
return new List<Dialog>()
{
new SendActivity("${AdaptiveCard()}"),
};
}
}
I have posted question regarding firebase two days ago:
Android Firebase - add authenticated user into database
I got help that I needed and that solved first problem. But now I have a new problem. I was googling for quite some time, there are some posts about this issue but nothing solved my problem. I din't want to spam the previous question so I posted a new one.
When I try reading inserted data from the firebase database I get this error:
Newtonsoft.Json.JsonSerializationException: Error converting value
"test#user.com" to type 'carServiceApp.My_Classes.Account'. Path
'email', line 1, position 24.
Here is the code:
private async Task LoadData()
{
FirebaseUser users = FirebaseAuth.GetInstance(loginActivity.app).CurrentUser;
id = users.Uid;
var firebase = new FirebaseClient(loginActivity.FirebaseURL);
var items = await firebase.Child("users").Child(id).OnceAsync<Account>();
foreach (var item in items)
{
Account user = new Account();
user.uid = item.Object.uid;
user.name = item.Object.name;
user.lastName = item.Object.lastName;
user.phone = item.Object.phone;
user.email = item.Object.email;
userInput_ime.Text = user.name;
userInput_prezime.Text = user.lastName;
userInput_broj.Text = user.phone;
userInput_email.Text = user.email;
}
}
This is firebase data:
-users
-jwAP2dYNzJeiF3QlmEIEQoruUkO2
email: "test#user.com"
lastName: "user"
name: "test"
phone: "12421"
uid: "jwAP2dYNzJeiF3QlmEIEQoruUkO2"
Interesting thing is that when I try reading data with this:
var items = await firebase.Child("users").OnceAsync<Account>();
This works fine (I get last inserted user) . But when I add 'uid' node, then I get error. I was trying to solve this for quite some time but I just can't figure it out. I guess that there is no problem with the account class because it works in the case without uid node but doesn't work when another child() method is added.
Other information (Account class code and the way of storing that data into the database) you can see in the link at the top.
Note: I tried adding constructor in Account class but that doesn't help.
Ok, so I didn't exactly find a solution for this problem nor do I really understand why was this happening but I have found a workaround. I believe it's not ideal solution and that it does not fix existing problem. Or maybe it was problem with me not understanding firebase logic but here is what I came up with.
So, considering that it was all working fine if I didn't specify that uid node it was obvious there was some problem with class and data in firebase, matching problem I guess. Anyway, I decided to have that last uid node so I can have specific user selected and also to have the same data in firebase as it was in case where it was all working. So, this is how I have inserted data into firebase:
var item = firebase.Child("users").Child(id).PostAsync<Account>(user);
This created users node and child node. And PostAsync method created one more node with random key.
So when I tried reading with this:
var data = await firebase.Child("users").Child(id).OnceAsync<Account>();
It worked without problem. Now firebase data looks like this:
users
JPKdQbwcXbhBatZ2ihBNLRauhV83
-LCXyLpvdfQ448KOPKUp
email: "spider#man.com"
lastName: "man"
name: "spider"
phone: "14412"
uid: "JPKdQbwcXbhBatZ2ihBNLRauhV83"
There is a bit of redundancy, I basically have two ID's, but I don't understand how to create my class so I can get that data any other way so I made it this way. It works fine.
If anyone has better solution, I will gladly change it. Cheers
This was suppose to be a comment, but this is just suppose to be an addition for anyone that needs help with this issue.
I know that this answer has been out there for a while but this still seems to be a running structural quirk with Firebase and the usage of their rules. I ran into this issue with a complex structure that looked kind of like this
-Orders
-9876trfghji (User ID)
-0
BusnID: "ty890oihg"
Name: "Some Name"
AddOns: Object
ItemData: Object(containing other objects)
UserID: "9876trfghji"
Note: In this case as well as the case with cordas, you will see that both of the final objects has a UserID or uid.
I also was running into the issue of class de-serialization of the object without having the actual User ID in the objects data when it was being sent back to the device.
The reason that you have a “redundant” usage of the user id is for a security measure with the Firebase rules. The first UserID with the structure above you are able to control the access to the information based off of the users id without having to have an extra validation clause in the rules. Currently as of this post the the rule below would protect the data based on the User ID.
“Orders” : {
"$uid":{
".read":"auth != null",
".write":"auth.uid == $uid"
}
}
this allows the user with only the authorized user id to write content but anyone that has valid credentials can view the data.
The second User ID has to be placed in the object because without it you would not be able to do a standard cast to the object because your object would not have all of the data it would need to create the object. Regardless of if you are using a package like GoogleGson or Newtonsoft.Json the object still isn't full.
There is how ever a work around for this problem besides re-entering the User ID into the object. With the object that I have above I decided to just re-enter the User ID in my personal code to save the time and hassle of manual creation.
Using the Firebase.Database NuGet package you can manually create the object. Here is an example of the object in cordas problem
public static void GetUser_Firebase(User user, FirebaseApp app)
{
FirebaseDatabase database = FirebaseDatabase.GetInstance(app);
DatabaseReference reference = database.GetReference($"/users/{user.UserID}");
//"Using for getting firebase information", $"/users/{user.UserID}"
reference.AddListenerForSingleValueEvent(new UserInfo_DataValue());
}
class UserInfo_DataValue : Java.Lang.Object, IValueEventListener
{
private string ID;
public UserInfo_DataValue(string uid)
{
this.ID = uid;
}
public void OnCancelled(DatabaseError error)
{
//"Failed To Get User Information For User "
}
public void OnDataChange(DataSnapshot snapshot)
{
Dictionary<string, string> Map = new Dictionary<string, string>();
var items = snapshot.Children?.ToEnumerable<DataSnapshot>(); // using Linq
foreach(DataSnapshot item in items)
{
try
{
Map.Add(item.Key, item.Value.ToString()); // item.value is a Java.Lang.Object
}
catch(Exception ex)
{
//"EXCEPTION WITH DICTIONARY MAP"
}
}
User toReturn = new User();
toReturn.UserID this.ID;
foreach (var item in Map)
{
switch (item.Key)
{
case "email":
toReturn.email = item.Value;
break;
case "lastName":
toReturn.lastName = item.Value;
break;
case "name":
toReturn.name = item.Value;
break;
case "phone":
toReturn.phone = item.Value;
break;
}
}
}
}
Update
There is something that I would like to mention that I left out when I was writing this and that is the usage of Firebase.Database NuGet package with the Gson NuGet package and the Newtonsoft.Json Library
If you decide to use the FIrebase.Database library just know that you will be working very close with the Java.Lang and the Java.Util libraries. Objects like Java.Lang.Object can be very difficult and time consuming to write the code needed to de-serialize the data, but don't fear Gson is here!
The Gson package if you allow it can take a large load of work off of your hands for class de-serialization if you allow it. Gson is a library that will allow you to do Java.Lang.Obj to json string de-serialization. I know it seems weird, hand it an object get back a string sounds counter intuitive I know but just bear with me.
Here is an example of how to us the Gson Library with the object in cordas problem.
public static void Get_User(User user, FirebaseApp app)
{
FirebaseDatabase database = FirebaseDatabase.GetInstance(app);
DatabaseReference reference = database.GetReference($"Users/{user.UserID}");
reference.AddListenerForSingleValueEvent(new User_DataValue(user, app));
//$"Trying to make call for user orders Users/{user.UserID}");
}
class User_DataValue : Java.Lang.Object, IValueEventListener
{
private User User;
private FirebaseApp app;
public UserOrderID_Init_DataValue(User user, FirebaseApp app)
{
this.User = user;
this.app = app;
}
public void OnCancelled(DatabaseError error)
{
//$"Failed To Get User Orders {error.Message}");
}
public void OnDataChange(DataSnapshot snapshot)
{
//"Data received for user orders");
var gson = new GsonBuilder().SetPrettyPrinting().Create();
var json = gson.ToJson(snapshot.Value); // Gson extention method obj -> string
Formatted_Output("Data received for user order json ", json);
User user = JsonConvert.DeserializeObject<User>(json); //Newtonsoft.Json extention method string -> object
//now the user is a fully populated object with very little work
}
For anyone that might run into this in the future I hope that this helps
Here is an interesting requirement. How would I solve this with Breezejs?
(note, using a SPA design based on and extremely similar to the one in John Papa's Angular + Breezejs Pluralsight course)
We have a business rule that says that when I add or edit customer phone number, I need to check the phone number to see if there is a different customer with the same number (can happen due to phone number reassignment, etc).
If I find that it is a dup, I need to prompt the user. The user has the option to say "yah, that's fine", and then the phone number will save anyway.
So, I get how to do a basic validation with BeforeSaveEntity, and to fail if I find the dup, but suppose the user checks the "save anyway" option. How do I include this "out of band", non-data row information in my save set so that I can override the server-side validation rule?
And also, I don't want this validation to look like a "normal" error to the user on save -- I want to detect that it was the phone number clash thing, so I can display the view that prompts them to override.
Out of band data can be passed either by using the SaveOptions.tag property or by going to a separate named endpoint in your save call. i.e.
var so = new SaveOptions({ tag: "Special kind of save with extra data" });
return myEntityManager.saveChanges(null, so);
or
var so = new SaveOptions({ resourceName: "SaveWithSpecialValidation", tag: "any special data" });
return em.saveChanges(null, so);
In terms of how to return a special server side save validation
[HttpPost]
public SaveResult SaveWithSpecialValidation(JObject saveBundle) {
// custom tag passed from the client
var theTag = ContextProvider.SaveOptions.Tag;
ContextProvider.BeforeSaveEntitiesDelegate = BeforeSaveWithException;
return ContextProvider.SaveChanges(saveBundle);
}
private Dictionary<Type, List<EntityInfo>> BeforeSaveWithException(Dictionary<Type, List<EntityInfo>> saveMap) {
List<EntityInfo> orderInfos;
if (saveMap.TryGetValue(typeof(Order), out orderInfos)) {
if (YourErrorCheckHere(orderInfos)) {
var errors = orderInfos.Select(oi => {
return new EFEntityError(oi, "WrongMethod", "My custom exception message", "OrderID");
});
// This is a special exception that will be forwarded correctly back to the client.
var ex = new EntityErrorsException("test of custom exception message", errors);
// if you want to see a different error status code use this.
// ex.StatusCode = HttpStatusCode.Conflict; // Conflict = 409 ; default is Forbidden (403).
throw ex;
}
}
return saveMap;
}
Hope this makes sense. :)