Skip to content

Default LuaAutostarters.xml#

LuaAutostarters.xml
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
<?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>