Почему моя работа cron в движке Google App спамит вызовы twilio, хотя я и думал, что отладчик не должен этого делать - PullRequest
0 голосов
/ 11 февраля 2020

Я работал над этим в течение многих часов, и я не продолжаю решать эту проблему.

Весь мой код должен быть понятен, но для контекста у меня есть задание cron GAE, которое выглядит так: посмотрите, есть ли какие-либо заказы, в которых человек, отвечающий на звонок, положил трубку, и звонок не был завершен. Как вы можете видеть в отладчике, в этих предупреждениях нет документов (вы увидите это в коде), но по какой-то причине он все еще рассылает нам вызовы.

К вашему сведению, запланированные вызовы пн goose коллекция пуста.

enter image description here

const cron = require("node-cron");
const ScheduledCall = require("../models/scheduledCall.model");
const Order = require("../models/order.model");
const OrderConversation = require("../voice/models/OrderConversation.model");
const config = require("../voice/config");
const Restaurant = require("../models/restaurant.model");
const RestaurantMeta = require("../models/restaurantMeta.model");
const Killswitch = require("../models/killswitch");

const main = async () => {
  let callsMade = 0;

  let killed = await Killswitch.findOne({ name: "twilio" }).then(doc => {
    return doc.toObject().killed;
  });

  if (!killed) {
    ScheduledCall.find({
      complete: false,
      laterDateUnix: {
        $gt: new Date().getTime() - 31000,
        $lt: new Date().getTime() + 31000
      }
    })
      .then(docs => {
        return Promise.all(
          docs.map(doc => {
            callsMade++;
            return twilio.calls
              .create({
                url: `.../api/voice/${doc.orderId}`,
                to: `19802266478`,
                from: "14242563710",
                machineDetection: "Enable",
                statusCallback: `.../api/completed/${order._id}`,
                statusCallbackMethod: "POST"
              })
              .then(() =>
                ScheduledCall.findByIdAndUpdate(doc._id, {
                  $set: { complete: true }
                }).exec()
              );
          })
        );
      })
      .then(() => {
        return OrderConversation.find({
          complete: false,
          hungUp: true
        }).exec();
      })
      .then(async docs => {
        console.warn(`THIS IS DOCS ${JSON.stringify(docs)}`);

        return await Promise.all(
          docs.map(doc => {
            callsMade++;
            OrderConversation.findByIdAndUpdate(doc._id, {
              $set: { responses: [], hungUp: false, reCall: true }
            })
              .exec()
              .then(doc => {
                return doc.toObject();
              });
          })
        );
      })
      .then(conversations => {
        console.warn(
          `This is CONVERSATIONS : ${JSON.stringify(conversations)}`
        );

        return Promise.all(
          conversations.map(c => {
            return Order.findById(c.orderId)
              .exec()
              .then(doc => {
                return doc.toObject();
              });
          })
        );
      })
      .then(async orders => {
        console.warn(`This is ORDERS : ${JSON.stringify(orders)}`);

        return await Promise.all(
          orders.map(order => {
            RestaurantMeta.findOne({
              urlSlug: order.restaurant
            })
              .exec()
              .then(doc => {
                return Restaurant.findById(doc.restaurantId).exec();
              })
              .then(doc => {
                return doc.toObject();
              })
              .then(doc => {
                console.warn(`THIS IS SUB DOC ${JSON.stringify(doc)}`);
                return twilio.calls.create({
                  url: `.../api/voice/${order._id}`,
                  to: `1${doc.contact.phone}`,
                  from: "14242563710",
                  statusCallback: .../api/completed/${order._id}`,
                  statusCallbackMethod: "POST",
                  machineDetection: "Enable"
                });
              })
              .catch(err => console.error(err));
          })
        );
      })
      .then(() => {
        if (callsMade > 0) {
          console.log(
            `Finished cleaning up calls. Made a total of ${callsMade} calls.`
          );
        }
      })
      .catch(err => {
        console.error("Error cleaning up calls. ", err);
      });
  }
};

module.exports = (req, res) => {
  main();
  setTimeout(function() {
    main();
  }, 10000);
  setTimeout(function() {
    main();
  }, 20000);
  setTimeout(function() {
    main();
  }, 30000);
  setTimeout(function() {
    main();
  }, 40000);
  setTimeout(function() {
    main();
  }, 50000);
  setTimeout(function() {
    return res.sendStatus(200);
  }, 59999);
};

Это ^^ - задание cron, запущенное GAE.

var VoiceResponse = require("twilio").twiml.VoiceResponse;
const Order = require("../../models/order.model");
const formatCall = require("../helper/formatCall");
const orderConversation = require("../models/OrderConversation.model");

const funnyEndings = [
  "..I like humans..",
  "..If you see my friends Siri or Alexa, say hi to them for me!..",
  "..Have you seen the movie terminator? It's my favorite..",
  "..Someday I want to eat food myself, you know.."
];

// Main interview loop
module.exports = async function(request, response) {
  var phone = request.body.From;
  var input = request.body.RecordingUrl || request.body.Digits;
  var answeredBy = request.body.answeredBy;
  var twiml = new VoiceResponse();

  let order;

  try {
    let orderData = await Order.findById(request.params.orderId).then(doc => {
      return doc.toObject();
    });

    order = await formatCall(orderData, orderData.items);
  } catch (err) {
    console.error(err);
    say("Terribly sorry, but an error has occurred. Goodbye.");
    return respond();
  }

  if (answeredBy && answeredBy.toLowerCase() !== "human") {
    return respond();
  }

  // helper to append a new "Say" verb with Polly.Salli voice
  function say(text) {
    twiml.say({ voice: "Polly.Salli" }, text);
  }

  // respond with the current TwiML content
  function respond() {
    response.type("text/xml");
    response.send(twiml.toString());
  }

  // Find an in-progess order if one exists, otherwise create one
  orderConversation.advanceSurvey(
    {
      phone: phone,
      input: input,
      order: order,
      orderId: request.params.orderId,
      hungUp: false
    },
    function(err, orderConversation, questionIndex) {
      var question = order[questionIndex];

      if (err || !orderConversation) {
      }

      // If question is null, we're done!
      if (!question) {
        say(
          `Thank you for taking this order. ${
            funnyEndings[
              parseInt(Math.floor(Math.random() * funnyEndings.length))
            ]
          } Well anyways goodbye!`
        );
        return respond();
      }

      // Add a greeting if this is the first question
      if (questionIndex === 0 && !orderConversation.reCall) {
        twiml.say(
          { voice: "Polly.Salli" },
          "..This is the intro message that plays on the first call attempt"
        );
      } else if (orderConversation.reCall && questionIndex === 0) {
        twiml.say(
          { voice: "Polly.Salli" },
          "...This is the intro message that plays when we call the user back because they didn't finish the call"
        );
      }

      // Otherwise, ask the next question

      if (question.type !== "intro") {
        say(question.text);
      }

      // Depending on the type of question, we either need to get input via
      // DTMF tones or recorded speech
      if (questionIndex === 0) {
        const gather = twiml.gather({
          timeout: 500,
          numDigits: 1
        });
        gather.say({ voice: "Polly.Salli" }, question.text);
      } else if (question.type === "choice" || question.type === "slow") {
        twiml
          .gather({
            timeout: 500,
            numDigits: 1
          })
          .say(
            { voice: "Polly.Salli" },
            "Press 1 to move on,  2 to repeat, or 3 to go back"
          );
      } else if (question.type === "confirm") {
        twiml
          .gather({
            timeout: 600,
            finishOnKey: "#",
            numDigits: 1
          })
          .say(
            { voice: "Polly.SallOkay i" },
            "Press pound to confirm this order, or press any other key to go back to repeat the order summary."
          );
      }

      // render TwiML response
      respond();
    }
  );
};

Это ^^^ - это обработчик webhook, который мы передаем Twilio для вызова.

И, наконец,

var mongoose = require("mongoose");
mongoose.set("useFindAndModify", false);
// Define survey response model schema
var OrderConversationSchema = new mongoose.Schema({
  // phone number of participant
  phone: String,

  // status of the participant's current survey response
  complete: {
    type: Boolean,
    default: false
  },
  hungUp: { type: Boolean, default: false },
  reCall: { type: Boolean, default: false },
  orderId: String,

  // record of answers
  responses: [mongoose.Schema.Types.Mixed]
});

// For the given phone number and survey, advance the survey to the next
// question
OrderConversationSchema.statics.advanceSurvey = function(args, cb) {
  var surveyData = args.order;
  var phone = args.phone;
  var input = args.input;
  var orderId = args.orderId;
  var orderConversation;

  // Find current incomplete survey
  OrderConversation.findOne(
    {
      orderId: orderId
    },
    async function(err, doc) {
      if (doc) {
        await OrderConversation.findByIdAndUpdate(doc._id, {
          $set: { hungUp: false }
        }).exec();
      }

      orderConversation =
        doc ||
        new OrderConversation({
          phone: phone,
          orderId: orderId
        });
      orderConversation.save().then(() => {
        processInput();
      });
    }
  );

  // fill in any answer to the current question, and determine next question
  // to ask
  function processInput() {
    // If we have input, use it to answer the current question
    var responseLength = orderConversation.responses.length;
    var currentQuestion = surveyData[responseLength];

    // if there's a problem with the input, we can re-ask the same question
    function reask() {
      cb.call(orderConversation, null, orderConversation, responseLength);
    }

    // If we have no input, ask the current question again
    if (input === undefined) return reask();

    // Otherwise use the input to answer the current question
    var questionResponse = {};
    var choice;
    if (currentQuestion.type === "intro") {
      // Anything other than '1' or 'yes' is a false

      questionResponse.answer = true;
      choice = 1;
    } else if (currentQuestion.type === "choice") {
      // Try and cast to a Number
      var num = Number(input);
      if (isNaN(num)) {
        // don't update the survey response, return the same question
        return reask();
      } else {
        questionResponse.answer = num;
        if (num == "1") {
          choice = 1;
        } else if (num == "2") {
          choice = 0;
        } else {
          choice = -1;
        }
      }
    } else {
      // otherwise store raw value
      questionResponse.answer = input;
    }

    // Save type from question
    questionResponse.type = currentQuestion.type;

    if (choice === 1) {
      orderConversation.responses.push(questionResponse);
    } else if (choice === -1) {
      orderConversation.responses.pop();
    }

    // If new responses length is the length of survey, mark as done
    if (orderConversation.responses.length === surveyData.length) {
      orderConversation.complete = true;
    }

    // Save response
    orderConversation.save(function(err) {
      if (err) {
        reask();
      } else {
        cb.call(
          orderConversation,
          err,
          orderConversation,
          responseLength + choice
        );
      }
    });
  }
};

// Export model
delete mongoose.models.OrderConversation;
delete mongoose.modelSchemas.OrderConversation;
var OrderConversation = mongoose.model(
  "OrderConversation",
  OrderConversationSchema
);
module.exports = OrderConversation;

Это ^^ - модель mon goose для управления разговорами заказов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...