[general]static=yeswriteprotect=noclearglobalvars=no[globals]VM_HOME=/var/spool/asterisk/voicemail ; root directory of the voicemail applicationRELOAD_VM_CONFIGURATION=0 ; causes the voicemail.conf file to be re-read back into the AstDB ; enable from the CLI with: 'core set global RELOAD_VM_CONFIGURATION 1' (without the quotes);FUNCTION_TEST=${SHELL(echo "foo_bar")} ; currently causes a segfault of Asterisk ; currently causes a segfault of Asterisk[phones]; Testing Area!; SAMPLE: Creation of new voicemail envelopeexten => 5,1,Answer()exten => 5,n,GoSub(setup_mailbox,s,1(default,100,INBOX))exten => 5,n,GoSub(get_msg_count,s,1())exten => 5,n,Set(MSG_COUNT=${GOSUB_RETVAL})exten => 5,n,GoSub(set_msg_number,s,1(${MSG_COUNT}))exten => 5,n,Set(MSGNUM=${GOSUB_RETVAL})exten => 5,n,System(touch ${PWD}/msg${MSGNUM}.txt); VoiceMail(mailbox[@context][&mailbox[@context]][...][|options])exten => 100,1,GoSub(voicemail,s,1())exten => 101,1,Voicemail()exten => 200,1,GoSub(voicemail,s,1(100&1234))exten => 201,1,Voicemail(100&1234)exten => 300,1,GoSub(voicemail,s,1(100,u))exten => 301,1,Voicemail(100,u)exten => 400,1,GoSub(voicemail,s,1(100,su))exten => 401,1,Voicemail(100,us); Invalid voicemailexten => 500,1,GoSub(voicemail,s,1(101,u))exten => 500,n,DumpChan()exten => 501,1,Voicemail(101,u)exten => 501,n,DumpChan(); VoiceMailMain([mailbox][@context][|options])exten => 1100,1,GoSub(voicemailmain,s,1())exten => 1101,1,VoicemailMain()exten => 1200,1,GoSub(voicemailmain,s,(100))exten => 1201,1,VoicemailMain(100)exten => 1,1,GoSub(read_voicemail_conf,s,1())exten => 2,1,Set(LINE_NUMBER=2)exten => 2,n,NoOp(${SHELL(sed -e '/\\\;/d' /etc/asterisk/voicemail.conf \| grep '.' \| tr "\\\n"\, "&" \| cut -d \\\& -f ${LINE_NUMBER})})exten => 2,n,NoOp(${COMMAND})exten => 3,1,Set(information=foo_bar)exten => 3,n,Set(RES=${SET(information2=foo_bar)})exten => 3,n,DumpChan()exten => 3,n,Answer()exten => 4,1,NoOp()exten => 4,n,Set(LINE_NUMBER=1)exten => 4,n,While($[${LEN(${SHELL(sed -e '/\\\;/d' /etc/asterisk/voicemail.conf \| grep '.' \| tr "\\\n" "~" \| cut -d \\\~ -f ${LINE_NUMBER})})} > 1])exten => 4,n,Verbose(1|${SHELL(sed -e '/\\\;/d' /etc/asterisk/voicemail.conf \| grep '.' \| tr "\\\n" "~" \| cut -d \\\~ -f ${LINE_NUMBER})})exten => 4,n,Set(LINE_NUMBER=$[${LINE_NUMBER} + 1])exten => 4,n,EndWhile()[voicemail]; Leif Madsen;--[Description] VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]): Thisapplication allows the calling party to leave a message for the specifiedlist of mailboxes. When multiple mailboxes are specified, the greeting willbe taken from the first mailbox specified. Dialplan execution will stop if thespecified mailbox does not exist. The Voicemail application will exit if any of the following DTMF digits arereceived: 0 - Jump to the 'o' extension in the current dialplan context. * - Jump to the 'a' extension in the current dialplan context. This application will set the following channel variable upon completion: VMSTATUS - This indicates the status of the execution of the VoiceMail application. The possible values are: SUCCESS | USEREXIT | FAILED Options: b - Play the 'busy' greeting to the calling party. g(#) - Use the specified amount of gain when recording the voicemail message. The units are whole-number decibels (dB). s - Skip the playback of instructions for leaving a message to the calling party. u - Play the 'unavailable' greeting. j - Jump to priority n+101 if the mailbox is not found or some other error occurs.--;exten => s,1,NoOp()exten => s,n,Set(REQUESTED_MAILBOXES=${ARG1})exten => s,n,Set(OPTIONS=${ARG2}) ; save the options requested for Voicemail(); Get the value of VM_RELOAD_CONFIGURATION so we can determine if it needs to be resetexten => s,n,Set(RESET_RELOAD_VM_CONFIGURATION=${GLOBAL(RELOAD_VM_CONFIGURATION)})exten => s,n,GoSubIf($[${RELOAD_VM_CONFIGURATION}]?read_voicemail_conf,s,1()) ; Load the voicemail.conf file into the AstDB; Reset the Global variable VM_RELOAD_CONFIGURATION so we don't load it again next time a call comes inexten => s,n,Exec(${IF($[${RESET_RELOAD_VM_CONFIGURATION}]?Set(GLOBAL(RELOAD_VM_CONFIGURATION)=0):NoOp())}); Define some starter variablesexten => s,n,Set(MAX_TRIES=3) ; default tries for when we ask the user for input; Parse for multiple mailboxesexten => s,n,Set(MBX=1) ; Mailbox Counterexten => s,n,Set(HASH_POSITION=0)exten => s,n,While($[${EXISTS(${CUT(REQUESTED_MAILBOXES,&,${MBX})})}])exten => s,n,Set(THIS_DATA=${CUT(REQUESTED_MAILBOXES,&,${MBX})}) ; the current mailbox@context we're working withexten => s,n,Set(THIS_MAILBOX=${CUT(THIS_DATA,@,1)})exten => s,n,Set(THIS_CONTEXT=${IF($[${EXISTS(${CUT(THIS_DATA,@,2)})}]?${CUT(THIS_DATA,@,2)}:default)});; Save the requested mailboxes to an associative array; ${HASH(MAILBOXES,--POSITION--)}; If the mailbox exists in the local database, then we'll save it to the MAILBOXES hash;; --- start of data into the hash ---exten => s,n,GotoIf($[!${DB_EXISTS(voicemail/conf/[${THIS_CONTEXT}]/${THIS_MAILBOX})}]?check_next_box)exten => s,n,Set(HASH(MAILBOXES|${HASH_POSITION})=${THIS_MAILBOX}@${THIS_CONTEXT})exten => s,n,Set(HASH_POSITION=$[${HASH_POSITION} + 1]); --- end of data put into the hash ---; Check the next boxexten => s,n(check_next_box),NoOp()exten => s,n,Set(MBX=$[${MBX} + 1])exten => s,n,EndWhile(); Check if the voicemail exists in our configurationexten => s,n,GoSubIf($[${ISNULL(${HASH(MAILBOXES|0)})}]?get_mailbox,1) ; if no mailbox passed to us, get it from the userexten => s,n,GotoIf($[${EXISTS(${HASH(MAILBOXES|0)})}]?set_options)exten => s,n,Verbose(1|No known mailbox passed to the [voicemail] subroutine)exten => s,n,Set(VMSTATUS=FAILED) ; invalid voicemail box, return with statusexten => s,n,Return(); Save status of optionsexten => s,n(set_options),NoOp() ; if we have a valid mailbox, we can continueexten => s,n,Set(b_option=${REGEX("b" ${OPTIONS})})exten => s,n,Set(g_option=${REGEX("g" ${OPTIONS})})exten => s,n,Set(s_option=${REGEX("s" ${OPTIONS})})exten => s,n,Set(u_option=${REGEX("u" ${OPTIONS})})exten => s,n,Set(j_option=${REGEX("j" ${OPTIONS})})exten => s,n,GoSub(setup_mailbox,s,1(${HASH(MAILBOXES|0)})) ; setup the mailbox present working directory (PWD); Check options so we can determine which greeting to play;exten => s,n,GoSubIf($[${u_option} & !$[0${STAT(e,${PWD}/unavail.gsm)} = 0 & 0${STAT(e,${PWD}/unavail.wav)} = 0 & 0${STAT(e,${PWD}/unavail.WAV)} = 0]]?cust_greeting,1(unavail))exten => s,n,GotoIf($[${EXISTS(${RECORDED_FILE})}]?goodbye) ; if a file exists, we recorded something, and we're doneexten => s,n,GoSubIf($[${b_option} & !$[0${STAT(e,${PWD}/busy.gsm)} = 0 & 0${STAT(e,${PWD}/busy.wav)} = 0 & 0${STAT(e,${PWD}/busy.WAV)} = 0]]?cust_greeting,1(busy))exten => s,n,GotoIf($[${EXISTS(${RECORDED_FILE})}]?goodbye) ; we're done, file was recordedexten => s,n,GoSub(std_greeting,1(${IF($[${u_option}]?unavail:${IF($[${b_option}]?busy)})}))exten => s,n,GotoIf($[${EXISTS(${RECORDED_FILE})}]?goodbye) ; we're done, file was recordedexten => s,n,Verbose(1|File recording: ${IF($[${EXISTED(${RECORDED_FILE})}]?SUCCESSFUL:FAILED)})exten => s,n(goodbye),NoOp()exten => s,n,Set(VMSTATUS=SUCCESS)exten => s,n,Return()exten => get_mailbox,1,NoOp()exten => get_mailbox,n,Read(READ_IN_MAILBOX|vm-whichbox|||${MAX_TRIES})exten => get_mailbox,n,Set(HASH(MAILBOXES|0)=${READ_IN_MAILBOX}@default)exten => get_mailbox,n,Return()exten => cust_greeting,1,NoOp()exten => cust_greeting,n,Set(GREETING=${ARG1})exten => cust_greeting,n,Playback(${PWD}/${GREETING})exten => cust_greeting,n,Exec(${IF($[${s_option} = 0]?Playback(vm-intro):NoOp())})exten => cust_greeting,n,GoSub(record_voicemail,s,1())exten => cust_greeting,n,Return()exten => std_greeting,1,NoOp()exten => std_greeting,n,Set(GREETING=${ARG1})exten => std_greeting,n,GotoIf($["${GREETING}" != "unavail" | "${GREETING}" != "busy"]?no_greeting) ; if no greeting requested, just play an intro fileexten => std_greeting,n,Background(vm-theperson)exten => std_greeting,n,Set(STDGREET=0)exten => std_greeting,n,Set(MAILBOX=${CUT(HASH(MAILBOXES|0),@,1)})exten => std_greeting,n,While($[${STDGREET} < ${LEN(${MAILBOX})}])exten => std_greeting,n,Background(digits/${MAILBOX:${STDGREET}:1})exten => std_greeting,n,Set(STDGREET=$[${STDGREET} + 1])exten => std_greeting,n,EndWhile()exten => std_greeting,n,Background(vm-is${GREETING})exten => std_greeting,n,Exec(${IF($[${s_option} = 0]?Background(vm-intro):NoOp())})exten => std_greeting,n,Goto(record) ; don't play the intro file, just record; If we don't request a greeting type, then just play the introexten => std_greeting,n(no_greeting),Background(vm-intro)exten => std_greeting,n(record),GoSub(record_voicemail,s,1())exten => std_greeting,n,Return()[voicemailmain]; Drop in replacement for the dialplan application VoicemailMain(); Leif Madsen;--[Description] VoiceMailMain([mailbox][@context][|options]): This application allows thecalling party to check voicemail messages. A specific mailbox, and optionalcorresponding context, may be specified. If a mailbox is not provided, thecalling party will be prompted to enter one. If a context is not specified,the 'default' context will be used. Options: p - Consider the mailbox parameter as a prefix to the mailbox that is entered by the caller. g(#) - Use the specified amount of gain when recording a voicemail message. The units are whole-number decibels (dB). s - Skip checking the passcode for the mailbox. a(#) - Skip folder prompt and go directly to folder specified. Defaults to INBOX--;exten => s,1,NoOp()exten => s,n,GoSub(setup_mailbox,s,1(default,100,INBOX)exten => s,n,GoSub(get_msg_count,s,1())exten => s,n,Set(TOTAL_MSGS=${GOSUB_RETVAL})exten => s,n,GoSub(play_msg,s,1(0))exten => s,n,Playback(vm-goodbye)[read_voicemail_conf]; Purpose: To read the voicemail.conf file into AstDB; LIMITATION: The regex's in the SHELL() command will remove all lines that contain a comment (even if the start of the line is uncommented); That means you'll have to make sure your comments are on separate lines from the options you want activated.;; Leif Madsen;exten => s,1,NoOp()exten => s,n,DBdeltree(voicemail) ; clear out the current values and reload themexten => s,n,Set(LINE_NUMBER=1); While we have data, loop through the file, pulling it in one line at a timeexten => s,n,While($[${LEN(${SHELL(sed -e '/\\\;/d' /etc/asterisk/voicemail.conf \| grep '.' \| tr "\\\n" "~" \| cut -d \\\~ -f ${LINE_NUMBER})})} > 1]); Save the data to the VM_FILE_DATA channel variable (one line from voicemail.conf)exten => s,n,NoOp(${SET(VM_FILE_DATA=${SHELL(sed -e '/\\\;/d' /etc/asterisk/voicemail.conf \| grep '.' \| tr "\\\n" "~" \| cut -d \\\~ -f ${LINE_NUMBER})})}); If the line starts with an alphanumeric string (not a square brace), then goto the write_data label, otherwise, save the data to the SECTION_NAME channel varexten => s,n,Exec(${IF(${REGEX("^[a-z A-Z 0-9]" ${VM_FILE_DATA})}?Goto(write_data):Set(SECTION_NAME=${VM_FILE_DATA}))})exten => s,n,Goto(next_iteration) ; if we hit here, then we're going to go to the next iteration of the loop ; because we've already set the SECTION_NAME variable, which will be used ; as part of the family keyname in AstDB; Write the database to the AstDBexten => s,n(write_data),NoOp(); NOTE: We're using the SET() function because the Set() application will take commas and pipes as argument separators; In order to make it literally assign the whole string to the AstDB, we have to write it out with the SET() function; which does not have multiple arguments;; Split the data we're getting into 2 separate variables (left and right of the equals sign)exten => s,n,NoOp(${SET(VARNAME=${CUT(VM_FILE_DATA,=,1)})})exten => s,n,NoOp(${SET(VARVALUE=${CUT(VM_FILE_DATA,=,2)})}); Save the value to the AstDB because once we parse it again, Asterisk likes to forget the values for some reason...exten => s,n,NoOp(${SET(DB(voicemail/conf/${SECTION_NAME}/${VARNAME})=${VARVALUE})}); If the start of the VARVALUE variable contains a >, then we want to strip it out in the next priority. Otherwise, go to the next loop iterationexten => s,n,Exec(${IF($[${EXISTS(${CUT(VARVALUE,>,2)})}]?NoOp():Goto(next_iteration))}); If we make it here, then we've gotta strip the > from the beginning of the line and assign the remainder to the AstDBexten => s,n,NoOp(${SET(DB(voicemail/conf/${SECTION_NAME}/${VARNAME})=${CUT(VARVALUE,>,2)})})exten => s,n(next_iteration),Set(LINE_NUMBER=$[${LINE_NUMBER} + 1])exten => s,n,EndWhile()exten => s,n,Return()[get_msg_count]; get_msg_count: Get the number of messages in the current folder; Leif Madsen; exten => s,1,Verbose(1|Getting number of messages in the mailbox)exten => s,n,Set(MSG_COUNT=0000)exten => s,n,Verbose(2|DEBUG: $[${STAT(e,${PWD}/msg${MSG_COUNT}.txt)}])exten => s,n,Verbose(2|DEBUG: ${PWD})exten => s,n,While($[${STAT(e,${PWD}/msg${MSG_COUNT}.txt)}])exten => s,n,Verbose(1|Current message count: ${MSG_COUNT})exten => s,n,Set(MSG_COUNT=$[${MSG_COUNT} + 1])exten => s,n,GoSub(set_msg_number,s,1(${MSG_COUNT}))exten => s,n,Set(MSG_COUNT=${GOSUB_RETVAL})exten => s,n,EndWhile()exten => s,n,Return(${MSG_COUNT})[set_msg_number]; set_msg_number: Pad numbers with a LEN < 4 with zeros; Leif Madsen;exten => s,1,NoOp()exten => s,n,Set(MSGNUM=${IF($[${LEN(${ARG1})} = 1]?000:${IF($[${LEN(${ARG1})} = 2]?00:${IF($[${LEN(${ARG1})} = 3]?0)})})}${ARG1})exten => s,n,Return(${MSGNUM})[setup_mailbox]; setup_mailbox: Setup the path to a folder in a mailbox to work in; Leif Madsen;; ARG1: mailbox@context; ARG2: mailbox sub-folder (optional);exten => s,1,NoOp()exten => s,n,Set(VM_CONTEXT=${CUT(ARG1,@,2)})exten => s,n,Set(MAILBOX=${CUT(ARG1,@,1)})exten => s,n,Set(FOLDER=${ARG2})exten => s,n,Set(PWD=${VM_HOME}/${VM_CONTEXT}/${MAILBOX}${IF($[${EXISTS(${ARG2})}]?/${FOLDER})})exten => s,n,Return()[get_envelope]; get_envelope: Get the contents of an envelope an assign the contents to variables; Leif Madsen;; Returns and Sets: ; ; - origmailbox; - context; - macrocontext; - exten; - priority; - callerchan; - callerid; - origdate; - origtime; - category; - duration6;exten => s,1,NoOp()exten => s,n,Set(LINE_NUMBER=5)exten => s,n,While($[${LEN(${SHELL(tr "\\\n"\, "&" < ${PWD}/msg${ARG1}.txt \| cut -d \\\& -f ${LINE_NUMBER})})} > 1])exten => s,n,Set(DATA=${SHELL(tr "\\\n"\, "&" < ${PWD}/msg${ARG1}.txt \| cut -d \\\& -f ${LINE_NUMBER})})exten => s,n,Set(${CUT(DATA,=,1)}=${CUT(DATA,=,2)})exten => s,n,Set(LINE_NUMBER=$[${LINE_NUMBER} + 1])exten => s,n,EndWhile()exten => s,n,Return()[record_voicemail]; record_voicemail: Record audio and save to disk (saved to INBOX); Leif Madsenexten => s,1,NoOp(); LIMITATIONS: ; * Can't record to multiple formats; * Can't save the file if the user hangs up (have to press #); * Need duration of recording; exten => s,n,Record(/tmp/vm-${SIPCALLID}-%d.wav)exten => s,n,Set(REC_COUNT=0)exten => s,n,While($[${EXISTS(${HASH(MAILBOXES|${REC_COUNT})})}])exten => s,n,GoSub(create_envelope,s,1(${HASH(MAILBOXES|${REC_COUNT})})) ; by creating the envelope, we setup the PWD to copy the recording toexten => s,n,System(cp ${RECORDED_FILE}.wav ${PWD}/msg${MSGNUM}.wav)exten => s,n,Set(REC_COUNT=$[${REC_COUNT} + 1])exten => s,n,EndWhile()exten => s,n,Playback(auth-thankyou)exten => s,n,Return()[create_envelope]; create_envelope: Create the message envelope in the filesystem; Leif Madsen;; ARG1: mailbox@context;exten => s,1,NoOp()exten => s,n,GoSub(setup_mailbox,s,1(${ARG1},INBOX))exten => s,n,GoSub(get_msg_count,s,1())exten => s,n,Set(MSG_COUNT=${GOSUB_RETVAL})exten => s,n,GoSub(set_msg_number,s,1(${MSG_COUNT}))exten => s,n,Set(MSGNUM=${GOSUB_RETVAL})exten => s,n,System(touch ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "\;" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "\; Message Information file" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "\;" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "[message]" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "origmailbox=${MAILBOX}" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "context=INACCURATE" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "exten=INACCURATE" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "priority=INACCURATE" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "callerchan=INACCURATE" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "callerid=\\"${CALLERID(name)}\\" ${CALLERID(num)}" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "origdate=${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)}" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "origtime=${EPOCH}" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "category=" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,System(echo "duration=${ANSWEREDTIME}" >> ${PWD}/msg${MSGNUM}.txt)exten => s,n,Return()