Telegram add and retrieve metadata from message - aws-lambda

Hi I'm looking for a way to store user session/metadata with the least amount of latency and that will not cost me an arm and a leg.
Brief problem description.
I have a bot that helps users download files from Google Drive.
It uses a Webhook of an AWS lambda function.
Users are provided with clickable filenames, e.g.
/File.pdf
Once they click on it, it needs to be downloaded and sent to the user.
The problem is I need a way of knowing what file the user chose without having to use a database or iterating through all my files by name.
E.g. Is there a way of adding metadata to the clickable message? Such that I can add that metadata to the clickable and if a user clicks /File.pdf, I'll be able to extract the metadata.

You can send InlineKeyboardButton like in this example and set in callback_data whatever you need. When user clicks on that button - your bot will receive that data in update:
button_list = [
InlineKeyboardButton("File1.pdf", callback_data="https://drive.google.com/invoice.pdf"),
InlineKeyboardButton("File2.pdf", callback_data="https://drive.google.com/presentation.pdf"),
InlineKeyboardButton("File3.pdf", callback_data="https://drive.google.com/report.pdf")
]
reply_markup = InlineKeyboardMarkup(button_list)
bot.send_message(chat_id=chat_id, "Files list:", reply_markup=reply_markup)
# in update handler:
def some_update_handler(update, context):
url = update.callback_query.data
# ...
# further processing
This can be also useful in any other case when Telegram bot user should see some nice message, but shouldn't see some internal value sent to Telegram bot.

Related

Microsoft Teams Bot - Is it possible to display an Adaptive Card to get user input when a user enters a command?

I want to develop a Microsoft Teams Bot that when a user enters a keyword, such as 'pto' it will show an Adaptive Card form that the user can populate with information which will then get sent to a web service for processing.
Is it possible to display an Adaptive Card based on a user entering a text command?
Yes, absolutely possible. A "Command" is really just a regular message, and it's how you interpret / respond to the message. You can either detect these messages yourself by looking at the content, or you can use a conversational AI engine like LUIS.ai (part of Azure) which can more easily be configured to check for variants (e.g. "pto", "PTO", "POT", "[whatever 'pto' stands for']" etc. In this case, LUIS is basically building a ton of "if" statements to find a match, and the returning you an "intent" (e.g. "User is requesting whatever "PTO" means"). Because it's using AI to generate and maintain this "if" list, language conversational engines like LUIS are widely used in bots, but they're not -required-. It's why you see them in most samples though.
With regards sending an Adaptive Card, that's also pretty standard stuff in Teams bots, and they can be sent in response to a user's message, like you're trying here, as well as other ways to invoke them.
I actually cover both of these topics one after the other in a conference session earlier this year - see the video here: https://www.youtube.com/watch?v=mM7-fYdcJhw&t=1398s (the earlier parts might actually be of interest too).
Bot Framework can't handle the card prompts natively, so the best solution I've come up with is to
Display the card as a standard activity (i.e. before the prompt)
Provide a message indicating you are waiting for prompt input (e.g. waiting for selection...)
Validate that the prompt input is an object (e.g. input.match(/{.+/g))
Use the values from the object in your next step to call the webservice
So in code it ends up like this:
await step.context.sendActivity(CardHelper.datePicker());
return await step.prompt(DATE_PROMPT, `*waiting for selection...*`);
with a validator to make sure you are receiving an object like this:
async validateDateCard(prompt) {
prompt.activeDialog = await this.userDialogStateAccessor;
prompt.context = await prompt.context;
const input = prompt.recognized.value;
if (input.match(/{.+/g)) {
return true;
} else {
await prompt.context.sendActivity(`Please use the calendar widget above to enter the date.`);
return false;
}
}
In my case my widget just has a single field for date, but it works the same regardless of how many fields you have. They will all be in the submitted object. If you try to type something in manually, you are reprompted to use the widget. In general I find prompting for plain text values in sequence instead of using cards works fine and it not too cumbersome for users. But if you have a ton of inputs or need specialized controls like the above date widget, sometimes cards are the only way.

Responding to Conversations async: Graph or Bot?

I have a Teams Message extension that returns a Task response which is a medium sized embedded web view iFrame
This is working successfully; including added a custom Tab within the channel and other nice magic calls to Microsoft Graph.
What I am confused about is how to do (and this is probably my not understanding the naming of things)
insert "something" Back into the Message/Post stream which is a link to newly created Tab ... like the what you get when you have a "configureTabs" style Tab created -- there is a friendly Message (Post) in the chat pointing to this new Tab.
do I do this with Microsoft Graph or back through the Bot?
the code that does the communication may be a different service elsewhere that is acting async ... so it needs to communicate with something somewhere with context. Confused if this is the Bot with some params or Microsoft Graph with params.
how to insert an image (rather than a link to the tab) into the Message/Post stream -- but showing the image not a link off to some random URL (ie: )
could not find any samples that do this; again, will be async as per above; but the format of the message will be a Card or something custom?
So just to be clear, a Task Response is NOT the same as a Tab, albeit that they might end up hosted in the same backend web application (and also albeit that your TAB can actual bring up your Task Response popup/iframe using the Teams javascript library).
Aside from that, in order to post something back to the channel, like when the Tab is created, there are two ways to do so:
First is to use Graph Api's Create ChatMessage option (this link is just for a channel though - not sure if your tab/task apply to group chats and/or 1-1 chats as well).
2nd Option is to have a Bot be part of your application as well. Then, when you're ready to send something to the channel, you'd effectively be sending something called a "pro-active messaging". You need to have certain reference data to do this, which you would get when the bot is installed into the channel ("conversation reference", "ServiceUrl", and so on). I describe this more in my answer at Programmatically sending a message to a bot in Microsoft Teams
With regards sending the image, either of the above would work here too, in terms of how to send the image. As to the sending of an image, you'd need to make use of one of the kinds of "Cards" (basically "richer" messages than just raw text). You can learn more about this at Introducing cards and about the types of cards for Teams at Card reference. There are a few that can be used to send an image, depending on perhaps what else you want the card to do. For instance, an Adaptive Card can send an image, some text, and an action button of some sort.
Hope that helps
To close the loop for future readers.
I used the following Microsoft Graph API docs, and the posting above, and this is working: Create chatMessage in a channel and Creating a Custom Microsoft Graph call from the SDK
The custom graph call (as it is not implemented in the .NET SDK at the time of this response) looks something like:
var convoReq = $"https://graph.microsoft.com/beta/teams/{groupId}/channels/{channelId}/messages";
var body = this.TeamsMessageFactory(newCreatedTabUrl, anotherstring).ToJson();
var postMessage = new HttpRequestMessage(HttpMethod.Post, convoReq)
{
Content = new StringContent(body, System.Text.Encoding.UTF8, "application/json")
};
await _graphClient.CurrentGraphClient.AuthenticationProvider.AuthenticateRequestAsync(postMessage);
var response = await _graphClient.CurrentGraphClient.HttpProvider.SendAsync(postMessage);
if (response.IsSuccessStatusCode)
{
var content = await response.Content.ReadAsStringAsync();
return true;
}
The groupId and channelId are found elsewhere; and the TeamsMessageFactory is just some boilerplate that serialized the C# object graph for the POST request, as detailed in Create chatMessage in a channel

Return initial data on subscribe event in django graphene subscriptions

I'm trying to response to user on subscribe. By example, in a chatroom when an user connect to subscription, the subscription responses him with data (like a welcome message), but only to same user who just connect (no broadcast).
How can I do that? :(
Update: We resolve to use channels. DjangoChannelsGraphqlWs does not allow direct back messages.
Take a look at this DjangoChannelsGraphQL example. Link points to the part which is there to avoid "user self-notifications" (avoid user being notified about his own actions). You can use the same trick to send notification only to the user who made the action, e.g. who just subscribed.
Modified publish handler could look like the following:
def publish(self, info, chatroom=None):
new_msg_chatroom = self["chatroom"]
new_msg_text = self["text"]
new_msg_sender = self["sender"]
new_msg_is_greetings = self["is_greetings"]
# Send greetings message only to the user who caused it.
if new_msg_is_greetings:
if (
not info.context.user.is_authenticated
or new_msg_sender != info.context.user.username
):
return OnNewChatMessage.SKIP
return OnNewChatMessage(
chatroom=chatroom, text=new_msg_text, sender=new_msg_sender
)
I did not test the code above, so there could be issues, but I think it illustrates the idea quite well.

Put a user on hold with Amazon Lex

We are using Amazon Connect, Lex and Lambda to create a phone bot. One use case we have is that we need to put the user on hold while we find information in other systems. So the conversation will be something like this:
- bot: hi, what can I do for you?
- user: i want to make a reservation
- bot: wait a minute while I fetch information about available rooms
... after 5 seconds ...
- bot: I found a free room blah blah
I don't see a way to send the wait a minute... message and keep control of the conversation. How can we achieve that?
You can accomplish this inside a single Lex bot by setting the intent to be fulfilled by a lambda function, the response of the function would play a message saying “please wait” and then chain another internet to perform the search using the data from the original intent.
See this link for information about sharing data between intents.
You can chain or switch to the next intent by passing the confirmIntent dialog action back in the lambda response. See this link for more information on the lambda input and response format.
You can use wait block in aws connect https://docs.aws.amazon.com/connect/latest/adminguide/flow-control-actions-wait.html
By using this block you can set time to 5 secs . after time expired you can play prompt.
This is a very common problem typically when we want to do backend lookups in an IVR. The problem is lex does not provide any means to just play prompts.
One way to do it is:
Create a dummy slot in your intent (the reservation intent from your example above) with any type (e.g. AMAZON.NUMBER), we don't really care what the value is in this slot
From the lex code-hook for the intent, return ElicitSlot for this dummy slot with prompt as "Wait a minute while I fetch available rooms... "
If you do only this much, the problem you will face is that Lex will expect input from caller and will wait for around 4 seconds before passing control back to the Init and Validation Lambda, so there will be unnecessary delay. To overcome this, you need to set timeout properties as session attribute in "Get Customer Input" block from connect.
Property1:
Lex V2 Property name: x-amz-lex:audio:start-timeout-ms:[intentName]:[slotToElicit]
Lex Classic Property name x-amz-lex:start-silence-threshold-ms:[intentName]:[slotToElicit]
value: 10 (or any small number, this is in millseconds)
Property2:
Only available in Lex Classic, to disable barge-in on Lex V2, you can do it for required slot from lex console
Property name: x-amz-lex:barge-in-enabled:[intentName]:[slotToElicit]
Value: false
If barge-in is not disabled, there is a chance user may speak in middle of your "Please wait..." prompt and it will not be played completely.
Official documentation for these properties:
https://docs.aws.amazon.com/connect/latest/adminguide/get-customer-input.html
https://docs.aws.amazon.com/lexv2/latest/dg/session-attribs-speech.html
Another way:
Whenever such a prompt needs to be played, store the lex context temporarily either as a contact attribute after serialization, or if too big in size to be stored as contact attribute in a store like dynamodb.
Return control back to connect, play the prompt using 'Play prompt' module in connect. To give control back to bot, you will need invoke a lambda to re-initialize Lex with the full lex context again- using PostText API and then again passing control to same bot using 'Get Customer Input'
I have implemented option1 and it works well. You can even create cover-prompt which gets played if the backend lookup takes longer than expected. The actual lookup could be delegated to another lambda so that the code-hook lambda can continue doing customer interaction ever x (say 5) seconds to keep them informed that you are still looking up information.

How would I design this scenario in Twilio?

I'm working on a YRS 2013 project and would like to use Twilio. I already have a Twilio account set up with over $100 worth of funds on it. I am working on a project which uses an external API and finds events near a location and date. The project is written in Ruby using Sinatra (which is going to be deployed to Heroku).
I am wondering whether you guys could guide me on how to approach this scenario: a user texts to the number of my Twilio account (the message would contain the location and date data), we process the body of that sms, and send back the results to the number that asked for them. I'm not sure where to start; for example if Twilio would handle some of that task or I would just use Twilio's API and do checking for smss and returning the results. I thinking about not using a database.
Could you guide me on how to approach this task?
I need to present the project on Friday; so I'm on a tight deadline! Thanks for our help.
They have some great documentation on how to do most of this.
When you receive a text you should parse it into the format you need
Put it into your existing project and when it returns the event or events in the area you need to check how long the string is due to a constraint that twilio has of restricting messages to 160 characters or less.
Ensure that you split the message elegantly and not in the middle of an event. If you were returned "Boston Celtics Game", "The Nut Cracker Play". you want to make sure that if both events cannot be put in one message that the first message says "Boston Celtics Game, Another text coming in 1 second" Or something similar.
In order to receive a text message from a mobile device, you'll have to expose an endpoint that is reachable by Twilio. Here is an example
class ReceiveTextController < ActionController
def index
# let's pretend that we've mapped this action to
# http://localhost:3000/sms in the routes.rb file
message_body = params["Body"]
from_number = params["From"]
SMSLogger.log_text_message from_number, message_body
end
end
In this example, the index action receives a POST from Twilio. It grabs the message body, and the phone number of the sender and logs it. Retrieving the information from the Twilio POST is as simple as looking at the params hash
{
"AccountSid"=>"asdf876a87f87a6sdf876876asd8f76a8sdf595asdD",
"Body"=> body,
"ToZip"=>"94949",
"FromState"=>"MI",
"ToCity"=>"NOVATO",
"SmsSid"=>"asd8676585a78sd5f548a64sd4f64a467sg4g858",
"ToState"=>"CA",
"To"=>"5555992673",
"ToCountry"=>"US",
"FromCountry"=>"US",
"SmsMessageSid"=>"hjk87h9j8k79hj8k7h97j7k9hj8k7",
"ApiVersion"=>"2008-08-01",
"FromCity"=>"GRAND RAPIDS",
"SmsStatus"=>"received",
"From"=>"5555992673",
"FromZip"=>"49507"
}
Source

Resources