numbers: any free internal number of 1 to 6 digits; ranges only suggest
Owner wanted the first user on 1. Numbering ranges now choose the Next free suggestion (Settings → Numbering), the setup wizard takes a number per person, and a one-digit number can't also be a menu key of a digital receptionist with direct dialing. Dialplan patterns that take an internal number match one digit (_X!); golden case short-numbers proves it in Asterisk.
This commit is contained in:
1 parent
4143f6d064
commit
aa6681da03
42 files changed
+932
-137
No files matched your search
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
+7
-7
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
+7
-7
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
; GENERATED BY SelfPhoneNG — DO NOT EDIT. Changes will be overwritten.
|
||||
[sp_admin_user]
|
||||
type = user
|
||||
admin = yes
|
||||
marked = yes
|
||||
music_on_hold_when_empty = yes
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
; GENERATED BY SelfPhoneNG — DO NOT EDIT. Changes will be overwritten.
|
||||
SP_DEFAULT_CID=
|
||||
SP_DEFAULT_E911_CID=
|
||||
SP_REC_ANNOUNCE=
|
||||
SP_CLASS_1=2
|
||||
SP_CLASS_12=2
|
||||
@@ -0,0 +1,321 @@
|
||||
; GENERATED BY SelfPhoneNG — DO NOT EDIT. Changes will be overwritten.
|
||||
; ─── Normalize a number. ARG1 = raw. Returns normalized in GOSUB_RETVAL. (matches 00 §8)
|
||||
[sp-sub-normalize]
|
||||
exten => s,1,Set(N=${FILTER(0123456789+,${ARG1})})
|
||||
same => n,ExecIf($["${N:0:2}" = "+1"]?Set(N=${N:1}))
|
||||
same => n,ExecIf($["${N:0:1}" = "+"]?Set(N=011${N:1}))
|
||||
same => n,ExecIf($[${LEN(${N})} = 10]?Set(N=1${N}))
|
||||
same => n,Return(${N})
|
||||
|
||||
; ─── Ring every registered device of a user. ARG1 = ext, ARG2 = seconds.
|
||||
; Sets DIALSTATUS. Rings desk/softphone contacts (PJSIP/101) and browser (PJSIP/101-web).
|
||||
[sp-sub-dial-user]
|
||||
exten => s,1,Set(DS=)
|
||||
same => n,Gosub(sp-sub-contacts,s,1(${ARG1}))
|
||||
same => n,Gosub(sp-sub-contacts,s,1(${ARG1}-web))
|
||||
same => n,GotoIf($["${DS}" = ""]?unavail)
|
||||
same => n,Dial(${DS},${ARG2},tx)
|
||||
same => n,Return()
|
||||
same => n(unavail),Set(DIALSTATUS=CHANUNAVAIL)
|
||||
same => n,Return()
|
||||
|
||||
; ─── Add AOR ARG1's reachable contacts to DS as PJSIP/<ARG1>/<uri>. Not PJSIP_DIAL_CONTACTS: iOS
|
||||
; Linphone's push params (pn-prid=…:voip&…:remote) hold '&', which splits Dial's device list.
|
||||
; pn-* only matter to a push server, so they're dropped; FILTER keeps whatever else a phone puts
|
||||
; in its Contact from reaching Dial's options (',') or another device ('&').
|
||||
[sp-sub-contacts]
|
||||
exten => s,1,Set(LOCAL(CL)=${PJSIP_AOR(${ARG1},contact)})
|
||||
same => n(next),Set(LOCAL(C)=${POP(CL)})
|
||||
same => n,GotoIf($["${C}" = ""]?done)
|
||||
same => n,Set(LOCAL(ST)=${PJSIP_CONTACT(${C},status)})
|
||||
same => n,GotoIf($["${ST}" = "Unavailable" | "${ST}" = "Unreachable"]?next)
|
||||
same => n,Set(LOCAL(U)=${PJSIP_CONTACT(${C},uri)})
|
||||
same => n,Set(LOCAL(D)=${CUT(U,\;,1)})
|
||||
same => n,Set(LOCAL(I)=1)
|
||||
same => n(param),Set(I=$[${I} + 1])
|
||||
same => n,GotoIf($[${I} > ${FIELDQTY(U,\;)}]?add)
|
||||
same => n,Set(LOCAL(P)=${CUT(U,\;,${I})})
|
||||
same => n,GotoIf($["${P:0:3}" = "pn-"]?param)
|
||||
same => n,Set(D=${D}\;${P})
|
||||
same => n,Goto(param)
|
||||
same => n(add),Set(DS=${DS}${IF($["${DS}" = ""]?:&)}PJSIP/${ARG1}/${FILTER(0-9a-zA-Z.:@\;=_+%[]\x2d,${D})})
|
||||
same => n,Goto(next)
|
||||
same => n(done),Return()
|
||||
|
||||
; ─── Call a user with all their personal rules.
|
||||
; ARG1 = ext, ARG2 = ring seconds, ARG3 = voicemail enabled (1/0), ARG4 = record mode
|
||||
[sp-sub-user]
|
||||
exten => s,1,Set(__SP_TARGET=${ARG1})
|
||||
same => n,ExecIf($["${SP_DIRECTION}" = "inbound" & ("${ARG4}" = "inbound" | "${ARG4}" = "all")]?Gosub(sp-sub-record,s,1))
|
||||
same => n,GotoIf($["${DB(SP/DND/${ARG1})}" = "1"]?busy)
|
||||
same => n,Set(FWD=${DB(SP/CFU/${ARG1})})
|
||||
same => n,GotoIf($["${FWD}" != ""]?forward)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${ARG1},${ARG2}))
|
||||
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?done)
|
||||
same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy)
|
||||
same => n(noanswer),Set(FWD=${DB(SP/CFNA/${ARG1})})
|
||||
same => n,GotoIf($["${FWD}" != ""]?forward)
|
||||
same => n,GotoIf($["${ARG3}" = "1"]?vmu)
|
||||
same => n,Return()
|
||||
same => n(vmu),VoiceMail(${ARG1}@default,u)
|
||||
same => n,Return()
|
||||
same => n(busy),Set(FWD=${DB(SP/CFB/${ARG1})})
|
||||
same => n,GotoIf($["${FWD}" != ""]?forward)
|
||||
same => n,GotoIf($["${ARG3}" = "1"]?vmb)
|
||||
same => n,Busy(5)
|
||||
same => n,Return()
|
||||
same => n(vmb),VoiceMail(${ARG1}@default,b)
|
||||
same => n,Return()
|
||||
same => n(forward),Gosub(sp-sub-forward,s,1(${FWD},${ARG1}))
|
||||
same => n(done),Return()
|
||||
|
||||
; ─── Forward a call. ARG1 = target number, ARG2 = ext whose forward rule this is.
|
||||
; Stops after 3 hops so 101→102→101 can't loop forever.
|
||||
[sp-sub-forward]
|
||||
exten => s,1,Set(__SP_FWDHOPS=$[${IF($["${SP_FWDHOPS}" = ""]?0:${SP_FWDHOPS})} + 1])
|
||||
same => n,GotoIf($[${SP_FWDHOPS} > 3]?stop)
|
||||
same => n,GotoIf($[${DIALPLAN_EXISTS(sp-local-numbers,${ARG1},1)}]?internal)
|
||||
same => n,Set(SP_CLASS=${GLOBAL(SP_CLASS_${ARG2})})
|
||||
same => n,Set(SP_LIMITEXT=${ARG2}) ; counts toward that extension's call limit
|
||||
same => n,Set(__SP_FORWARDED=1)
|
||||
same => n,Goto(sp-outbound-routes,${ARG1},1)
|
||||
same => n(internal),Goto(sp-local-numbers,${ARG1},1)
|
||||
same => n(stop),Return()
|
||||
|
||||
; ─── Start recording this call once. File: YYYY/MM/DD/<uniqueid>.wav under the monitor dir.
|
||||
[sp-sub-record]
|
||||
exten => s,1,GotoIf($["${SP_REC}" != ""]?done)
|
||||
same => n,Set(__SP_REC=${STRFTIME(${EPOCH},,%Y/%m/%d)}/${UNIQUEID}.wav)
|
||||
same => n,Set(CDR(SP_REC)=${SP_REC}) ; cdr_manager maps CDR variables only
|
||||
same => n,MixMonitor(${SP_REC},b)
|
||||
same => n(done),Return()
|
||||
|
||||
; ─── Auto-answer headers for paging/intercom (Yealink, Grandstream, Polycom, Snom)
|
||||
[sp-sub-autoanswer]
|
||||
exten => s,1,Set(PJSIP_HEADER(add,Call-Info)=<sip:${SP_EXT}>\;answer-after=0)
|
||||
same => n,Set(PJSIP_HEADER(add,Alert-Info)=info=alert-autoanswer)
|
||||
same => n,Return()
|
||||
|
||||
; ─── 911 alert ring-out. Originated by the api (04 §5.3): Channel PJSIP/<notify ext>,
|
||||
; Exten = the extension that dialed 911.
|
||||
[sp-emergency-alert]
|
||||
exten => _X!,1,Answer() ; X! also matches "0" (caller unknown)
|
||||
same => n,Wait(1)
|
||||
same => n,Playback(beep&beep&beep)
|
||||
same => n,Playback(extension)
|
||||
same => n,SayDigits(${EXTEN})
|
||||
same => n,Playback(beep&beep&beep)
|
||||
same => n,Playback(extension)
|
||||
same => n,SayDigits(${EXTEN})
|
||||
same => n,Hangup()
|
||||
|
||||
; ─── Voicemail logins (*97, *98) from a phone: one at a time and 10 an hour, 3 PIN tries each
|
||||
; (voicemail.conf maxlogins), so a phone can't run through the PINs (08 §6). Returns "ok".
|
||||
[sp-sub-vmlimit]
|
||||
exten => s,1,Set(GROUP(spvm)=${SP_EXT})
|
||||
same => n,GotoIf($[${GROUP_COUNT(${SP_EXT}@spvm)} > 1]?no)
|
||||
same => n,Set(LOCAL(H)=${STRFTIME(${EPOCH},UTC,%Y%m%d%H)})
|
||||
same => n,Set(LOCAL(V)=${DB(SP/VMLOGIN/${SP_EXT})})
|
||||
same => n,Set(LOCAL(N)=${IF($["${CUT(V,:,1)}" = "${H}"]?${CUT(V,:,2)}:0)})
|
||||
same => n,GotoIf($[${N} >= 10]?no)
|
||||
same => n,Set(DB(SP/VMLOGIN/${SP_EXT})=${H}:$[${N} + 1])
|
||||
same => n,Return(ok)
|
||||
same => n(no),Return()
|
||||
|
||||
; ─── Feature codes (03 §6.13).
|
||||
[sp-featurecodes]
|
||||
exten => *97,1,Answer() ; my voicemail
|
||||
same => n,Gosub(sp-sub-vmlimit,s,1)
|
||||
same => n,GotoIf($["${GOSUB_RETVAL}" != "ok"]?full)
|
||||
same => n,VoiceMailMain(${SP_EXT}@default)
|
||||
same => n,Hangup()
|
||||
same => n(full),Playback(an-error-has-occurred)
|
||||
same => n,Hangup()
|
||||
exten => *98,1,Answer() ; any voicemail (asks mailbox)
|
||||
same => n,Gosub(sp-sub-vmlimit,s,1)
|
||||
same => n,GotoIf($["${GOSUB_RETVAL}" != "ok"]?full)
|
||||
same => n,VoiceMailMain(@default)
|
||||
same => n,Hangup()
|
||||
same => n(full),Playback(an-error-has-occurred)
|
||||
same => n,Hangup()
|
||||
exten => *78,1,Answer() ; DND on
|
||||
same => n,Set(DB(SP/DND/${SP_EXT})=1)
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 1)
|
||||
same => n,Playback(do-not-disturb&activated)
|
||||
same => n,Hangup()
|
||||
exten => *79,1,Answer() ; DND off
|
||||
same => n,Set(X=${DB_DELETE(SP/DND/${SP_EXT})})
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
same => n,Playback(call-fwd-unconditional&activated)
|
||||
same => n,Hangup()
|
||||
exten => *73,1,Answer() ; cancel forward always
|
||||
same => n,Set(X=${DB_DELETE(SP/CFU/${SP_EXT})})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
; line covers calls that reach *8 through the dialplan. Uses named_pickup_group.
|
||||
exten => *8,1,Pickup()
|
||||
same => n,Hangup()
|
||||
exten => *43,1,Answer() ; echo test
|
||||
same => n,Playback(demo-echotest)
|
||||
same => n,Echo()
|
||||
same => n,Playback(demo-echodone)
|
||||
same => n,Hangup()
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
same => n,PauseQueueMember(${Q},Local/${SP_EXT}@sp-queue-member/n)
|
||||
same => n,GotoIf($["${PQMSTATUS}" != "PAUSED"]?deny) ; not an agent of that queue
|
||||
same => n,Set(DB(${K})=1)
|
||||
same => n,UserEvent(SpToggle,Key: QPAUSE,Queue: ${Q},Ext: ${SP_EXT},Value: 1)
|
||||
same => n,Playback(agent-loggedoff)
|
||||
same => n,Hangup()
|
||||
same => n(login),Set(X=${DB_DELETE(${K})})
|
||||
same => n,UnpauseQueueMember(${Q},Local/${SP_EXT}@sp-queue-member/n)
|
||||
same => n,GotoIf($["${UPQMSTATUS}" != "UNPAUSED"]?deny)
|
||||
same => n,UserEvent(SpToggle,Key: QPAUSE,Queue: ${Q},Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(agent-loginok)
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
same => n,Hangup()
|
||||
same => n(deny),Answer()
|
||||
same => n,Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
; time condition overrides *31N open, *32N closed, *33N auto (admins only)
|
||||
exten => _*3[123]X,1,Answer()
|
||||
same => n,GotoIf($["${GLOBAL(SP_TCADMIN_${SP_EXT})}" != "1"]?deny)
|
||||
same => n,Goto(sp-tc-override,${EXTEN},1)
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-ext-forward]
|
||||
exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external destination
|
||||
same => n,Set(__SP_FORWARDED=1)
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
include => sp-emergency-dial
|
||||
include => sp-featurecodes
|
||||
include => sp-local-numbers
|
||||
include => sp-outbound-routes
|
||||
|
||||
[sp-local-numbers]
|
||||
include => sp-parkedcalls
|
||||
|
||||
; users (sorted by number)
|
||||
exten => 1,1,Gosub(sp-sub-user,s,1(1,25,1,never))
|
||||
same => n,Hangup()
|
||||
exten => 1,hint,PJSIP/1&PJSIP/1-web
|
||||
exten => 12,1,Gosub(sp-sub-user,s,1(12,25,1,never))
|
||||
same => n,Hangup()
|
||||
exten => 12,hint,PJSIP/12&PJSIP/12-web
|
||||
|
||||
; ring groups
|
||||
exten => 5,1,Goto(sp-rg-22222222-2222-4222-8222-222222222005,s,1)
|
||||
|
||||
; queues
|
||||
exten => 6,1,Goto(sp-queue-44444444-4444-4444-8444-444444444006,s,1)
|
||||
|
||||
; digital receptionists
|
||||
exten => 0,1,Goto(sp-ivr-33333333-3333-4333-8333-333333333000,s,1)
|
||||
|
||||
; ─── Ring group 5 "Sales" (ringall)
|
||||
[sp-rg-22222222-2222-4222-8222-222222222005]
|
||||
exten => s,1,Set(__SP_TARGET=5)
|
||||
same => n,Dial(Local/1@sp-rg-member&Local/12@sp-rg-member,25,t)
|
||||
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?done)
|
||||
same => n,Goto(sp-vm-direct,1,1)
|
||||
same => n(done),Hangup()
|
||||
|
||||
; ─── Queue 6 "Support"
|
||||
[sp-queue-44444444-4444-4444-8444-444444444006]
|
||||
exten => s,1,Set(__SP_TARGET=6)
|
||||
same => n,Answer()
|
||||
same => n,Queue(6,t,,,300)
|
||||
same => n,GotoIf($["${QUEUESTATUS}" = "TIMEOUT" | "${QUEUESTATUS}" = "FULL" | "${QUEUESTATUS}" = "JOINEMPTY" | "${QUEUESTATUS}" = "LEAVEEMPTY" | "${QUEUESTATUS}" = "JOINUNAVAIL" | "${QUEUESTATUS}" = "LEAVEUNAVAIL"]?fail)
|
||||
same => n,Hangup()
|
||||
same => n(fail),UserEvent(SpQueueExit,Queue: 6,Status: ${QUEUESTATUS})
|
||||
same => n,Hangup()
|
||||
|
||||
; ─── Digital receptionist 0 "Main menu"
|
||||
[sp-ivr-33333333-3333-4333-8333-333333333000]
|
||||
include => sp-ivr-direct
|
||||
exten => s,1,Answer()
|
||||
same => n,Set(SP_IVR_TRIES=0)
|
||||
same => n(menu),Set(TIMEOUT(digit)=3)
|
||||
same => n,Background(custom/44444444-4444-4444-8444-444444444444)
|
||||
same => n,WaitExten(5)
|
||||
exten => 2,1,Goto(sp-rg-22222222-2222-4222-8222-222222222005,s,1)
|
||||
exten => i,1,Set(SP_IVR_TRIES=$[${SP_IVR_TRIES} + 1])
|
||||
same => n,GotoIf($[${SP_IVR_TRIES} >= 3]?giveup)
|
||||
same => n,Playback(pbx-invalid)
|
||||
same => n,Goto(s,menu)
|
||||
same => n(giveup),Hangup()
|
||||
exten => t,1,Set(SP_IVR_TRIES=$[${SP_IVR_TRIES} + 1])
|
||||
same => n,GotoIf($[${SP_IVR_TRIES} >= 3]?giveup)
|
||||
same => n,Goto(s,menu)
|
||||
same => n(giveup),Hangup()
|
||||
|
||||
[sp-ivr-direct]
|
||||
exten => 0,1,Goto(sp-local-numbers,0,1)
|
||||
exten => 1,1,Goto(sp-local-numbers,1,1)
|
||||
exten => 5,1,Goto(sp-local-numbers,5,1)
|
||||
exten => 6,1,Goto(sp-local-numbers,6,1)
|
||||
exten => 12,1,Goto(sp-local-numbers,12,1)
|
||||
|
||||
; ─── Office hours overrides (admins only, see sp-featurecodes)
|
||||
[sp-tc-override]
|
||||
exten => _*3[123]X,1,Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-inbound]
|
||||
; anything else
|
||||
exten => _[0-9a-z].,1,Hangup()
|
||||
|
||||
[sp-outbound-routes]
|
||||
exten => pending,1,Hangup() ; no routes yet
|
||||
|
||||
[sp-emergency-dial]
|
||||
exten => 911,1,Goto(sp-emergency,s,1)
|
||||
exten => 9911,1,Goto(sp-emergency,s,1) ; people who dial 9 first out of habit
|
||||
|
||||
[sp-emergency]
|
||||
exten => s,1,Set(CID=${IF($["${SP_E911CID}" != ""]?${SP_E911CID}:${SP_DEFAULT_E911_CID})})
|
||||
same => n,UserEvent(SpEmergency,Ext: ${SP_EXT},CallerId: ${CID},Dialed: 911)
|
||||
same => n,ExecIf($["${CID}" != ""]?Set(CALLERID(num)=${CID}))
|
||||
same => n,Set(__SP_DIRECTION=outbound)
|
||||
same => n,Set(CDR(SP_DIRECTION)=outbound)
|
||||
same => n,Hangup()
|
||||
@@ -0,0 +1 @@
|
||||
; GENERATED BY SelfPhoneNG — DO NOT EDIT. Changes will be overwritten.
|
||||
@@ -0,0 +1,173 @@
|
||||
; GENERATED BY SelfPhoneNG — DO NOT EDIT. Changes will be overwritten.
|
||||
[global]
|
||||
type = global
|
||||
user_agent = SelfPhoneNG
|
||||
endpoint_identifier_order = ip,username,anonymous
|
||||
keep_alive_interval = 90
|
||||
max_initial_qualify_time = 4
|
||||
|
||||
[system]
|
||||
type = system
|
||||
timer_t1 = 500
|
||||
timer_b = 32000
|
||||
|
||||
[transport-udp]
|
||||
type = transport
|
||||
protocol = udp
|
||||
bind = 0.0.0.0:5060
|
||||
|
||||
[transport-tcp]
|
||||
type = transport
|
||||
protocol = tcp
|
||||
bind = 0.0.0.0:5060
|
||||
|
||||
[transport-ws]
|
||||
type = transport
|
||||
protocol = ws
|
||||
bind = 0.0.0.0
|
||||
|
||||
; ── extension 1 (Dan Doe) ──
|
||||
[1]
|
||||
type = endpoint
|
||||
context = sp-from-ext
|
||||
disallow = all
|
||||
allow = g722,ulaw,alaw
|
||||
auth = 1-auth
|
||||
aors = 1
|
||||
direct_media = no
|
||||
rtp_symmetric = yes
|
||||
force_rport = yes
|
||||
rewrite_contact = yes
|
||||
send_pai = yes
|
||||
callerid = "Dan Doe" <1>
|
||||
mailboxes = 1@default
|
||||
mwi_subscribe_replaces_unsolicited = yes
|
||||
dtmf_mode = rfc4733
|
||||
allow_subscribe = yes
|
||||
language = en
|
||||
moh_suggest = default
|
||||
tone_zone = us
|
||||
set_var = SP_EXT=1
|
||||
set_var = SP_CLASS=2
|
||||
set_var = SP_RECMODE=never
|
||||
set_var = __SP_DIRECTION=internal
|
||||
|
||||
[1-auth]
|
||||
type = auth
|
||||
auth_type = userpass
|
||||
username = 1
|
||||
password = Ab3dEf9hIj2kLm4nOp6qRs8t
|
||||
|
||||
[1]
|
||||
type = aor
|
||||
max_contacts = 5
|
||||
remove_existing = yes
|
||||
qualify_frequency = 30
|
||||
mailboxes = 1@default
|
||||
|
||||
[1-web]
|
||||
type = endpoint
|
||||
context = sp-from-ext
|
||||
webrtc = yes
|
||||
disallow = all
|
||||
allow = g722,ulaw
|
||||
auth = 1-web-auth
|
||||
aors = 1-web
|
||||
from_domain = selfphoneng.invalid
|
||||
callerid = "Dan Doe" <1>
|
||||
mailboxes = 1@default
|
||||
mwi_subscribe_replaces_unsolicited = yes
|
||||
dtmf_mode = rfc4733
|
||||
allow_subscribe = yes
|
||||
language = en
|
||||
moh_suggest = default
|
||||
tone_zone = us
|
||||
set_var = SP_EXT=1
|
||||
set_var = SP_CLASS=2
|
||||
set_var = SP_RECMODE=never
|
||||
set_var = __SP_DIRECTION=internal
|
||||
|
||||
[1-web-auth]
|
||||
type = auth
|
||||
auth_type = userpass
|
||||
username = 1-web
|
||||
password = Ab3dEf9hIj2kLm4nOp6qRs8t
|
||||
|
||||
[1-web]
|
||||
type = aor
|
||||
max_contacts = 3
|
||||
remove_existing = yes
|
||||
qualify_frequency = 0
|
||||
|
||||
; ── extension 12 (Sue Smith) ──
|
||||
[12]
|
||||
type = endpoint
|
||||
context = sp-from-ext
|
||||
disallow = all
|
||||
allow = g722,ulaw,alaw
|
||||
auth = 12-auth
|
||||
aors = 12
|
||||
direct_media = no
|
||||
rtp_symmetric = yes
|
||||
force_rport = yes
|
||||
rewrite_contact = yes
|
||||
send_pai = yes
|
||||
callerid = "Sue Smith" <12>
|
||||
mailboxes = 12@default
|
||||
mwi_subscribe_replaces_unsolicited = yes
|
||||
dtmf_mode = rfc4733
|
||||
allow_subscribe = yes
|
||||
language = en
|
||||
moh_suggest = default
|
||||
tone_zone = us
|
||||
set_var = SP_EXT=12
|
||||
set_var = SP_CLASS=2
|
||||
set_var = SP_RECMODE=never
|
||||
set_var = __SP_DIRECTION=internal
|
||||
|
||||
[12-auth]
|
||||
type = auth
|
||||
auth_type = userpass
|
||||
username = 12
|
||||
password = Bb3dEf9hIj2kLm4nOp6qRs8t
|
||||
|
||||
[12]
|
||||
type = aor
|
||||
max_contacts = 5
|
||||
remove_existing = yes
|
||||
qualify_frequency = 30
|
||||
mailboxes = 12@default
|
||||
|
||||
[12-web]
|
||||
type = endpoint
|
||||
context = sp-from-ext
|
||||
webrtc = yes
|
||||
disallow = all
|
||||
allow = g722,ulaw
|
||||
auth = 12-web-auth
|
||||
aors = 12-web
|
||||
from_domain = selfphoneng.invalid
|
||||
callerid = "Sue Smith" <12>
|
||||
mailboxes = 12@default
|
||||
mwi_subscribe_replaces_unsolicited = yes
|
||||
dtmf_mode = rfc4733
|
||||
allow_subscribe = yes
|
||||
language = en
|
||||
moh_suggest = default
|
||||
tone_zone = us
|
||||
set_var = SP_EXT=12
|
||||
set_var = SP_CLASS=2
|
||||
set_var = SP_RECMODE=never
|
||||
set_var = __SP_DIRECTION=internal
|
||||
|
||||
[12-web-auth]
|
||||
type = auth
|
||||
auth_type = userpass
|
||||
username = 12-web
|
||||
password = Bb3dEf9hIj2kLm4nOp6qRs8t
|
||||
|
||||
[12-web]
|
||||
type = aor
|
||||
max_contacts = 3
|
||||
remove_existing = yes
|
||||
qualify_frequency = 0
|
||||
@@ -0,0 +1,16 @@
|
||||
; GENERATED BY SelfPhoneNG — DO NOT EDIT. Changes will be overwritten.
|
||||
; ─── Queue 6 "Support"
|
||||
[6]
|
||||
strategy = rrmemory
|
||||
timeout = 15
|
||||
retry = 5
|
||||
wrapuptime = 10
|
||||
maxlen = 0
|
||||
musicclass = default
|
||||
announce-frequency = 60
|
||||
announce-position = yes
|
||||
ringinuse = no
|
||||
setinterfacevar = yes
|
||||
joinempty = paused,unavailable,invalid,unknown
|
||||
leavewhenempty = paused,unavailable,invalid,unknown
|
||||
member => Local/1@sp-queue-member/n,0,Dan Doe,hint:1@sp-local-numbers
|
||||
@@ -0,0 +1,4 @@
|
||||
; GENERATED BY SelfPhoneNG — DO NOT EDIT. Changes will be overwritten.
|
||||
rtpstart = 10000
|
||||
rtpend = 20000
|
||||
icesupport = yes
|
||||
@@ -0,0 +1,28 @@
|
||||
; GENERATED BY SelfPhoneNG — DO NOT EDIT. Changes will be overwritten.
|
||||
[general]
|
||||
format = wav
|
||||
attach = no ; the api sends emails itself
|
||||
maxmsg = 100
|
||||
maxsecs = 300
|
||||
minsecs = 1
|
||||
maxsilence = 10
|
||||
silencethreshold = 128
|
||||
maxlogins = 3
|
||||
minpassword = 4
|
||||
; PINs changed from the phone pass the api's rules first (asterisk/sp-vm-pincheck); the api
|
||||
; stores them from VoicemailPasswordChange (services/vmpin.ts).
|
||||
externpasscheck = /usr/local/bin/sp-vm-pincheck
|
||||
envelope = yes
|
||||
saycid = yes
|
||||
review = no
|
||||
nextaftercmd = yes
|
||||
sendvoicemail = no
|
||||
pollmailboxes = yes
|
||||
pollfreq = 30
|
||||
|
||||
[zonemessages]
|
||||
local = America/Chicago|'vm-received' Q 'digits/at' IMp
|
||||
|
||||
[default]
|
||||
1 => 4826,Dan Doe,,,tz=local|attach=no
|
||||
12 => 4826,Sue Smith,,,tz=local|attach=no
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"extensions": [
|
||||
{
|
||||
"id": "00000000-0000-4000-8000-000000000001",
|
||||
"number": "1",
|
||||
"name": "Dan Doe",
|
||||
"voicemailEnabled": true,
|
||||
"sipPassword": "Ab3dEf9hIj2kLm4nOp6qRs8t"
|
||||
},
|
||||
{
|
||||
"id": "00000000-0000-4000-8000-000000000012",
|
||||
"number": "12",
|
||||
"name": "Sue Smith",
|
||||
"sipPassword": "Bb3dEf9hIj2kLm4nOp6qRs8t"
|
||||
}
|
||||
],
|
||||
"ringGroups": [
|
||||
{
|
||||
"id": "22222222-2222-4222-8222-222222222005",
|
||||
"number": "5",
|
||||
"name": "Sales",
|
||||
"members": ["1", "12"],
|
||||
"noAnswerDestination": { "type": "voicemail", "id": "00000000-0000-4000-8000-000000000001" }
|
||||
}
|
||||
],
|
||||
"queues": [
|
||||
{
|
||||
"id": "44444444-4444-4444-8444-444444444006",
|
||||
"number": "6",
|
||||
"name": "Support",
|
||||
"members": [{ "number": "1", "name": "Dan Doe", "penalty": 0 }]
|
||||
}
|
||||
],
|
||||
"ivrs": [
|
||||
{
|
||||
"id": "33333333-3333-4333-8333-333333333000",
|
||||
"number": "0",
|
||||
"name": "Main menu",
|
||||
"greeting": "custom/44444444-4444-4444-8444-444444444444",
|
||||
"allowDirectDial": true,
|
||||
"options": [
|
||||
{
|
||||
"digit": "2",
|
||||
"destination": { "type": "ring_group", "id": "22222222-2222-4222-8222-222222222005" }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"asteriskMatches": [
|
||||
["1", "sp-local-numbers"],
|
||||
["5", "sp-local-numbers"],
|
||||
["0", "sp-local-numbers"],
|
||||
["1", "sp-rg-member"],
|
||||
["1", "sp-queue-member"],
|
||||
["1", "sp-vm-direct"],
|
||||
["1", "sp-ivr-direct"],
|
||||
["**1", "sp-featurecodes"],
|
||||
["*456", "sp-featurecodes"],
|
||||
["*721", "sp-featurecodes"],
|
||||
["*801", "sp-featurecodes"]
|
||||
]
|
||||
}
|
||||
+7
-7
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
+7
-7
@@ -148,7 +148,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: ${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/${SP_EXT})=${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: ${GOSUB_RETVAL})
|
||||
@@ -159,7 +159,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: ${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=${FILTER(0-9,${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/${T}-&PJSIP/${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -174,7 +174,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=${FILTER(0-9,${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/${Q}/${SP_EXT})
|
||||
same => n,GotoIf($["${DB(${K})}" = "1"]?login)
|
||||
@@ -192,7 +192,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=${FILTER(0-9,${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["${T}" != "${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["${PJSIP_ENDPOINT(${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -208,7 +208,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -218,12 +218,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["${DB(SP/DND/${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -151,7 +151,7 @@ exten => *79,1,Answer() ; DND off
|
||||
same => n,UserEvent(SpToggle,Key: DND,Ext: \${SP_EXT},Value: 0)
|
||||
same => n,Playback(do-not-disturb&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _*72X.,1,Answer() ; *72<number> forward always
|
||||
exten => _*72X!,1,Answer() ; *72<number> forward always
|
||||
same => n,Gosub(sp-sub-normalize,s,1(\${EXTEN:3}))
|
||||
same => n,Set(DB(SP/CFU/\${SP_EXT})=\${GOSUB_RETVAL})
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: \${SP_EXT},Value: \${GOSUB_RETVAL})
|
||||
@@ -162,7 +162,7 @@ exten => *73,1,Answer() ; cancel forward alw
|
||||
same => n,UserEvent(SpToggle,Key: CFU,Ext: \${SP_EXT},Value: )
|
||||
same => n,Playback(call-fwd-unconditional&de-activated)
|
||||
same => n,Hangup()
|
||||
exten => _**X.,1,Set(T=\${FILTER(0-9,\${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
exten => _**X!,1,Set(T=\${FILTER(0-9,\${EXTEN:2})}) ; digits only: "&" or "," would add arguments
|
||||
same => n,PickupChan(PJSIP/\${T}-&PJSIP/\${T}-web-) ; directed pickup **101
|
||||
same => n,Hangup()
|
||||
; *8 group pickup: features.conf pickupexten catches it straight from a phone's INVITE; this
|
||||
@@ -177,7 +177,7 @@ exten => *43,1,Answer() ; echo test
|
||||
exten => *65,1,Answer() ; say my extension
|
||||
same => n,SayDigits(\${SP_EXT})
|
||||
same => n,Hangup()
|
||||
exten => _*45X.,1,Answer() ; *45<queue> toggle queue login
|
||||
exten => _*45X!,1,Answer() ; *45<queue> toggle queue login
|
||||
same => n,Set(Q=\${FILTER(0-9,\${EXTEN:3})})
|
||||
same => n,Set(K=SP/QPAUSE/\${Q}/\${SP_EXT})
|
||||
same => n,GotoIf($["\${DB(\${K})}" = "1"]?login)
|
||||
@@ -195,7 +195,7 @@ exten => _*45X.,1,Answer() ; *45<queue> toggle
|
||||
same => n,Hangup()
|
||||
same => n(deny),Playback(ss-noservice)
|
||||
same => n,Hangup()
|
||||
exten => _*80X.,1,Set(T=\${FILTER(0-9,\${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
exten => _*80X!,1,Set(T=\${FILTER(0-9,\${EXTEN:3})}) ; *80<ext> intercom, both ways
|
||||
same => n,GotoIf($["\${T}" != "\${EXTEN:3}"]?deny)
|
||||
same => n,GotoIf($["\${PJSIP_ENDPOINT(\${T},context)}" != "sp-from-ext"]?deny) ; desk phones only
|
||||
same => n,Page(PJSIP/\${T},db(sp-sub-autoanswer^s^1),30)
|
||||
@@ -211,7 +211,7 @@ exten => _*3[123]X,1,Answer()
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-vm-direct]
|
||||
exten => _X.,1,Answer()
|
||||
exten => _X!,1,Answer()
|
||||
same => n,VoiceMail(\${EXTEN}@default,u)
|
||||
same => n,Hangup()
|
||||
|
||||
@@ -221,12 +221,12 @@ exten => _[0-9*#+].,1,Set(SP_CLASS=3) ; admin-configured external desti
|
||||
same => n,Goto(sp-outbound-routes,\${EXTEN},1)
|
||||
|
||||
[sp-rg-member] ; one member of a ring group (Local channel)
|
||||
exten => _X.,1,GotoIf($["\${DB(SP/DND/\${EXTEN})}" = "1"]?skip)
|
||||
exten => _X!,1,GotoIf($["\${DB(SP/DND/\${EXTEN})}" = "1"]?skip)
|
||||
same => n,Gosub(sp-sub-dial-user,s,1(\${EXTEN},600))
|
||||
same => n(skip),Hangup()
|
||||
|
||||
[sp-queue-member] ; one queue agent (Local channel)
|
||||
exten => _X.,1,Gosub(sp-sub-dial-user,s,1(\${EXTEN},600))
|
||||
exten => _X!,1,Gosub(sp-sub-dial-user,s,1(\${EXTEN},600))
|
||||
same => n,Hangup()
|
||||
|
||||
[sp-from-ext]
|
||||
|
||||
@@ -37,7 +37,7 @@ function contactStatus(s: string | undefined): ContactLive['status'] {
|
||||
|
||||
/** "101" and "101-web" both belong to extension 101; trunks and others don't match. */
|
||||
export const extOfEndpoint = (endpoint: string): string | null =>
|
||||
/^(\d{3,6})(?:-web)?$/.exec(endpoint)?.[1] ?? null;
|
||||
/^(\d{1,6})(?:-web)?$/.exec(endpoint)?.[1] ?? null;
|
||||
|
||||
/** "trunk-<uuid>", "trunk-<uuid>-reg" → the trunk id. */
|
||||
export const trunkOfObject = (name: string): string | null =>
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
describeDestination,
|
||||
findReferences,
|
||||
} from '../services/destinations.ts';
|
||||
import { assertNumberAvailable } from '../services/numbering.ts';
|
||||
import { assertNumberAvailable, ownerOf } from '../services/numbering.ts';
|
||||
|
||||
type Row = typeof ivrs.$inferSelect;
|
||||
type Tx = Parameters<Parameters<Db['transaction']>[0]>[0];
|
||||
@@ -99,9 +99,26 @@ export async function ivrRoutes(app: FastifyInstance) {
|
||||
|
||||
app.get('/api/ivrs/:id', async (req) => toDto(db, await getIvr(IdParams.parse(req.params).id)));
|
||||
|
||||
/** With direct dialing on, a menu key can't also be a one-digit number: pressing it picks the option. */
|
||||
async function assertMenuKeys(directDial: boolean, keys: string[]) {
|
||||
if (!directDial) return;
|
||||
for (const key of keys) {
|
||||
const owner = /^\d$/.test(key) ? await ownerOf(db, key) : null;
|
||||
if (owner)
|
||||
throw conflict(
|
||||
`Key ${key} is also the number of ${owner}: with "callers can dial numbers" on, pressing ${key} would always choose the menu option. Use another key, or turn that off`,
|
||||
{ owner },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
app.post('/api/ivrs', async (req, reply) => {
|
||||
const { options = [], ...body } = IvrCreateSchema.parse(req.body);
|
||||
await assertNumberAvailable(db, body.number, 'ivr');
|
||||
await assertMenuKeys(
|
||||
body.allowDirectDial ?? true,
|
||||
options.map((o) => o.digit),
|
||||
);
|
||||
await assertPrompt(db, body.greetingSoundId);
|
||||
await assertPrompt(db, body.invalidSoundId);
|
||||
await assertDestinations(db, [
|
||||
@@ -139,6 +156,15 @@ export async function ivrRoutes(app: FastifyInstance) {
|
||||
const before = await getIvr(id);
|
||||
if (body.number && body.number !== before.number)
|
||||
await assertNumberAvailable(db, body.number, 'ivr', id);
|
||||
if (options || body.allowDirectDial)
|
||||
await assertMenuKeys(
|
||||
body.allowDirectDial ?? before.allowDirectDial,
|
||||
options
|
||||
? options.map((o) => o.digit)
|
||||
: (await db.select().from(ivrOptions).where(eq(ivrOptions.ivrId, id))).map(
|
||||
(o) => o.digit,
|
||||
),
|
||||
);
|
||||
await assertPrompt(db, body.greetingSoundId);
|
||||
await assertPrompt(db, body.invalidSoundId);
|
||||
// Pointing at itself is fine here: the IVR exists.
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function checkForward(
|
||||
const digits = dialedDigits(input);
|
||||
if (!digits) throw badRequest('Enter a number');
|
||||
if (digits === ext.number) throw badRequest("You can't forward calls to yourself");
|
||||
if (/^\d{3,6}$/.test(digits)) {
|
||||
if (/^\d{1,6}$/.test(digits)) {
|
||||
if (await ownerOf(db, digits)) return digits;
|
||||
throw badRequest(`${digits} isn't an extension or group here`);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
import { and, eq, ne } from 'drizzle-orm';
|
||||
import { isReservedNumber, type NumberKind } from '@selfphoneng/shared';
|
||||
import type { Db } from '../db/client.ts';
|
||||
import { conferences, extensions, ivrs, pagingGroups, queues, ringGroups } from '../db/schema.ts';
|
||||
import {
|
||||
conferences,
|
||||
extensions,
|
||||
ivrOptions,
|
||||
ivrs,
|
||||
pagingGroups,
|
||||
queues,
|
||||
ringGroups,
|
||||
} from '../db/schema.ts';
|
||||
import { badRequest, conflict } from '../errors.ts';
|
||||
import { getSetting } from './settings.ts';
|
||||
|
||||
@@ -46,20 +54,48 @@ export async function ownerOf(db: Db, number: string, excludeId?: string): Promi
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A digital receptionist that lets callers dial numbers and has `key` in its menu: callers pressing
|
||||
* it get the menu option, so a one-digit number `key` can't be dialed from there.
|
||||
*/
|
||||
export async function menuKeyOwner(db: Db, key: string, excludeIvrId?: string) {
|
||||
if (!/^\d$/.test(key)) return null;
|
||||
const [row] = await db
|
||||
.select({ name: ivrs.name })
|
||||
.from(ivrOptions)
|
||||
.innerJoin(ivrs, eq(ivrs.id, ivrOptions.ivrId))
|
||||
.where(
|
||||
and(
|
||||
eq(ivrOptions.digit, key),
|
||||
eq(ivrs.allowDirectDial, true),
|
||||
...(excludeIvrId ? [ne(ivrs.id, excludeIvrId)] : []),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
return row?.name ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Any free internal number, for any kind (owner, 2026-10-21): the numbering ranges only choose the
|
||||
* suggested next number (nextFreeNumber). `kind` is kept for that and for callers' readability.
|
||||
*/
|
||||
export async function assertNumberAvailable(
|
||||
db: Db,
|
||||
number: string,
|
||||
kind: NumberKind,
|
||||
_kind: NumberKind,
|
||||
excludeId?: string,
|
||||
): Promise<void> {
|
||||
if (!/^\d{3,6}$/.test(number)) throw badRequest('Internal numbers have 3 to 6 digits');
|
||||
if (!/^\d{1,6}$/.test(number)) throw badRequest('Internal numbers have 1 to 6 digits');
|
||||
if (isReservedNumber(number))
|
||||
throw badRequest(`${number} is reserved (emergency and service codes can't be used)`);
|
||||
const [from, to] = await getSetting(db, RANGE_KEY[kind]);
|
||||
const n = Number(number);
|
||||
if (n < from || n > to) throw badRequest(`Use a number from ${from} to ${to}`);
|
||||
const owner = await ownerOf(db, number, excludeId);
|
||||
if (owner) throw conflict(`${number} is already used by ${owner}`, { owner });
|
||||
const menu = await menuKeyOwner(db, number);
|
||||
if (menu)
|
||||
throw conflict(
|
||||
`${number} is a menu key of the digital receptionist "${menu}", which also lets callers dial numbers: pressing ${number} there would always choose the menu option`,
|
||||
{ owner: `digital receptionist "${menu}"` },
|
||||
);
|
||||
}
|
||||
|
||||
/** Lowest free number in the kind's range, or null when the range is full. */
|
||||
|
||||
@@ -141,6 +141,14 @@ async function check(name) {
|
||||
if (!shownQ.startsWith(`${q} has`) || listed !== members)
|
||||
problems.push(`queue ${q}: expected ${members} members, got: ${shownQ.trim()}`);
|
||||
}
|
||||
// A case can list [number, context] pairs Asterisk must match (short-numbers: one-digit
|
||||
// numbers, which a pattern like _X. needs two digits for).
|
||||
const snap = JSON.parse(readFileSync(join(GOLDEN, name, 'snapshot.json'), 'utf8'));
|
||||
for (const [exten, ctx] of snap.asteriskMatches ?? []) {
|
||||
const d = cli(`dialplan show ${exten}@${ctx}`).out;
|
||||
if (!/-= \d+ extensions? \(/.test(d) || /There is no existence/.test(d))
|
||||
problems.push(`${exten} doesn't match in ${ctx}`);
|
||||
}
|
||||
// Asterisk colors its log: strip the escape codes before looking for ERROR[.
|
||||
// eslint-disable-next-line no-control-regex -- matching ANSI escapes is the point
|
||||
const log = docker('logs', NAME).out.replace(/\x1b\[[0-9;]*m/g, '');
|
||||
|
||||
@@ -62,7 +62,7 @@ describe.skipIf(!hasTestDb)('/api/conferences', () => {
|
||||
it('checks the number and the PINs', async () => {
|
||||
const bad = (body: object) =>
|
||||
req('POST', '/api/conferences', { number: '801', name: 'X', ...body });
|
||||
expect((await bad({ number: '700' })).statusCode).toBe(400); // parking, not 800–849
|
||||
expect((await bad({ number: '700' })).statusCode).toBe(409); // the parking lot
|
||||
expect((await bad({ number: '800' })).statusCode).toBe(409);
|
||||
expect((await bad({ pin: '12' })).statusCode).toBe(400);
|
||||
expect((await bad({ pin: '12a4' })).statusCode).toBe(400);
|
||||
|
||||
@@ -72,9 +72,12 @@ describe.skipIf(!hasTestDb)('extensions API', () => {
|
||||
const make = (number: string, extra = {}) =>
|
||||
req('POST', '/api/extensions', { number, name: 'X', ...extra });
|
||||
expect((await make('211')).json().error.message).toMatch(/reserved/);
|
||||
expect((await make('600')).json().error.message).toMatch(/from 100 to 499/);
|
||||
expect((await make('12')).statusCode).toBe(400);
|
||||
expect((await make('710')).statusCode).toBe(400); // outside the users range anyway
|
||||
expect((await make('1234567')).statusCode).toBe(400); // 1 to 6 digits
|
||||
expect((await make('710')).json().error.message).toMatch(/parking/);
|
||||
// Any free number, in or out of the users range (owner, 2026-10-21): "1" for the first user.
|
||||
const one = await make('1');
|
||||
expect(one.statusCode, one.body).toBe(201);
|
||||
expect((await req('DELETE', `/api/extensions/${one.json().id}`)).statusCode).toBe(200);
|
||||
const dup = await make('101');
|
||||
expect(dup.statusCode).toBe(409);
|
||||
expect(dup.json().error.message).toMatch(/extension "Dan Doe"/);
|
||||
|
||||
@@ -81,7 +81,24 @@ describe.skipIf(!hasTestDb)('/api/ivrs', () => {
|
||||
const post = (body: object) => req('POST', '/api/ivrs', { ...base(), number: '601', ...body });
|
||||
const missing = '00000000-0000-4000-8000-000000000999';
|
||||
expect((await post({ number: '600' })).statusCode).toBe(409);
|
||||
expect((await post({ number: '500' })).statusCode).toBe(400); // ring-group range
|
||||
expect((await post({ number: '1234567' })).statusCode).toBe(400);
|
||||
// A one-digit number and a menu key (direct dialing on) can't be the same, either way round.
|
||||
const one = await req('POST', '/api/extensions', { number: '1', name: 'X' });
|
||||
expect(one.statusCode).toBe(409);
|
||||
expect(one.json().error.message).toMatch(/menu key of the digital receptionist "Main menu"/);
|
||||
const four = await req('POST', '/api/extensions', { number: '4', name: 'Four' });
|
||||
expect(four.statusCode, four.body).toBe(201);
|
||||
const clash = await post({ options: [{ digit: '4', destination: { type: 'hangup' } }] });
|
||||
expect(clash.statusCode).toBe(409);
|
||||
expect(clash.json().error.message).toMatch(/Key 4 is also the number of extension "Four"/);
|
||||
const noDial = await post({
|
||||
allowDirectDial: false,
|
||||
number: '602',
|
||||
options: [{ digit: '4', destination: { type: 'hangup' } }],
|
||||
});
|
||||
expect(noDial.statusCode).toBe(201);
|
||||
await req('DELETE', `/api/ivrs/${noDial.json().id}`);
|
||||
await req('DELETE', `/api/extensions/${four.json().id}`);
|
||||
expect((await post({ greetingSoundId: missing })).statusCode).toBe(400);
|
||||
const twice = await post({
|
||||
options: [
|
||||
|
||||
@@ -53,7 +53,7 @@ describe.skipIf(!hasTestDb)('/api/paging-groups', () => {
|
||||
it('checks the number and the members', async () => {
|
||||
const bad = (body: object) =>
|
||||
req('POST', '/api/paging-groups', { number: '851', name: 'X', ...body });
|
||||
expect((await bad({ number: '800' })).statusCode).toBe(400); // conference range
|
||||
expect((await bad({ number: '1234567' })).statusCode).toBe(400); // 1 to 6 digits
|
||||
expect((await bad({ number: '850' })).statusCode).toBe(409);
|
||||
expect((await bad({ memberIds: [ext['101'], ext['101']] })).statusCode).toBe(400);
|
||||
expect((await bad({ memberIds: ['00000000-0000-4000-8000-000000000000'] })).statusCode).toBe(
|
||||
|
||||
@@ -99,7 +99,7 @@ describe.skipIf(!hasTestDb)('/api/queues', () => {
|
||||
timeoutDestination: { type: 'hangup' },
|
||||
...body,
|
||||
});
|
||||
expect((await bad({ number: '500' })).statusCode).toBe(400); // outside 550–599
|
||||
expect((await bad({ number: '1234567' })).statusCode).toBe(400); // 1 to 6 digits
|
||||
expect((await bad({ number: '550' })).statusCode).toBe(409);
|
||||
const twice = { extensionId: ext['101'], penalty: 0 };
|
||||
expect((await bad({ members: [twice, twice] })).statusCode).toBe(400);
|
||||
|
||||
@@ -64,7 +64,7 @@ describe.skipIf(!hasTestDb)('/api/ring-groups', () => {
|
||||
noAnswerDestination: { type: 'hangup' },
|
||||
...body,
|
||||
});
|
||||
expect((await bad({ number: '101' })).statusCode).toBe(400); // outside 500–549
|
||||
expect((await bad({ number: '101' })).statusCode).toBe(409); // extension 101 has it
|
||||
expect((await bad({ number: '500' })).statusCode).toBe(409);
|
||||
expect((await bad({ memberIds: [ext['101'], ext['101']] })).statusCode).toBe(400);
|
||||
expect((await bad({ memberIds: ['00000000-0000-4000-8000-000000000999'] })).statusCode).toBe(
|
||||
|
||||
@@ -502,12 +502,13 @@ function NumbersStep({ next }: { next: () => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
type NewUser = { name: string; email: string };
|
||||
type NewUser = { name: string; email: string; number: string };
|
||||
|
||||
function UsersStep({ next }: { next: () => void }) {
|
||||
const qc = useQueryClient();
|
||||
const users = useQuery({ queryKey: ['users'], queryFn: usersApi.list });
|
||||
const [rows, setRows] = useState<NewUser[]>([{ name: '', email: '' }]);
|
||||
const blank = { name: '', email: '', number: '' };
|
||||
const [rows, setRows] = useState<NewUser[]>([blank]);
|
||||
const [created, setCreated] = useState<
|
||||
{ name: string; email: string; number: string; password: string }[]
|
||||
>([]);
|
||||
@@ -520,7 +521,8 @@ function UsersStep({ next }: { next: () => void }) {
|
||||
email: r.email.trim(),
|
||||
role: 'user',
|
||||
});
|
||||
const number = await extensionsApi.nextNumber('user');
|
||||
// Their own number if typed in (any free one, 1 to 6 digits), else the next free.
|
||||
const number = r.number.trim() || (await extensionsApi.nextNumber('user'));
|
||||
if (number) await extensionsApi.create({ number, name: r.name.trim(), userId: user.id });
|
||||
out.push({
|
||||
name: r.name,
|
||||
@@ -533,7 +535,7 @@ function UsersStep({ next }: { next: () => void }) {
|
||||
return out;
|
||||
},
|
||||
onSettled: () => {
|
||||
setRows([{ name: '', email: '' }]);
|
||||
setRows([blank]);
|
||||
void qc.invalidateQueries({ queryKey: ['users'] });
|
||||
void qc.invalidateQueries({ queryKey: ['extensions'] });
|
||||
},
|
||||
@@ -546,7 +548,8 @@ function UsersStep({ next }: { next: () => void }) {
|
||||
<>
|
||||
<p className="text-sm text-zinc-500">
|
||||
Add the people who get a phone (up to 10 here; more later under Users & Extensions).
|
||||
Each gets the next free extension and a temporary password, shown once.
|
||||
Each gets the extension you type (any free number, like 1), or the next free one, and a
|
||||
temporary password, shown once.
|
||||
</p>
|
||||
{withExt.length > 0 && (
|
||||
<ul className="text-sm">
|
||||
@@ -565,7 +568,16 @@ function UsersStep({ next }: { next: () => void }) {
|
||||
}}
|
||||
>
|
||||
{rows.map((r, i) => (
|
||||
<div key={i} className="grid grid-cols-2 gap-2">
|
||||
<div key={i} className="grid grid-cols-[6rem_1fr_1fr] gap-2">
|
||||
<input
|
||||
aria-label={`Extension ${i + 1}`}
|
||||
placeholder="Next free"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]{1,6}"
|
||||
value={r.number}
|
||||
onChange={(e) => set(i, 'number', e.target.value)}
|
||||
className={inputClass}
|
||||
/>
|
||||
<input
|
||||
aria-label={`Name ${i + 1}`}
|
||||
placeholder="Name"
|
||||
@@ -585,9 +597,7 @@ function UsersStep({ next }: { next: () => void }) {
|
||||
))}
|
||||
<div className="flex gap-2">
|
||||
{rows.length < 10 && (
|
||||
<Button onClick={() => setRows([...rows, { name: '', email: '' }])}>
|
||||
Another person
|
||||
</Button>
|
||||
<Button onClick={() => setRows([...rows, blank])}>Another person</Button>
|
||||
)}
|
||||
<Button variant="primary" type="submit" disabled={save.isPending}>
|
||||
Add users
|
||||
|
||||
@@ -188,7 +188,7 @@ function IvrForm({
|
||||
<Field
|
||||
label="Number"
|
||||
error={errors.number}
|
||||
hint="Dial it inside, or point a phone number at it"
|
||||
hint="Any free number, 1 to 6 digits. Dial it inside, or point a phone number at it"
|
||||
>
|
||||
{(p) => (
|
||||
<input
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Settings (05 §2). M3: Email and Emergency; M7: Recording; M11: Network (public IP watch), Security (phone ACL, provisioning, SIP TLS), Backups,
|
||||
// Webhooks, API tokens.
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useState } from 'react';
|
||||
import { Fragment, useState } from 'react';
|
||||
import { NavLink, useParams } from 'react-router';
|
||||
import {
|
||||
WEBHOOK_EVENTS,
|
||||
@@ -27,6 +27,7 @@ const TABS = [
|
||||
{ id: 'email', label: 'Email' },
|
||||
{ id: 'emergency', label: 'Emergency' },
|
||||
{ id: 'recording', label: 'Recording' },
|
||||
{ id: 'numbering', label: 'Numbering' },
|
||||
{ id: 'network', label: 'Network' },
|
||||
{ id: 'security', label: 'Security' },
|
||||
{ id: 'backups', label: 'Backups' },
|
||||
@@ -60,6 +61,7 @@ export function SettingsPage() {
|
||||
{settings.data && tab === 'email' && <EmailTab s={settings.data} />}
|
||||
{settings.data && tab === 'emergency' && <EmergencyTab s={settings.data} />}
|
||||
{settings.data && tab === 'recording' && <RecordingTab s={settings.data} />}
|
||||
{settings.data && tab === 'numbering' && <NumberingTab s={settings.data} />}
|
||||
{settings.data && tab === 'network' && <NetworkTab s={settings.data} />}
|
||||
{settings.data && tab === 'security' && <SecurityTab s={settings.data} />}
|
||||
{settings.data && tab === 'backups' && <BackupsTab s={settings.data} />}
|
||||
@@ -413,6 +415,73 @@ function RecordingTab({ s }: { s: SettingsDto }) {
|
||||
}
|
||||
|
||||
/** The public IP Asterisk tells callers about, and re-checking it on a dynamic IP (01 §4.3, 04 §8). */
|
||||
const RANGES = [
|
||||
['numbering.users', 'Users (extensions)'],
|
||||
['numbering.ringGroups', 'Ring groups'],
|
||||
['numbering.queues', 'Queues'],
|
||||
['numbering.ivrs', 'Digital receptionists'],
|
||||
['numbering.conferences', 'Conference rooms'],
|
||||
['numbering.paging', 'Paging groups'],
|
||||
] as const;
|
||||
type RangeKey = (typeof RANGES)[number][0];
|
||||
|
||||
/** Where "Next free" looks; any free number can still be typed in (owner, 2026-10-21). */
|
||||
function NumberingTab({ s }: { s: SettingsDto }) {
|
||||
const save = useSave();
|
||||
const [v, setV] = useState(
|
||||
() =>
|
||||
Object.fromEntries(RANGES.map(([k]) => [k, s[k].map(String)])) as Record<RangeKey, string[]>,
|
||||
);
|
||||
const set = (k: RangeKey, i: 0 | 1, x: string) =>
|
||||
setV((o) => ({ ...o, [k]: i === 0 ? [x, o[k][1]] : [o[k][0], x] }));
|
||||
return (
|
||||
<form
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
save.mutate(Object.fromEntries(RANGES.map(([k]) => [k, v[k].map(Number)])));
|
||||
}}
|
||||
>
|
||||
<p className="text-sm text-zinc-600 dark:text-zinc-400">
|
||||
Where <strong>Next free</strong> looks when you add a user, ring group, queue, digital
|
||||
receptionist, conference room or paging group. You can always type any other free number of
|
||||
1 to 6 digits instead, for example <strong>1</strong> for the first user: SelfPhoneNG checks
|
||||
that nothing else has it. Parking uses 700–720, and 911, 933 and the other N11 service codes
|
||||
can't be used.
|
||||
</p>
|
||||
<div className="grid grid-cols-[1fr_auto_auto] items-center gap-x-3 gap-y-2 text-sm">
|
||||
<span />
|
||||
<span className="text-zinc-500">From</span>
|
||||
<span className="text-zinc-500">To</span>
|
||||
{RANGES.map(([k, label]) => (
|
||||
<Fragment key={k}>
|
||||
<span>{label}</span>
|
||||
{([0, 1] as const).map((i) => (
|
||||
<input
|
||||
key={i}
|
||||
aria-label={`${label} ${i === 0 ? 'from' : 'to'}`}
|
||||
type="number"
|
||||
min={0}
|
||||
max={999999}
|
||||
className={`${inputClass} w-28`}
|
||||
value={v[k][i]}
|
||||
onChange={(e) => set(k, i, e.target.value)}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Button type="submit" variant="primary" disabled={save.isPending}>
|
||||
Save
|
||||
</Button>
|
||||
{save.isSuccess && <span className="text-sm text-zinc-500">Saved.</span>}
|
||||
</div>
|
||||
{save.error && <Alert>{save.error.message}</Alert>}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function NetworkTab({ s }: { s: SettingsDto }) {
|
||||
const save = useSave();
|
||||
const [ip, setIp] = useState(s['network.publicIp'] ?? '');
|
||||
|
||||
@@ -206,7 +206,7 @@ function GeneralTab({
|
||||
hint={
|
||||
ext
|
||||
? 'Changing it renumbers their phones and voicemail box.'
|
||||
: 'Leave empty for a user without a phone.'
|
||||
: 'Any free number, 1 to 6 digits (1, 101, …). Leave empty for a user without a phone.'
|
||||
}
|
||||
>
|
||||
{(a) => (
|
||||
@@ -260,7 +260,7 @@ const PhoneSchema = ExtensionUpdateSchema.pick({
|
||||
]),
|
||||
blfNumbers: z
|
||||
.string()
|
||||
.regex(/^(\s*\d{3,6}\s*(,\s*\d{3,6}\s*)*)?$/, 'Internal numbers separated by commas'),
|
||||
.regex(/^(\s*\d{1,6}\s*(,\s*\d{1,6}\s*)*)?$/, 'Internal numbers separated by commas'),
|
||||
});
|
||||
type PhoneValues = z.input<typeof PhoneSchema>;
|
||||
|
||||
|
||||
Reference in new issue
Block a user