; ; This software (C) 2004-2005 Michael Loftis ; ; To satisfy the legalities of all of this I've selected the LGPL : ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU Lesser General Public License as published by ; the Free Software Foundation; either version 2.1 of the License, or ; (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU Lesser General Public License for more details. ; ; You should have received a copy of the GNU Lesser General Public License ; along with this program; if not, write to the Free Software ; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ; ; This is a set of macros to assist in implementing chan_agent like features ; but within the framework of a standard dialplan instead of chan_agent ; it ONLY implements AgentCallBackLogin type functionality. ; ; ; I realise the documentation is horribyl sparse here, there is an example ; extensions.conf that I've distributed seperately that show some uses of the ; code. ; ; ; ; AGENT CHANNEL ENV SETTINGS ; This is a list of channel envornment/local vars that we set or touch ; Most of them are set by the AgentGetSettings macro much later in this ; File, and most of the macro's prior to that require it be called before ; they get called, or may call it themselves. ; ; AgentAuth is an exception to this it will NEVER call AgentGetSettings. ; ; AGENTIDNUM ; AGENTPIN ; Caller*ID Number to AGENTIDNUM (optional) ; AGENTCIDNUM to ORIGINAL CALLERIDNUM ; AGENTCB CallBack Tech/num@blah for Dial() ; AGENTRTO Set to custom timeout or defaults to ${AGDB}/Default/RESPONSETIMEOUT ; AGENTTRIES Equal to number of AgentAuth tries for last run at it. ; ; Database descriptions. ; family is ${AGDB} (Agent by default), key is eitehr AGENTIDNUM or Default ; if AGENTIDNUM/type doesnt' exist. ; ; PIN -- PIN/Auth Code (can NOT default!) ; RESPONSETIMEOUT -- Used for ResponseTimeout setting ; CALLBACK -- current callback setting, should only be set/exists when ; logged in, shouldn't set default for this. ; CONTEXT -- context that AgentCall dispatches into ; EXTEN -- extension that AgentCall dispatches into. ; CBCONTEXT -- context that callbacks are sent to ; CBTECH -- technology used for callbacks NOT the same as above, this allows ; you to service agents off a different (IAX, SIP) switch, or ; PSTN directly instead of always doing Local/@! ; EMAILTO -- who/where to send email to on auto logout. ; Q? -- where ? is a queue number to login to on agentlogin starting at 0 ; these must be sequential! ; DQ? -- Default Queue entries for adding queues to Default key that everyone ; gets logged into, again SEQUENTIAL. DQ is used to simplify things ; in the dialplan code. ; LQ? -- Queues an agent is 'known' to be logged into with CALLBACK. Used ; Primarily by the logout stuff. ; LQNUM -- Number of queues logged into IE if LQ0 is set then this is 1. ; ; You can also of course add your own things to this. Locally we lookup ; OUTCID to decide outbound Caller*ID number. ; ; you NEED to copy and set these in the main extensions.conf ; since pbx_config will only allow a particular secion to occur once. ; ; AGAUTHSAY is still here just ebcause it was written before i completed the ; general database/settings component. ;[globals] ;AGDB=Agent ;AGAUTHSAY=agent-user ; ; Agent Login...assumes authorization handled. ; Variables are ; ${ARG1} Agent ID Number ; ${ARG2} (Optional) Where to call back, must be FULL Tech/Exten@Context ; for Dial() ; ; Agents can enter an empty callback here to get a logout. ; If ARG2 is set we set it in the DB and log them into queues like that. ; If ARG2 is NOT set we prompt and fill it in with ; ${AGENTCBTECH}/@${AGENTCBCONTEXT} -- check to see if it's OK ; and if so then we store THAT and go on to bringing up queues. ; ; System reads in DQ? and logs those in first, then reads in Q? and logs those ; in. We check 'logged in' status and run through the Agents LQ? variables ; and log them out first. ; ; 500 retrieve settings ; 600 error block for 500 ; 1000 retrieve some callback information and update AGENTCB and CALLBACK data ; 3000 get them logged out if they're logged in ; 5000 start working the actual queue add -- Macro(AgentDoList,...) ; we do the DQ stuff first, then the Q stuff. [macro-AgentLogin] exten=s,1,NoOp exten=s,2,Goto(500) exten=s,500,Macro(AgentGetSettings,${ARG1}) exten=s,501,Goto(1000) ; Agent doesn't exist, return error. exten=s,600,NoOp exten=s,601,SetVar(MACRO_OFFSET=100) ; Figure out where they'll be called back and if it's valid. exten=s,1000,SetVar(newexten=${ARG2}) ; If ARG2 is set we attempt to check if it's valid ish then go exten=s,1001,GotoIf($[ "${newexten}" != "" ]?2000) exten=s,1002,Read(newexten,agent-newlocation) ; if they enter nothing we call the logout function and thats it. exten=s,1003,GotoIf($[ "${newexten}" != "" ]?1200) ; they entered # so log them off and goodbye... exten=s,1004,Macro(AgentLogout,${AGENTIDNUM}) ; newexten isn't empty, so build a full extension then jump to 2000... exten=s,1200,SetVar(newexten=${AGENTCBTECH}/${newexten}@${AGENTCBCONTEXT}) exten=s,1201,Goto(2000) ; Validate newexten if we can.... exten=s,2000,Goto(3000) ; Lets get them logged out (quietly) if logged in. exten=s,3000,Macro(AgentLogout,${AGENTIDNUM},1) exten=s,3001,NoOp(X121X MACRO_OFFSET ${MACRO_OFFSET}) exten=s,3002,Goto(5000) ; in case AgentLogout ever error exists, loop back. exten=s,3100,Goto(3001) ; Agent exists, login state checked and logged out/cleared.... ; if ARG2 is set we log them in using that and set their ; CB data exten=s,5000,Macro(AgentDoList,DQ,${AGENTIDNUM},AgentLoginToQueue,${newexten}) exten=s,5001,Macro(AgentDoList,Q,${AGENTIDNUM},AgentLoginToQueue,${newexten}) exten=s,5002,Macro(AgentSetVar,CALLBACK,${newexten},${AGENTIDNUM}) exten=s,5003,Playback(agent-loginok) ; loop back on errror returns and continue by not having 5100's. ; AgentLoginToQueue ; Called like (taken form the AgentDoList example calling) ; Macro(AgentLoginToQueue,supportq,4119,Local/4119@agentlogin) ; ; Loopable macro for logging into a single queue.... ; ${ARG1} Queue to log into (Agent/4119/Q0 = supportq or Agent/Default/DQ0...) ; ${ARG2} Agent ID to work on ; ${ARG3} Extension/Line to add to queue ; [macro-AgentLoginToQueue] exten=s,1,NoOp exten=s,2,Macro(AgentGetVar,LQNUM,${AGENTIDNUM}) exten=s,3,SetVar(AGENTLQNUM=${AGENTVAR}) exten=s,4,Goto(500) exten=s,103,SetVar(AGENTVAR=0) exten=s,104,Goto(3) exten=s,500,AddQueueMember(${ARG1}|${ARG3}) exten=s,501,Goto(1000) ; error return, usually means already logged into that queue, return... exten=s,601,SetVar(MACRO_OFFSET=100) exten=s,1000,Macro(AgentSetVar,LQNUM,$[ ${AGENTLQNUM} + 1 ],${AGENTIDNUM}) exten=s,1001,Macro(AgentSetVar,LQ${AGENTLQNUM},${ARG1},${AGENTIDNUM}) exten=s,1002,SetVar(AGENTLQNUM=$[${AGENTLQNUM} + 1]) ; AgentLogoutFromQueue ; Called like (taken form the AgentDoList example calling) ; Macro(AgentLogoutFromQueue,supportq,4119,Local/4119@agentlogin) ; ; Loopable macro for logging into a single queue.... ; ${ARG1} Queue to log into (Agent/4119/Q0 = supportq or Agent/Default/DQ0...) ; ${ARG2} Agent ID to work on ; ${ARG3} Extension/Line to remove from queue ; ; This is complicated ish because we have to find it in the LQ list ; then YANK that from the LQ list and reorder the list. ; *SO* rather than worry about all that we assume we're just nuking everything ; in sequence using the ${I} iterator coming in from AgentDoList for now ; and the caller of AgentDoList will AgentDelVar LQNUM for us.... ; [macro-AgentLogoutFromQueue] exten=s,1,NoOp exten=s,2,Macro(AgentDelVar,LQ${I},${AGENTIDNUM}) exten=s,3,Goto(500) exten=s,103,Goto(500) exten=s,500,RemoveQueueMember(${ARG1}|${ARG3}) ; error return, usually means not logged into that queue, return... exten=s,601,SetVar(MACRO_OFFSET=100) ; Agent Logout... ; Variables are ; ${ARG1} Agent ID Number ; ${ARG2} (Optional) Set to 1 to do so quietly and not notify user. [macro-AgentLogout] exten=s,1,NoOp exten=s,2,Macro(AgentGetSettings,${ARG1}) exten=s,3,Goto(2000) ; invalid....exit+101 exten=s,103,SetVar(MACRO_OFFSET=100) exten=s,2000,Macro(AgentDoList,LQ,${AGENTIDNUM},AgentLogoutFromQueue,${AGENTCB}) exten=s,2001,Macro(AgentDelVar,LQNUM,${AGENTIDNUM}) exten=s,2002,Macro(AgentDelVar,CALLBACK,${AGENTIDNUM}) exten=s,2003,GotoIf($[ "foo${ARG2}" != "foo1" ]?2500) ; we wait a second because otherwise we seem to lose part of the stream exten=s,2500,Wait(1) exten=s,2501,Playback(agent-loggedoff) ; Add a queue to an agents list, checking for duplicates. ; ${ARG1} Queue ; ${ARG2} (Optional) Agent ID Number [macro-AgentAddQueue] exten=s,1,NoOp ; Remove a queue from an agents list. ; ${ARG1} Queue ; ${ARG2} (Optional) Agent ID Number) [macro-AgentDelQueue] exten=s,1,NoOp ; Send a call to an agent, we don't do VM. ; Requires that AgentGetSettings was called beforehand. ; ; ${ARG1} Agent ID Number to call ; ${ARG2} (optional) context to call agent in (defaults followed) ; ${ARG3} (optional) extension to call agent on (defaults followed, failing ; that we just dial the Agent ID Number in the context) ; ; On busy we just pass that back. ; On unavailable we figure out why, if channel issue we trudge on ; if anything else (unanswered/etc) then we log them out automatically ; If the agent doesn't exist, we exit+101, i suggest you make that a ; Congestion in the calling context at the very least. [macro-AgentCall] exten=s,1,NoOp(Starting AgentCall 1:${ARG1} 2:${ARG2} 3:${ARG3}) exten=s,2,Macro(AgentGetSettings,${ARG1}) exten=s,3,Goto(2000) ; invalid....exit+101 exten=s,103,SetVar(MACRO_OFFSET=100) ; Agent exists, figure out context... exten=s,2000,Macro(AgentGetVar,CONTEXT,${AGENTIDNUM}) exten=s,2001,SetVar(CBCTXT=${AGENTVAR}) exten=s,2002,Macro(AgentGetVar,EXTEN,${AGENTIDNUM}) exten=s,2003,SetVar(CBEXTEN=${AGENTVAR}) exten=s,2004,Goto(3000) ; AgentGetVar for CONTEXT totally failed.... exten=s,2101,SetVar(MACRO_OFFSET=100) ; AgentGetVar for EXTEN totally failed, fudge it! exten=s,2103,SetVar(AGENTVAR=${AGENTIDNUM}) exten=s,2104,Goto(2003) ; Dialout, we allow transfer. exten=s,3000,Dial(Local/${CBEXTEN}@${CBCTXT},${AGENTRTO},t) exten=s,3001,Goto(s-${DIALSTATUS},1) exten=s,3101,Goto(s-${DIALSTATUS},1) ; dialout status returns... exten=s-CANCEL,1,Goto(s-AutoLogout,1);Macro(AgentLogout,${AGENTIDNUM},1) ; Successful call... exten=s-ANSWER,1,NoOp ; The agent didn't answer... exten=s-NOANSWER,1,Goto(s-AutoLogout,1);Macro(AgentLogout,${AGENTIDNUM},1) ; Busy and congestion we just propogate. exten=s-BUSY,1,Busy exten=s-CONGESTION,1,Congestion ; Treat an unavailable channel as Congestion.... exten=s-CHANUNAVAIL,1,Congestion exten=s-AutoLogout,1,Macro(AgentLogout,${AGENTIDNUM},1) exten=s-AutoLogout,2,Macro(AgentGetVar,EMAILTO,${AGENTIDNUM}) exten=s-AutoLogout,3,System(asterisk-autoagentloggedout ${AGENTVAR}) ; If they have no EMAILTO set. exten=s-AutoLogout,103,NoOp ; Agent Authentication GENERIC.... ; Gets and sets AGENTIDNUM ; does not return UNLESS auth suceeds. ; ; ${ARG1} (Optional) Agent ID Number to Auth.... ; ${ARG2} (Optional) Sound to playback instead of 'AGAUTHSAY' ; [macro-AgentAuth] exten=s,1,NoOp(Starting agentauth...) exten=s,2,SetVar(AGENTTRIES=0) exten=s,3,SetVar(authsay=${AGAUTHSAY}) ; if ARG1 is set then try it first... exten=s,4,Goto(50) exten=s,50,GotoIf($[ "${ARG2}" = "" ]?60) exten=s,51,SetVar(authsay=${ARG2}) exten=s,52,Goto(60) ; Passed in, if so set and then try. ; otherwise prompt exten=s,60,GotoIf($[ "${ARG1}" = "" ]?100) exten=s,61,SetVar(AGENTIDNUM=${ARG1}) exten=s,62,Goto(101) exten=s,100,Read(AGENTIDNUM,${authsay}) exten=s,101,Macro(AgentGetVar,PIN,${AGENTIDNUM}) ; DBget(AGENTPIN=${AGDB}/${AGENTIDNUM}/PIN) exten=s,102,SetVar(AGENTPIN=${AGENTVAR}) exten=s,103,Authenticate(${AGENTPIN}) exten=s,104,NoOp(Succeeded agentauth...) ; Invalid Agent Number, DBget@101 failed... exten=s,202,Goto(800) exten=s,800,SetVar(AGENTTRIES=$[ ${AGENTTRIES} + 1]) exten=s,801,GotoIf($[ ${AGENTTRIES} = 3 ]?1000:100) ; struck out... exten=s,1000,Playback(vm-goodbye) exten=s,1001,Hangup ; Agent Get Settings ; Sets CID to agent number, gets and exposes various settings into the env... ; ${ARG1} Agent ID Number to get settings for... ; ${ARG2} (Optional) Set to 1 to NOT SetCIDNum ; ; sets ; See above for 'AGENT CHANNEL ENV SETTINGS' ; AGENTOK=1 if agent exists and stuff was done ; AGENTOK=0 if agent didn't exist and nothing was done, shouldn't happen... ; [macro-AgentGetSettings] exten=s,1,NoOp(Started to retrieve agent settings.) exten=s,2,SetVar(AGENTOK=0) ; everyone has a PIN, we use this to determine success... exten=s,3,Macro(AgentGetVar,PIN,${ARG1}) ; AGENTPIN=${AGDB}/${ARG1}/PIN) exten=s,4,SetVar(AGENTPIN=${AGENTVAR}) exten=s,5,Goto(2000) ; Failed to find.... exten=s,104,NoOp(Could not find Agent aborting.) exten=s,105,SetVar(MACRO_OFFSET=100) ; Passes...start setting.... exten=s,2000,SetVar(AGENTOK=1) exten=s,2001,SetVar(AGENTCIDNUM=${CALLERIDNUM}) exten=s,2002,SetVar(AGENTIDNUM=${ARG1}) ; Check to see if we want to set the CID number and branch on decision. exten=s,2003,GotoIf($[ "foo${ARG2}" = "foo1"]?2100:2050) exten=s,2050,SetCIDNum(${AGENTIDNUM}) exten=s,2051,Goto(2100) exten=s,2100,Macro(AgentGetVar,CALLBACK,${AGENTIDNUM}) ; AGENTCB=${AGDB}/${AGENTIDNUM}/CALLBACK) exten=s,2101,SetVar(AGENTCB=${AGENTVAR}) exten=s,2102,Goto(2500) exten=s,2500,Macro(AgentGetVar,RESPONSETIMEOUT,${AGENTIDNUM}) ; AGENTRTO=${AGDB}/${AGENTIDNUM}/RESPONSETIMEOUT) exten=s,2501,SetVar(AGENTRTO=${AGENTVAR}) exten=s,2502,NoOp(RTO Set ${AGENTRTO}) exten=s,2503,Goto(3000) exten=s,3000,Macro(AgentGetVar,CBTECH,${AGENTIDNUM}) exten=s,3001,SetVar(AGENTCBTECH=${AGENTVAR}) exten=s,3002,Goto(3500) exten=s,3500,Macro(AgentGetVar,CBCONTEXT,${AGENTIDNUM}) exten=s,3501,SetVar(AGENTCBCONTEXT=${AGENTVAR}) ; Gets a setting or the default ; ${ARG1} Setting to retrieve ; ${ARG2} (Optional) Agent ID to retrieve it for ; returns it in AGENTVAR [macro-AgentGetVar] exten=s,1,NoOp(AgentGetVar ${ARG1} ${ARG2}) exten=s,2,SetVar(AGENTVAR="") exten=s,3,GotoIf($[ "${ARG1}" = "" ]?402) exten=s,4,Goto(200) exten=s,200,DBget(AGENTVAR=${AGDB}/${ARG2}/${ARG1}) exten=s,301,DBget(AGENTVAR=${AGDB}/Default/${ARG1}) ; if both fail then see if caller pri+101 exists, and return there, like DBget exten=s,402,SetVar(MACRO_OFFSET=100) ; AgentSetVar ; Modifies a setting or the default ; ${ARG1} Setting to modify ; ${ARG2} Value to set it to ; ${ARG3} (Optional) Agent ID to set it under. [macro-AgentSetVar] exten=s,1,NoOp(AgentSetVar ${ARG1} ${ARG2} ${ARG3}) exten=s,2,GotoIf($[ "${ARG3}" = "" ]?200:500) exten=s,200,DBput(${AGDB}/Default/${ARG1}=${ARG2}) exten=s,500,DBput(${AGDB}/${ARG3}/${ARG1}=${ARG2}) ; AgentDelVar ; DELETES a setting or the default ; ${ARG1} Setting to modify ; ${ARG2} (Optional) Agent ID to delete it from. [macro-AgentDelVar] exten=s,1,NoOp(AgentDelVar ${ARG1} ${ARG2}) exten=s,2,GotoIf($[ "${ARG2}" = "" ]?200:500) exten=s,200,DBdel(${AGDB}/Default/${ARG1}) exten=s,500,DBdel(${AGDB}/${ARG2}/${ARG1}) ; Generic 'Do something with dialplan agent database list' ; ; ${ARG1} The database list IE Agent/4119/Q then this is Q ; We tack on the iterator behind that. ; ${ARG2} Agent ID Number (4119 in the example above) also passed as ARG2 to the ; macro called in ARG3 ; ${ARG3} Macro to call for each item. Called with ARG1 set to ; the value of database return, and ARG4 set to the ; ARG3 variable ; ${ARG4} (Optional) passed to the called macro for 'user data' as ARG3 ; ; The macro called in ${ARG3} can access the ${I} iterator, and even change it ; so we go on. If it exits+101 we increment and go on. ; [macro-AgentDoList] exten=s,1,NoOp exten=s,2,SetVar(I=0) exten=s,3,Goto(500) exten=s,500,Macro(AgentGetVar,${ARG1}${I},${ARG2}) ; Macro(AgentLoginToQueue,supportq,4119,Local/4119@agentlogin) exten=s,501,Macro(${ARG3},${AGENTVAR},${ARG2},${ARG4}) exten=s,502,SetVar(I=$[ ${I} + 1 ]) exten=s,503,Goto(500) ; error returns from the 500 block... exten=s,600,NoOp ; if we hit 601 we're done (+101 from AgentGetVar@500 above...) exten=s,601,Goto(5000) ; if the called macro@501 goes +101 turn around to next step anyway. exten=s,602,Goto(502) exten=s,5000,NoOp