Twilio 功能可将未接电话或在办公时间以外接听的电话转移到语音信箱,否则拨打 sip

Twilio Function to divert unanswered calls or calls received outside office hours to voicemail, otherwise dial sip

我的 Twilio 函数部分已经有了这个函数,虽然我不确定我是从某个地方复制粘贴这个函数还是默认:

var moment = require('moment-timezone')

exports.handler = function(context, event, callback) {

  let now = moment().tz('Australia/Brisbane');

  let isWorkday = (now.isoWeekday() < 6);
  let isWorkingHour = (now.hour() > 7 && now.hour() < 17);
  let response = {};


  if(isWorkday && isWorkingHour) {
    callback(null, response);
  } else {
    callback("Service is closed");
  }  
};

我还发现了另一个 SO post,其中有人包含一个基本功能,可以在无人接听电话时转移到语音信箱:

exports.handler = function(context, event, callback) {
    const twiml = new Twilio.twiml.VoiceResponse();
    if (event.DialCallStatus === 'completed' || event.DialCallStatus === 'answered') {
        twiml.hangup();
    } else {
        twiml.say("Service is closed");
        twiml.record({
            transcribe: true,
            transcribeCallback: "http://twimlets.com/voicemail?Email=example@domain.com",
             action: "/hangup"
        });
    }
    callback(null, twiml);
};

我想做的基本上是将这两者结合起来:

  1. 如果在 !isWorkday || !isWorkingHour 处接到电话,则直接发送至语音信箱。根本不要按 phone。
  2. 如果在 isWorkday && isWorkingHour 处接听电话,那么 运行 像这样的 Twiml bin:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial>
    <Sip>
      me@myuniqueid.sip.us1.twilio.com;region=au1
    </Sip>
  </Dial>
</Response>
  1. 如果呼叫在 20 秒内未接听,则发送至语音信箱(使用与步骤 1 不同的问候语)。

奖金问题:我显然还需要能够收听语音邮件,因为我怀疑转录是否会非常准确。在创建新语音邮件时,有什么方法可以在我收到的电子邮件中向语音邮件(或语音邮件 mp3 本身)添加 link 吗?或者我可以为拨出电话创建一个 function/twiml 垃圾箱,让我可以拨打号码并收听我的语音邮件,就像普通语音邮件的工作方式一样吗??

嘿哟,这里是 Twilio 开发者布道师。

我刚刚在单个函数中设置并构建了您的用例。这个函数内部有很多事情要做(我可能建议将工作分成几个函数)。

我也很乐意回答您的额外问题,但请单独打开它以保持此答案的重点。 :)

让我们看一个工作示例!

const moment = require('moment-timezone');

function isServiceOpen() {
  let now = moment().tz('Australia/Brisbane');
  let isWorkday = now.isoWeekday() < 6;
  let isWorkingHour = now.hour() > 7 && now.hour() < 17;

  return isWorkday && isWorkingHour;
}

exports.handler = function(context, event, callback) {
  const twiml = new Twilio.twiml.VoiceResponse();

  console.log(event);

  const callIncludesRecording = !!event.RecordingUrl;
  const callWasAnswered = event.DialCallStatus === 'completed';
  const callWasNotAnswered = event.DialCallStatus === 'no-answer';
  const serviceIsOpen = isServiceOpen();

  if (callWasAnswered) {
    twiml.hangup();
  } else if (callIncludesRecording) {
    console.log('Call includes recording!');

    // do something with the recording URL here
    console.log(event.RecordingUrl);

    twiml.say("Thank you! We'll come back to you shortly.");
    twiml.hangup();
  } else if (callWasNotAnswered) {
    console.log('Call was not answered...');

    twiml.say(
      'Unfortunately no one can answer right now. But you can leave a message.'
    );
    twiml.record({
      action: '/handle-call'
    });
  } else if (!serviceIsOpen) {
    console.log('Service is closed...');
    twiml.say('Service is closed but you can leave a message');
    twiml.record({
      action: '/handle-call'
    });
  } else {
    twiml.dial(
      {
        action: '/handle-call',
        method: 'POST',
        timeout: 5
      },
      '+4915...'
    );
  }

  callback(null, twiml);
};

您在上面看到的功能在 /handle-call 端点下可用,并应答所有 webhooks 的调用。

场景 1 - 无人接听电话

在函数末尾,您会看到 dial function call. The important piece for this case is that dial supports a timeout and an action 属性。

twiml.dial(
  {
    action: '/handle-call',
    method: 'POST',
    timeout: 30
  },
  '+49157...'
);

以上内容告诉 Twilio 尝试拨打号码 +49157... 30 秒(实际上接近 35 - 您可以在 the docs 中阅读详细信息)。如果通话结束或在达到超时之前没有人接听电话,Twilio 将要求定义的 action URL 进行额外的 TwiML 配置。

action属性中的URL引用相同的函数路径(/handle-call),同样的函数将再次执行,但这次event对象将包括 no-answerDialCallStatus(查看变量 callWasNotAnswered)。如果电话无人接听,您可以 return TwiML 说一条消息并告诉 API 开始通话录音。

// no one answered – let's record
twiml.say(
  'Unfortunately, no one can answer right now. But you can leave a message.'
);
twiml.record({
  action: '/handle-call'
});

record verb also allows an action attribute 可让您定义录制完成后应请求的 URL(我们将在相同的 /handle-call 端点下再次使用相同的函数)。

对同一个 URL 的调用将在 event 对象中包含一个 RecordingUrl。如果此 属性 存在,您就知道这是录音的结果。在这个阶段,是时候对录音 URL 做一些事情了(发送一条消息,记录它,......),在说 "goodbye" 之后结束通话并挂断电话。

// do something with the recording URL here
console.log(event.RecordingUrl);

twiml.say("Thank you! We'll come back to you shortly.");
twiml.hangup();

webhook流程如下:

  1. POST /handle-call(初始 webhook)-> 拨打 +49157...
  2. POST /handle-call (超时后呼叫) -> 说 "unfortunately, ..." & 录音
  3. POST /handle-call (录音完成) -> say "thank you" & hangup

场景 2 - 电话在工作时间以外

对于这种情况,我采用了您已经提供的逻辑并创建了一个 isServiceOpen 辅助函数。当在工作时间以外打来电话时,该功能会使用定义消息和录音的 TwiML 进行响应。

    twiml.say('Service is closed but you can leave a message');
    twiml.record({
      action: '/handle-call'
    });

录制完成后调用我们的函数(/handle-call)。这次,请求将包含 RecordingUrl,处理方式与场景 1 相同(记录录音 URL 和挂断)。

webhook流程如下:

  1. POST /handle-call(初始 webhook)-> 说 "service is closed" 并记录
  2. POST /handle-call (录音完成) -> say "thank you" & hangup

场景 3 – 电话已接听

如果电话发生在工作时间并且得到足够快的应答,则不需要录音。因为拨号动词包含一个 action 属性(我们在场景 1 中使用了它),所以在通话结束后将发送另一个 webhook。

此 webhook 将包含一个 DialCallStatus 参数和 completed 值。然后是时候结束通话挂断了。

twiml.hangup();
  1. POST /handle-call(初始 webhook)-> 拨“+49157...”
  2. POST /handle-call(通话结束)-> 挂断

希望以上内容对您有所帮助。正如最初提到的,将功能拆分为 /handle-call/handle-recording 和其他功能可能是个好主意。

让我知道这是否有帮助,如果您有任何其他问题。 :)