en:products:comfortel-d-series:developer:keys:templates:examples:teams_integration:teams_all.xml

teams_all.xml
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <configuration patchDefault="true">
  3. <luaAutoStarters>
  4. <luaScript name="Automatic Token Request">
  5. <code><![CDATA[
  6. -- Variables that depend on Microsofts API
  7. local refreshUrl = "https://login.microsoftonline.com/organizations/oauth2/v2.0/token"
  8. local content = "client_id=[Client-ID]&scope=presence.read&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient&grant_type=refresh_token&refresh_token="
  9.  
  10. -- Configure the timing. Tokens last for 3599 seconds so the overall refresh-time must be lower.
  11. local refreshInterval = 20 -- check every 20 seconds if there is something new to do
  12. local refreshCountdownMax = 150 -- once authentication was successful, wait 150 times 20 seconds until refresh
  13. local refreshCountdown = 0 -- current live-ticker decremented once every 20 seconds, new credentials are fetched when it reaches 0
  14.  
  15. -- Analyzes the response from Microsoft to the request of (new) tokens
  16. local function analyzeResponse(responseCode, responseBody, responseHeaders)
  17. if responseCode == 200 and responseBody then
  18. values = json.eval(responseBody)
  19. debug.log("Teams Automatic Token Refresh: updating tokens")
  20. persisted["msTeamsToken"] = values["access_token"]
  21. persisted["msTeamsRefresh"] = values["refresh_token"]
  22. refreshCountdown = refreshCountdownMax
  23. else
  24. debug.log("Teams Automatic Token Refresh: failed to receive tokens", "e")
  25. refreshCountdown = 0
  26. end
  27. shared["msTeamsReAuthNeeded"] = "false"
  28. end
  29.  
  30. local function refreshLoop()
  31. if persisted["msTeamsToken"] and persisted["msTeamsRefresh"] then
  32. refreshCountdown = refreshCountdown - 1
  33. if shared["msTeamsReAuthNeeded"] and shared["msTeamsReAuthNeeded"] == "true" then
  34. debug.log("Teams Automatic Token Refresh: other scripts failed to authenticate -> trigger refresh at "..tostring(refreshCountdown), "e")
  35. refreshCountdown = 0
  36. end
  37. if refreshCountdown <= 0 then
  38. debug.log("Teams Automatic Token Refresh: inquire token update", "d")
  39. http.post(refreshUrl, analyzeResponse, content..persisted["msTeamsRefresh"])
  40. end
  41. else
  42. debug.log("Teams Automatic Token Refresh: not yet valid", "i")
  43. refreshCountdown = 0
  44. end
  45. time.callbackIn(refreshLoop, refreshInterval)
  46. end
  47.  
  48. -- start refreshing tokens
  49. refreshLoop()
  50. ]]></code>
  51. </luaScript>
  52. <luaScript name="Get User-Code">
  53. <code><![CDATA[
  54. -- Variables that depend on Microsofts API
  55. local tokenRequestUrl = "https://login.microsoftonline.com/organizations/oauth2/v2.0/token"
  56. local tokenRequestContent ="client_id=[Client-ID]&scope=presence.read&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient&grant_type=authorization_code&code="
  57.  
  58. -- Analyze the response from Microsoft to the request of tokens
  59. local function analyzeTokenResponse(responseCode, responseBody, responseHeaders)
  60. if responseBody then
  61. debug.log("Teams Get User-Code got response: "..responseBody)
  62. end
  63. if responseCode == 200 and responseBody then
  64. values = json.eval(responseBody)
  65. debug.log("Teams Get User-Code storing access_token: "..values["access_token"])
  66. debug.log("Teams Get User-Code storing refresh_token: "..values["refresh_token"])
  67. persisted["msTeamsToken"] = values["access_token"]
  68. persisted["msTeamsRefresh"] = values["refresh_token"]
  69. end
  70. end
  71.  
  72. -- Get a code out of the query parameters of the given URL and calls Microsofts servers to receive the tokens with the code
  73. local function analyzeRequest(requestUrl, requestBody, requestHeader, requestVariables)
  74. x1, x2 = string.find(requestVariables["query_string"], "code=")
  75. y1, y2 = string.find(requestVariables["query_string"], "&session_state")
  76. debug.log("Teams Get User-Code got request to start: "..x1.." - "..x2.." - "..y1.." - "..y2)
  77. if x1 and y2 then
  78. code = string.sub(requestVariables["query_string"], x2+1, y1-1)
  79. http.post(tokenRequestUrl,analyzeTokenResponse,tokenRequestContent..code)
  80. end
  81. end
  82.  
  83. -- Listen to the address <Phone-IP>/api/v1/exec/getTeamsCode and gives the request to "analyzeRequest"
  84. http.listen("getTeamsCode", analyzeRequest, true)]]></code>
  85. </luaScript>
  86. </luaAutoStarters>
  87. <templates>
  88. <template name="@string/speed_dial_title" prio="30" icon="@drawable/invite">
  89. <keyConfiguration>
  90. <lua>
  91. <code><![CDATA[local gUserpath = "/identities/identity["..pIdentity.."]/"
  92. local gSubscriptionIsTrying = false
  93. local gSubscriptionIsWorking = false
  94. local gUser = "" -- username of associated identity
  95. local gDomain = "" -- domain (registrar) of associated identity
  96. local gPickupCode = nil -- pickup-code of associated identity
  97. local gDoPickupWithReplaces = false -- whether or not to use a replaces header (instead of pickup-code) to pick a call
  98. local gRemoteUri = nil -- sipUri-object of remote
  99. local gRemoteUser = nil -- user-part of remote uri
  100. local gOngoingSubscription = nil
  101. local gWithSubscription = pSubscriptionEnabled=="true"
  102. local gWithPickup = pDoPickup=="true"
  103. local gWithMissed = pBlinkOnMissed=="true"
  104. local gWithAutoAnswer = pRequestAutoAnswer=="true"
  105. local gHaveMissedCall = false
  106. -- remember what we're signaling to the user, this will influence what is expected when key is pressed
  107. -- one of:
  108. -- - errors-state: error, trying
  109. -- - subscription-event: idle, pickable, busy
  110. -- - local call: calling, active, holding, ringing
  111. -- - missed call: missed
  112. -- - nothing going on: idle
  113. local gLastReportedState = nil
  114. local gLocalCallId = nil -- wenn we have local call with gRemoteUser -> store its call-id here (e.g. TC@4)
  115. local gLocalCallState = nil -- wenn we have local call with gRemoteUser -> store its state here (calling, active, holding or ringing)
  116. local gFctName = "Blf" -- name for this key in logs (uri and identity will be added during initialization)
  117. -- keep infos of all monitored calls on our remote, only used when gWithSubscription==true
  118. -- contains these entries:
  119. -- - "entity" : attribute from dialog-info-xml
  120. -- - "version" : attribute from dialog-info-xml
  121. -- - "metaState" : one of free/pickable/busy -> combined meta-state accumulated from all dialogs (monitored calls)
  122. -- - "activeDialogs" : array of all dialog-ids of the all dialogs responsible for signaled meta-state.
  123. -- - "dialogs" : map by dialog-id of all dialogs (monitored calls)
  124. local gDialogInfo = nil
  125. local gTeamsFailedIdAquire = false
  126. local gTeamsReceivedServerResponse = false
  127. local gTeamsServerResponseWasGood = false
  128. local gTeamsServerResponse = 42
  129. local gTeamsState = "Unknown" -- one of: Available, Away, BeRightBack, Busy, DoNotDisturb, Offline, Unknown
  130. local gTeamsRefreshInterval = 17 -- poll for new state every xy seconds
  131. local gTeamsId = nil
  132. local gTeamsPresenceUrl = "https://graph.microsoft.com/beta/communications/presences/"
  133. local gTeamsFetchIdUrl = "https://graph.microsoft.com/beta/users/"
  134.  
  135. local function isValid() -- reports if key has all it's params set to valid values. Set to nil to keep this default
  136. if pIdentity == "" or pIdentity == nil then
  137. return false
  138. end
  139. if pRemote == "" or pRemote == nil then
  140. return false
  141. end
  142. return true
  143. end
  144.  
  145. -- Create value for replaces-header for first pickable dialog
  146. local function getFirstReplacesInfo()
  147. if #gDialogInfo.activeDialogs == 0 or not gDialogInfo.metaState == "pickable" then
  148. debug.log(gFctName..": cannot find pickable call for replaces", "e")
  149. return nil
  150. end
  151. local dlgXml = gDialogInfo.dialogs[gDialogInfo.activeDialogs[1]]
  152. local callId = dlgXml["call-id"]
  153. if not callId or string.len(callId) == 0 then
  154. debug.log(gFctName..": cannot find call-id for replaces-header", "e")
  155. return nil
  156. end
  157. return sipTools.mkReplaces{callId=callId, to=dlgXml["remote-tag"], from=dlgXml["local-tag"]}
  158. end
  159.  
  160. -- analyze existing dialogs and find a text that describes who our monitored remote is talking to
  161. local function getBlfRemoteText()
  162. if #gDialogInfo.activeDialogs == 0 then
  163. return nil
  164. elseif #gDialogInfo.activeDialogs > 1 and not gDoPickupWithReplaces then
  165. -- wouldn't know which one would be picked
  166. return "multiple calls"
  167. end
  168. -- find something to name the pick-party:
  169. local remote = gDialogInfo.dialogs[gDialogInfo.activeDialogs[1]]:find("remote")
  170. if not remote then
  171. debug.log(gFctName..": missing <remote> within <dialog-info>, cannot name remote party", "w")
  172. return "@string/call_unknown"
  173. end
  174. local dispName = remote:find("identity", "display")
  175. if dispName and string.len(dispName.display) > 0 then return dispName.display end
  176. local remote_usr = remote:find("identity")
  177. if not remote_usr then remote_usr = remote:find("target") end
  178. if not remote_usr or not remote_usr[1] then
  179. debug.log(gFctName..": missing remote-uri within <dialog-info>, cannot name remote party", "w")
  180. return "@string/call_unknown"
  181. end
  182. if string.find(string.lower(remote_usr[1]), "anonymous") then
  183. return "@string/call_anonyme"
  184. end
  185. local remote_uri = sipTools.parseUri{sipUri=remote_usr[1]}
  186. if not remote_uri or not remote_uri:getUiNumber() then
  187. return remote_usr[1]
  188. end
  189. return remote_uri:getUiNumber()
  190. end
  191.  
  192. local function updateKey()
  193. local desiredInfo = " "
  194. local desiredIcon = nil
  195.  
  196. if not isValid() then
  197. gLastReportedState = "error"
  198. key:setLed("orange", false)
  199. desiredInfo = "setup failed"
  200. desiredIcon = "@drawable/block"
  201. else
  202. gLastReportedState = "idle"
  203. if gLocalCallState == "calling" then
  204. gLastReportedState = "calling"
  205. key:setLed("green", false)
  206. desiredInfo = "calling"
  207. elseif gLocalCallState == "ringing" then
  208. gLastReportedState = "ringing"
  209. key:setLed("green", true)
  210. desiredInfo = "ringing"
  211. elseif gLocalCallState == "holding" then
  212. gLastReportedState = "holding"
  213. key:setLed("green", true)
  214. desiredInfo = "holding"
  215. elseif gLocalCallState == "active" then
  216. gLastReportedState = "active"
  217. key:setLed("green", false)
  218. desiredInfo = "active"
  219. elseif gWithSubscription then
  220. if gSubscriptionIsWorking then
  221. if gDialogInfo.metaState == "busy" then
  222. gLastReportedState = "busy"
  223. key:setLed("red", false)
  224. desiredInfo = getBlfRemoteText()
  225. elseif gDialogInfo.metaState == "pickable" then
  226. gLastReportedState = "pickable"
  227. key:setLed("red", true)
  228. desiredIcon = "@drawable/pick_up"
  229. desiredInfo = getBlfRemoteText()
  230. else
  231. gLastReportedState = "idle"
  232. key:setLed("off")
  233. end
  234. elseif gSubscriptionIsTrying then
  235. gLastReportedState = "trying"
  236. key:setLed("orange", true)
  237. desiredInfo = "trying to subscribe"
  238. else
  239. gLastReportedState = "error"
  240. key:setLed("orange", false)
  241. desiredInfo = "failed to subscribe"
  242. end
  243. else
  244. key:setLed("off")
  245. end
  246. if gLastReportedState == "idle" and gTeamsState ~= "Unknown" then
  247. desiredInfo = gTeamsState
  248. if gTeamsState == "DoNotDisturb" or gTeamsState == "Busy" then
  249. gLastReportedState = "busy"
  250. key:setLed("red")
  251. elseif gTeamsState == "Available" then
  252. gLastReportedState = "idle"
  253. key:setLed("green")
  254. else -- Away, BeRightBack, Offline
  255. gLastReportedState = "idle"
  256. key:setLed("off")
  257. end
  258. end
  259. if gLastReportedState == "idle" and gHaveMissedCall then
  260. gLastReportedState = "missed"
  261. key:setLed("green", true)
  262. desiredInfo = "missed call"
  263. end
  264. end
  265. key:setInfo(desiredInfo)
  266. key:setIcon(desiredIcon)
  267. end
  268.  
  269. function onDbResult(entries)
  270. gHaveMissedCall = false
  271. for i, queryParam in ipairs(entries) do
  272. local entryUser = sipTools.parseUri{sipUri=queryParam.number}:getUser()
  273. if entryUser == gRemoteUser then
  274. debug.log(gFctName..": found missed call from "..tostring(queryParam.number), "d")
  275. gHaveMissedCall = true
  276. break
  277. end
  278. end
  279. updateKey()
  280. end
  281.  
  282. function onCallEvent(event, call1, call2)
  283. if event == nil or call1 == nil then
  284. debug.log(gFctName..": invalid parameters at callListChanged("..tostring(event).." ,"..tostring(call1)..")", "e")
  285. return
  286. end
  287. local callId = call1:getId()
  288. local state = call1:getState()
  289. local number, name = call1:getRemote()
  290. local regId = tostring(call1:getIdentityIndex())
  291. if regId ~= pIdentity then
  292. return
  293. end
  294. debug.log("received "..tostring(event).." for call to "..tostring(number)..", id="..tostring(regId)..", state="..tostring(state)..", callId="..tostring(callId), "v")
  295. local remoteUri = sipTools.parseUri{sipUri=number}
  296. local remoteUser = remoteUri:getUser()
  297. if remoteUser ~= gRemoteUser then
  298. if callId == gLocalCallId then
  299. debug.log("call-partner of local "..callId.." changed, call no longer belongs to "..gFctName, "d")
  300. gLocalCallId = nil
  301. gLocalCallState = nil
  302. updateKey()
  303. end
  304. return
  305. end
  306. if state == "calling" or state == "ringing" or state == "active" or state == "holding" then
  307. if gLocalCallId ~= callId then
  308. debug.log(gFctName.." found new local call "..callId.." in state "..state, "d")
  309. elseif gLocalCallState ~= state then
  310. debug.log(gFctName.." "..callId.." changed state to "..state, "d")
  311. end
  312. gLocalCallId = callId
  313. gLocalCallState = state
  314. else
  315. debug.log(gFctName.." "..callId.." ended", "d")
  316. gLocalCallId = nil
  317. gLocalCallState = nil
  318. end
  319. updateKey()
  320. end
  321.  
  322. local function createDialogInfoStruct(dlgVers, dlgEntity)
  323. local result = {}
  324. result.entity = dlgEntity
  325. result.version = dlgVers
  326. result.metaState = "free"
  327. result.activeDialogs = {}
  328. result.dialogs = {}
  329. return result
  330. end
  331.  
  332. local function handleNotify(subscr, data, headers)
  333. gSubscriptionIsWorking = true
  334. gSubscriptionIsTrying = false
  335. local dlgInfo = xml.eval(data)
  336. if dlgInfo ~= nil then
  337. debug.log(gFctName.." received Notify", "d")
  338. if dlgInfo:tag() ~= "dialog-info" then
  339. debug.log(gFctName..": did not receive a dialog-info, ignoring "..tostring(dlgInfo:tag()) , "e")
  340. return
  341. end
  342. local dlgVers = dlgInfo.version
  343. local dlgEntity = dlgInfo.entity
  344. local dlgState = dlgInfo.state
  345. if not dlgEntity or not dlgVers or not dlgState then
  346. debug.log(gFctName..": received dialog-info is missing essencial attributes, ignoring dialog-info with "
  347. ..tostring(dlgVers).." / "..tostring(dlgEntity).." / "..tostring(dlgState) , "e")
  348. return
  349. end
  350. if not gDialogInfo then
  351. -- assuming this is the first dialog-info we've received -> initialize gDialogInfo
  352. gDialogInfo = createDialogInfoStruct(dlgVers, dlgEntity)
  353. else
  354. if gDialogInfo.entity ~= dlgEntity then -- strange -> begin again
  355. debug.log(gFctName..": entity in dialog-info changed, re-initializing gDialogInfo", "w")
  356. gDialogInfo = createDialogInfoStruct(dlgVers, dlgEntity)
  357. elseif gDialogInfo.version >= dlgVers then
  358. debug.log(gFctName..": received outdated dialog-info -> ignoring version "..tostring(dlgVers), "i")
  359. end
  360. end
  361. if dlgState == "full" then
  362. gDialogInfo.dialogs = {}
  363. end
  364. for _,dlg in ipairs(dlgInfo) do
  365. local dlgId = dlg.id
  366. if dlgId and string.len(dlgId) > 0 then
  367. gDialogInfo.dialogs[dlgId] = dlg
  368. end
  369. end
  370. -- evaluate metaState:
  371. local busyDlgs = {}
  372. local pickableDlgs = {}
  373. for dlgId, dlg in pairs(gDialogInfo.dialogs) do
  374. local stateXml = dlg:find("state")
  375. local state = nil
  376. if stateXml and #stateXml >= 1 then state = stateXml[1] end
  377. if state == "confirmed" then
  378. busyDlgs[#busyDlgs+1] = dlgId
  379. elseif state == "terminated" then
  380. -- ignore
  381. else
  382. -- assuming proceeding, early oder trying
  383. local direction = dlg.direction
  384. if direction == "initiator" then
  385. busyDlgs[#busyDlgs+1] = dlgId
  386. else
  387. pickableDlgs[#pickableDlgs+1] = dlgId
  388. end
  389. end
  390. end
  391. if #pickableDlgs > 0 then
  392. gDialogInfo.activeDialogs = pickableDlgs
  393. gDialogInfo.metaState = "pickable"
  394. elseif #busyDlgs > 0 then
  395. gDialogInfo.activeDialogs = busyDlgs
  396. gDialogInfo.metaState = "busy"
  397. else
  398. gDialogInfo.activeDialogs = {}
  399. gDialogInfo.metaState = "free"
  400. end
  401. debug.log(gFctName..": state from Notify: "..gDialogInfo.metaState, "d")
  402. else
  403. debug.log(gFctName..": received empty notify from "..subscr:getUri() , "e")
  404. end
  405. updateKey()
  406. end
  407.  
  408. local function unsubscribe()
  409. gSubscriptionIsWorking = false
  410. gSubscriptionIsTrying = false
  411. if (gOngoingSubscription) then
  412. gOngoingSubscription:unsubscribe()
  413. gOngoingSubscription = nil
  414. end
  415. end
  416.  
  417. local function subscriptionTerminated()
  418. gSubscriptionIsWorking = false
  419. gSubscriptionIsTrying = false
  420. updateKey()
  421. end
  422.  
  423. local function onSubscrIsTrying()
  424. gSubscriptionIsWorking = false
  425. gSubscriptionIsTrying = true
  426. updateKey()
  427. end
  428.  
  429. local function subscribe()
  430. unsubscribe()
  431. gOngoingSubscription = sip.subscribe{uri=pRemote, onNotify=handleNotify, line=pIdentity,
  432. onTerminated=subscriptionTerminated, onTrying=onSubscrIsTrying}
  433. end
  434.  
  435. local function identityChanged(path)
  436. gPickupCode = config.get(gUserpath .. "pickupCode")
  437. gDoPickupWithReplaces = not gPickupCode or string.len(gPickupCode) == 0
  438. local new_user = config.get(gUserpath .. "username")
  439. local new_domain = sip.identities.getDomain(pIdentity);
  440. if new_user ~= gUser or new_domain ~= domain then
  441. unsubscribe()
  442. gUser = new_user
  443. gDomain = new_domain
  444. subscribe()
  445. updateKey()
  446. end
  447. end
  448.  
  449. local function setupRegChange()
  450. config.register(gUserpath, identityChanged)
  451. identityChanged()
  452. end
  453.  
  454. function onKeyUp()
  455. local headers = {}
  456. local doPickup = false
  457.  
  458. debug.log(gFctName.."-key pressed", "d")
  459. if not isValid() then
  460. debug.log("missing parameter(s), "..gFctName.."-key won't work ever", "e")
  461. return
  462. elseif gLastReportedState == "error" then
  463. debug.log(gFctName..": subscription had failed before, attempting restart", "d")
  464. subscribe()
  465. updateKey()
  466. return
  467. elseif gLastReportedState == "trying" then
  468. debug.log(gFctName..": subscription is being tried: stopping and restarting", "d")
  469. unsubscribe()
  470. subscribe()
  471. updateKey()
  472. return
  473. elseif gLastReportedState == "calling" then
  474. debug.log(gFctName..": outgoing call thats not connected yet -> ignore key-press", "d")
  475. return
  476. elseif gLastReportedState == "active" then
  477. debug.log(gFctName..": connected call -> hold", "d")
  478. sip.calls.hold(gLocalCallId)
  479. return
  480. elseif gLastReportedState == "holding" then
  481. debug.log(gFctName..": held call -> retrieve", "d")
  482. sip.calls.resume(gLocalCallId)
  483. return
  484. elseif gLastReportedState == "ringing" then
  485. debug.log(gFctName..": ringing call -> accept", "d")
  486. sip.calls.accept(gLocalCallId)
  487. return
  488. elseif gLastReportedState == "pickable" then
  489. debug.log(gFctName..": picking the call", "d")
  490. local replHdr, replVal = getFirstReplacesInfo()
  491. if replHdr then -- always add replace, even with pickup-code .. cant hurt
  492. headers[replHdr] = replVal
  493. end
  494. if not gDoPickupWithReplaces then
  495. debug.log(gFctName..": picking with pickup-code '"..gPickupCode.."'", "d")
  496. doPickup = true
  497. else
  498. if replHdr then
  499. debug.log(gFctName..": picking with Replaces-Header: "..replVal, "d")
  500. else
  501. debug.log(gFctName..": cannot pick, replaces-info unavailable", "e")
  502. return
  503. end
  504. end
  505. else
  506. debug.log(gFctName..": simply calling the remote (state: "..gLastReportedState..", autoAnswer: "..tostring(gWithAutoAnswer)..")", "d")
  507. if gWithAutoAnswer then
  508. headers['Alert-Info'] = '<http://192.168.182.156>;info=alert-autoanswer'
  509. end
  510. end
  511. sip.invite{uri=pRemote, line=pIdentity, hidden=false, headers=headers, pickup=doPickup}
  512. end
  513.  
  514. --Analyzes the response of Microsoft and check if the user in question is available
  515. local function onTeamsPresenceStatus(responseCode, responseBody, responseHeaders)
  516. gTeamsReceivedServerResponse = true
  517. gTeamsServerResponseWasGood = responseCode == 200 and responseBody
  518. gTeamsServerResponse = responseCode
  519. if gTeamsServerResponseWasGood then
  520. values = json.eval(responseBody)
  521. if values["availability"] == "AvailableIdle" then
  522. gTeamsState = "Available"
  523. elseif values["availability"] == "PresenceUnknown" then
  524. gTeamsState = "Unknown"
  525. elseif values["availability"] == "BusyIdle" then
  526. gTeamsState = "Busy"
  527. else
  528. gTeamsState = values["availability"]
  529. end
  530. else
  531. gTeamsState = "Unknown"
  532. if responseCode == 401 then
  533. shared["msTeamsReAuthNeeded"] = "true"
  534. end
  535. end
  536. updateKey()
  537. end
  538.  
  539. --Requests the teams status of someone
  540. local function requestTeamsPresenceStatus()
  541. gTeamsReceivedServerResponse = false
  542. local header = {}
  543. header["Authorization"] = "Bearer "..persisted["msTeamsToken"]
  544. http.get(gTeamsPresenceUrl..gTeamsId, onTeamsPresenceStatus, header)
  545. end
  546.  
  547. -- read persons ID from response
  548. local function onTeamsIdResponse(responseCode, responseBody, responseHeaders)
  549. gTeamsServerResponse = responseCode
  550. if responseCode== 200 and responseBody then
  551. values = json.eval(responseBody)
  552. gTeamsId = values["id"]
  553. debug.log(gFctName..", Teams: got id"..gTeamsId, "v")
  554. requestTeamsPresenceStatus()
  555. else
  556. debug.log(gFctName..", Teams: failed to inquire id", "d")
  557. gTeamsFailedIdAquire = true
  558. if responseCode == 401 then
  559. shared["msTeamsReAuthNeeded"] = "true"
  560. end
  561. end
  562. updateKey()
  563. end
  564.  
  565. -- if pTeamsId is an e-mail -> requests basic data from Microsoft to get the ID
  566. local function fetchTeamsId()
  567. x,y = string.find(pTeamsId, "@", 1, true)
  568. if x ~= nil then
  569. local header = {}
  570. header["Authorization"] = "Bearer "..persisted["msTeamsToken"]
  571. http.get(gTeamsFetchIdUrl..pTeamsId, onTeamsIdResponse, header)
  572. else
  573. gTeamsId = pTeamsId
  574. debug.log(gFctName..", Teams: got id"..gTeamsId, "v")
  575. requestTeamsPresenceStatus()
  576. end
  577. end
  578.  
  579. local function teamsRefreshLoop()
  580. if not persisted["msTeamsToken"] then
  581. debug.log(gFctName..", Teams: not ready yet", "d")
  582. elseif not pTeamsId then
  583. debug.log(gFctName..", Teams: disabled", "d")
  584. return
  585. elseif not gTeamsId then
  586. debug.log(gFctName..", Teams: fetch id", "v")
  587. fetchTeamsId()
  588. else
  589. requestTeamsPresenceStatus()
  590. end
  591. time.callbackIn(teamsRefreshLoop, gTeamsRefreshInterval)
  592. end
  593.  
  594. local function initialize()
  595. if isValid() then
  596. setupRegChange()
  597. else
  598. debug.log("missing parameters, "..gFctName.."-key won't work", "e")
  599. updateKey()
  600. return
  601. end
  602. gRemoteUri = sipTools.parseUri{sipUri=pRemote, fromUserInput=true, idIdx=pIdentity}
  603. gRemoteUser = gRemoteUri:getUser()
  604. local dbQuery = "(is_read=0 OR is_read is NULL ) AND new=1 AND subscription_id="..pIdentity.." AND number LIKE '%"..gRemoteUser.."%' AND (type=3 OR type=5)"
  605. database.subscribe{dbUri="content://call_log/calls", onChanged=onDbResult, query=dbQuery, columns={"number"}}
  606. sip.calls.listen(onCallEvent)
  607. gFctName = "BLF["..gRemoteUri:getUiNumber().." on id "..pIdentity.."]"
  608. teamsRefreshLoop()
  609. updateKey()
  610. end
  611.  
  612. initialize()]]></code>
  613. <params>
  614. <param name="pRemote"/>
  615. <param name="pIdentity"/>
  616. <param name="pSubscriptionEnabled"><value>true</value></param>
  617. <param name="pDoPickup"><value>true</value></param>
  618. <param name="pRequestAutoAnswer"><value>false</value></param>
  619. <param name="pBlinkOnMissed"><value>true</value></param>
  620. <param name="pTeamsId"/>
  621. </params>
  622. </lua>
  623. </keyConfiguration>
  624. <parameters>
  625. <parameter name="@string/uri" type="text">
  626. <path>//param[@name="pRemote"]/value</path>
  627. </parameter>
  628. <parameter optional="true" name="@string/identity" type="identity">
  629. <path>//param[@name="pIdentity"]/value</path>
  630. </parameter>
  631. <parameter optional="true" name="@string/subscription_enabled" type="boolean">
  632. <path>//param[@name="pSubscriptionEnabled"]/value</path>
  633. </parameter>
  634. <parameter optional="true" name="@string/do_pickup" type="boolean">
  635. <path>//param[@name="pDoPickup"]/value</path>
  636. </parameter>
  637. <parameter optional="true" name="@string/auto_answer" type="boolean">
  638. <path>//param[@name="pRequestAutoAnswer"]/value</path>
  639. </parameter>
  640. <parameter optional="true" name="@string/with_calllog" type="boolean">
  641. <path>//param[@name="pBlinkOnMissed"]/value</path>
  642. </parameter>
  643. <parameter optional="true" name="E-Mail" type="text">
  644. <path>//param[@name="pTeamsId"]/value</path>
  645. </parameter>
  646. </parameters>
  647. </template>
  648. <template name="Teams Presence">
  649. <keyConfiguration>
  650. <lua>
  651. <code><![CDATA[
  652. local gTeamsFailedIdAquire = false
  653. local gTeamsReceivedServerResponse = false
  654. local gTeamsServerResponseWasGood = false
  655. local gTeamsServerResponse = 42
  656. local gTeamsState = "Unknown" -- one of: Available, Away, BeRightBack, Busy, DoNotDisturb, Offline, Unknown
  657. local gTeamsRefreshInterval = 17 -- poll for new state every xy seconds
  658. local gTeamsId = nil
  659. local gTeamsPresenceUrl = "https://graph.microsoft.com/beta/communications/presences/"
  660. local gTeamsFetchIdUrl = "https://graph.microsoft.com/beta/users/"
  661.  
  662. --Updates the led depending on the Status
  663. local function updateKey()
  664. if gTeamsFailedIdAquire then
  665. key:setLed("orange")
  666. key:setInfo("error getting ID: "..tostring(gTeamsServerResponse))
  667. elseif not persisted["msTeamsToken"] then
  668. key:setLed("orange")
  669. key:setInfo("refresh failure")
  670. elseif not gTeamsReceivedServerResponse then
  671. key:setLed("orange", true)
  672. key:setInfo("aquiring")
  673. elseif not gTeamsServerResponseWasGood then
  674. key:setLed("orange")
  675. key:setInfo("server response: "..tostring(gTeamsServerResponse))
  676. elseif gTeamsState == "Available" then
  677. key:setLed("green")
  678. key:setInfo(gTeamsState)
  679. else
  680. key:setLed("red")
  681. key:setInfo(gTeamsState)
  682. end
  683. end
  684.  
  685. --Analyzes the response of Microsoft and check if the user in question is available
  686. local function onTeamsPresenceStatus(responseCode, responseBody, responseHeaders)
  687. gTeamsReceivedServerResponse = true
  688. gTeamsServerResponseWasGood = responseCode == 200 and responseBody
  689. gTeamsServerResponse = responseCode
  690. if gTeamsServerResponseWasGood then
  691. values = json.eval(responseBody)
  692. debug.log("Teams Presence: current activity is "..values["activity"].." in state "..values["availability"])
  693. if values["availability"] == "AvailableIdle" then
  694. gTeamsState = "Available"
  695. elseif values["availability"] == "PresenceUnknown" then
  696. gTeamsState = "Unknown"
  697. elseif values["availability"] == "BusyIdle" then
  698. gTeamsState = "Busy"
  699. else
  700. gTeamsState = values["availability"]
  701. end
  702. else
  703. gTeamsState = "Unknown"
  704. if responseCode == 401 then
  705. debug.log("Teams Presence: triggering re-auth")
  706. shared["msTeamsReAuthNeeded"] = "true"
  707. end
  708. end
  709. updateKey()
  710. end
  711.  
  712. --Requests the teams status of someone
  713. local function requestTeamsPresenceStatus()
  714. gTeamsReceivedServerResponse = false
  715. debug.log("Teams Presence: requesting state", "d")
  716. local header = {}
  717. header["Authorization"] = "Bearer "..persisted["msTeamsToken"]
  718. http.get(gTeamsPresenceUrl..gTeamsId, onTeamsPresenceStatus, header)
  719. end
  720.  
  721. -- read persons ID from response
  722. local function onTeamsIdResponse(responseCode, responseBody, responseHeaders)
  723. gTeamsServerResponse = responseCode
  724. if responseCode== 200 and responseBody then
  725. values = json.eval(responseBody)
  726. gTeamsId = values["id"]
  727. debug.log("Teams Presence: got id"..gTeamsId, "d")
  728. requestTeamsPresenceStatus()
  729. else
  730. gTeamsFailedIdAquire = true
  731. if responseCode == 401 then
  732. debug.log("Teams Presence: triggering re-auth")
  733. shared["msTeamsReAuthNeeded"] = "true"
  734. end
  735. end
  736. updateKey()
  737. end
  738.  
  739. -- if pTeamsId is an e-mail -> requests basic data from Microsoft to get the ID
  740. local function fetchTeamsId()
  741. x,y = string.find(pTeamsId, "@", 1, true)
  742. if x ~= nil then
  743. local header = {}
  744. header["Authorization"] = "Bearer "..persisted["msTeamsToken"]
  745. http.get(gTeamsFetchIdUrl..pTeamsId, onTeamsIdResponse, header)
  746. else
  747. gTeamsId = pTeamsId
  748. debug.log("Teams Presence: got id"..gTeamsId, "d")
  749. requestTeamsPresenceStatus()
  750. end
  751. end
  752.  
  753. local function teamsRefreshLoop()
  754. if not persisted["msTeamsToken"] then
  755. debug.log("Teams Presence: not ready yet", "d")
  756. elseif not pTeamsId then
  757. debug.log("Teams Presence: disabled", "d")
  758. return
  759. elseif not gTeamsId then
  760. debug.log("Teams Presence: fetch id", "d")
  761. fetchTeamsId()
  762. else
  763. requestTeamsPresenceStatus()
  764. end
  765. time.callbackIn(teamsRefreshLoop, gTeamsRefreshInterval)
  766. end
  767.  
  768. function onKeyUp()
  769. -- think of something to do
  770. end
  771.  
  772. teamsRefreshLoop()
  773. updateKey()
  774. ]]></code>
  775. <params>
  776. <param name="pTeamsId"/>
  777. </params>
  778. </lua>
  779. </keyConfiguration>
  780. <parameters>
  781. <parameter name="E-Mail">
  782. <path>//param[@name="pTeamsId"]/value</path>
  783. </parameter>
  784. </parameters>
  785. </template>
  786. <template name="Teams dnd toggle">
  787. <keyConfiguration>
  788. <lua>
  789. <code><![CDATA[
  790. local gTeamsFailedIdAquire = false
  791. local gTeamsReceivedServerResponse = false
  792. local gTeamsServerResponseWasGood = false
  793. local gTeamsServerResponse = 42
  794. local gTeamsState = "Unknown" -- one of: Available, Away, BeRightBack, Busy, DoNotDisturb, Offline, Unknown
  795. local gTeamsRefreshInterval = 7 -- poll for new state every xy seconds
  796. local gTeamsId = nil
  797. local gTeamsPresenceUrl = "https://graph.microsoft.com/beta/communications/presences/"
  798. local gTeamsFetchIdUrl = "https://graph.microsoft.com/beta/users/"
  799. -- variables needed to activate "Do not Disturb"
  800. local gDndOn = "##8*211"
  801. local gDndOff = "##8*210"
  802. local gUserpath = "/identities/identity["..pIdentity.."]/"
  803. local gUser = config.get(gUserpath .. "username")
  804.  
  805.  
  806. --Updates the led depending on the Status
  807. local function updateKey()
  808. if gTeamsFailedIdAquire then
  809. key:setLed("orange")
  810. key:setInfo("error getting ID: "..tostring(gTeamsServerResponse))
  811. elseif not persisted["msTeamsToken"] then
  812. key:setLed("orange")
  813. key:setInfo("refresh failure")
  814. elseif not gTeamsReceivedServerResponse then
  815. key:setLed("orange", true)
  816. key:setInfo("aquiring")
  817. elseif not gTeamsServerResponseWasGood then
  818. key:setLed("orange")
  819. key:setInfo("server response: "..tostring(gTeamsServerResponse))
  820. elseif gTeamsState == "Available" then
  821. key:setLed("green")
  822. key:setInfo(gTeamsState)
  823. else
  824. key:setLed("red")
  825. key:setInfo(gTeamsState)
  826. end
  827. end
  828.  
  829. --Analyzes the response of Microsoft and check if the user in question is available
  830. local function onTeamsPresenceStatus(responseCode, responseBody, responseHeaders)
  831. gTeamsReceivedServerResponse = true
  832. gTeamsServerResponseWasGood = responseCode == 200 and responseBody
  833. gTeamsServerResponse = responseCode
  834. if gTeamsServerResponseWasGood then
  835. values = json.eval(responseBody)
  836. debug.log("Teams Presence: current activity is "..values["activity"].." in state "..values["availability"])
  837. local newTeamsState = values["availability"]
  838. if values["availability"] == "AvailableIdle" then
  839. newTeamsState = "Available"
  840. elseif values["availability"] == "PresenceUnknown" then
  841. newTeamsState = "Unknown"
  842. elseif values["availability"] == "BusyIdle" then
  843. newTeamsState = "Busy"
  844. end
  845. if newTeamsState ~= gTeamsState then
  846. if newTeamsState == "Available" then
  847. sip.invite{uri=gDndOff.."#"..gUser, line=pIdentity, hidden=true}
  848. elseif gTeamsState == "Available" then
  849. sip.invite{uri=gDndOn.."#"..gUser, line=pIdentity, hidden=true}
  850. end
  851. gTeamsState = newTeamsState
  852. end
  853. else
  854. gTeamsState = "Unknown"
  855. if responseCode == 401 then
  856. debug.log("Teams Presence: triggering re-auth")
  857. shared["msTeamsReAuthNeeded"] = "true"
  858. end
  859. end
  860. updateKey()
  861. end
  862.  
  863. --Requests the teams status of someone
  864. local function requestTeamsPresenceStatus()
  865. gTeamsReceivedServerResponse = false
  866. debug.log("Teams Presence: requesting state", "d")
  867. local header = {}
  868. header["Authorization"] = "Bearer "..persisted["msTeamsToken"]
  869. http.get(gTeamsPresenceUrl..gTeamsId, onTeamsPresenceStatus, header)
  870. end
  871.  
  872. -- read persons ID from response
  873. local function onTeamsIdResponse(responseCode, responseBody, responseHeaders)
  874. gTeamsServerResponse = responseCode
  875. if responseCode== 200 and responseBody then
  876. values = json.eval(responseBody)
  877. gTeamsId = values["id"]
  878. debug.log("Teams Presence: got id"..gTeamsId, "d")
  879. requestTeamsPresenceStatus()
  880. else
  881. gTeamsFailedIdAquire = true
  882. if responseCode == 401 then
  883. debug.log("Teams Presence: triggering re-auth")
  884. shared["msTeamsReAuthNeeded"] = "true"
  885. end
  886. end
  887. updateKey()
  888. end
  889.  
  890. -- if pTeamsId is an e-mail -> requests basic data from Microsoft to get the ID
  891. local function fetchTeamsId()
  892. x,y = string.find(pTeamsId, "@", 1, true)
  893. if x ~= nil then
  894. local header = {}
  895. header["Authorization"] = "Bearer "..persisted["msTeamsToken"]
  896. http.get(gTeamsFetchIdUrl..pTeamsId, onTeamsIdResponse, header)
  897. else
  898. gTeamsId = pTeamsId
  899. debug.log("Teams Presence: got id"..gTeamsId, "d")
  900. requestTeamsPresenceStatus()
  901. end
  902. end
  903.  
  904. local function teamsRefreshLoop()
  905. if not persisted["msTeamsToken"] then
  906. debug.log("Teams Presence: not ready yet", "d")
  907. elseif not pTeamsId then
  908. debug.log("Teams Presence: disabled", "d")
  909. return
  910. elseif not gTeamsId then
  911. debug.log("Teams Presence: fetch id", "d")
  912. fetchTeamsId()
  913. else
  914. requestTeamsPresenceStatus()
  915. end
  916. time.callbackIn(teamsRefreshLoop, gTeamsRefreshInterval)
  917. end
  918.  
  919. function onKeyUp()
  920. -- think of something to do
  921. end
  922.  
  923. teamsRefreshLoop()
  924. updateKey()
  925. ]]></code>
  926. <params>
  927. <param name="pTeamsId"/>
  928. <param name="pIdentity"/>
  929. </params>
  930. </lua>
  931. </keyConfiguration>
  932. <parameters>
  933. <parameter name="E-Mail">
  934. <path>//param[@name="pTeamsId"]/value</path>
  935. </parameter>
  936. <parameter name="@string/identity">
  937. <path>//param[@name="pIdentity"]/value</path>
  938. </parameter>
  939. </parameters>
  940. </template>
  941. </templates>
  942. </configuration>
  • en/products/comfortel-d-series/developer/keys/templates/examples/teams_integration/teams_all.xml.txt
  • Last modified: 22.03.2022 13:19
  • by hoehne