<?xml version="1.0" encoding="utf-8"?>
<luaAutoStarters version="1.34.0">
<luaScriptParameterized name="ActionUrlReporting">
<code><![CDATA[
local stringx = require "pl.stringx"
local SERVER_SETTING_PATH = "remoteAccess/actionUrlServer"
local mServer = ""
local mDbgTag = "ActionUrlReporting: "
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(mDbgTag.."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(mDbgTag.."unknown call-event: "..event, "w")
end
end
-- event-query-strings that are still waiting to be send to listening-servers. Each must wait till all servers somehow answered to the previous event
local mQueuedEvents = {}
-- set of all servers that were send an event and that did not yet answer their GET (event) request
local mServersThatStillNeedToAnswer = {}
-- todo
local mInformServers = nil
function createNewPostedToServerCallback(server)
onPostedToServer = function (responseCode, content, headers)
if responseCode ~= 200 then
if server == mServer then
debug.log(mDbgTag.."action-url-server from settings ("..SERVER_SETTING_PATH.."): '"..tostring(server).."' answered with "..tostring(responseCode), "w")
else
debug.log(mDbgTag.."removing dynamic action-url-server '"..tostring(server).."', cause last action-posting was answered with "..tostring(responseCode), "w")
end
if shared["action_url_servers"] ~= nil then
shared["action_url_servers"][server] = nil
end
end
mServersThatStillNeedToAnswer[server] = nil
if not next(mServersThatStillNeedToAnswer) and #mQueuedEvents > 0 then
local query = table.remove(mQueuedEvents, 1)
mInformServers(query)
end
end
return onPostedToServer
end
mInformServers = function (query)
local serversToInform = {}
if #mServer > 0 then
serversToInform[mServer] = true
end
if shared["action_url_servers"] ~= nil then
for server, _ in pairs(shared["action_url_servers"]) do
serversToInform[server] = true
end
end
if next(serversToInform) then
-- we have at least one interested server
debug.log(mDbgTag.."sending action-url event: "..query, "d")
mServersThatStillNeedToAnswer = serversToInform
for server, _ in pairs(serversToInform) do
http.get{url=server..query, callback=createNewPostedToServerCallback(server)}
end
end
end
function informServer(query)
if #mQueuedEvents == 0 and not next(mServersThatStillNeedToAnswer) then
-- no pending previous event requests -> send new one directly
mInformServers(query)
else
debug.log(mDbgTag.."queueing event '"..tostring(query).."', cause last action-posting was not yet answered by all servers.")
mQueuedEvents[#mQueuedEvents+1] = 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(mDbgTag.."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(mDbgTag.."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(mDbgTag.."call state changed: "..callId..", state="..state, "i")
if state == "holding" then
event = "holding"
elseif state == "active" then
event = "connected"
else
debug.log(mDbgTag.."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(mDbgTag.."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(mDbgTag.."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(mDbgTag..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(mDbgTag.."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(mDbgTag.."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>
</luaScriptParameterized>
<luaScriptParameterized 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
local mDbgTag = "ActionUrlAccepting: "
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
function addActionUrlMonitor(paramMap)
local server = ""
if paramMap.server then
server = paramMap.server
end
if #server == 0 then
debug.log(mDbgTag.."ignoring request to add dynamic action-url-server, missing server value.", "e")
return
end
if not stringx.startswith(string.lower(server), "http") then
server = "http://"..server
end
if shared["action_url_servers"] == nil then
shared["action_url_servers"] = {}
end
debug.log(mDbgTag.."adding dynamic action-url-server '"..tostring(server).."'")
shared["action_url_servers"][server] = true
end
function removeActionUrlMonitor(paramMap)
local server = ""
if paramMap.server then
server = paramMap.server
end
if #paramMap.server == 0 then
debug.log(mDbgTag.."ignoring request to remove dynamic action-url-server, missing server value.", "e")
return
end
if not stringx.startswith(string.lower(server), "http") then
server = "http://"..server
end
if shared["action_url_servers"] ~= nil then
debug.log(mDbgTag.."removing dynamic action-url-server '"..tostring(server).."'")
shared["action_url_servers"][server] = nil
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.update = { executeMe=system.intent, strParams = { url="data" }, boolParams = { fireandforget="!returnHttpResult"},
fixedParams = { action="auerswald.intent.action.UPDATE_CHECK", component="de.auerswald.provisioning/.UpdateReceiver", asBroadcast=true, extras={force=true}} }
cmdMap.show = { executeMe=system.toast, strParams = { text="text"}, boolParams = { fireandforget="!returnHttpResult"} }
-- local functions:
cmdMap.addMonitor = { executeMe=addActionUrlMonitor, strParams = { server="server"} }
cmdMap.removeMonitor = { executeMe=removeActionUrlMonitor, strParams = { server="server"} }
-- aliases:
cmdMap.offhook = { duplicates="accept" }
cmdMap.hangup = { duplicates="terminate" }
cmdMap.addmonitor = { duplicates="addMonitor" }
cmdMap.removemonitor = { duplicates="removeMonitor" }
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(mDbgTag..url, "d")
debug.log(mDbgTag..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(mDbgTag.."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.fixedParams then
for key, val in pairs(details.fixedParams) do
result[key] = val
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(mDbgTag..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
if cmd.executeMe == addActionUrlMonitor then
result = result.."\n".."nil addMonitor(server)\n\nAdd a server for receiving action-url events. This server will be informed of new call events until it is either removed via action=removeServer or when it doesn't answer to a GET (event) request with 200 OK or when the phone reboots."
elseif cmd.executeMe == removeActionUrlMonitor then
result = result.."\n".."nil removeMonitor(server)\n\nRemove a previously installed dynamic action-url server."
else
result = result.."\n"..debug.getDocu(cmd.executeMe)
end
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(mDbgTag.."no longer listening to incoming ActionUrl's")
end
function listenForCallCmds()
mCommandListener = http.listen("command", onActionUrl, true)
debug.log(mDbgTag.."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>
</luaScriptParameterized>
<luaScriptParameterized name="@string/action_url_periodic_ping">
<code><![CDATA[
function getIp()
local ips = phoneInfo.getIPs()
if #ips > 0 then
return ips[1]
end
return "[phoneIp]"
end
function sendPing()
time.callbackIn(sendPing, pInterval)
local mac = phoneInfo.getMacAddress()
local ip = getIp()
local username = config.get("/identities/identity[1]/username")
local request = pServer.."?event=ping&mac="..mac.."&ip="..ip.."&uri="..username
debug.log("http-get pinging: "..request)
http.get(request)
end
if #pServer > 0 and pInterval > 0 then
sendPing()
end
]]></code>
<parameters>
<parameter name="@string/server" type="web_uri" variable="pServer" description="where to send http-get-pings to" />
<parameter name="@string/seconds_interval" type="number" variable="pInterval" description="time between pings (seconds)" value="60" />
</parameters>
</luaScriptParameterized>
</luaAutoStarters>