Skip to content

Default LuaAutostarters.xml#

LuaAutostarters.xml
<?xml version="1.0" encoding="utf-8"?><!--
--><luaAutoStarters version="1.20.8"><!--
--><luaScript name="ActionUrlReporting"><!--
--><code><![CDATA[
local stringx = require "pl.stringx"
local SERVER_SETTING_PATH = "remoteAccess/actionUrlServer"
local mServer = ""
function settingChanged(path)
    mServer = config.get(SERVER_SETTING_PATH)
    if #mServer > 0 and not stringx.startswith(string.lower(mServer), "http") then
        mServer = "http://"..mServer
    end
end
config.register(SERVER_SETTING_PATH, settingChanged)
settingChanged(SERVER_SETTING_PATH)
function callListChanged(event, call1, call2)
    if event == nil or call1 == nil then
        debug.log("invalid parameters at callListChanged("..tostring(event).." ,"..tostring(call1)..")", "w")
        return
    end
    if event == "add" then onCallAdded(call1)
    elseif event == "remove" then onCallRemoved(call1)
    elseif event == "state_changed" then onStateChanged(call1)
    elseif event == "remote_update" then onRemoteChanged(call1)
    elseif event == "transferred" then onTransferred(call1)
    elseif event == "conferenced" then onConferenced(call1, call2)
    elseif event == "unconferenced" then onUnconferenced(call1, call2)
    elseif event == "transfer_initiated" or event == "transfer_failed" or event == "transfer_done" then
        onTransferAway(call1, call2, event)
    else debug.log("unknown call-event: "..event, "w")
    end
end
function informServer(query)
    if #mServer > 0 then
        http.get(mServer..query)
    end
end
function onCallAdded(call)
    local mac = phoneInfo.getMacAddress()
    local regId = call:getIdentityIndex()
    local callId = call:getId()
    local state = call:getState()
    local incoming = call:isIncoming()
    local event = "outgoing"
    if incoming then
        event = "incoming"
    end
    debug.log("call added: "..callId..", reg="..regId..", state="..state..", event="..event..", "..getRemoteParameters(call), "i")
    informServer("?event="..event.."&mac="..mac.."&callId="..callId.."&accountId="..regId..getRemoteParameters(call))
end
function onCallRemoved(call)
    local mac = phoneInfo.getMacAddress()
    local callId = call:getId()
    debug.log("call removed: "..callId, "i")
    informServer("?event=disconnected&callId="..callId.."&mac="..mac)
end
function onStateChanged(call)
    local mac = phoneInfo.getMacAddress()
    local callId = call:getId()
    local state = call:getState()
    local event = "unknown"
    debug.log("call state changed: "..callId..", state="..state, "i")
    if state == "holding" then
        event = "holding"
    elseif state == "active" then
        event = "connected"
    else
        debug.log("call state changed ignored cause neither hold nor connected", "i")
        return
    end
    informServer("?event="..event.."&mac="..mac.."&callId="..callId)
end
function onRemoteChanged(call)
    local mac = phoneInfo.getMacAddress()
    local callId = call:getId()
    debug.log("remote updated: "..callId..", "..getRemoteParameters(call), "i")
    informServer("?event=remoteUpdate&mac="..mac.."&callId="..callId..getRemoteParameters(call))
end
function onTransferred(call)
    local mac = phoneInfo.getMacAddress()
    local callId = call:getId()
    local state = call:getState()
    if state == "active" then
        state = "connected"
    end
    debug.log("remote changed: "..callId..", "..getRemoteParameters(call), "i")
    informServer("?event=transferred&mac="..mac.."&callId="..callId.."&state="..state..getRemoteParameters(call))
end
function onTransferAway(call, target, evtType)
    local mac = phoneInfo.getMacAddress()
    local callId = call:getId()
    local targetParam = ""
    if type(target) == "string" then
        targetParam = "&number="..target
    else
        targetParam = "&callId2="..target:getId()
    end
    debug.log(evtType ..": "..callId..", "..targetParam, "i")
    informServer("?event="..evtType.."&mac="..mac.."&callId="..callId..targetParam)
end
function onUnconferenced(call1, call2)
    local mac = phoneInfo.getMacAddress()
    local callId1 = call1:getId()
    local callId2 = call2:getId()
    debug.log("conference disolved: "..callId1.." and "..callId2, "i")
    informServer("?event=unconferenced&mac="..mac.."&callId="..callId1.."&callId2="..callId2)
end
function onConferenced(call1, call2)
    local mac = phoneInfo.getMacAddress()
    local callId1 = call1:getId()
    local callId2 = call2:getId()
    debug.log("conferenced: "..callId1.." and "..callId2, "i")
    informServer("?event=conferenced&mac="..mac.."&callId="..callId1.."&callId2="..callId2)
end
function getRemoteParameters(call)
    local number, name = call:getRemote()
    -- TODO: enclose number and name in "" once we figure out how to make http.get(..) not encode it to %22
    if name == nil then
        return "&number="..number.."&name="
    else
        return "&number="..number.."&name="..tostring(name)..""
    end
end
callsCb = sip.calls.listen(callListChanged)--]]></code><!--
--></luaScript><!--
--><luaScript name="ActionUrlAccepting"><!--
--><code><![CDATA[
local pretty = require "pl.pretty"
local stringx = require "pl.stringx"
local tablex = require "pl.tablex"
local SETTING_PATH = "remoteAccess/allowCallManipulationViaHttp"
local PRINT_HLP_ERROR = "printHelp"
local mEnable = false
function settingChanged(path)
    local newEnable = config.get(SETTING_PATH) == "true"
    if newEnable ~= mEnable then
        mEnable = newEnable
        if mEnable then
            listenForCallCmds()
        else
            stopListeningToCallCmds()
        end
    end
end
local cmdMap = {}
cmdMap.accept =  { executeMe=sip.calls.accept, strParams = { callid="call", callid1="call" },  boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.call =    { executeMe=sip.calls.make,   strParams = { number="uri", accountid="line" }, boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.terminate =
              { executeMe=sip.calls.terminate, strParams = { callid="call", callid1="call", callid2="call2" }, boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.dtmf =    { executeMe=sip.calls.dtmf,   strParams = { callid="call", callid1="call", number="dtmf" },   boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.hold =    { executeMe=sip.calls.hold,   strParams = { callid="call", callid1="call", callid2="call2" }, boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.resume =  { executeMe=sip.calls.resume, strParams = { callid="call", callid1="call", callid2="call2" }, boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.conference =
             { executeMe=sip.calls.conference, strParams = { callid="call", callid1="call", callid2="call2" }, boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.join =    { executeMe=sip.calls.join,   strParams = { callid="call", callid1="call", callid2="call2" }, boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.transfer =
               { executeMe=sip.calls.transfer, strParams = { callid="call", callid1="call", number="number" }, boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.log =     { executeMe=debug.log,        strParams = { text="message", level="debug_level"},             boolParams = { fireandforget="!returnHttpResult"} }
cmdMap.show =    { executeMe=system.toast,     strParams = { text="text"},                                     boolParams = { fireandforget="!returnHttpResult"} }
-- aliases:
cmdMap.offhook = { duplicates="accept" }
cmdMap.hangup =  { duplicates="terminate" }
local uniqueCmds = {}
for action, details in pairs(cmdMap) do
    if details.duplicates == nil then
        uniqueCmds[action] = details
    end
end
function onActionUrl(url, body, headers, vars)
    debug.log(url, "d")
    debug.log(pretty.write(vars.query_map), "d")
    local query = vars.query_map
    local action = query.action
    query.action = nil -- remove so it won't get interpreted as parameter
    if action == nil then
        if query.help ~= nil then
            return 200, getCmdList()
        end
        debug.log("unknown action in request -> ignoring: ?"..tostring(vars.query_string))
        return 400, "missing action=...\n\n"..getCmdList()
    end
    -- find action within our mapping, what should be done?:
    action = string.lower(tostring(action))
    if cmdMap[action] == nil then
        if action == "help" then
            return 200, getCmdList()
        else
            return 404, "unknown action '"..action.."'\n\n"..getCmdList()
        end
    end
    command = cmdMap[action]
    if command.duplicates ~= nil then
        command = cmdMap[command.duplicates]
    end
    -- interpret the query-parameters, how do they get passed on to the system-function?:
    local paramMap, error = createParamMap(query, command)
    if error ~= nil then
        if error == PRINT_HLP_ERROR then
            return 200, "action: "..action.." - parameters: "..getCmdHelp(command)
        else
            return 400, error.."\n\nexpected: "..getCmdHelp(command)
        end
    end
    -- finally, do it:
    return command.executeMe(paramMap)
end
-- return 2 results:
-- first one is the parameter-map (query parameters converted into dict for lua-function)
-- when query contains any none-mapped parameters, then 2nd result is that parameter
function createParamMap(query, details)
    local result = {}
    local queryKeys = {}
    -- find the real map-keys [i.e. all but the array-indexes] .. also check for special help-param
    for key, val in pairs(query) do
        if type(key) ~= 'number' or key < 1 or key > #query then
            queryKeys[#queryKeys+1]=key
        elseif val == "help" then
            return result, PRINT_HLP_ERROR
        end
    end
    if details.boolParams then
        for i, queryParam in ipairs(query) do
            -- iterating through all query-params that dont have a value. These are interpreted as bool
            -- these bools represent a true by just being in the query - false when mapped-param starts with !
            local destination = details.boolParams[queryParam:lower()]
            if destination == nil then
                destination = details.strParams and details.strParams[queryParam:lower()]
                if destination == nil then
                    return result, "unknown parameter: "..queryParam
                end
                return result, queryParam.." is missing an assigned value."
            end
            local value = true
            if destination:sub(1, 1) == "!" then
                destination = destination:sub(2)
                value = false
            end
            result[destination] = value
        end
    else -- no bool-params expected, report error if we received any
        for i, queryParam in ipairs(query) do
            return result, "unknown parameter: "..queryParam
        end
    end
    if details.strParams then
        for _, key in ipairs(queryKeys) do
            local value = query[key]
            debug.log(tostring(key).." -> "..tostring(value), "d")
            local destination = details.strParams[key:lower()]
            if destination == nil then
                destination = details.boolParams and details.boolParams[key:lower()]
                if destination == nil then
                    return result, "unknown parameter: "..key
                end
                -- so its a bool param -> convert str-value:
                value = (value:lower() == "true" or value:lower() == "on")
                if destination:sub(1, 1) == "!" then
                    destination = destination:sub(2)
                    value = not value
                end
            end
            result[destination] = value
        end
    end
    if details.boolParams then
        -- now set all boolean values that were not part of the query:
        for _, boolParam in pairs(details.boolParams) do
            local value = false -- wasn't added to query thus false
            if boolParam:sub(1, 1) == "!" then
                boolParam = boolParam:sub(2)
                value = not value
            end
            if result[boolParam] == nil then
                result[boolParam] = value
            end
        end
    end
    return result
end
function getCmdHelp(cmd)
    local result = ""
    if cmd.strParams and cmd.boolParams then
        result = stringx.join(", ", tablex.keys(tablex.union(cmd.strParams, cmd.boolParams))).."\n"
    elseif cmd.strParams then
        result = stringx.join(", ", tablex.keys(cmd.strParams)).."\n"
    elseif cmd.boolParams then
        result = stringx.join(", ", tablex.keys(cmd.boolParams)).."\n"
    else
        result = "[no parameters]"
    end
    local conversion = ""
    if cmd.strParams then
        for query, param in pairs(cmd.strParams) do
            if query ~= param then
                conversion = conversion.."  "..query.." -> "..param.."\n"
            end
        end
    end
    if cmd.boolParams then
        for query, param in pairs(cmd.boolParams) do
            if query ~= param then
                conversion = conversion.."  "..query.." -> "..param.."\n"
            end
        end
    end
    if #conversion > 0 then
        result = result.."\nMapping of query-parameters to function-parameters:\n"
        result = result..conversion
    end
    result = result.."\n"..debug.getDocu(cmd.executeMe)

    return result
end
function getCmdList()
    local result = "choose one of the following actions: help, "..stringx.join(", ", tablex.keys(uniqueCmds))
    result = result.."\n\nYou can get detailed help for an action by adding 'help' as parameter, e.g.: ..?action=call&help\n"
    return result
end
local mCommandListener = nil
function stopListeningToCallCmds()
    http.stop_listen(mCommandListener)
    mCommandListener = nil
    debug.log("no longer listening to incoming ActionUrl's")
end
function listenForCallCmds()
    mCommandListener = http.listen("command", onActionUrl, true)
    debug.log("listening for commands on https://" .. getIp() .. "/api/v1/exec/command?action=...")
end
function getIp()
    local ips = phoneInfo.getIPs()
    if #ips > 0 then
        return ips[1]
    end
    return "[phoneIp]"
end
config.register(SETTING_PATH, settingChanged)
settingChanged(SETTING_PATH)--]]></code><!--
--></luaScript><!--
--></luaAutoStarters>