All Downloads are FREE. Search and download functionalities are using the official Maven repository.

simpleivr.Ivr.scala Maven / Gradle / Ivy

package simpleivr

import cats.implicits._


class Ivr(sayables: Sayables) {

  import sayables._


  def loopUntilDefined[A](step: => IvrStep[Option[A]]): IvrStep[A] =
    step.flatMap {
      case None    => loopUntilDefined(step)
      case Some(a) => IvrStep(a)
    }

  def record(desc: Sayable, path: AudioPath, timeLimitInSeconds: Int): IvrStep[Unit] =
    (IvrStep.say(`Please say` & desc & `after the tone, and press pound when finished.`) *>
      IvrStep.recordFile(path.pathAndName, "wav", Set(DTMF.`#`), timeLimitInSeconds * 1000, 0, beep = true, 3))
      .void

  def confirmRecording(desc: Sayable, file: Sayable): IvrStep[Option[Boolean]] =
    askYesNo(desc & `is` & file & `Is that correct?`)

  def sayAndGetDigit(msgs: Sayable, wait: Int = 5000): IvrStep[Option[DTMF]] =
    IvrStep.say(msgs, DTMF.values).flatMap {
      case Some(c) => IvrStep(Some(c))
      case None    => IvrStep.waitForDigit(wait)
    }

  /**
    * @param handle A function, passed the accumulated previous digits and the latest digit or None if user was silent.
    *               Should return
    *               Some(Left(Sayable)) to indicate the input is invalid and should be retried,
    *               Some(Right(x)) to indicate the input is complete and the value to return,
    *               or None for input to continue.
    */
  def sayAndHandle[A](message: Sayable)
                     (handle: (String, Option[DTMF]) => Option[Either[Sayable, A]]): IvrStep[A] = {
    def calcRes(acc: String)(ch: Option[DTMF]): IvrStep[Either[Sayable, A]] =
      handle(acc, ch) match {
        case Some(res) => IvrStep(res)
        case None      => IvrStep.waitForDigit(5000).flatMap(calcRes(acc + ch.mkString))
      }

    sayAndGetDigit(message)
      .flatMap(calcRes(""))
      .flatMap {
        case Right(x)  => IvrStep(x)
        case Left(msg) => IvrStep.say(msg) *> sayAndHandle(message)(handle)
      }
  }

  private val simpleHandler: PartialFunction[String, String] = {
    case x => x
  }

  /**
    * None means * was pressed, signifying that inputting was canceled
    */
  def sayAndHandleDigits[A](min: Int, max: Int, msgs: Sayable)
                           (handle: PartialFunction[String, A] = simpleHandler): IvrStep[Option[A]] =
    sayAndHandle(msgs) {
      case (_, Some(DTMF.*))                                   => Some(Right(None))
      case (acc, Some(c)) if acc.length + 1 < max && c.isDigit => None
      case (acc, x)                                            =>
        def sayDigitOrDigits(n: Int) = numberWords(n) & (if (n == 1) `digit` else `digits`)

        val str = acc + x.filter(_ != DTMF.`#`).mkString
        val validated =
          if ((min == max) && (str.length != min)) Left(`You must enter ` & sayDigitOrDigits(min))
          else if (str.length < min) Left(`You must enter at least` & sayDigitOrDigits(min))
          else if (str.length > max) Left(`You cannot enter more than` & sayDigitOrDigits(max))
          else
            handle
              .andThen(a => Right(Some(a)))
              .applyOrElse(str, (_: String) => Left(`That entry is not valid`))
        Some(validated)
    }

  def askYesNo(msgs: Sayable): IvrStep[Option[Boolean]] =
    sayAndGetDigit(msgs & `Press 1 for yes, or 2 for no.`) flatMap {
      case Some(DTMF.`1`) => IvrStep(Some(true))
      case Some(DTMF.`2`) => IvrStep(Some(false))
      case Some(DTMF.*)   => IvrStep(None)
      case None           => IvrStep.say(`Please make a selection`) *> askYesNo(msgs)
      case _              => IvrStep.say(`That is not one of the choices.`) *> askYesNo(msgs)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy