I encountered a “date” issue with Bot in TEAMS, The date value cannot render at LeaveRequest() properly. However, the same bot works fine in Bot Emulator.
Following is my sample code. How to fix (or workaround) this issue?
Select a date from date picker and submit
System does not render the first field value.
public class EchoBot
{
public static Attachment LeaveRequest()
{
AdaptiveSchemaVersion schemaVersion = new AdaptiveSchemaVersion(1, 0);
AdaptiveCard card = new AdaptiveCard(schemaVersion);
card.Body.Add(new AdaptiveTextBlock()
{
Text = "Enter new Date",
Size = AdaptiveTextSize.Large,
Weight = AdaptiveTextWeight.Bolder
});
AdaptiveDateInput fromDate = new AdaptiveDateInput()
{
Id = "FromDate",
Placeholder = "From Date"
};
card.Body.Add(fromDate);
AdaptiveTextInput toDate = new AdaptiveTextInput()
{
Id = "ToDate",
Placeholder = "To Date",
Value = DateTime.Today.ToUniversalTime().ToString("u")
};
card.Body.Add(toDate);
card.Actions.Add(new AdaptiveSubmitAction()
{
Title = "Submit"
});
Attachment cardAttachment = new Attachment()
{
ContentType = AdaptiveCard.ContentType,
Content = card
};
return cardAttachment;
}
}
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
public virtual async Task MessageReceivedAsync(IDialogContext context,
IAwaitable<IMessageActivity> result)
{
var activity = await result as Activity;
Boolean first = true;
if (activity.Value != null)
{
var jObjectValue = activity.Value as JObject;
var fromDateString = jObjectValue.Value<string>("FromDate");
var toDateString = jObjectValue.Value<string>("ToDate");
await context.PostAsync($"from Date Entered: {fromDateString}");
await context.PostAsync($"to Date Entered: {toDateString}");
first = false;
}
if (first)
{
var reply = activity.CreateReply();
var card = EchoBot.LeaveRequest();
var msg = context.MakeMessage();
msg.Attachments.Add(card);
await context.PostAsync(msg);
}
}
}
Thanks for reaching out!! This is a known issue. We have a bug on this and we are working on getting this fixed.
Related
I was trying the multi-language chat bot in bot framework. https://github.com/Microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/17.multilingual-bot.
I was successful in translating ordinary message with no attachment. But I'm having a problem with attachment like herocard or suggestedcard.
var reply = stepContext.Context.Activity.CreateReply();
var card = new HeroCard();
card.Buttons = new List<CardAction>()
{
new CardAction() { Title = "1. All lights are green.", Type = ActionTypes.ImBack, Value = "All lights are green." },
new CardAction() { Title = "2. DSL light is OFF/Red/Blinking Green.", Type = ActionTypes.ImBack, Value = "DSL light is OFF/Red/Blinking Green." },
new CardAction() { Title = "3. Internet light is OFF/Red/Amber or blinking red and green.", Type = ActionTypes.ImBack, Value = "Internet light is OFF/Red/Amber or blinking red and green." },
new CardAction() { Title = "4. Power light is OFF/Red/Amber or blinking.", Type = ActionTypes.ImBack, Value = "Power light is OFF/Red/Amber or blinking." },
};
reply.Attachments = new List<Attachment>() { card.ToAttachment() };
reply.AttachmentLayout = AttachmentLayoutTypes.List;
var options = new PromptOptions()
{
Prompt = reply,
};
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
Thanks!
You need to check if there are any attachments. I have some middleware that sets the speak on message and use the following code:
if (string.IsNullOrEmpty(message.Text))
{
if (message.Attachments[0].Content is HeroCard attachment)
{
message.Speak = TextToSpeechHelper.ConvertTextToSpeechText(attachment.Text);
}
}
else
{
message.Speak = TextToSpeechHelper.ConvertTextToSpeechText(message.Text);
}
You'd have to adjust it to set the text and translate.
Below is a full example of what my middleware is. Keep in mind this middleware sets the speak to the text and sets the input hint.
public class TextToSpeechMiddleware : IMiddleware
{
private readonly IEnumerable<string> ignoreList;
public TextToSpeechMiddleware(string speakIgnore)
{
ignoreList = GetSpeakIgnore(speakIgnore);
}
public Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default(CancellationToken))
{
turnContext.OnSendActivities(OnSendActivities);
turnContext.OnUpdateActivity(OnUpdateActivity);
return next(cancellationToken);
}
private static IEnumerable<string> GetSpeakIgnore(string value)
{
string[] ignoreList = value.Split(';');
return ignoreList.Select(i => i.Trim())
.Where(i => !string.IsNullOrEmpty(i));
}
private Task<ResourceResponse> OnUpdateActivity(ITurnContext turnContext, Activity activity, Func<Task<ResourceResponse>> next)
{
ConvertTextToSpeech(activity);
return next();
}
private Task<ResourceResponse[]> OnSendActivities(ITurnContext turnContext, List<Activity> activities, Func<Task<ResourceResponse[]>> next)
{
foreach (Activity currentActivity in activities.Where(a => a.Type == ActivityTypes.Message))
{
ConvertTextToSpeech(currentActivity);
}
return next();
}
private void ConvertTextToSpeech(Activity message)
{
Activity initialMessage = message;
try
{
if (message.Type == ActivityTypes.Message)
{
bool ignoredSpeak = false;
if (string.IsNullOrEmpty(message.Speak))
{
if (string.IsNullOrEmpty(message.Text))
{
if (message.Attachments[0].Content is HeroCard attachment)
{
message.Speak = TextToSpeechHelper.ConvertTextToSpeechText(attachment.Text);
}
}
else
{
message.Speak = TextToSpeechHelper.ConvertTextToSpeechText(message.Text);
}
message.Speak = message.Speak.Trim();
if (ignoreList.Where(i => message.Speak.ToLower().StartsWith(i.ToLower())).Count() != 0)
{
message.Speak = null;
ignoredSpeak = true;
}
}
else if (string.IsNullOrWhiteSpace(message.Speak))
{
message.Speak = " ";
}
if ((!string.IsNullOrEmpty(message.Speak) && (message.Speak.EndsWith("?") || message.Speak.StartsWith("Is this correct?")))
|| (!string.IsNullOrEmpty(message.Text) && message.Text.EndsWith("?"))
|| ignoredSpeak)
{
message.InputHint = InputHints.ExpectingInput;
}
// IOs won't work with expecting input
if (message.Recipient.Name.EndsWith(":ios"))
{
message.InputHint = InputHints.AcceptingInput;
}
}
// Logic needed to increase speech speed.
// if (!string.IsNullOrEmpty(message.Speak))
// {
// message.Speak = #"<speak version='1.0' " + "xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='en-GB'><prosody rate=\"1.5\">" + message.Speak + "</prosody></speak>";
// }
}
catch (Exception)
{
message = initialMessage;
}
}
}
I made a bot in C# using BotFramework, Adaptive Cards, LUIS and FormFlow. It/he is responsible for managing meetings in a team.
return new FormBuilder<MeetingRequestInput>()
...
.Field(
nameof(MeetingRequestInput.RequestedDate),
"What date would you like to meet?"
)
...
.Build();
During tests we noticed problems when a user was supposed to type the desired meeting date/time (people would type dd/mm/yy, mm/dd/yy, dd-mm-yy, only dd, etcetra) so we would like to use some kind of "Form" with formatted inputs to avoid parsing problems and retaining usability.
I think we can't change the desired keyboard type (kinda like in mobile, where sometimes the keyboard only shows numbers, or shows a DateTime Picker), nor apply a pattern auto-complete, at least using BotFramework.
To solve this, I'd like to use an AdaptiveCard with Date and Time pickers in my FormFlow prompt, at least when the user is asked to type the requested date, like so:
Input example using Adaptive Cards
In this example, the user would fill the AdaptiveDateInput and the AdaptiveTimeInput, then press the Confirm button. Doing so, it would grab the values inside the inputs, then "type and send" for the user the desired DateTime in a specific template, avoiding the previous parsing problems.
Problem is, I can't replace the "normal" FormFlow Card (who's expecting a simple string as a prompt parameter) with an entire Adaptive Card. How do I solve this problem? Are AdaptiveCards the best answer or are there alternatives?
Right now I'm displaying the card manually like so:
AdaptiveCard card = new AdaptiveCard();
card.Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "What date would you like to meet?"
},
new AdaptiveDateInput()
{
Value = DateTime.Now.ToShortDateString(),
Id = "dateInp"
},
new AdaptiveTimeInput()
{
Value = DateTime.Now.ToShortTimeString(),
Id = "timeInp"
}
};
card.Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Type = "Action.Submit",
Title = "Confirm"
}
};
var msg = context.MakeMessage();
msg.Attachments.Add(
new Attachment()
{
Content = card,
ContentType = "application/vnd.microsoft.card.adaptive",
Name = "Requested Date Adaptive Card"
}
);
await context.PostAsync(msg);
I've read this question, but I don't know if we have the exact same problem. And their solution does not apply to me: even though I made the examples above in english, the bot will actually expect inputs in other languages, so yeah we can parse "February 2th" using a Recognizer, but we don't have the same luck with "2 de Fevereiro" nor "2 Fevralya".
Using the FormBuilder.Prompter, you can customize FormFlow messages any way you like. However, since AdaptiveCards send responses in the .Value, the code will need to transfer .Value to .Text property before validation.
Here is an example of a FormFlow form that sends an AdaptiveCard for a RequestedDate field:
[Serializable]
public class AdaptiveCardsFormFlow
{
public string Name { get; set; }
public DateTime? RequestedDate { get; set; }
public static IForm<AdaptiveCardsFormFlow> BuildForm()
{
IFormBuilder<AdaptiveCardsFormFlow> formBuilder = GetFormbuilder();
var built = formBuilder
.Field(nameof(Name), "What is your name?")
.Field(nameof(RequestedDate))
.Confirm("Is this information correct? {*}")
.Build();
return built;
}
private static AdaptiveCard GetDateCard()
{
AdaptiveCard card = new AdaptiveCard();
card.Body = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text = "What date would you like to meet?"
},
new AdaptiveDateInput()
{
Value = DateTime.Now.ToShortDateString(),
Id = "dateInp"
},
new AdaptiveTimeInput()
{
Value = DateTime.Now.ToShortTimeString(),
Id = "timeInp"
}
};
card.Actions = new List<AdaptiveAction>()
{
new AdaptiveSubmitAction()
{
Type = "Action.Submit",
Title = "Confirm"
}
};
return card;
}
private static IFormBuilder<AdaptiveCardsFormFlow> GetFormbuilder()
{
IFormBuilder<AdaptiveCardsFormFlow> formBuilder = new FormBuilder<AdaptiveCardsFormFlow>()
.Prompter(async (context, prompt, state, field) =>
{
var preamble = context.MakeMessage();
var promptMessage = context.MakeMessage();
if (prompt.GenerateMessages(preamble, promptMessage))
{
await context.PostAsync(preamble);
}
if (field != null && field.Name == nameof(AdaptiveCardsFormFlow.RequestedDate))
{
var attachment = new Attachment()
{
Content = GetDateCard(),
ContentType = AdaptiveCard.ContentType,
Name = "Requested Date Adaptive Card"
};
promptMessage.Attachments.Add(attachment);
}
await context.PostAsync(promptMessage);
return prompt;
}).Message("Please enter your information to schedule a callback.");
return formBuilder;
}
}
Using this class:
private class DateTimeInp
{
public string dateInp { get; set; }
public string timeInp { get; set; }
public DateTime? ToDateTime()
{
string fullDateTime = dateInp + " " + timeInp;
DateTime toDateTime;
if(DateTime.TryParse(fullDateTime, out toDateTime))
{
return toDateTime;
}
return null;
}
}
Then, in the Messages Controller, add the Adaptive Card's return value to the .Text property of the activity:
if(activity.Value != null)
{
DateTimeInp input = JsonConvert.DeserializeObject<DateTimeInp>(activity.Value.ToString());
var toDateTime = input.ToDateTime();
if(toDateTime != null)
{
activity.Text = toDateTime.ToString();
}
}
working on QnAMaker. The bot responses well with both Text and HeroCard responses on the Emulator and the WebChat. However, it is not sending the formatted HeroCard via my channel(Skype). I have rebuilt the application using build.cmd. Restarted the Azure services. Still no help.
// BasicQnAMakerDialog.cs:
public RootDialog() : base(new QnAMakerService
(new QnAMakerAttribute(
RootDialog.GetSetting("QnAAuthKey") == null ? ConfigurationManager.AppSettings["QnAAuthKey"] : ConfigurationManager.AppSettings["QnAAuthKey"],
Utils.GetAppSetting("QnAKnowledgebaseId") == null ? ConfigurationManager.AppSettings["QnAKnowledgebaseId"] : ConfigurationManager.AppSettings["QnAKnowledgebaseId"],
"Not sure how to help with that. Try asking about the ALOTB, Pricing, Technology and scope to find more info or get in touch with sriram.chandrasekaran#epsilon.com", 0.5, 1,
Utils.GetAppSetting("QnAEndpointHostName") == null ? ConfigurationManager.AppSettings["QnAEndpointHostName"] : ConfigurationManager.AppSettings["QnAEndpointHostName"])))
{
}
//override method
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
var answer = result.Answers.First().Answer;
Activity reply = ((Activity)context.Activity).CreateReply();
string[] qnaAnswerData = answer.Split(';');
int dataSize = qnaAnswerData.Length;
if (dataSize > 1 && dataSize <= 6)
{
var attachment = GetSelectedCard(answer);
reply.Attachments.Add(attachment as Attachment);
await context.PostAsync(reply);
}
else
{
await context.Forward(new BasicQnAMakerDialog(), AfterAnswerAsync, message, CancellationToken.None);
await context.PostAsync(reply);
}
}
private static Attachment GetSelectedCard(string answer)
{
int len = answer.Split(';').Length;
switch (len)
{
case 4: return GetHeroCard(answer);
default: return GetHeroCard(answer);
}
}
private static Attachment GetHeroCard(string answer)
{
string[] qnaAnswerData = answer.Split(';');
string title = qnaAnswerData[0];
string description = qnaAnswerData[1];
string url = qnaAnswerData[2];
string imageURL = qnaAnswerData[3];
HeroCard card = new HeroCard
{
Title = title,
Subtitle = description,
};
card.Buttons = new List<CardAction>
{
new CardAction(ActionTypes.OpenUrl, "Learn More", value: url)
};
card.Images = new List<CardImage>
{
new CardImage( url = imageURL)
};
return card.ToAttachment();
}
I am new in stripe, how can we set default payment method in stripe.
And can we pass cardId/sourceId to charge customer along with customerId.
Code:-
private static async Task<string> ChargeCustomer(string customerId)
{
return await System.Threading.Tasks.Task.Run(() =>
{
var myCharge = new StripeChargeCreateOptions
{
Amount = 50,
Currency = "gbp",
Description = "Charge for property sign and postage",
CustomerId = customerId
};
var chargeService = new StripeChargeService();
var stripeCharge = chargeService.Create(myCharge);
return stripeCharge.Id;
});
}
And 1 more question, how to get charge-list, I am using below code but getting exception(conversion error):-
private IEnumerable<StripeCharge> GetChargeList()
{
var chargeService = new StripeChargeService();
return chargeService.List();
}
This is what I ended up doing. Not sure why Stripe Checkout didn't set the card for the subscription setup as the default. Anyway, this fires triggered from the payment_intent.succeeded web hook. Sure there is a better way, but...
var customerService = new CustomerService(Configs.STRIPE_SECRET_KEY);
var c = customerService.Get(pi.CustomerId);
if (!string.IsNullOrEmpty(c.InvoiceSettings.DefaultPaymentMethodId)) {
status = "already has default payment method, no action";
hsc = HttpStatusCode.OK;
return;
}
var paymentMethodService = new PaymentMethodService(Configs.STRIPE_SECRET_KEY);
var lopm = paymentMethodService.ListAutoPaging(options: new PaymentMethodListOptions {
CustomerId = pi.CustomerId,
Type = "card"
});
if (!lopm.Any()) {
status = "customer has no payment methods";
hsc = HttpStatusCode.BadRequest;
return;
}
var pm = lopm.FirstOrDefault();
customerService.Update(pi.CustomerId, options: new CustomerUpdateOptions {
InvoiceSettings = new CustomerInvoiceSettingsOptions {
DefaultPaymentMethodId = pm.Id
}
});
hsc = HttpStatusCode.OK;
return;
We can pass cardId/BankAccountId/TokenId/SourceId in SourceTokenOrExistingSourceId property of StripeChargeCreateOptions,
private static async Task<string> ChargeCustomer(string customerId, string cardId)
{
try
{
return await System.Threading.Tasks.Task.Run(() =>
{
var myCharge = new StripeChargeCreateOptions
{
Amount = 50,
Currency = "gbp",
Description = "Charge for property sign and postage",
CustomerId = customerId,
SourceTokenOrExistingSourceId = cardId
};
var chargeService = new StripeChargeService();
var stripeCharge = chargeService.Create(myCharge);
return stripeCharge.Id;
});
}
catch(Exception ex)
{
return "";
}
}
To set/change default payment method:-
public void ChangeDefaultPayment(string customerId, string sourceId)
{
var myCustomer = new StripeCustomerUpdateOptions();
myCustomer.DefaultSource = sourceId;
var customerService = new StripeCustomerService();
StripeCustomer stripeCustomer = customerService.Update(customerId, myCustomer);
}
Still looking for how to get charge-list.
I am attempting to create an event through the Microsoft Graph API inviting users as attendees. The code to set the attendees is as follows:
var attendees = new List<Microsoft.Graph.Attendee>();
foreach (var e in emailAddresses)
{
var userProfile = await AzureGraph.GetOtherUserProfile(e);
if (e != currentUserEmail.First())
{
Microsoft.Graph.EmailAddress email = new Microsoft.Graph.EmailAddress();
email.Name = userProfile.DisplayName;
email.Address = e;
attendees.Add(new Microsoft.Graph.Attendee()
{
EmailAddress = email,
Type = Microsoft.Graph.AttendeeType.Optional
});
}
}
await AzureGraph.AddEvent(new Microsoft.Graph.Event
{
Subject = string.Format("Follow Up: {0}", Id),
Body = new Microsoft.Graph.ItemBody
{
Content = "content"
},
Start = start,
End = start.AddMinutes(30),
Attendees = attendees
});
However, when making the request, i get a Bad Request response. The reason for this is that the 'Type' of an attendee is a n enum of Microsoft.Graph.AttendeeType and this is not being enumerated properly. Therefore it is attempting to send through the numeric value '1' instead of the string value "Optional" causing it to fail.
I was able to confirm this using fiddler, if i manually change the numeric value to the string value then it works no problem.
Has anybody come across this or have any ideas how i can solve this?
Many Thanks for your help in advance :)
I have now managed to solve this. The solution is a bit of a hack but it works. My original call code was as follows:
public static async Task AddEvent(Event e)
{
using (var client = new HttpClient())
{
using (var req = new HttpRequestMessage(HttpMethod.Post, _calendarUrl))
{
var token = await GetToken();
req.Headers.Add("Authorization", string.Format("Bearer {0}", token));
req.Headers.TryAddWithoutValidation("Content-Type", "application/json");
var requestContent = JsonConvert.SerializeObject(new
{
Subject = e.Subject,
Body = new
{
ContentType = "HTML",
Content = e.Body.Content
},
Start = new
{
DateTime = e.Start,
TimeZone = "UTC"
},
End = new
{
DateTime = e.End,
TimeZone = "UTC"
}
});
req.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
using (var response = await client.SendAsync(req))
{
if (response.IsSuccessStatusCode)
{
return;
}
else
{
throw new HttpRequestException("Event could not be added to calendar");
}
}
}
}
}
I have now changed this to:
public static async Task AddEvent(Event e)
{
using (var client = new HttpClient())
{
using (var req = new HttpRequestMessage(HttpMethod.Post, _calendarUrl))
{
var token = await GetToken();
req.Headers.Add("Authorization", string.Format("Bearer {0}", token));
req.Headers.TryAddWithoutValidation("Content-Type", "application/json");
IList<Attendee> attendees = new List<Attendee>();
foreach(var a in e.Attendees)
{
attendees.Add(new Attendee()
{
EmailAddress = a.EmailAddress,
Type = Enum.GetName(typeof(AttendeeType), AttendeeType.Optional)
});
}
var requestContent = JsonConvert.SerializeObject(new
{
Subject = e.Subject,
Body = new
{
ContentType = "HTML",
Content = e.Body.Content
},
Start = new
{
DateTime = e.Start,
TimeZone = "UTC"
},
End = new
{
DateTime = e.End,
TimeZone = "UTC"
},
Attendees = attendees
});
req.Content = new StringContent(requestContent, Encoding.UTF8, "application/json");
using (var response = await client.SendAsync(req))
{
if (response.IsSuccessStatusCode)
{
return;
}
else
{
throw new HttpRequestException("Event could not be added to calendar");
}
}
}
}
}
As well as adding the following local class:
private class Attendee
{
public EmailAddress EmailAddress { get; set; }
public string Type { get; set; }
}
Essentially, the Graph Attendee expected:
1. an EmailAddress object containing Name (string) and Email (string).
2. a Type object of type AttendeeType which was the enum not being passed correctly.
Therefore, i created my own version of the class Attendee to contain the same EmailAddress object and Type of type string as the API expects it to be.
I then had to change the enum type to the name of the enum rather than the int value. This was done as follows:
attendees.Add(new Attendee()
{
EmailAddress = a.EmailAddress,
Type = Enum.GetName(typeof(AttendeeType), AttendeeType.Optional)
});
This gave me the value "Optional" rather than 1 which made it acceptable to the API.
I hope this helps someone in the future.
It looks like a major oversight on microsoft's part to use an enum in the code and the expect a string rather than an integer in the API and i think this needs addressing.