<?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>