Ver código fonte

Status change events callback implemented

Hal De 4 anos atrás
pai
commit
8f7dfe6a87
3 arquivos alterados com 74 adições e 35 exclusões
  1. 1 0
      app.env.dist
  2. 72 34
      app/app.py
  3. 1 1
      docker-compose.yml

+ 1 - 0
app.env.dist

@@ -12,3 +12,4 @@ APP_AMI_PING_INTERVAL=10
 APP_AMI_TIMEOUT=5
 APP_AUTH_HEADER=APP-auth-token
 APP_AUTH_SECRET=secret
+#APP_STATE_CALLBACK_URL=

+ 72 - 34
app/app.py

@@ -1,11 +1,11 @@
 #!/usr/bin/env python3
 import asyncio
+import aiohttp
 import logging
 import os
 import re
 import json
-import base64
-from quart import jsonify, request, render_template_string
+from quart import jsonify, request, render_template_string, abort
 from quart_openapi import Pint, Resource
 from http import HTTPStatus
 from panoramisk import Manager, Message
@@ -36,7 +36,10 @@ app.config.update({
   'AUTH_HEADER':            os.getenv('APP_AUTH_HEADER', 'APP-auth-token'),
   'AUTH_SECRET':            os.getenv('APP_AUTH_SECRET', '3bfbeaabf363dd64fe263bd36830a6b6'),
   'SWAGGER_JS_URL':         os.getenv('APP_SWAGGER_JS_URL', SWAGGER_JS_URL),
-  'SWAGGER_CSS_URL':        os.getenv('APP_SWAGGER_CSS_URL', SWAGGER_CSS_URL)})
+  'SWAGGER_CSS_URL':        os.getenv('APP_SWAGGER_CSS_URL', SWAGGER_CSS_URL),
+  'STATE_CALLBACK_URL':     os.getenv('APP_STATE_CALLBACK_URL', None),
+  'STATE_CACHE':           {'user':{},
+                            'presence':{}}})
 
 manager = Manager(
   loop=main_loop,
@@ -77,19 +80,35 @@ class AuthMiddleware:
 
 app.asgi_app = AuthMiddleware(app.asgi_app)
 
+@manager.register_event('FullyBooted')
+async def fullyBootedCallback(mngr: Manager, msg: Message):
+  await refreshStatesCache()
+
 @manager.register_event('ExtensionStatus')
 async def extensionStatusCallback(mngr: Manager, msg: Message):
-  user = msg.exten
-  hint = msg.hint
+  user = msg.exten #hint = msg.hint
   state = msg.statustext.lower()
-  print('ExtensionStatus', user, hint, state)
+  if user in app.config['STATE_CACHE']['user']:
+    _combinedState = getUserStateCombined(user)
+    app.config['STATE_CACHE']['user'][user] = state
+    combinedState = getUserStateCombined(user)
+    if combinedState != _combinedState:
+      await userStateChangeCallback(user, combinedState)
 
 @manager.register_event('PresenceStatus')
 async def presenceStatusCallback(mngr: Manager, msg: Message):
-  user = msg.exten
-  hint = msg.hint
+  user = msg.exten #hint = msg.hint
   state = msg.status.lower()
-  print('PresenceStatus', user, hint, state)
+  if user in app.config['STATE_CACHE']['user']:
+    _combinedState = getUserStateCombined(user)
+    app.config['STATE_CACHE']['presence'][user] = state
+    combinedState = getUserStateCombined(user)
+    if combinedState != _combinedState:
+      await userStateChangeCallback(user, combinedState)
+
+@app.before_first_request
+async def initHttpClient():
+  app.config['HTTP_CLIENT'] = aiohttp.ClientSession(loop=main_loop)
 
 @app.route('/openapi.json')
 async def openapi():
@@ -265,6 +284,7 @@ async def amiPresenceStateList():
       if message.event == 'PresenceStateChange':
         user = re.search('CustomPresence:(\d+)', message.presentity).group(1)
         states[user] = message.status
+    app.logger.warning('PresenceStateList: {}'.format(','.join(states.keys())))
   return states
 
 async def amiExtensionStateList():
@@ -275,6 +295,7 @@ async def amiExtensionStateList():
       if ((message.event == 'ExtensionStatus') and
           (message.context == 'ext-local')):
         states[message.exten] = message.statustext.lower()
+    app.logger.warning('ExtensionStateList: {}'.format(','.join(states.keys())))
   return states
 
 async def amiCommand(command):
@@ -348,7 +369,6 @@ async def amiDeviceChannel(device):
   if len(reply) >= 2:
     for message in reply:
       if message.event == 'CoreShowChannel':
-        print(message.calleridnum)
         if message.calleridnum == device:
           return message.channel
   return None
@@ -410,22 +430,39 @@ async def setUserDeviceStates(user, device, queues, ast):
     _cf = await amiDBGet('CF', user)
     await amiSetVar('DEVICE_STATE(Custom:DEVCF{})'.format(device), 'INUSE' if _cf != '' else 'NOT_INUSE')
 
-async def getUsersStates():
-  states = {}
-  uStates = await amiExtensionStateList()
-  pStates = await amiPresenceStateList()
-  for user, uState in uStates.items():
-    if (uState == 'idle') and (user in pStates.keys()):
-      if pStates[user] in ('not_set','available', 'xa', 'chat'):
-        states[user] = 'available'
-      else:
-        states[user] = pStates[user]
+async def refreshStatesCache():
+  app.config['STATE_CACHE']['user'] = await amiExtensionStateList()
+  app.config['STATE_CACHE']['presence'] = await amiPresenceStateList()
+  return len(app.config['STATE_CACHE']['user'])
+
+async def userStateChangeCallback(user, state):
+  reply = None
+  if ((app.config['STATE_CALLBACK_URL'] not in NONEs) and
+      ('HTTP_CLIENT' in app.config)):
+    reply = await app.config['HTTP_CLIENT'].post(app.config['STATE_CALLBACK_URL'],
+                                                 json={'user': user,
+                                                       'state': state})
+  else:
+    app.logger.warning('{} changed state to: {}'.format(user, state))
+  return reply
+
+def getUserStateCombined(user):
+  if user not in app.config['STATE_CACHE']['user']:
+    return None
+  _state = app.config['STATE_CACHE']['user'][user]
+  if (_state == 'idle') and (user in app.config['STATE_CACHE']['presence']):
+    if app.config['STATE_CACHE']['presence'][user] in ('not_set','available', 'xa', 'chat'):
+      return 'available'
     else:
-      if uState in ('unavailable', 'ringing'):
-        states[user] = uState
-      else:
-        states[user] = 'busy'
-  return states
+      return app.config['STATE_CACHE']['presence'][user]
+  else:
+    if _state in ('unavailable', 'ringing'):
+      return _state
+    else:
+      return 'busy'
+
+def getUsersStatesCombined():
+  return {user:getUserStateCombined(user) for user in app.config['STATE_CACHE']['user']}
 
 @app.route('/atxfer/<userA>/<userB>')
 class AtXfer(Resource):
@@ -441,17 +478,17 @@ class AtXfer(Resource):
     '''
     device = await getUserDevice(userA)
     if device in NONEs:
-      return '', HTTPStatus.NOT_FOUND
+      abort(HTTPStatus.NOT_FOUND)
     channel = await amiDeviceChannel(device)
     if channel in NONEs:
-      return '', HTTPStatus.NOT_FOUND
+      abort(HTTPStatus.NOT_FOUND)
     reply = await manager.send_action({'Action':'Atxfer',
                                        'Channel':channel,
                                        'async':'false',
                                        'Exten':userB})
     if (isinstance(reply, Message) and reply.success):
       return '', HTTPStatus.OK
-    return '', HTTPStatus.BAD_REQUEST
+    abort(HTTPStatus.BAD_REQUEST)
 
 @app.route('/users/states')
 class UsersStates(Resource):
@@ -461,7 +498,8 @@ class UsersStates(Resource):
     '''Returns all users with their states.
     Possible states are: available, away, dnd, inuse, busy, unavailable, ringing
     '''
-    return await getUsersStates()
+    await refreshStatesCache()
+    return getUsersStatesCombined()
 
 @app.route('/user/<user>/presence')
 class UserPresenceState(Resource):
@@ -476,10 +514,10 @@ class UserPresenceState(Resource):
     '''
     cidnum = await getUserCID(user) # Check if user exists in astdb
     if cidnum is None:
-      return '', HTTPStatus.NOT_FOUND
+      abort(HTTPStatus.NOT_FOUND)
     state = await amiPresenceState(user)
     if state is None:
-      return '', HTTPStatus.BAD_REQUEST
+      abort(HTTPStatus.BAD_REQUEST)
     return state
 
 @app.route('/user/<user>/presence/<state>')
@@ -498,12 +536,12 @@ class SetUserPresenceState(Resource):
     '''
     cidnum = await getUserCID(user) # Check if user exists in astdb
     if cidnum is None:
-      return '', HTTPStatus.NOT_FOUND
+      abort(HTTPStatus.NOT_FOUND)
     if await amiSetVar('PRESENCE_STATE(CustomPresence:{})'.format(user),
                        state):
-      return ''
+      return '', HTTPStatus.OK
     else:
-      return '', HTTPStatus.BAD_REQUEST
+      abort(HTTPStatus.BAD_REQUEST)
 
 @app.route('/device/<device>/<user>/on')
 @app.route('/user/<user>/<device>/on')

+ 1 - 1
docker-compose.yml

@@ -51,7 +51,7 @@ services:
       - /etc/localtime:/etc/localtime:ro
       - ./app:/app
     command:
-      - -p requests
+      - -p aiohttp
       - -p quart
       - -p quart-openapi
       - -p hypercorn