What's the right way to execute a Twilio Studio conversation flow? - python-3.x

Im learning how to set a twilio studio flow with python, I'm currently testing one of the templates that Twilio provides, and Im communicating with the bot from WhatsApp. However, I can only send the first message of the flow and if I send another message, this message pop up:
Unable to create record: Execution XXXXXXXXXXXXXXXXXXXXX is already active for this contact. End the active Execution before creating a new one
I tried to add .update(status='ended') to my variable, but it just kinda looped every time I sent a message, I know that every time that theres an incoming message it will trigger the conversation. So my question is, how can I continue the conversation flow without creating a new trigger every time that theres an incomming message?
Here's my flow in case it's necessary.
And this is the functions and endpoints that I'm using to trigger the action:
# twilio.route('/incoming_message', methods=['GET', 'POST'])
def incoming_message_data() -> str:
if request.method == 'POST':
response = {}
error, message, code = False, '', ''
message = incoming_message()
response.update({'sucess': True, 'message': message, 'message': f'{message}', 'status_code': 200, 'error': None, 'code': f'{code}'} if message and message != [{}]else {
'sucess': False, 'message': 'Message could not be sent', 'status_code': 400, 'error': f'{error}', 'code': f'{code}'})
return message
def twilio_studio_flow(phone_number: str) -> str:
'''
Twilio Studio Flow
'''
response = request.values.get('Body', '').lower()
execution = twilio_client.studio \
.v2 \
.flows(Config.TWILIO_STUDIO_FLOW_SID) \
.executions \
.create(to=(f'whatsapp:{phone_number}'), from_=Config.TWILIO_PHONE_NUMBER,
parameters={
"appointment_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
})\
.update(status='ended')
def validate_phone_number(phone_number: str) -> bool:
'''
Validate phone number
'''
try:
phone = phonenumbers.parse(phone_number.strip(), None)
client = Client.query.filter_by(phone=phone_number).first()
return phonenumbers.is_valid_number(phone) and client is not None
except Exception:
return False
def incoming_message() -> str:
'''
Receive incoming messages
'''
# Get the message the user sent our Twilio number
incoming_message = request.values.get('Body', '').lower()
# Get the phone number of the person sending the text message
phone_number = request.values.get('From', None).replace('whatsapp:', '')
resp = MessagingResponse()
if validate_phone_number(phone_number) and incoming_message:
resp.message(twilio_studio_flow(phone_number))
else:
resp.message(
'Lo sentimos, no pudimos validar tu numero de telefono 😟')
return str(resp)
Thanks in advance for helping me :).
As I mentioned previously, I want to know the right way to execute a Twilio Studio. Honestly, I cheked the docs but it's a little bit unclear on how to this.

Based on your answer in the comments, I'd suggest to link the Studio flow directly to the WhatsApp sender in the console (instead of invoking the flow manually via the API).
To connect the Studio Flow to your WhatsApp number ("Sender"). Click on the Trigger (Start) Widget and locate the Webhook URL field in the right-hand menu. Copy that URL to your clipboard.
Next, navigate to your WhatsApp Senders in the Twilio console. Click to select the sender that you want to use with this Studio Flow. Paste the Webhook URL that you copied from your Studio Flow into the field Webhook URL for incoming messages. Don't forget to click Update WhatsApp Sender.
Now, any time you receive an inbound message on your selected WhatsApp-enabled sender (number), it will be routed to your new Studio Flow.
Taken from the documentation.

Related

Gmail API - How to relabel emails as spam using Filters?

I am attempting to write a bot which searches for an email by a keyword, then creates a filter sending all future emails from that sender to spam.
Here is my code:
async def remove_subscription(service, from_email, message):
# labels = service.users().labels().list(userId='me').execute().get('labels', [])
# for label in labels:
# print(label['id'])
filter_content = {
'criteria': {
'from': from_email
},
'action': {
'addLabelIds': ['SPAM'],
'removeLabelIds': ['INBOX']
}
}
try:
result = service.users().settings().filters().create(
userId='me', body=filter_content).execute()
print(F'Created filter with id: {result.get("id")}')
return True
except:
print('An error occurred')
await message.channel.send('An error occurred.')
return False
As you can see, it is pretty much the given code from the google docs:
https://developers.google.com/gmail/api/guides/filter_settings?hl=en
The api won't allow me to use addLabelIds: ['SPAM'], and it does not work while attempting to do the same query on the gmail docs in-webpage testing (So I know it isnt an issue with the rest of my code). Here is the error:
raise HttpError(resp, content, uri=self.uri)
googleapiclient.errors.HttpError: <HttpError 400 when requesting https://gmail.googleapis.com/gmail/v1/users/me/settings/filters?alt=json returned "Invalid label SPAM in AddLabelIds". Details: "[{'message': 'Invalid label SPAM in AddLabelIds', 'domain': 'global', 'reason': 'invalidArgument'}]">
I can't seem to find an explanation anywhere which states that I can't add SPAM as a label ID. I confirmed with lines 2-4 that the label ID is indeed 'SPAM".
Is this just not allowed by the api? How could I create a filter which sends all emails from a specific sender to spam?
Thanks ahead of time.
Issue:
I don't think Gmail allows messages to be sent to SPAM via filters. This is true not just for the API, but for the user-interface too:
If you'd like this feature to be added to Gmail, I'd suggest requesting it via Send feedback on the user-interface. Nevertheless, it's likely that this is a deliberate limitation. In any case, you could use a custom label to act as SPAM.
Related:
How to send messages to spam in Gmail filter?

send message from bot to direct message after typing /slash command

I'm trying to make SlackBot and if I call him in some public channel it works fine but when I call him (type slash-command) in any direct channel I receive "The server responded with: {'ok': False, 'error': 'channel_not_found'}". In public channels where I've invited my bot it works fine, but if I type "/my-command" in any DM-channel I receive response in separate DM-channel with my bot. I expect to receive these responses in that DM-channel where I type the command.
Here is some part of my code:
if slack_command("/command"):
self.open_quick_actions_message(user_id, channel_id)
return Response(status=status.HTTP_200_OK)
def open_quick_actions_message(self, user, channel):
"""
Opens message with quick actions.
"""
slack_template = ActionsMessage()
message = slack_template.get_quick_actions_payload(user=user)
client.chat_postEphemeral(channel=channel, user=user, **message)
Here are my Event Eubscriptions
Here are my Bot Token Scopes
Can anybody help me to solve this?
I've already solved my problem. Maybe it will help someone in the future. I've sent my payload as the immediate response as it was shown in the docs and the response_type by default is set to ephemeral.
The part of my code looks like this now:
if slack_command("/command"):
res = self.slack_template.get_quick_actions_payload(user_id)
return Response(data=res, status=status.HTTP_200_OK)
else:
res = {"text": "Sorry, slash command didn't match. Please try again."}
return Response(data=res, status=status.HTTP_200_OK)
Also I have an action-button and there I need to receive some response too. For this I used the response_url, here are the docs, and the requests library.
Part of this code is here:
if action.get("action_id", None) == "personal_settings_action":
self.open_personal_settings_message(response_url)
def open_personal_settings_message(self, response_url):
"""
Opens message with personal settings.
"""
message = self.slack_template.get_personal_settings_payload()
response = requests.post(f"{response_url}", data=json.dumps(message))
try:
response.raise_for_status()
except Exception as e:
log.error(f"personal settings message error: {e}")
P. S. It was my first question and first answer on StackOverflow, so don't judge me harshly.

sending response to particular django websocket client from rest api or a server

consumer.py
# accept websocket connection
def connect(self):
self.accept()
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
command = text_data_json['command']
job_id = text_data_json['job_id']
if command == 'subscribe':
self.subscribe(job_id)
elif command == 'unsubscribe':
self.unsubscribe(job_id)
else:
self.send({
'error': 'unknown command'
})
# Subscribe the client to a particular 'job_id'
def subscribe(self, job_id):
self.channel_layer.group_add(
'job_{0}'.format(job_id),
self.channel_name
)
# call this method from rest api to get the status of a job
def send_job_notification(self, message, job_id):
channel_layer = get_channel_layer()
group_name = 'job_{0}'.format(job_id)
channel_layer.group_send(
group_name,
{
"type": "send.notification",
"message": message,
}
)
# Receive message from room group
def send_notification(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps(
message))
In the above code what I am trying to do is connect clients to the socket and subscribe clients to a particular "job_id" by creating a group called "job_1" using "subscribe" method and add it to the channel layer. Creation of groups are dynamic.
I am using below "simple websocket client extension" from Google to connect to the above websocket. I am able to make a connection with the websocket and send request to it as shown in the picture below.
Now since the client is connected and subscribed to a particular "job_id",
I am using "Postman" to send notification to the above connected client(simple websocket client extension) subscribed to particular "job_id" by passing in the job_id in the request as highlighted in yellow below.
when I do a post to the "REST-API" I am calling "send_job_notification(self, message, job_id)" method of "consumer.py" file along with the "job_id" as '1' shown in the picture below in yellow
After doing all this I don't see any message sent to the connected client subscribed to a "job_id" from the "REST-API" call.
Any help would be highly appreciated as it has been dragging on for a long time.
Edit:
thank you for the suggestion Ken its worth to make the method as "#staticmethod" but Ken how do I make the API send job status updates to the connected Clients because my long running jobs would run in some process and send update messages back to the backend via REST-API and the updates then need to be sent to the correct Client (via websockets).
My API call to the socket consumer is as below:
from websocket_consumer import consumers
class websocket_connect(APIView):
def post(self, request, id):
consumers.ChatConsumer.send_job_notification("hello",id)
My socket consumer code is as below:
Edit
`CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("localhost", 6379)],
},
},
}`
As you can see 'Redis' service is also running
Edit-1
You cannot call the method in the consumer directly from an external code because you need to get the particular consumer instance connected to your client. This is the job of the channel layer achieved by using a message passing system or broker as reddis.
From what I can see, you're already going towards the right direction, except that the send_job_notification is an instance method which will require instantiating the consumer. Make it a static method instead, so you can call it directly without a consumer instance
#staticmethod
def send_job_notification(message, job_id):
channel_layer = get_channel_layer()
group_name = 'job_{0}'.format(job_id)
channel_layer.group_send(
group_name,
{
"type": "send.notification",
"message": message,
}
And in your API view, you can simply call it as:
ChatConsumer.send_job_notification(message, job_id)

Difference in the receive and send method in a websocket

I am looking over the following code which does a 'group chat' with different members:
# Receive message from WebSocket
def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Send message to room group
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': 'OK'
}
)
# Receive message from room group
def chat_message(self, event):
message = event['message']
# Send message to WebSocket
self.send(text_data=json.dumps({
'message': message
}))
My questions is what do the two items do? I see that receive(), also does the group_send, so what purpose does the chat_message have if the receive sends it upon receiving it?
That chat server code is a simple example on how to send group messages.
In the code:
async_to_sync(self.channel_layer.group_send)(
self.room_group_name,
{
'type': 'chat_message',
'message': 'OK'
}
)
this line
'type': 'chat_message',
is responsible for calling method chat_message() with { 'message': 'OK'}
Before sending this message to the group members you may want to modify or check the data, or need to do other stuff. That's why self.channel_layer.group_send doesn't directly sends message to the group but calls another method (in this case chat_message) to handle sending of message and to keep receive() method's code clean.

Messenger Send API

I am creating a self built Python chatbot that does not use a chatbot platform such as Dialogflow. The issue is that there is no easy integration with messaging apps such as Messenger to connect it too. I am trying to create a webhook to Messenger using the Messenger Send API. I am looking at the documentation and it shows me how to request a POST api call. However when I look at examples online they all seem to deal with json values called "entry" and "messaging" which I can't find anywhere and can't seem to see why it is necessary. I was wondering how exactly the input body of a Messenger Send API looks like so I can call it appropriately and what json objects are in its body. This is what I have so far from following online examples. I am using Flask. And am using Postman to test this
#app.route("/webhook", methods=['GET','POST'])
def listen():
if request.method == 'GET':
return verify_webhook(request)
if request.method == 'POST':
payload = request.json
event = payload['entry'][0]['messaging']
for x in event:
if is_user_message(x):
text = x['message']['text']
sender_id = x['sender']['id']
respond(sender_id, text)
return "ok"
Below is what I think the body of the request looks like:
{
"object":"page",
"entry":[
{
"id":"1234",
"time":1458692752478,
"messaging":[
{
"message":{
"text":"book me a cab"
},
"sender":{
"id":"1234"
}
}
]
}
]
}
But it is unable to read this and gives me an error of:
File"/Users/raphael******/Documents/*****_Project_Raphael/FacebookWebHookEdition.py", line 42, in listen
event = payload['entry'][0]['messaging']
TypeError: 'NoneType' object is not subscriptable
Where am I going wrong that the webhook is not registering the body correctly as json objects?
Here is how we do it:
# GET: validate facebook token
if request.method == 'GET':
valid = messenger.verify_request(request)
if not valid:
abort(400, 'Invalid Facebook Verify Token')
return valid
# POST: process message
output = request.get_json()
if 'entry' not in output:
return 'OK'
for entry in output['entry']:
if 'messaging' not in entry:
continue
for item in entry['messaging']:
# delivery notification (skip)
if 'delivery' in item:
continue
# get user
user = item['sender'] if 'sender' in item else None
if not user:
continue
else:
user_id = user['id']
# handle event
messenger.handle(user_id, item)
# message processed
return 'OK'
EDIT:
If you are using postman, please make sure to also set Content-Type header to application/json, otherwise Flask can't decode it with request.json. I guess that's where None comes from in your case.

Resources