bashagi.sh (Source)

#!/bin/bash
##---------------------------------------------------------------------------##
#   AGI framework for bash. To use:
#
#       #!/bin/bash
#       source $AST_AGI_DIR/bashagi.sh
#       AGI_read_vars
#       (your script goes here)
##---------------------------------------------------------------------------##
#   A Bash-AGI script can run on one of two modes:
#   - Standard: handled by Asterisk
#   - Background: Commands are "handled" (usually ignored) by this script
#     because its stdin/stdout are not connected to Asterisk's stdout/stdin
#   See the AGI_background funcion for more information.
##---------------------------------------------------------------------------##

##---------------------------------------------------------------------------##
#   AGI_read_vars: Read standard input and convert lines to shell variables
##---------------------------------------------------------------------------##
#  AGI_ACCOUNTCODE=""
#  AGI_ARG_1="<value of passed arg 1>"  (repeats for additional args)
#  AGI_CALLERID="2025550112"        (received from the trunk)
#  AGI_CALLERIDNAME="Caller Name"   (received from the trunk)
#  AGI_CALLINGANI2="0"
#  AGI_CALLINGPRES="0"
#  AGI_CALLINGTNS="0"
#  AGI_CALLINGTON="0"
#  AGI_CHANNEL="PJSIP/trunkname-00000030"
#  AGI_CONTEXT="context-name"
#  AGI_DNID="12025550123"           (Trunk's DID)
#  AGI_ENHANCED="0.0"
#  AGI_EXTENSION="s"
#  AGI_LANGUAGE="en"
#  AGI_PRIORITY="1"
#  AGI_RDNIS="unknown"
#  AGI_REQUEST="whitelist.sh"
#  AGI_THREADID="140092990433024"
#  AGI_TYPE="PJSIP"
#  AGI_UNIQUEID="1578321547.115"
#  AGI_VERSION="16.6.2"
##---------------------------------------------------------------------------##
function AGI_read_vars {
    eval "$(awk '/^[[:space:]]*$/ { exit}
        { print "export " toupper(substr($1, 1, length($1)-1)) "=\"" substr($0, length($1)+2) "\"" }')"
    AGI_BACKGROUND=false
}

##---------------------------------------------------------------------------##
#   AGI_command: send an AGI command to Asterisk and read the response.
#   Parameters:
#     1. AGI command: see list at end of script
#     2. Parameters. Should be quoted unless it truly is a single word; for
#        example, AGI_DATABASE_GET 'family variable' SHELL_VAR
#        (Exception: AGI_VERBOSE combines all parameters into a phrase)
#     3. If the command is 'GET VARIABLE' or 'DATABASE GET' and this param
#        has a value, it names an environment variable for storing the
#        value returned by Asterisk.
##---------------------------------------------------------------------------##
function AGI_command {
    local STATUS

    # Handle background mode
    if $AGI_BACKGROUND
    then
        if [ "$1" == 'VERBOSE' ]
        then
            shift
            MSG="[$(date +'%Y-%m-%d %H:%M:%S')] VERBOSE[$$]$AGI_CALL_NUMBER $AGI_SCRIPT_NAME(bg): $@"
            echo "$MSG" >>${AGI_VERBOSE_FN:-/dev/stdout}
        fi
        return 0
    fi

    # Send the command to Asterisk
    if [ "$1" == 'VERBOSE' ]
    then
        local TEXT=''
        while [ "$1" ]; do shift; TEXT="${TEXT}$1 "; done
        I=$((${#TEXT}-2))
        echo VERBOSE "\"${TEXT:0:$I}\""
    else
        echo $1 $2
    fi

    # Read the response
    read STATUS
    [[ "$STATUS" =~ result=([0-9]+) ]] && RC=${BASH_REMATCH[1]} || RC=0
    if [ "$1" == 'GET VARIABLE' -o  "$1" == 'DATABASE GET' ] &&
       [ "$2" -a $RC == 1 ]
    then
        REGEXP='200 result=1 \((.*)\)'
        [[ "$STATUS" =~ $REGEXP ]] && eval "$3=\"${BASH_REMATCH[1]}\""
    fi

    # If STATUS ends with '-', continue reading from stdin until a blank line
    L="${#STATUS}"
    if [ "${STATUS:0:3}" == '520' -o "${STATUS:$L:1}" == '-' ]
    then
        read LINE   
        while [ "$LINE" ]; do
            STATUS="${STATUS}_NL_$LINE"
            [ "${LINE:0:3}" == '520' ] && LINE="" || read LINE
        done
        echo -e "VERBOSE \"Multi-line status: $STATUS\""
        read STATUS_1
    fi
    return $RC
}

##---------------------------------------------------------------------------##
#   AGI_background: Run a new copy of the script in the background
#
#   Running in the background allows the script return control to Asterisk
#   as soon as possible while the background part does the work. But it
#   disconnects the script from the stdin and stdout passed from Asterisk,
#   meaning AGI functions no longer work. To allow logging via 'AGI_VERBOSE,'
#   this function puts the call number (C-xxxxxxxx) into AGI_CALL_NUMBER
#   and sets AGI_BACKGROUND to true.  Subesquent calls to AGI_VERBOSE are
#   sent to the log file if this script determines if it was called from
#   Asterisk, else it sends them to stdout.
#
#   In background mode most other functions are ignored. AGI_READ_VARIABLE 
#   and AGI_DATABASE_GET return no value.
#
#   It is up to the caller to get variables as needed, export them so
#   the background script can see them, and start the background part.
#   Suggested code:
#
#       #!/bin/bash
#       source $AST_AGI_DIR/bashagi.sh
#
#       # Prepare variables and start background operation
#       if [ "$1" != '--background' ]
#       then
#           AGI_read_vars
#           AGI_GET_VARIABLE 'CHANNEL(language)' CHAN_LANG
#           AGI_GET_VARIABLE 'MIXMON_FORMAT' MXM_FMT
#           AGI_DATABASE_GET 'CustomDevState DAYNIGHT0' DB_DAYNIGHT0
#           export CHAN_LANG DB_DAYNIGHT0 MXM_FMT
#           AGI_background
#           exit
#       fi
#
#       # This is the background part--AGI functions no longer work, but
#       # AGI_VERBOSE is emulated
#           (... do background stuff ...)
#       AGI_VERBOSE Background script exit
#       exit
##---------------------------------------------------------------------------##
function AGI_background {
    # Determine the call number
    X="${AGI_CHANNEL//\//\\/}"      # Change all '/' to '\/'
    AGI_CALL_NUMBER="$(tail -n100 $AST_LOG_DIR/full | awk "
        match(\$0,/(\\[C-[0-9a-f]+\\]).*$X/,a){print a[1];exit}
    ")"

    # Determine if AGI_VERBOSE should write to the log file or stdout
    AGI_VERBOSE_FN=/dev/stdout
    TEMP_FN=/tmp/bash-agi.$$.tmp    # Need to bounce pstree output through temp file:
    pstree >$TEMP_FN                # using pipe alters output and grep fails
    grep -q 'asterisk.*pstree' $TEMP_FN && AGI_VERBOSE_FN=$AST_LOG_DIR/full
    rm -f $TEMP_FN

    # Relaunch the script, passing '--background' as parameter 1
    AGI_BACKGROUND=true
    AGI_SCRIPT_NAME="$(basename $0)"
    export AGI_BACKGROUND AGI_CALL_NUMBER AGI_SCRIPT_NAME AGI_VERBOSE_FN
    SELF=$0
    shift
    nohup $SELF --background "$@" >/dev/null 2>&1 &

    unset AGI_CALL_NUMBER AGI_SCRIPT_NAME AGI_VERBOSE_FN
    AGI_BACKGROUND=false
}

##---------------------------------------------------------------------------##
#   Asterisk AGI function stubs
##---------------------------------------------------------------------------##
function AGI_ANSWER                     { AGI_command 'ANSWER'; }
function AGI_ASYNCAGI_BREAK             { AGI_command 'ASYNCAGI BREAK'; }
function AGI_CHANNEL_STATUS             { AGI_command 'CHANNEL STATUS' "$@"; }
function AGI_CONTROL_STREAM_FILE        { AGI_command 'CONTROL STREAM FILE' "$@"; }
function AGI_DATABASE_DEL               { AGI_command 'DATABASE DEL' "$@"; }
function AGI_DATABASE_DELTREE           { AGI_command 'DATABASE DELTREE' "$@"; }
function AGI_DATABASE_GET               { AGI_command 'DATABASE GET' "$@"; }
function AGI_DATABASE_PUT               { AGI_command 'DATABASE PUT' "$@"; }
function AGI_EXEC                       { AGI_command 'EXEC' "$@"; }
function AGI_GET_DATA                   { AGI_command 'GET DATA' "$@"; }
function AGI_GET_FULL_VARIABLE          { AGI_command 'GET FULL VARIABLE' "$@"; }
function AGI_GET_OPTION                 { AGI_command 'GET OPTION' "$@"; }
function AGI_GET_VARIABLE               { AGI_command 'GET VARIABLE' "$@"; }
function AGI_GOSUB                      { AGI_command 'GOSUB' "$@"; }
function AGI_HANGUP                     { AGI_command 'HANGUP'; }
function AGI_NOOP                       { AGI_command 'NOOP' "$@"; }
function AGI_RECEIVE_CHAR               { AGI_command 'RECEIVE CHAR' "$@"; }
function AGI_RECEIVE_TEXT               { AGI_command 'RECEIVE TEXT' "$@"; }
function AGI_RECORD_FILE                { AGI_command 'RECORD FILE' "$@"; }
function AGI_SAY_ALPHA                  { AGI_command 'SAY ALPHA' "$@"; }
function AGI_SAY_DATE                   { AGI_command 'SAY DATE' "$@"; }
function AGI_SAY_DATETIME               { AGI_command 'SAY DATETIME' "$@"; }
function AGI_SAY_DIGITS                 { AGI_command 'SAY DIGITS' "$@"; }
function AGI_SAY_NUMBER                 { AGI_command 'SAY NUMBER' "$@"; }
function AGI_SAY_PHONETIC               { AGI_command 'SAY PHONETIC' "$@"; }
function AGI_SAY_TIME                   { AGI_command 'SAY TIME' "$@"; }
function AGI_SEND_IMAGE                 { AGI_command 'SEND IMAGE' "$@"; }
function AGI_SEND_TEXT                  { AGI_command 'SEND TEXT' "$@"; }
function AGI_SET_AUTOHANGUP             { AGI_command 'SET AUTOHANGUP' "$@"; }
function AGI_SET_CALLERID               { AGI_command 'SET CALLERID' "$@"; }
function AGI_SET_CONTEXT                { AGI_command 'SET CONTEXT' "$@"; }
function AGI_SET_EXTENSION              { AGI_command 'SET EXTENSION' "$@"; }
function AGI_SET_MUSIC                  { AGI_command 'SET MUSIC' "$@"; }
function AGI_SET_PRIORITY               { AGI_command 'SET PRIORITY' "$@"; }
function AGI_SET_VARIABLE               { AGI_command 'SET VARIABLE' "$@"; }
function AGI_SPEECH_ACTIVATE_GRAMMAR    { AGI_command 'SPEECH ACTIVATE GRAMMAR' "$@"; }
function AGI_SPEECH_CREATE              { AGI_command 'SPEECH CREATE' "$@"; }
function AGI_SPEECH_DEACTIVATE_GRAMMAR  { AGI_command 'SPEECH DEACTIVATE GRAMMAR' "$@"; }
function AGI_SPEECH_DESTROY             { AGI_command 'SPEECH DESTROY'; }
function AGI_SPEECH_LOAD_GRAMMAR        { AGI_command 'SPEECH LOAD GRAMMAR' "$@"; }
function AGI_SPEECH_RECOGNIZE           { AGI_command 'SPEECH RECOGNIZE' "$@"; }
function AGI_SPEECH_SET                 { AGI_command 'SPEECH SET' "$@"; }
function AGI_SPEECH_UNLOAD_GRAMMAR      { AGI_command 'SPEECH UNLOAD GRAMMAR' "$@"; }
function AGI_STREAM_FILE                { AGI_command 'STREAM FILE' "$@"; }
function AGI_TDD_MODE                   { AGI_command 'TDD MODE' $1; }
function AGI_VERBOSE                    { AGI_command 'VERBOSE' "$@"; }

# vim: tabstop=4