MessageSend to all PJSIP contacts

Hello!
Does anyone know how can we send a message with MessageSend to all PJSIP extension with multiple contacts.
We’ve tried somethink like:
[from-internal-msg]
exten => s,1,NoOp(Sending message!)
same => n,NoOp(To ${MESSAGE(to)})
same => n,NoOp(From ${MESSAGE(from)})
same => n,NoOp(Body ${MESSAGE(body)})
same => n,Set(FROM=${CUT(MESSAGE(from),@,1)})
same => n,Set(FROMNO=${CUT(FROM,:,2)})
same => n,Set(ACTUALTO=${CUT(MESSAGE(to),@,1)})
same => n,Set(ACTUAL=${CUT(ACTUALTO,:,2)})
same => n,Set(dial=${DB(DEVICE/${ACTUAL}/dial)})
same => n,Set(tech=${TOLOWER(${CUT(dial,/,1)})})
same => n,Set(contacts=${PJSIP_AOR(100,contact)})
same => n,While($["${SET(contact=${SHIFT(contacts,)})}" != “”])
same => n,Set(contacturi=${PJSIP_CONTACT(${contact},uri)})
same => n,Set(sipuri=${CUT(contacturi,;,1)})
same => n,Set(uri=${CUT(sipuri,@,2)})
same => n,MessageSend(${tech}:${uri},sip:${FROMNO}@sip.example.org)
same => n,NoOp(Send status is ${MESSAGE_SEND_STATUS})
same => n,EndWhile
same => n,HangUp()

Thank you for your time and support!

That’s not really a use case I think anyone has tried to tackle, so I’m not sure if there is really a clean way to do that unfortunately…

I figured out how to do it for each contact and is working fine. It can be achieved like this:

[from-internal-msg]
exten => s,1,NoOp(Sending message!)
same => n,NoOp(To ${MESSAGE(to)})
same => n,NoOp(From ${MESSAGE(from)})
same => n,NoOp(Body ${MESSAGE(body)})
same => n,Set(from=${CUT(MESSAGE(from),@,1)})
same => n,Set(from_domain=${CUT(MESSAGE(from),@,2)})
same => n,Set(from_domain=${CUT(from_domain,;,1)})
same => n,Set(from_domain=${CUT(from_domain,>,1)})
same => n,Set(from_no=${CUT(from,:,2)})
same => n,Set(to=${CUT(MESSAGE(to),@,1)})
same => n,Set(to_no=${CUT(to,:,2)})
same => n,Set(dial=${DB(DEVICE/${to_no}/dial)})
same => n,Set(tech=${TOLOWER(${CUT(dial,/,1)})})
same => n,GotoIf($["${tech}" != “pjsip”]?sendmsg)
same => n,Set(contacts=${PJSIP_AOR(${to_no},contact)})
same => n,While($["${SET(contact=${SHIFT(contacts,)})}" != “”])
same => n,Set(contacturi=${PJSIP_CONTACT(${contact},uri)})
same => n,Set(sipuri=${CUT(contacturi,;,1)})
same => n,Set(to_no=${CUT(contacturi,@,2)})
same => n(sendmsg),MessageSend(${tech}:${to_no},sip:${from_no}@${from_domain})
same => n,NoOp(Send status is ${MESSAGE_SEND_STATUS})
same => n,GotoIf($[${EXISTS(${contacturi})}]?:end)
same => n,EndWhile
same => n(end),HangUp()

2 Likes

Hello fiftyz

I know it was a while since you posted this, butIi am very interested in the user-case that you outline here (send messages to all pjsip contacts).

I have tried your dialplan code and variants of it. I cannot get it to send messages to any contacts except the first one. So, I have this question:

Were you able to send messages to all pjsip contacts? If so, perhaps you can help me understand how that was achieved by provide examples of the actual “to” arguments of MessageSend()? For example:

  1. pjsip:endpoint
  2. pjsip:endpoint@uri.that.can.reach.a.contact.other.than.the.first.one

Love to hear from you.
Kind regards

Hello again fiftyz and all others who find this topic interesting.

With a bit of more debugging I also managed to get this working. :slight_smile:

As a courtesy to others I post my implementation here, which also include some explanation of how it works.

Kind regards

;================================ SUB_TEXT_USER ==
; Instant messaging subroutine for PJSIP
; Send message to all pjsip contacts of an endpoint
; Usage: Gosub(sub_text_user,${EXTEN},1(${HINT}))
; ${EXTEN} - Extension, eg 0735698294
; ${ARG1} - pjsip tech/endpoint: ${HINT}, eg PJSIP/myuser
;
; Implementation details
; PJSIP_DIAL_CONTACTS() return all contact URLs separated by an "&", eg: 
; PJSIP/myuser/sip:myuser@10.10.10.100:61863;option=value&PJSIP/myuser/sip:myuser@217.103.237.202:35678;option=value
; Within a While() loop, we cut this string at the "&" using ${SHIFT(contacts,&):6}. 
; The ":6" strips off the initial "PJSIP/".
; MessageSend() needs the URL to be slightly reformatted, eg
; pjsip:myuser/sip:myuser@10.10.10.100:61863;option=value
; so we simply prepend with "pjsip:"
; MessageSend() accepts but ignores any provided options
;
[sub_text_user]
exten = _X.,1,Verbose(2, "Sending message, To ${MESSAGE(to)}, Hint ${ARG1}, From ${MESSAGE(from)}, CID ${CALLERID}, Body ${MESSAGE(body)}")
 same = n,Set(LOCAL(endpoint)=${CUT(ARG1,/,2)})
 same = n,Set(LOCAL(contacts)=${PJSIP_DIAL_CONTACTS(${endpoint})})
 same = n,While($["${SET(contact=${SHIFT(contacts,&):6})}" != ""])
 same = n,MessageSend(pjsip:${contact},${MESSAGE(from)})
 same = n,Verbose(2, "Send status is ${MESSAGE_SEND_STATUS}")
 same = n,EndWhile
 same = n,HangUp()

Thanks for sharing, this are my two cents:

Dialplan context:

;---------- SMS -------------------------------------
[websms]
exten => _*.,1,Goto(from-internal,${EXTEN},1)

exten => _XXXX,1,Verbose(4, "SMS checking dialplan invoked")
 same = n,GosubIf($[${LEN(${MESSAGE(from)})} > 0]?sms)
 same = n,Verbose(4, "It is not SMS, going to Call ${EXTEN}")
 same = n,Goto(from-internal,${EXTEN},1)
 same = n,HangUp()
 same = n(sms),Macro(sms-user,)
 same = n,HangUp()
 
exten => h,1,Macro(hangupcall,)

[app-fakeanswer]
exten => _X.,1,NoCDR
 same = n,Verbose(4, "Sending message from queue")
 same = n,Set(ThisDevState=${DEVICE_STATE(PJSIP/${EXTEN})})
 same = n,GotoIf($[${ThisDevState}=UNAVAILABLE]?hang)
 same = n,GotoIf($[${ThisDevState}=UNKNOWN]?hang)
 same = n,GotoIf($[${ThisDevState}=INVALID]?hang)
 same = n,Answer()
 same = n,Hangup()
 same = n(hang),Hangup()

[macro-sms-user]
exten => s,1,Verbose(4, "Sending message, To ${MESSAGE(to)}, From ${MESSAGE(from)}}")
 same = n,Set(LOCAL(EndpointTo)=${CUT(MESSAGE(to),@,1)})
 same = n,Set(LOCAL(Tech)=${CUT(EndpointTo,:,1)})
 same = n,Set(LOCAL(EndpointTo)=${CUT(EndpointTo,:,2)})
 same = n,Verbose(4, "DEVICE_STATE: ${DEVICE_STATE(PJSIP/${EndpointTo})}")
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointTo})}=UNAVAILABLE]?sendfailedmsg)
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointTo})}=UNKNOWN]?sendfailedmsg)
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointTo})}=INVALID]?invalid)
 same = n,Verbose(4, "Sending now message, To ${ActualTo}, From ${MESSAGE(from)})
;
 same = n,Set(LOCAL(ContactsTo)=${PJSIP_DIAL_CONTACTS(${EndpointTo})})
 same = n,While($["${SET(ContactTo=${SHIFT(ContactsTo,&):6})}" != ""])
 same = n,Set(LOCAL(ActualTo)=${Tech}:${ContactTo})
 same = n,MessageSend(${ActualTo},${MESSAGE(from)})
 same = n,Verbose(4, "Send status is ${MESSAGE_SEND_STATUS}")
 same = n,EndWhile
 same = n,Hangup()
;
 same = n(sendfailedmsg),Macro(sms-failed,1)
 same = n,Hangup()
 same = n(invalid),Macro(sms-invalid,1)
 same = n,Hangup()
 
[macro-sms-failed]
exten => s,1,Verbose(4, "Sending error Unavailable to user, Original: To ${MESSAGE(to)}, From ${MESSAGE(from)}}")
 same = n,Set(LOCAL(Msg)=${MESSAGE(body)})
 same = n,Set(LOCAL(MsgTime)=${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)})
 same = n,Set(MESSAGE(body)=[${MsgTime}] Your message "${Msg}" to ${EndpointTo} has failed. Sending when extension becomes available again)
 same = n,Set(LOCAL(EndpointFrom)=${CUT(MESSAGE(from),<,2)})
 same = n,Set(LOCAL(EndpointFrom)=${CUT(EndpointFrom,@,1)})
 same = n,Set(LOCAL(EndpointFrom)=${CUT(EndpointFrom,:,2)})
 same = n,Verbose(4, "DEVICE_STATE: ${DEVICE_STATE(PJSIP/${EndpointFrom})}")
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointFrom})}=UNAVAILABLE]?hang)
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointFrom})}=UNKNOWN]?hang)
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointFrom})}=INVALID]?hang)
 same = n,Verbose(4, "Replying: To ${ReplyTo}, From ${ReplyFrom}")
;
 same = n,Set(LOCAL(ContactsFrom)=${PJSIP_DIAL_CONTACTS(${EndpointFrom})})
 same = n,While($["${SET(ContactFrom=${SHIFT(ContactsFrom,&):6})}" != ""])
 same = n,Set(LOCAL(ReplyTo)=${Tech}:${ContactFrom})
 same = n,Set(LOCAL(ReplyFrom)=<${MESSAGE(to):2}>)
 same = n,MessageSend(${ReplyTo},${ReplyFrom})
 same = n,Verbose(4, "Send status is ${MESSAGE_SEND_STATUS}")
 same = n,EndWhile
 same = n,GotoIf($["${INQUEUE}" != "1"]?startq)
 same = n,Hangup()
;
 same = n(startq),Macro(sms-queue,1)
 same = n,Hangup()
 same = n(hang),Hangup()

[macro-sms-queue]
exten => s,1,Verbose(4, "Queueing messaging for offline")
 same = n,SYSTEM(/var/lib/asterisk/agi-bin/astqueue.sh -SRC '${MESSAGE(from)}' -DST '${MESSAGE(to)}' -MSG '${Msg}' -TIME '${MsgTime}')
 same = n,Hangup()

[macro-sms-invalid]
exten => s,1,Verbose(4, "Sending error Invalid to user, Original: To ${MESSAGE(to)}, From ${MESSAGE(from)}}")
 same = n,Set(LOCAL(Msg)=${MESSAGE(body)})
 same = n,Set(LOCAL(MsgTime)=${STRFTIME(${EPOCH},,%d%m%Y-%H:%M:%S)})
 same = n,Set(MESSAGE(body)=[${MsgTime}] Your message "${Msg}" to ${EndpointTo} has failed. Extension is invalid or does no exist)
 same = n,Set(LOCAL(EndpointFrom)=${CUT(MESSAGE(from),<,2)})
 same = n,Set(LOCAL(EndpointFrom)=${CUT(EndpointFrom,@,1)})
 same = n,Set(LOCAL(EndpointFrom)=${CUT(EndpointFrom,:,2)})
 same = n,Verbose(4, "DEVICE_STATE: ${DEVICE_STATE(PJSIP/${EndpointFrom})}")
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointFrom})}=UNAVAILABLE]?hang)
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointFrom})}=UNKNOWN]?hang)
 same = n,GotoIf($[${DEVICE_STATE(${Tech}/${EndpointFrom})}=INVALID]?hang)
 same = n,Verbose(4, "Replying: To ${ReplyTo}, From ${ReplyFrom}")
;
 same = n,Set(LOCAL(ContactsFrom)=${PJSIP_DIAL_CONTACTS(${EndpointFrom})})
 same = n,While($["${SET(ContactFrom=${SHIFT(ContactsFrom,&):6})}" != ""])
 same = n,Set(LOCAL(ReplyTo)=${Tech}:${ContactFrom})
 same = n,Set(LOCAL(ReplyFrom)=<${MESSAGE(to):2}>)
 same = n,MessageSend(${ReplyTo},${ReplyFrom})
 same = n,Verbose(4, "Send status is ${MESSAGE_SEND_STATUS}")
 same = n,EndWhile
 same = n,Hangup()

Script astqueue.sh:

#!/bin/bash
#VARIABLES
maxretry=100
retryint=30
#
#CONSTANTS
ERRORCODE=0
d_unique=`date +%s`
d_friendly=`date +%T_%D`
astbin=`which asterisk`
myrandom=$[ ( $RANDOM % 1000 )  + 1 ]
#
function bail(){
    echo "SMS:[$ERRORCODE] $MSGOUT. Runtime:$d_friendly. UniqueCode:$d_unique"
    exit $ERRORCODE
}
while test -n "$1"; do
    case "$1" in
        -SRC)
            source="$2"
            shift
           ;;
        -DST)
            dest="$2"
            shift
           ;;
        -MSG)
            message="$2"
            shift
           ;;
        -TIME)
            originaltime="$2"
            shift
           ;;
esac
shift
done
#
#
if [[ "$source" == "" ]]; then
    echo "ERROR: No source. Quitting."
    ERRORCODE=1
    bail
fi
if [[ "$dest" == "" ]]; then
    echo "ERROR: No usable destination. Quitting."
    ERRORCODE=1
    bail
fi
if [[ "$message" == "" ]]; then
    echo "ERROR: No message specified.Quitting."
    ERRORCODE=1
    bail
fi
#
# generate call file
mydate=`date +%d%m%y`
logdate=`date`
#
# Check to see if extension exists
destexten=`echo $dest | cut -d @ -f1 | cut -d : -f2`
ifexist=`$astbin -rx "pjsip show endpoints" | grep $destexten | grep -c Endpoint`
if [[ "$ifexist" == "0" ]]; then
    echo "Destination extension don't exist, exiting.."
    ERRORCODE=1
    bail
fi
# If that conditions passes, then we will queue the message,
# Todo: write other conditions too to keep the sanity of the looping
filename="$destexten-$originaltime-$d_unique.$myrandom.call"
echo -e "Channel: Local/$destexten@app-fakeanswer
CallerID: $source
Maxretries: $maxretry
RetryTime: $retryint
Context: websms
Extension: $destexten
Priority: 1 
Set: MESSAGE(body)=$message
Set: MESSAGE(to)=$dest
Set: MESSAGE(from)=$source
Set: INQUEUE=1 "> /var/spool/asterisk/tmp/$filename
# move files
chown asterisk:asterisk /var/spool/asterisk/tmp/$filename
chmod 777 /var/spool/asterisk/tmp/$filename
sleep 3
mv /var/spool/asterisk/tmp/$filename /var/spool/asterisk/outgoing/
#
ERRORCODE=0
bail

Regards, and thanks again for the good 2 post.