I am new to Twilio's API, and for that matter, Twilio as a whole.
I'm using python to make an automated system in which, when an event happens, a call is placed to a particular number. Below is what I'm running (in Python):
from twilio.rest import Client
account_sid = 'ACxx...'
auth_token = 'xx...'
client = Client(account_sid, auth_token)
call = client.calls.create(
url='my/twilio/function'
to="+11111111111",
from="+12222222222",
)
print(call)
And the following is my function in my Twilio account:
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say("A call was placed on the following extension.");
callback(null, twiml);
};
This will successfully make the call to my test number, and plays the message "A call was placed on the following extension". However, what I would like is to be able to pass parameters to say on which extension the call was made, e.g., "A call was placed on extension 100". How do I accomplish passing this custom parameter?
Any query parameters or POST body parameters sent to your Function's URL should be available in the event argument to your Function. From your Python code, consider adding a query parameter to your Function URL with the data your Function will need (the extension dialed, it sounds like?). So instead of /my/function it would be /my/function?extension=100, which should then be available in your Function code as event.extension.
Related
I cannot, for the life of me, get Twilio to execute the twiml instructions within a Flask.redirect. I cannot see anything wrong when I execute the query via Postman, so what am I missing? I can also see the redirect within the console outputs from Flask.
The expected behaviour is that when an outbound call is answered (CallStatus == 'in-progress') the call is redirected to a greeting message. What really happens is that the call lasts for 5 seconds——which is defined in dial() to ensure that Twilio has enough time to react——and the, hangs-up.
I have even tried incorporating a very long pause verb of a 100 seconds into the returned response within greeting(), but that was simply ignored.
So... what am I missing? Any insights would be appreciated.
#app.route("/call/dail/<number>", methods=['POST'])
def dial(number):
call = client.calls.create(
status_callback='http://<tunnel>.ngrok.io/call/status_update',
status_callback_method='GET',
status_callback_event=['initiated', 'answered', 'completed'],
twiml='<Response><Pause length="5"/></Response>',
to=number,
from_=from_number
)
return call.sid
#app.route("/call/status_update", methods=['GET'])
def call_status_update():
called_number = request.values.get("Called")
callsid = request.values.get("CallSid")
callstatus = request.values.get("CallStatus")
if callstatus == 'in-progress':
return redirect(url_for('greeting'))
return "Waiting for someone to pickup..."
#app.route("/call/greeting", methods=['GET', 'POST'])
def greeting():
response = VoiceResponse()
wait_message = 'This is a greeting.'
response.say(wait_message)
return Response(str(response), 200, mimetype="application/xml")
You're returning the following TwiML when the call is answered:
<Response><Pause length="5"/></Response> and then the call hangs up. You can either use TwiML to redirect to another URL or use the URL parameter of the calls resource and point to the /call/greeting URL.
When Twilio runs out of TwiML to execute, the call ends. You got to keep feeding it TwiML. Note, statusCallbacks do not control the call flow (so Twilio will not execute any TwiML returned by a statusCallback).
Side Note: typo here: /call/dail/<number> (dial)
I have a situation where I am trying to create two Cloud Functions namely CF1 & CF2 and I have one Cloud Scheduler. Both cloud functions are having authenticated invocation enabled. My flow is Cloud Scheduler will trigger CF1. On completion of CF1, the CF1 will trigger CF2 as a http call. I have referred Cannot invoke Google Cloud Function from GCP Scheduler to access authenticated CF1 from Cloud Scheduler and able to access CF1. But I am getting problem when accessing CF2 from CF1. The CF1 does not trigger CF2 and also not giving any error message. Do we need to follow any other technique when accessing authenticated Cloud Function from another authenticated Cloud Function.
CF1 code:
import json
import logging
from requests_futures.sessions import FuturesSession
def main(request):
# To read parameter values from request (url arguments or Json body).
raw_request_data = request.data
string_request_data = raw_request_data.decode("utf-8")
request_json: dict = json.loads(string_request_data)
request_args = request.args
if request_json and 'cf2_endpoint' in request_json:
cf2_endpoint = request_json['cf2_endpoint']
elif request_args and 'cf2_endpoint' in request_args:
cf2_endpoint = request_args['cf2_endpoint']
else:
cf2_endpoint = 'Invalid endpoint for CF2'
logger = logging.getLogger('test')
try:
session = FuturesSession()
session.get("{}".format(cf2_endpoint))
logger.info("First cloud function executed successfully.")
except RuntimeError:
logger.error("Exception occurred {}".format(RuntimeError))
CF2 code:
import logging
def main(request):
logger = logging.getLogger('test')
logger.info("second cloud function executed successfully.")
Current output logs:
First cloud function executed successfully.
Expected output logs:
First cloud function executed successfully.
second cloud function executed successfully.
Note: Same flow is working if I use unauthenticated access to the both cloud functions.
Two things are happening here:
You're not using request-futures entirely correctly. Since the request is made asynchronously, you need to block on the result before the function implicitly returns, otherwise it might return before your HTTP request completes (although it probably is in this example):
session = FuturesSession()
future = session.get("{}".format(cf2_endpoint))
resp = future.result() # Block on the request completing
The request you're making to the second function is not actually an authenticated request. Outbound requests from a Cloud Function are not authenticated by default. If you looked at what the actual response is above, you would see:
>>> resp.status_code
403
>>> resp.content
b'\n<html><head>\n<meta http-equiv="content-type" content="text/html;charset=utf-8">\n<title>403 Forbidden</title>\n</head>\n<body text=#000000 bgcolor=#ffffff>\n<h1>Error: Forbidden</h1>\n<h2>Your client does not have permission to get URL <code>/function_two</code> from this server.</h2>\n<h2></h2>\n</body></html>\n'
You could jump through a lot of hoops to properly authenticate this request, as detailed in the docs: https://cloud.google.com/functions/docs/securing/authenticating#function-to-function
However, a better alternative would be to make your second function a "background" function and invoke it via a PubSub message published from the first function instead:
from google.cloud import pubsub
publisher = pubsub.PublisherClient()
topic_name = 'projects/{project_id}/topics/{topic}'.format(
project_id=<your project id>,
topic='MY_TOPIC_NAME', # Set this to something appropriate.
)
def function_one(request):
message = b'My first message!'
publisher.publish(topic_name, message)
def function_two(event, context):
message = event['data'].decode('utf-8')
print(message)
As long as your functions have the permissions to publish PubSub messages, this avoids the need to add authorization to the HTTP requests, and also ensures at-least-once delivery.
Google Cloud Function provide REST API interface what include call method that can be used in another Cloud Function HTTP invokation.
Although the documentation mention using Google-provided client libraries there is still non one for Cloud Function on Python.
And instead you need to use general Google API Client Libraries. [This is the python one].3
Probably, the main difficulties while using this approach is an understanding of authentification process.
Generally you need provide two things to build a client service:
credentials ans scopes.
The simpliest way to get credentials is relay on Application Default Credentials (ADC) library. The rigth documentation about that are:
https://cloud.google.com/docs/authentication/production
https://github.com/googleapis/google-api-python-client/blob/master/docs/auth.md
The place where to get scopes is the each REST API function documentation page.
Like, OAuth scope: https://www.googleapis.com/auth/cloud-platform
The complete code example of calling 'hello-world' clound fucntion is below.
Before run:
Create default Cloud Function on GCP in your project.
Keep and notice the default service account to use
Keep the default body.
Notice the project_id, function name, location where you deploy function.
If you will call function outside Cloud Function environment (locally for instance) setup the environment variable GOOGLE_APPLICATION_CREDENTIALS according the doc mentioned above
If you will call actualy from another Cloud Function you don't need to configure credentials at all.
from googleapiclient.discovery import build
from googleapiclient.discovery_cache.base import Cache
import google.auth
import pprint as pp
def get_cloud_function_api_service():
class MemoryCache(Cache):
_CACHE = {}
def get(self, url):
return MemoryCache._CACHE.get(url)
def set(self, url, content):
MemoryCache._CACHE[url] = content
scopes = ['https://www.googleapis.com/auth/cloud-platform']
# If the environment variable GOOGLE_APPLICATION_CREDENTIALS is set,
# ADC uses the service account file that the variable points to.
#
# If the environment variable GOOGLE_APPLICATION_CREDENTIALS isn't set,
# ADC uses the default service account that Compute Engine, Google Kubernetes Engine, App Engine, Cloud Run,
# and Cloud Functions provide
#
# see more on https://cloud.google.com/docs/authentication/production
credentials, project_id = google.auth.default(scopes)
service = build('cloudfunctions', 'v1', credentials=credentials, cache=MemoryCache())
return service
google_api_service = get_cloud_function_api_service()
name = 'projects/{project_id}/locations/us-central1/functions/function-1'
body = {
'data': '{ "message": "It is awesome, you are develop on Stack Overflow language!"}' # json passed as a string
}
result_call = google_api_service.projects().locations().functions().call(name=name, body=body).execute()
pp.pprint(result_call)
# expected out out is:
# {'executionId': '3h4c8cb1kwe2', 'result': 'It is awesome, you are develop on Stack Overflow language!'}
I am trying to send variables from my flutter app through a http trigger to a python script on google cloud functions.
I can successfully trigger the function but my function does not receive the variable.
Here is my relevant app code:
callCloudFunction() async {
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'testPath',
);
dynamic resp = await callable.call(<String, dynamic>{
'uid': uid,
}).catchError((onError) {
//Handle your error here if the function failed
});
}
Here is my relevant python code from cloud functions:
def main(request):
name = 'empty'
request_args = request.args
request_json = request.get_json(silent=True)
if request_json and 'uid' in request_json:
name = request_json['uid']
elif request_args and 'uid' in request_args:
name = request_args['uid']
print(name)
No issues trigger the function. It has other functionality that I did not show here so I can confirm the trigger works. Its just passing that variable "uid" that is not working.
You're using the Firebase SDK for Cloud Functions to invoke a callable function, but your python function is just a regular HTTP function. This isn't going to work without implementing the protocol that the SDK uses to communicate with the function. Backend support for callables is only provided for nodejs when deploying with the Firebase CLI.
It will probably be easier if you just make a normal HTTP request from the client instead of writing all the code required for the protocol.
I needed to use the request.form.get('uid') to get data from my post request. Hope this helps someone!
I am trying to make a call using twilio and python with the code below:
account_sid = "***"
auth_token = "***"
client = Client(account_sid, auth_token)
call = client.calls.create(to=phone_number, from_="+***", record=True, url="https://handler.twilio.com/twiml/***")
print call.sid
Here is my xml on that url:
<Response>
<Say>Hi, Thanks for accepting our call!</Say>
</Response>
The call connects, but after the xml triggers, the call ends.
Can someone point me out what I am doing wrong?
I can successfully make a call by doing the approach below, but I need the callsid right after dial for storing the callsid in the database to retrieve the recording later:
resp = VoiceResponse()
dial = Dial(caller_id='+1***', record="record-from-ringing")
dial.number(phone_number, url="https://handler.twilio.com/twiml/***")
resp.append(dial)
return HttpResponse(resp, mimetype='text/xml')
The url above is the same as the first example, but after playing the SAY tag, the call connects. Doing this approach doesn't allow me to get the callsid.
Any ideas?
The first call example ends because you run out of TwiML. You can place your in that TwiML to then connect the outbound-api call to another party.
For the second example using a instead of a REST API to the Calls resource,
You can use the recordingStatusCallbackEvent attribute to be informed of those details, once the recording is available.
https://www.twilio.com/docs/voice/twiml/dial#recordingstatuscallbackevent
I've written a Python action on Bluemix OpenWhisk, and I need to call another action (actually a binding to a public package) from within this action. A sequence won't do it, because I need to call it a varying number of times with different parameters depending on the input.
How to invoke openwhisk action within openwhisk platform on bluemix? mentions how to do it from JavaScript, but the OpenWhisk package doesn't seem to be available for Python.
Actions can be invoked using a HTTP request to the platform API. The Python runtime in OpenWhisk includes the requests library for making HTTP calls.
Here's an example of an action that calls another (child) in the same namespace.
import os
import requests
APIHOST = os.environ.get('__OW_API_HOST')
NAMESPACE = os.environ.get('__OW_NAMESPACE')
USER_PASS = os.environ.get('__OW_API_KEY').split(':')
def main(params):
action = 'child'
url = APIHOST + '/api/v1/namespaces/' + NAMESPACE + '/actions/' + action
response = requests.post(url, data=params, params={'blocking': 'true'}, auth=(USER_PASS[0], USER_PASS[1]))
print(response.json())
return {"text": "invoked!"}
Swagger documentation for full API is available here.
There is an open issue to create a Python client library to make this easier.