|
|
@@ -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')
|