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

esl.domain.FSCommand.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 Call Handling Services Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package esl.domain

import akka.stream.QueueOfferResult
import esl.domain.EventNames.EventName

import scala.concurrent.Future
import scala.concurrent.duration.{Duration, DurationInt, FiniteDuration}
import CallCommands.{LINE_TERMINATOR, MESSAGE_TERMINATOR}
import esl.domain.CallCommands.Dial.DialConfig
import esl.domain.HangupCauses.HangupCause

import scala.language.postfixOps

sealed trait FSCommand {
  val eventUuid: String = java.util.UUID.randomUUID.toString
}

sealed trait FSExecuteApp extends FSCommand {
  val config: ApplicationCommandConfig
  val application: String
  def args: String = ""

  override def toString: String = {
    val b = StringBuilder.newBuilder
    val hasApplication = application.nonEmpty

    if (hasApplication) {
      b.append(s"sendmsg ${config.channelUuid}$LINE_TERMINATOR")
      b.append(s"Event-UUID: $eventUuid$LINE_TERMINATOR")
      b.append(s"call-command: execute$LINE_TERMINATOR")
      b.append(s"execute-app-name: $application$LINE_TERMINATOR")

      if (config.eventLock)
        b.append(s"event-lock: ${config.eventLock}$LINE_TERMINATOR")
      if (config.loops > 1) b.append(s"loops: ${config.loops}$LINE_TERMINATOR")
      if (config.async) b.append(s"async: ${config.async}$LINE_TERMINATOR")
    }

    if (hasApplication && args.nonEmpty) {
      b.append(s"content-type: text/plain$LINE_TERMINATOR")
      b.append(s"content-length: ${args.length}$MESSAGE_TERMINATOR")
    }

    b.append(s"$args") // Assumes args is already a String

    b.toString()
  }
}

/**
  *
  * @param channelUuid The uuid of the channel to execute the application command on,
  *                    not required in outbound mode if you want to effect the outbound channel
  * @param eventLock   Force application to complete before next command is parsed
  * @param loops       Number of times to invoke the command, default 1
  * @param async       Set the execution mode to async, has no effect in Outbound async mode
  */
final case class ApplicationCommandConfig(
    channelUuid: String = "",
    eventLock: Boolean = false,
    loops: Int = 1,
    async: Boolean = false,
    useSetVar: Boolean = false
) {
  def withChannelId(channelId: String): ApplicationCommandConfig =
    copy(channelUuid = channelId)
}

object CallCommands {

  private implicit class CommandVarsSyntax(private val vars: Seq[String]) {
    def asVarsString: String = vars.mkString("{", ",", "}")

    def asCommandArgs: String = vars.mkString("[", ",", "]")
  }

  val MESSAGE_TERMINATOR = "\n\n"
  val LINE_TERMINATOR = "\n"

  final case class None(config: ApplicationCommandConfig) extends FSExecuteApp {
    override val application: String = "none"
  }

  /**
    * Hangs up a channel, with an optional cause code supplied.
    * 
    *
    * @param cause  : HangupCause
    * @param config : ApplicationCommandConfig
    */
  final case class Hangup(
      cause: Option[HangupCause],
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String =
      cause.fold(s"hangup$MESSAGE_TERMINATOR")(_ => "hangup")
    override val args: String = cause.fold("")(_.name)
  }

  final case class Break(config: ApplicationCommandConfig, app: Option[String])
      extends FSCommand {
    override def toString: String =
      s"""bgapi uuid_break ${config.channelUuid}
         |Job-UUID: $eventUuid$MESSAGE_TERMINATOR""".stripMargin
  }

  /**
    * Plays a sound file on the current channel.
    *
    * @param filePath : String file path that you want to play
    * @param config   : ApplicationCommandConfig
    */
  final case class PlayFile(filePath: String, config: ApplicationCommandConfig)
      extends FSExecuteApp {
    override val application: String = "playback"
    override val args: String = filePath
  }

  /**
    * Immediately transfer the calling channel to a new context. If there happens to be an xml extension named 
    * then control is "warped" directly to that extension. Otherwise it goes through the entire context checking for a match.
    *
    * @param extension : String extension name
    * @param config    : ApplicationCommandConfig
    */
  final case class TransferTo(
      extension: String,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "transfer"
    override val args: String = extension
  }

  /**
    * Answer the call for a channel.This sets up duplex audio between the calling ''A'' leg and the FreeSwitch server.
    * It is not about other endpoints. The server might need to 'answer' a call to play an audio file or to receive DTMF from the call.
    * Once answered, calls can still be bridged to other extensions. Because a bridge after an answer is actually a transfer,
    * the ringback tones sent to the caller will be defined by transfer_ringback.
    *
    * @param config : ApplicationCommandConfig
    */
  final case class Answer(config: ApplicationCommandConfig)
      extends FSExecuteApp {
    override val application: String = s"answer$MESSAGE_TERMINATOR"
  }

  final case class AuthCommand(password: String) extends FSCommand {
    override def toString: String = s"auth $password$MESSAGE_TERMINATOR"
  }

  /**
    * Set a channel variable for the channel calling the application.
    * Usage set =
    *
    * @param varName  : String variable name
    * @param varValue : String variable value
    * @param config   : ApplicationCommandConfig
    * @return Future[CommandReply]
    */
  final case class SetVar(
      varName: String,
      varValue: String,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "set"
    override val args: String = s"$varName=$varValue"
  }

  /**
    * att_xfer 
    * Bridge a third party specified by channel_url onto the call, speak privately, then bridge original caller to target channel_url of att_xfer.
    *
    * @param destination   : String target channel_url of att_xfer
    * @param conferenceKey : Char "attxfer_conf_key" - can be used to initiate a three way transfer (deafault '0')
    * @param hangupKey     : Char "attxfer_hangup_key" - can be used to hangup the call after user wants to end his or her call (deafault '*')
    * @param cancelKey     : Char "attxfer_cancel_key" - can be used to cancel a tranfer just like origination_cancel_key, but straight from the att_xfer code (deafault '#')
    * @param config        : ApplicationCommandConfig
    */
  final case class AttXfer(
      destination: String,
      conferenceKey: Char,
      hangupKey: Char,
      cancelKey: Char,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "att_xfer"
    override val args: String =
      s"{attxfer_conf_key=$conferenceKey,attxfer_hangup_key=$hangupKey,attxfer_cancel_key=$cancelKey}$destination"
  }

  /**
    * To dial multiple contacts all at once:
    * 
    * To dial multiple contacts one at a time:
    * 
    *
    * @param targets  :List[String] list of an external SIP address or termination provider
    * @param dialType : DialType To dial multiple contacts all at once then separate targets by comma(,) or To dial multiple contacts one at a time
    *                 then separate targets by pipe(|)
    * @param config   :ApplicationCommandConfig
    */
  final case class Bridge(
      targets: List[String],
      dialType: DialType,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "bridge"
    override val args: String = targets.mkString(dialType.separator)
  }

  final case class BridgeUuid(
      targets: List[String],
      config: ApplicationCommandConfig
  ) extends FSCommand {
    override def toString: String =
      s"""bgapi uuid_bridge ${targets.mkString(" ")}
         |Job-UUID: $eventUuid$MESSAGE_TERMINATOR""".stripMargin
  }

  final case class AddToConference(
      conferenceId: String,
      profile: String,
      config: ApplicationCommandConfig,
      flags: Seq[String] = Seq.empty
  ) extends FSExecuteApp {
    override val application: String = "conference"
    override val args: String =
      s"$conferenceId@$profile${flags.mkString("+flags{", "|", "}")}"
  }

//  final case class LeaveConference(
//      conferenceId: String,
//      memberId: Option[String],
//      config: ApplicationCommandConfig
//  ) extends FSCommand {
//    override def toString: String =
//      s"""bgapi conference $conferenceId kick ${memberId.getOrElse("all")}
//         |Job-UUID: $eventUuid$MESSAGE_TERMINATOR""".stripMargin
//  }

  final case class ConferenceCommand(
      conferenceId: String,
      command: ConferenceCommand.ConferenceCommandType,
      commandConfig: ApplicationCommandConfig
  ) extends FSCommand {

    lazy val config: ApplicationCommandConfig =
      commandConfig.copy(channelUuid = conferenceId)

    override def toString: String =
      s"bgapi conference $conferenceId ${command.toString} ${LINE_TERMINATOR}Job-UUID: $eventUuid$MESSAGE_TERMINATOR"
  }

  object ConferenceCommand {

    sealed trait ConferenceCommandType {
      def forConference(
          conferenceId: String,
          config: ApplicationCommandConfig
      ): ConferenceCommand
    }

    final case class Play(
        file: String,
        nomux: Boolean = false,
        memberId: Option[String] = Option.empty,
        async: Boolean = true
    ) extends ConferenceCommandType {

      override def toString: String =
        s"play $file${(memberId, async, nomux) match {
          case (Some(memberId), _, true) => s" $memberId nomux"
          case (Some(memberId), _, _)    => s" $memberId"
          case (_, true, true)           => " async nomux"
          case (_, true, _)              => " async nomux"
          case _                         => ""
        }}"

      override def forConference(
          conferenceId: String,
          config: ApplicationCommandConfig
      ): ConferenceCommand = {
        ConferenceCommand(conferenceId, this, config)
      }
    }

    final case class PausePlay() extends ConferenceCommandType {

      override def toString: String = "pause_play"

      override def forConference(
          conferenceId: String,
          config: ApplicationCommandConfig
      ): ConferenceCommand = {
        ConferenceCommand(conferenceId, this, config)
      }
    }

    final case class SendConferenceCommand(
        memberId: Option[String],
        command: String,
        config: ApplicationCommandConfig
    ) extends ConferenceCommandType {
      override def toString: String =
        s"$command ${memberId match {
          case Some(id) => id
          case _        => "all"
        }}"

      override def forConference(
          conferenceId: String,
          config: ApplicationCommandConfig
      ): ConferenceCommand = {
        ConferenceCommand(conferenceId, this, config)
      }
    }
  }

  /*
  final case class Dial(
                         target: String,
                         uniqueId: String,
                         numberPresentation: Dial.NumberPresentation,
                         config: ApplicationCommandConfig,
                         timeout: FiniteDuration,
                         retries: Option[Dial.Retry] = Option.empty
                       ) extends FSCommand {
    override def toString: String = {
      val vars = Seq(
        "ignore_early_media=true",
        s"originate_timeout=${timeout.toSeconds}",
        s"origination_uuid=$uniqueId"
      ) ++
        numberPresentation.asOriginateArgs ++
        retries.map(_.asOriginateArgs).getOrElse(Nil)

      s"""bgapi originate ${vars.asVarsString}$target &park()
         |Job-UUID: $eventUuid""".stripMargin
    }
  }*/

  final case class ListenIn(
      config: ApplicationCommandConfig,
      bargeIn: Boolean,
      options: DialConfig,
      listenCallId: String
  ) extends FSExecuteApp {
    override lazy val args: String = {
      val command = if (bargeIn) {
        //{job_uuid=1234}sofia/internal/foo at bar.com
        s"""
           |bgapi {job_uuid=$eventUuid}eval $${${options.asReplace} 'queue_dtmf:w3@500,eavesdrop:$listenCallId' inline}""".stripMargin
      } else {
        s"""bgapi ${options.asReplace} &eavesdrop($listenCallId)
             |Job-UUID: $eventUuid""".stripMargin
      }
      s"""eavesdropResult_$eventUuid=$${$command}
         |
         |""".stripMargin
    }

    override val application: String = "set"
  }

  final case class Dial(
      options: DialConfig,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {

    override val application: String = "set"

    /*override def toString: String =
      s"""bgapi ${options.asOriginateCmd} &park()
         |Job-UUID: $eventUuid
         |
         |""".stripMargin*/

    override lazy val args: String = {

      s"""dial_$eventUuid=$${
         |bgapi ${options.asReplace} &park()
         |Job-UUID: $eventUuid
         |
         |
         |}
         |
         |""".stripMargin
    }
  }

  final case class DialSession(
      options: DialConfig,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {

    override val application: String =
      if (config.useSetVar) "set"
      else ""

    /*override def toString: String =
      s"""bgapi ${options.asOriginateCmd} &park()
         |Job-UUID: $eventUuid
         |
         |""".stripMargin*/

    override lazy val args: String = if (config.useSetVar) {
      s"""dial_$eventUuid=$${
         |bgapi ${options.asReplace} &park()
         |Job-UUID: $eventUuid
         |
         |
         |}
         |
         |""".stripMargin
    } else {
      s"""bgapi ${options.asReplace} &park()
         |Job-UUID: $eventUuid
         |
         |""".stripMargin
    }
  }

  /*
  final case class DialSession(options: DialConfig) extends FSCommand {

    override def toString: String =
      s"""bgapi ${options.asReplace} &park()
         |Job-UUID: $eventUuid
         |
         |""".stripMargin

  }*/

  object Dial {

    case class DialConfig(
        target: String,
        uniqueId: String,
        numberPresentation: Dial.NumberPresentation,
        timeout: FiniteDuration,
        retries: Option[Dial.Retry] = Option.empty,
        miscArgs: String = ""
    ) {
      def asOriginateCmd = {
        val vars = Seq(
          s"originate_timeout=${timeout.toSeconds}",
          s"origination_uuid=$uniqueId"
        ) ++
          numberPresentation.asOriginateArgs ++
          retries.map(_.asOriginateArgs).getOrElse(Nil) ++ {
          if (miscArgs.isBlank) Seq.empty else Seq(miscArgs.trim)
        }
        s"originate ${vars.asVarsString}$target"
      }

      def asReplace = {
        val dialString = {
          Map(
            "destination" -> target,
            "origination_uuid" -> uniqueId,
            "originate_timeout" -> timeout.toSeconds.toString
          ) ++ numberPresentation.asReplace ++ retries
            .map(_.asReplace)
            .getOrElse(Map.empty)
        }.foldLeft(miscArgs)({
          case (miscArgs, (key, replacement)) =>
            miscArgs.replace(s"<<$key>>", replacement)
        })

        s"originate $dialString"
      }

    }

    case class Retry(retries: Int = 1, sleep: FiniteDuration = 5 seconds) {
      def asOriginateArgs: Seq[String] = {
        Seq(
          s"originate_retries=$retries",
          s"originate_retry_sleep_ms=${sleep.toMillis}"
        )
      }

      def asReplace: Map[String, String] =
        Map(
          "originate_retries" -> retries.toString,
          "originate_retry_sleep_ms" -> sleep.toMillis.toString
        )
    }
    case class NumberPresentation(number: String, name: String) {
      def asOriginateArgs: Seq[String] = {
        Seq(
          s"origination_caller_id_number=$number",
          s"origination_caller_id_name=$name"
        )
      }

      def asReplace: Map[String, String] =
        Map(
          "origination_caller_id_number" -> number,
          "origination_caller_id_name" -> name
        )
    }

    object NumberPresentation {
      lazy val hidden: NumberPresentation =
        NumberPresentation("anonymous", "anonymous")
    }
  }

  final case object ConnectCommand extends FSCommand {
    override def toString: String = s"connect$MESSAGE_TERMINATOR"
  }

  final case object LingerCommand extends FSCommand {
    override def toString: String = s"linger$MESSAGE_TERMINATOR"
  }

  /**
    * Freeswitch command as raw command that present as string
    *
    * @param command : String
    */
  final case class CommandAsString(
      command: String,
      override val eventUuid: String
  ) extends FSCommand {
    override def toString: String = command
  }

  /**
    * The event command are used to subscribe on events from FreeSWITCH
    * event plain ALL
    * event plain CHANNEL_CREATE CHANNEL_DESTROY CUSTOM conference::maintenance sofia::register sofia::expire
    * event xml ALL
    * event json CHANNEL_ANSWER
    *
    * @param events : List[EventName]
    */
  final case class SubscribeEvents(events: List[EventName]) extends FSCommand {
    override def toString: String =
      s"event plain ${events.map(_.name).mkString(" ")}$MESSAGE_TERMINATOR"
  }

  /**
    * The 'myevents' subscription allows your inbound socket connection to behave like an outbound socket connect.
    * It will "lock on" to the events for a particular uuid and will ignore all other events
    * myevents plain 
    * myevents json 
    * myevents xml 
    * Once the socket connection has locked on to the events for this particular uuid it will NEVER see any events
    * that are not related to the channel, even if subsequent event commands are sent
    *
    * @param uuid : String
    */
  final case class SubscribeMyEvents(uuid: String) extends FSCommand {
    override def toString: String = s"myevents plain $uuid$MESSAGE_TERMINATOR"
  }

  final case class CreateUUID(config: ApplicationCommandConfig)
      extends FSCommand {

    override val eventUuid: String =
      java.util.UUID.randomUUID.toString.replace("-", "")
    override def toString: String =
      s"bgapi create_uuid $eventUuid${LINE_TERMINATOR}Job-UUID: $eventUuid$MESSAGE_TERMINATOR"
  }

  sealed trait DisplaceCommand
  final case class StartPlay() extends DisplaceCommand {
    override def toString: String = "start"
  }
  final case class StopPlay() extends DisplaceCommand {
    override def toString: String = "stop"
  }

  final case class UuidDisplace(
      target: String,
      command: DisplaceCommand,
      filePath: Option[String],
      limit: Option[Int],
      config: ApplicationCommandConfig
  ) extends FSCommand {

    override val eventUuid: String =
      java.util.UUID.randomUUID.toString.replace("-", "")
    override def toString: String = {
      val limitStr = limit.fold("0")(_.toString)
      val fileOpt = command match {
        case StartPlay() =>
          filePath.fold(s"${command.toString} moh $limitStr mux") { file =>
            s"${command.toString} $file $limitStr mux"
          }
        case StopPlay() =>
          filePath.fold("") { file =>
            s"${command.toString} $file"
          }
      }
      s"bgapi uuid_displace $target $fileOpt${LINE_TERMINATOR}Job-UUID: $eventUuid$MESSAGE_TERMINATOR"
    }
  }

  /**
    * Specify event types to listen for. Note, this is not a filter out but rather a "filter in," that is,
    * when a filter is applied only the filtered values are received. Multiple filters on a socket connection are allowed.
    * Usage:
    * filter  
    *
    * @param events : Map[EventName, String] mapping of events and their value
    * @param config : ApplicationCommandConfig
    */
  final case class Filter(
      events: Map[EventName, String],
      config: ApplicationCommandConfig
  ) extends FSCommand {
    override def toString: String =
      s"filter ${events.map { case (key, value) => s"$value ${key.name}" }.mkString(" ")}$MESSAGE_TERMINATOR"
  }

  /**
    * Specify events of specific channel UUId to listen for. Note, this is not a filter out but rather a "filter in," that is,
    * when a filter is applied only the filtered values are received. Multiple filters on a socket connection are allowed.
    * Usage:
    * filter  
    *
    * @param uuid : Channel uuid to filter in
    */
  final case class FilterUUId(uuid: String) extends FSCommand {
    override def toString: String =
      s"filter Unique-ID ${uuid}$MESSAGE_TERMINATOR"
  }

  final case class FilterX(filter: String, config: ApplicationCommandConfig)
      extends FSCommand {
    override def toString: String =
      s"filter ${filter}$MESSAGE_TERMINATOR"
  }

  /**
    * filter delete
    * Specify the events which you want to revoke the filter. filter delete can be used when some filters are applied wrongly or
    * when there is no use of the filter.
    * Usage:
    * filter delete  
    *
    * @param events :Map[EventName, String] mapping of events and their value
    * @param config :ApplicationCommandConfig
    */
  final case class DeleteFilter(
      events: Map[EventName, String],
      config: ApplicationCommandConfig
  ) extends FSCommand {
    override def toString: String =
      s"filter delete ${events.map { case (key, value) => s"$value ${key.name}" }.mkString(" ")}$MESSAGE_TERMINATOR"
  }

  /**
    * filterUUId delete
    * Specify channel UUId to revoke the filter. filter delete can be used when some filters are applied wrongly or
    * when there is no use of the filter.
    * Usage:
    * filter delete  
    *
    * @param uuid : Channel uuid to filter out
    * @param config :ApplicationCommandConfig
    */
  final case class DeleteUUIdFilter(
      uuid: String,
      config: ApplicationCommandConfig
  ) extends FSCommand {
    override def toString: String =
      s"filter delete Unique-ID ${uuid}$MESSAGE_TERMINATOR"
  }

  /**
    * Allows one channel to bridge itself to the a or b leg of another call. The remaining leg of the original call gets hungup
    * Usage: intercept [-bleg] 
    *
    * @param uuid   : String
    * @param config :ApplicationCommandConfig
    */
  final case class Intercept(uuid: String, config: ApplicationCommandConfig)
      extends FSExecuteApp {
    override val application: String = "intercept"
    override val args: String = uuid
  }

  /**
    * Read DTMF (touch-tone) digits.
    * Usage
    * read      
    * min = Minimum number of digits to fetch.
    * max = Maximum number of digits to fetch.
    * sound file = Sound file to play before digits are fetched.
    * variable name = Channel variable that digits should be placed in.
    * timeout = Number of milliseconds to wait on each digit
    * terminators = Digits used to end input if less than  digits have been pressed. (Typically '#')
    *
    * @param params : ReadParameters
    * @param config : ApplicationCommandConfig
    */
  final case class Read(
      params: ReadParameters,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "read"
    override val args: String = List(
      params.min.toString,
      params.max.toString,
      params.soundFile,
      params.variableName,
      params.timeout.toMillis.toString,
      params.terminators.map(_.toString).mkString(",")
    ).mkString(" ")
  }

  /**
    * Speak a phrase of text using a predefined phrase macro
    *
    * @param variableName : String variable name
    * @param config       : ApplicationCommandConfig
    */
  final case class Phrase(
      variableName: String,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "phrase"
    override val args: String = "spell,${" + variableName + "}"
  }

  /**
    * Pause the channel for a given number of milliseconds, consuming the audio for that period of time.
    * Calling sleep also will consume any outstanding RTP on the operating system's input queue,
    * which can be very useful in situations where audio becomes backlogged.
    * To consume DTMFs, use the sleep_eat_digits variable.
    * Usage: />
    */
  final case class Sleep(
      numberOfMillis: Duration,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "sleep"
    override val args: String = numberOfMillis.toMillis.toString
  }

  final case class OffHold(config: ApplicationCommandConfig) extends FSCommand {
    override def toString: String =
      s"bgapi uuid_hold off ${config.channelUuid}${LINE_TERMINATOR}Job-UUID: $eventUuid$MESSAGE_TERMINATOR"
  }

  final case class Hold(config: ApplicationCommandConfig) extends FSCommand {
    override def toString: String =
      s"bgapi uuid_hold ${config.channelUuid}${LINE_TERMINATOR}Job-UUID: $eventUuid$MESSAGE_TERMINATOR"
  }

  /*
  case class Hold(config: ApplicationCommandConfig) extends FSExecuteApp {
    override val application: String = "hold"
    override val args: String = config.channelUuid
  }
   */
//  case class OffHold(config: ApplicationCommandConfig) extends FSExecuteApp {
//    override val application: String = "unhold"
//    override val args: String = config.channelUuid
//  }

  /**
    * pre_answer establishes media (early media) but does not answer.
    *
    * @param config : ApplicationCommandConfig
    */
  final case class PreAnswer(config: ApplicationCommandConfig)
      extends FSExecuteApp {
    override val application: String = s"pre_answer$MESSAGE_TERMINATOR"
  }

  /**
    * Record to a file from the channel's input media stream
    * Record is used to record voice messages, such as in a voicemail system. This application will record to a file specified by .
    * After recording stops the record app sets the following read-only variables:
    *
    * record_ms — duration of most recently recorded file in milliseconds
    * record_samples — number of recorded samples
    * playback_terminator_used — TouchTone digit used to terminate recording
    *
    * @param filePath      : String An application will record to a file specified by file path.
    * @param timeLimitSecs : Duration it is the maximum duration of the recording in seconds
    * @param silenceThresh : Duration it is an energy level below which is considered silence.
    * @param silenceHits   : Duration it is how many seconds of audio below silence_thresh will be tolerated before the recording stops.
    *                      When omitted, the default value is 3 seconds
    * @param config        :ApplicationCommandConfig
    */
  final case class Record(
      filePath: String,
      timeLimitSecs: Duration,
      silenceThresh: Duration,
      silenceHits: Option[Duration],
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "record"
    override val args: String =
      s"$filePath ${timeLimitSecs.toSeconds} ${silenceThresh.toSeconds}${silenceHits
        .fold("")(f => s" ${f.toSeconds.toString}")}"
  }

  /**
    * Records an entire phone call or session.
    * Multiple media bugs can be placed on the same channel.
    *
    * @param filePathWithFormat : String file format like gsm,mp3,wav, ogg, etc
    * @param config             : ApplicationCommandConfig
    */
  final case class RecordSession(
      filePathWithFormat: String,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "record_session"
    override val args: String = filePathWithFormat
  }

  /**
    * Send DTMF digits from the session using the method(s) configured on the endpoint in use
    * If no duration is specified the default DTMF length of 2000ms will be used.
    *
    * @param dtmfDigits   : String DTMF digits
    * @param toneDuration : Option[Duration]
    * @param config       : ApplicationCommandConfig
    */
  final case class SendDtmf(
      dtmfDigits: String,
      toneDuration: Option[Duration],
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "send_dtmf"
    override val args: String =
      s"$dtmfDigits${toneDuration.fold("")(f => s"@${f.toMillis.toString}")}"
  }

  /**
    * Stop record session.
    * Usage: 
    *
    * @param filePath : String file name
    * @param config   : ApplicationCommandConfig
    */
  final case class StopRecordSession(
      filePath: String,
      config: ApplicationCommandConfig
  ) extends FSExecuteApp {
    override val application: String = "stop_record_session"
    override val args: String = filePath
  }

  /**
    * Stop record.
    * Usage: 
    *
    * @param filePath : String file name
    * @param config   : ApplicationCommandConfig
    */
  final case class StopRecord(
      config: ApplicationCommandConfig
  ) extends FSCommand {
    override def toString(): String =
      s"bgapi uuid_record ${config.channelUuid} stop${LINE_TERMINATOR}Job-UUID: $eventUuid$MESSAGE_TERMINATOR"
  }

  /**
    * Pause record.
    * Usage: 
    *
    * @param uuid : String channelId
    * @param config   : ApplicationCommandConfig
    */
  final case class PauseRecord(
      config: ApplicationCommandConfig
  ) extends FSCommand {
    override def toString(): String =
      s"bgapi uuid_record ${config.channelUuid} pause${LINE_TERMINATOR}Job-UUID: $eventUuid$MESSAGE_TERMINATOR"
  }

  /**
    * Resume record.
    * Usage: 
    *
    * @param uuid : String channelId
    * @param config   : ApplicationCommandConfig
    */
  final case class ResumeRecord(
      config: ApplicationCommandConfig
  ) extends FSCommand {
    override def toString(): String =
      s"bgapi uuid_record ${config.channelUuid} resume${LINE_TERMINATOR}Job-UUID: $eventUuid$MESSAGE_TERMINATOR"
  }

  /**
    * Places a channel "on hold" in the switch, instead of in the phone. Allows for a number of different options, including:
    * Set caller in a place where the channel won't be hungup on, while waiting for someone to talk to.
    * Generic "hold" mechanism, where you transfer a caller to it.
    * Please note that to retrieve a call that has been "parked", you'll have to bridge to them or transfer the call to a valid location.
    * Also, remember that parking a call does NOT supply music on hold or any other media.
    * Park is quite literally a way to put a call in limbo until you you bridge/uuid_bridge or transfer/uuid_transfer it.
    * Note that the park application takes no arguments, so the data attribute can be omitted in the definition.
    *
    * @param config : ApplicationCommandConfig
    */
  final case class Park(config: ApplicationCommandConfig) extends FSExecuteApp {
    override val application: String = s"park$MESSAGE_TERMINATOR"
  }

  /**
    * Close the socket connection.
    *
    * @param config : ApplicationCommandConfig
    */
  final case class Exit(config: ApplicationCommandConfig) extends FSCommand {
    override def toString: String = s"exit$MESSAGE_TERMINATOR"
  }

  /**
    * Enable log output. Levels same as the console.conf values
    *
    * @param logLevel : String
    * @param config   : ApplicationCommandConfig
    */
  final case class Log(logLevel: String, config: ApplicationCommandConfig)
      extends FSCommand {
    override def toString: String = s"log $logLevel$MESSAGE_TERMINATOR"
  }

}

final case class CommandRequest(
    command: FSCommand,
    queueOfferResult: Future[QueueOfferResult]
)

sealed trait DialType extends Product with Serializable {
  val separator: String
}

/**
  * To dial multiple contacts all at once then separate targets by comma(,)
  */
case object AllAtOnce extends DialType {
  override val separator: String = ","
}

/**
  * To dial multiple contacts one at a time then separate targets by pipe(|)
  */
case object OneAtATime extends DialType {
  override val separator: String = "|"
}

/**
  *
  * @param min          Minimum number of digits to fetch.
  * @param max          Maximum number of digits to fetch.
  * @param soundFile    Sound file to play before digits are fetched.
  * @param variableName Channel variable that digits should be placed in.
  * @param timeout      Number of milliseconds to wait on each digit
  * @param terminators  Digits used to end input if less than  digits have been pressed. (Typically '#')
  */
final case class ReadParameters(
    min: Int,
    max: Int,
    soundFile: String,
    variableName: String,
    timeout: Duration,
    terminators: List[Char]
)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy