Twilio Classic Functions, How do you work with Tags and SMS? - binding

Twilio function Problem: Tags are not working as expected.
I'm trying to create unique SMS subscription lists for users who SMS 'keyword1','keyword2','keyword3',etc to my number.
I'm doing this by intercepting the 'incoming message' events via a classic function.
I want to poll the keywords then assign tags to the users as I subscribe them to a list.
Then when I broadcast SMS I want to be able to send to only those users who are tagged with my keyword.
For example to capture the keyword 'test' and assign the tag 'test' to a user, I'm using the code below.
And then to send messages to all users with the tag 'test' I'm using a "sendtest" command.
I can broadcast to 'all' tags and it will work fine, but if I want to only send to users with tags ['test'], then there are no errors reported and the system tells me it was successful, but no subscribers will receive any messages.
I'm wondering if I have some problem in the way I am trying to define the tags? It looks like the data format is supposed to be a STRING[] array of some kind, I'm guessing ['test','two','three']. (If I can confirm this is right). But I notice as per the working examples provided by twilio if I set the notification arguments to a string IE: tags: 'all', then this syntax works to broadcast to all tags. Anything else though, will not work at all.
is there some trick to getting tags to work, or do they not work at all when trying to filter notifications via the classic function interface?
class TestCommand extends Command {
run(callback) {
// Create a new SMS Notify binding for this user's phone number
//and try to tag the user with keyword 'test'
notify.bindings.create({
identity: this.fromNumber,
bindingType: 'sms',
address: this.fromNumber,
tags: ['test']
}).then((response) => {
callback(null, 'test Message success')
}).catch(err => {
callback(err, 'test message fail')
})
}
}
class BroadcastTestCommand extends Command {
run(callback) {
// Check if sender is in list of admins, stored in the system environment
// as a comma-separated string
if (adminNumbers.indexOf(this.fromNumber) < 0) {
return callback(null, 'broadcast Not Authorized')
}
// Create a new SMS Notify binding for this user's phone number
//only notify users who are tagged with 'test'
notify.notifications.create({
tag: ['test'],
body: this.commandText
}).then((response) => {
callback(null, 'broadcast test Success')
}).catch(err => {
console.log(err)
callback(err, 'broadcast test Fail')
})
}
}
// Handle incoming SMS commands ####################
exports.handler = (context, event, callback) => {
// Get command text from incoming SMS body
let cmd = event.Body || ''
cmd = cmd.trim().split(' ')[0].toLowerCase()
// Default to help command
let cmdInstance = new HelpCommand(event, context)
// Choose other commands as appropriate
switch(cmd) {
case 'test': cmdInstance = new TestCommand(event, context); break;
case 'sendtest': cmdInstance = new BroadcastTestCommand(event, context); break;
}
// Execute command
cmdInstance.run((err, message) => {
let twiml = new twilio.twiml.MessagingResponse()
if (err) {
console.log(err)
message = 'There was a problem with your request. Try again!'
}
twiml.message(message)
callback(null, twiml)
})
}

Okay for anyone else pulling their hair out on this one..
The Twilio docs here on bindings will throw you off
https://www.twilio.com/docs/notify/api/binding-resource
Because the argument is NOT 'tags' it's 'tag'
This works ...
class TestCommand extends Command {
run(callback) {
// Create a new SMS Notify binding for this user's phone number
//and try to tag the user with keyword 'test'
notify.bindings.create({
identity: this.fromNumber,
bindingType: 'sms',
address: this.fromNumber,
tag: ['test']
}).then((response) => {
callback(null, 'test Message success')
}).catch(err => {
callback(err, 'test message fail')
})
}
}

Related

twilio/voice-sdk does not listen incoming call listener when I call on my twilio number

I am using the following stack with versions
Laravel (9.11) vue.js (2.x) php (8.1.0) twilio/voice-sdk
(2.1.1) twilio/sdk (6.37)
Workflow of my application:
I am making an inbound contact center for voice calls by using a task router, where a customer initiates the call from his/her phone to our contact center base number(+1 873 --- 0331)
Step #1
when the user call on this number(+1 873 --- 0331) voice webhook is called with the following code for IVR
public function webhookForContactCenterBaseNumber(Request $request)
{
$response = new VoiceResponse();
$params = array();
$params['action'] = secure_url('/api/webhook-for-contact-center-ivr');
$params['numDigits'] = 1;
$params['timeout'] = 10;
$params['method'] = "POST";
$gather = $response->gather($params);
$gather->say('For Spanish, please press one.', ['language' => 'es']);
$gather->say('For Enghlish,please press two.', ['language' => 'en']);
return $response;
}
Step #2
When the user presses A digit(1/2) I create a task with workflow via the task router
public function webhookForContactCenterIvr(Request $request)
{
$response = new VoiceResponse();
$digits = $request['Digits'];
$language = $digits == 1 ? 'es' : 'en';
switch ($digits) {
case 1 || 2:
$response->enqueue(null, [
'waitUrl' => 'http://twimlets.com/holdmusic?Bucket=com.twilio.music.classical',
'workflowSid' => 'WW456fb07f4fdc4f55779dcb6bd90f9273'
])
->task(json_encode([
'selected_language' => $language,
]));
break;
default:
$response->say("Sorry, Caller. You can only press 1 for spanish, or 2 for english.");
break;
}
return $response;
}
step #3
After task creation, I make the targeted agent available manually from the console with the label ‘idle’, then following webhook called.
According to documentation bridge call was created between caller and agent Twilio phone number via Twilio caller id
public function assigment(Request $request)
{
$assignment_instruction = [
'instruction' => 'dequeue',
'post_work_activity_sid' => 'WA92871fe67075e6556c02e92de6---924',
'from' => '+1647---4676' // a verified phone number from your twilio account
];
return $this->respond($assignment_instruction, ['Content-Type', 'application/json']);
}
Call logs:
step #4
namespace App\Http\Controllers\Api;
use Twilio\Jwt\AccessToken;
use Twilio\Jwt\Grants\VoiceGrant;
use Illuminate\Http\Request;
use Twilio\Rest\Client;
class TwilioController extends ApiController
{
// Required for all Twilio access tokens
private $twilioAccountSid;
private $twilioAccountAuthToken;
private $twilioApiKey;
private $twilioApiSecret;
private $identity;
public function __construct()
{
$this->twilioAccountSid = config('general.twilio_account_sid');
$this->twilioAccountAuthToken = config('general.twilio_auth_token');
$this->twilioApiKey = 'SK45e57c57f923e5c3c0903f48b70ba9de';
$this->twilioApiSecret = 'uqDNnlnDZbWZCKBwlmMdlMIIonhh3X3K';
// choose a random username for the connecting user
$this->identity = 'daffdfwerweds';
}
public function getCallAccessToken()
{
$token = new AccessToken(
$this->twilioAccountSid,
$this->twilioApiKey,
$this->twilioApiSecret,
3600,
$this->identity
);
// Create Voice grant
$voiceGrant = new VoiceGrant();
// Optional: add to allow incoming calls
$voiceGrant->setIncomingAllow(true);
// Add grant to token
$token->addGrant($voiceGrant);
return $this->respond([
'status' => true,
'message' => '',
'data' => [
'accessToken' => $token->toJWT()
]
]);
}
public function getTwilioKey($frindlyName)
{
$twilio = new Client($this->twilioAccountSid, $this->twilioAccountAuthToken);
return $twilio->newKeys->create(["friendlyName" => $frindlyName]);
}
public function getKeys()
{
$twilio = new Client($this->twilioAccountSid, $this->twilioAccountAuthToken);
$keys = $twilio->keys
->read(20);
foreach ($keys as $record) {
$twilio->keys($record->sid)
->delete();
}
}
public function getAllCalls(Request $request)
{
$twilio = new Client($this->twilioAccountSid, $this->twilioAccountAuthToken);
$calls = $twilio->calls
->read([], 20);
foreach ($calls as $record) {
// print($record->sid);
$twilio->calls($record->sid)
->delete();
}
}
}
Step #5
I have installed twilio/voice-sdk in vue and register my device with following code
const accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImN0eSI6InR3aWxpby1mcGE7dj0xIn0.eyJqdGkiOiJTSzQ1ZTU3YzU3ZjkyM2U1YzNjMDkwM2Y0OGI3MGJhOWRlLTE2NTU3MzgxNjMiLCJpc3MiOiJTSzQ1ZTU3YzU3ZjkyM2U1YzNjMDkwM2Y0OGI3MGJhOWRlIiwic3ViIjoiQUMwMWExYTRmMDdjMGMwMDlhMmIyZTEyYmJkZWVhYjQ2NSIsImV4cCI6MTY1NTc0MTc2MywiZ3JhbnRzIjp7ImlkZW50aXR5IjoiZGFmZmRmd2Vyd2VkcyIsInZvaWNlIjp7ImluY29taW5nIjp7ImFsbG93Ijp0cnVlfX19fQ.4COIn-EQMQnD6alKUSOZPGIWG3jB5k17K418xCsSiZs"
const device = new Device(accessToken, {
logLevel: 1,
// Set Opus as our preferred codec. Opus generally performs better, requiring less bandwidth and
// providing better audio quality in restrained network conditions.
codecPreferences: ["opus", "pcmu"]
});
const handleSuccessfulRegistration = () => {
console.log('The device is ready to receive incoming calls.')
}
device.register();
device.on('registered', handleSuccessfulRegistration);
device.on('error', (twilioError, call) => {
console.log('An error has occurred: ', twilioError);
});
device.on('incoming', call => {
console.log('call received-----------------')
});
Verify token on jwt.io
Test Device Registration in console:
I was facing the same issue, thanks for detailed information, I go through the whole detail, and here is the answer after that issue will be fixed,
in step #4 you are creating call access token, and you are adding worker/agent identity you need to add some identity against the worker inside the Twilio console, in your case, it should be like that,
in code
$this->identity = 'daffdfwerweds';
in Twilio console under task router/workspace/workers/open target work
most important part
{contact_uri":"client:daffdfwerweds"}
Your browser will listen the incoming call via SDK if call router toward you this worker.
that's all.

How can I access a Teams user's email address?

I'm trying to build a Microsoft Teams integration for an app, but I'm having some trouble getting a user's email address.
I used the Microsoft Teams extension for VS Code to scaffold a basic app. I'm using the BotFramework v4 (NodeJS) on my server. I'm able to receive requests from Teams and respond to them as well.
To get an user's email address, I am using the TeamsInfo.getMember(context, id) method, where the id is obtained from context.activity.from object. Unfortunately, calling this method results in a RestError: Unknown with a status code of 400.
I'm not sure what I'm missing here. My app is registered with the Azure Active Directory and has the User.Read.All permission. Am I missing something here?
Any help would be appreciated!
For some context, I'm trying to build a Messaging Extension Action Command.
Code:
import {
TurnContext,
TeamsActivityHandler,
CardFactory,
MessagingExtensionAction,
TeamsInfo,
} from 'botbuilder';
export default class TeamsMessagingExtensionsActionBot extends TeamsActivityHandler {
constructor() {
super();
}
// #ts-ignore
handleTeamsMessagingExtensionSubmitAction(
context: TurnContext,
action: MessagingExtensionAction,
) {
switch (action.commandId) {
case 'someCommand':
return handleCommand(context, action);
default:
throw new Error('NotImplemented');
}
}
}
async function handleCommand(
context: TurnContext,
action: MessagingExtensionAction,
) {
const card = CardFactory.heroCard(
'Some Command',
'We have received your command!',
);
const user = await TeamsInfo.getMember(context, context.activity.from.id);
console.log('User:', user);
const attachment = {
contentType: card.contentType,
content: card.content,
preview: card,
};
return {
composeExtension: {
type: 'result',
attachmentLayout: 'list',
attachments: [attachment],
},
};
}
This is the error I get when calling TeamsInfo.getMember(): JSON

Twilio statusCallback doesn't fire

I'm trying to set up messages delivery status check with twilio. For some reasons twilio statusCallback doesn't fire. Could you please help me to find an error?
Here is a file where I do initialization and send messages:
const Twilio = require('twilio');
const {
TWILIO_ACCOUNT_SID,
TWILIO_AUTH_TOKEN,
TWILIO_PHONE_NUMBER
} = require('config');
const client = new Twilio(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN);
module.exports = {
send: (body, phoneNumber) => client.messages.create({
body,
to: phoneNumber,
from: TWILIO_PHONE_NUMBER,
statusCallback: 'http://postb.in/b/XXXXXXXX'
})
.then((message) => {
console.log(message.sid);
return message.sid;
})
};
Here is a test where I call message sending after initialization:
const { expect } = require('chai');
const smsUtility = require('utils/sms');
describe('Sms utility', () => {
it('should send a message and return message sid', (done) => {
const body = 'Body';
const number = '+XXXXXXXXXXX';
smsUtility.send(body, number)
.then((messageSid) => {
expect(typeof messageSid).to.equal('string');
return done();
})
.catch((err) => {
console.log(err);
return done();
});
});
});
I use test credentials, but when I replace it with real twilio credentials, I successfully receive a message, so this part works fine. Also, when I try to ping postb.in url manually (with curl), it also works OK. Only statusCallback doesn't work.
Thanks.
Twilio developer evangelist here.
It looks to me like you have everything set up nicely aside from your Postbin URL.
I noticed you show your URL as http://postb.in/b/XXXXXXXX. But the /b/ version of the URL is the dashboard for your Postbin. Requests to the dashboard won't show up on the dashboard.
Instead, you should use the URL that looks like: http://postb.in/XXXXXXXX. Try that and let me know if it's working.

Embeded Watson Virtual Agent chatbot missing response

I've created an html file with embedded Watson Virtual Agent chat bot, code similar below, with WVA strictly using the building core capabilities:
IBMChat.init({
el: 'ibm_chat_root',
baseURL: 'https://api.ibm.com/virtualagent/run/api/v1',
botID: '',
XIBMClientID: '',
XIBMClientSecret: ''
});
What I noticed is if I run the WVA in Preview mode, and have input "pay bill", the WVA can come back with two piece response, with first:
Accessing your account information...
and second the make payment:
Your account balance is $42.01 due on 5/17/2017. What would you like to do? (More options coming soon!)
However, if I enter the same in my HTML chatbot, the response only comes back with the first part:
Accessing your account information...
and second part never comes out.
Does anyone else experience the same problem?
The version in the "Preview" mode has some mock "action" handlers setup. Obviously, not every one of you users would owe $42! In the sample code on the github, the mock action handlers are not setup. There are examples on how to subscribe to those action events with handlers here: https://github.com/watson-virtual-agents/chat-widget/tree/master/examples/basic-actions-example
As of 5/31/17 you can cover all the built in actions using the code snippet below...
const config = { instance: null };
const getUserProfileVariablesMap = {
'bill_amount': '42.01',
'payment_due_date': (() => {
const currentDate = new Date(new Date().getTime() + 24 * 60 * 60 * 1000);
return `${currentDate.getMonth() + 1}/${currentDate.getDate()}/${currentDate.getFullYear()}`;
})(),
'authorized_users': 'Bob Everyman and Jane Doe'
};
const getUserProfileVariables = (data) => {
const variables = data.message.action.args.variables;
variables.forEach(v => {
const value = getUserProfileVariablesMap[v];
(value) ? config.instance.profile.set(v, value) : config.instance.profile.set(v, '[sample data]');
});
config.instance.sendSilently('success');
};
const success = () => config.instance.sendSilently('success');
const agent = () => config.instance.receive('On your own site you would run code to connect to an agent now.');
const accountSettings = () => config.instance.receive('On your own site you would run code to open the Account Settings page now.');
function registerActions(instance) {
config.instance = instance;
instance.subscribe('action:getUserProfileVariables', getUserProfileVariables);
instance.subscribe('action:updateAddress', success);
instance.subscribe('action:updateUserName', success);
instance.subscribe('action:updatePhoneNumber', success);
instance.subscribe('action:updateEmail', success);
instance.subscribe('action:payBill', success);
instance.subscribe('action:sendPaymentReceipt', success);
instance.subscribe('action:agent', agent);
instance.subscribe('action:openAccountSettingsPage', accountSettings);
};
window.IBMChatActions = {
registerActions: registerActions
};
// window.IBMChatActions.registerActions(window.IBMChat);
On the Administrative Preview, you are getting fake code stubs that handle action requests from the agent.
When one of these actions are invoked, the widget will print the "Processing..." message and then invoke all registered subscribers for that action. It is up to these registered subscribers to continue the conversation flow by silently sending "success", "failure", or "cancel" back to the server.
For example, the agent might pass down the "payBill" action. You would want to call your payment gateway, determine if it was successful, and then notify the agent of the result:
IBMChat.init(/* Settings */);
IBMChat.subscribe('action:payBill', function() {
var data = {
amount: IBMChat.profile.get('amount'),
card: {
number: IBMChat.profile.get('cc_number'),
// ... other private card data
}
};
$.post('https://www.myserver.com/payment-gateway', data)
.done( function() {
IBMChat.sendSilently('success');
})
.fail( function() {
IBMChat.sendSilently('failure');
});
});
Actions Documentation
https://github.com/watson-virtual-agents/chat-widget/blob/master/docs/DOCS.md#actions

Pass custom data to service worker sync?

I need to make a POST request and send some data. I'm using the service worker sync to handle offline situation.
But is there a way to pass the POST data to the service worker, so it makes the same request again?
Cause apparently the current solution is to store requests in some client side storage and after client gets connection - get the requests info from the storage and then send them.
Any more elegant way?
PS: I thought about just making the service worker send message to the application code so it does the request again ... but unfortunately it doesn't know the exact client that registered the service worker :(
You can use fetch-sync
or i use postmessage to fix this problem, which i agree that indexedDB looks trouble.
first of all, i send the message from html.
// send message to serviceWorker
function sync (url, options) {
navigator.serviceWorker.controller.postMessage({type: 'sync', url, options})
}
i got this message in serviceworker, and then i store it.
const syncStore = {}
self.addEventListener('message', event => {
if(event.data.type === 'sync') {
// get a unique id to save the data
const id = uuid()
syncStore[id] = event.data
// register a sync and pass the id as tag for it to get the data
self.registration.sync.register(id)
}
console.log(event.data)
})
in the sync event, i got the data and fetch
self.addEventListener('sync', event => {
// get the data by tag
const {url, options} = syncStore[event.tag]
event.waitUntil(fetch(url, options))
})
it works well in my test, what's more you can delete the memory store after the fetch
what's more, you may want to send back the result to the page. i will do this in the same way by postmessage.
as now i have to communicate between each other, i will change the fucnction sync into this way
// use messagechannel to communicate
sendMessageToSw (msg) {
return new Promise((resolve, reject) => {
// Create a Message Channel
const msg_chan = new MessageChannel()
// Handler for recieving message reply from service worker
msg_chan.port1.onmessage = event => {
if(event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}
navigator.serviceWorker.controller.postMessage(msg, [msg_chan.port2])
})
}
// send message to serviceWorker
// you can see that i add a parse argument
// this is use to tell the serviceworker how to parse our data
function sync (url, options, parse) {
return sendMessageToSw({type: 'sync', url, options, parse})
}
i also have to change the message event, so that i can pass the port to sync event
self.addEventListener('message', event => {
if(isObject(event.data)) {
if(event.data.type === 'sync') {
// in this way, you can decide your tag
const id = event.data.id || uuid()
// pass the port into the memory stor
syncStore[id] = Object.assign({port: event.ports[0]}, event.data)
self.registration.sync.register(id)
}
}
})
up to now, we can handle the sync event
self.addEventListener('sync', event => {
const {url, options, port, parse} = syncStore[event.tag] || {}
// delete the memory
delete syncStore[event.tag]
event.waitUntil(fetch(url, options)
.then(response => {
// clone response because it will fail to parse if it parse again
const copy = response.clone()
if(response.ok) {
// parse it as you like
copy[parse]()
.then(data => {
// when success postmessage back
port.postMessage(data)
})
} else {
port.postMessage({error: response.status})
}
})
.catch(error => {
port.postMessage({error: error.message})
})
)
})
At the end. you cannot use postmessage to send response directly.Because it's illegal.So you need to parse it, such as text, json, blob, etc. i think that's enough.
As you have mention that, you may want to open the window.
i advice that you can use serviceworker to send a notification.
self.addEventListener('push', function (event) {
const title = 'i am a fucking test'
const options = {
body: 'Yay it works.',
}
event.waitUntil(self.registration.showNotification(title, options))
})
self.addEventListener('notificationclick', function (event) {
event.notification.close()
event.waitUntil(
clients.openWindow('https://yoursite.com')
)
})
when the client click we can open the window.
To comunicate with the serviceworker I use a trick:
in the fetch eventlistener I put this:
self.addEventListener('fetch', event => {
if (event.request.url.includes("sw_messages.js")) {
var zib = "some data";
event.respondWith(new Response("window.msg=" + JSON.stringify(zib) + ";", {
headers: {
'Content-Type': 'application/javascript'
}
}));
}
return;
});
then, in the main html I just add:
<script src="sw_messages.js"></script>
as the page loads, global variable msg will contain (in this example) "some data".

Resources