|
@@ -65,9 +65,12 @@ app.config.update({
|
|
|
os.getenv('MYSQL_SERVER', 'db'),
|
|
os.getenv('MYSQL_SERVER', 'db'),
|
|
|
os.getenv('APP_PORT_MYSQL', '3306'),
|
|
os.getenv('APP_PORT_MYSQL', '3306'),
|
|
|
os.getenv('FREEPBX_CDRDBNAME', None)),
|
|
os.getenv('FREEPBX_CDRDBNAME', None)),
|
|
|
- 'EXTRA_API_URL': os.getenv('APP_EXTRA_API_URL', None),
|
|
|
|
|
- 'STATE_CACHE': {'user':{},
|
|
|
|
|
- 'presence':{}}})
|
|
|
|
|
|
|
+ 'EXTRA_API_URL': os.getenv('APP_EXTRA_API_URL', None)})
|
|
|
|
|
+
|
|
|
|
|
+app.cache = {'devices':[],
|
|
|
|
|
+ 'ustates':{},
|
|
|
|
|
+ 'pstates':{},
|
|
|
|
|
+ 'queues':{}}
|
|
|
|
|
|
|
|
manager = Manager(
|
|
manager = Manager(
|
|
|
loop=main_loop,
|
|
loop=main_loop,
|
|
@@ -111,15 +114,17 @@ db = PintDB(app)
|
|
|
|
|
|
|
|
@manager.register_event('FullyBooted')
|
|
@manager.register_event('FullyBooted')
|
|
|
async def fullyBootedCallback(mngr: Manager, msg: Message):
|
|
async def fullyBootedCallback(mngr: Manager, msg: Message):
|
|
|
|
|
+ await refreshDevicesCache()
|
|
|
await refreshStatesCache()
|
|
await refreshStatesCache()
|
|
|
|
|
+ await refreshQueuesCache()
|
|
|
|
|
|
|
|
@manager.register_event('ExtensionStatus')
|
|
@manager.register_event('ExtensionStatus')
|
|
|
async def extensionStatusCallback(mngr: Manager, msg: Message):
|
|
async def extensionStatusCallback(mngr: Manager, msg: Message):
|
|
|
- user = msg.exten #hint = msg.hint
|
|
|
|
|
|
|
+ user = msg.exten
|
|
|
state = msg.statustext.lower()
|
|
state = msg.statustext.lower()
|
|
|
- if user in app.config['STATE_CACHE']['user']:
|
|
|
|
|
|
|
+ if user in app.cache['ustates']:
|
|
|
prevState = getUserStateCombined(user)
|
|
prevState = getUserStateCombined(user)
|
|
|
- app.config['STATE_CACHE']['user'][user] = state
|
|
|
|
|
|
|
+ app.cache['ustates'][user] = state
|
|
|
combinedState = getUserStateCombined(user)
|
|
combinedState = getUserStateCombined(user)
|
|
|
if combinedState != prevState:
|
|
if combinedState != prevState:
|
|
|
await userStateChangeCallback(user, combinedState, prevState)
|
|
await userStateChangeCallback(user, combinedState, prevState)
|
|
@@ -128,15 +133,15 @@ async def extensionStatusCallback(mngr: Manager, msg: Message):
|
|
|
async def presenceStatusCallback(mngr: Manager, msg: Message):
|
|
async def presenceStatusCallback(mngr: Manager, msg: Message):
|
|
|
user = msg.exten #hint = msg.hint
|
|
user = msg.exten #hint = msg.hint
|
|
|
state = msg.status.lower()
|
|
state = msg.status.lower()
|
|
|
- if user in app.config['STATE_CACHE']['user']:
|
|
|
|
|
|
|
+ if user in app.cache['ustates']:
|
|
|
prevState = getUserStateCombined(user)
|
|
prevState = getUserStateCombined(user)
|
|
|
- app.config['STATE_CACHE']['presence'][user] = state
|
|
|
|
|
|
|
+ app.cache['pstates'][user] = state
|
|
|
combinedState = getUserStateCombined(user)
|
|
combinedState = getUserStateCombined(user)
|
|
|
if combinedState != prevState:
|
|
if combinedState != prevState:
|
|
|
await userStateChangeCallback(user, combinedState, prevState)
|
|
await userStateChangeCallback(user, combinedState, prevState)
|
|
|
|
|
|
|
|
async def getCDR(start=None, end=None, **kwargs):
|
|
async def getCDR(start=None, end=None, **kwargs):
|
|
|
- cdr = {}
|
|
|
|
|
|
|
+ _cdr = {}
|
|
|
if end is None:
|
|
if end is None:
|
|
|
end = dt.now()
|
|
end = dt.now()
|
|
|
if start is None:
|
|
if start is None:
|
|
@@ -170,12 +175,15 @@ async def getCDR(start=None, end=None, **kwargs):
|
|
|
uniqueid;''',
|
|
uniqueid;''',
|
|
|
values={'start':start,
|
|
values={'start':start,
|
|
|
'end':end}):
|
|
'end':end}):
|
|
|
- call = {_k: str(_v) for _k, _v in row.items() if _k != 'linkedid' and _v != ''}
|
|
|
|
|
- cdr.setdefault(row['linkedid'],[]).append(call)
|
|
|
|
|
|
|
+ event = {_k: str(_v) for _k, _v in row.items() if _k != 'linkedid' and _v != ''}
|
|
|
|
|
+ _cdr.setdefault(row['linkedid'],[]).append(event)
|
|
|
|
|
+ cdr = []
|
|
|
|
|
+ for _id in sorted(_cdr.keys()):
|
|
|
|
|
+ cdr.append({'id':_id,'events':_cdr[_id]})
|
|
|
return cdr
|
|
return cdr
|
|
|
|
|
|
|
|
async def getCEL(start=None, end=None, **kwargs):
|
|
async def getCEL(start=None, end=None, **kwargs):
|
|
|
- cel = {}
|
|
|
|
|
|
|
+ _cel = {}
|
|
|
if end is None:
|
|
if end is None:
|
|
|
end = dt.now()
|
|
end = dt.now()
|
|
|
if start is None:
|
|
if start is None:
|
|
@@ -204,7 +212,10 @@ async def getCEL(start=None, end=None, **kwargs):
|
|
|
values={'start':start,
|
|
values={'start':start,
|
|
|
'end':end}):
|
|
'end':end}):
|
|
|
event = {_k: str(_v) for _k, _v in row.items() if _k != 'linkedid' and _v != ''}
|
|
event = {_k: str(_v) for _k, _v in row.items() if _k != 'linkedid' and _v != ''}
|
|
|
- cel.setdefault(row['linkedid'],[]).append(event)
|
|
|
|
|
|
|
+ _cel.setdefault(row['linkedid'],[]).append(event)
|
|
|
|
|
+ cel = []
|
|
|
|
|
+ for _id in sorted(_cel.keys()):
|
|
|
|
|
+ cel.append({'id':_id,'events':_cel[_id]})
|
|
|
return cel
|
|
return cel
|
|
|
|
|
|
|
|
@app.before_first_request
|
|
@app.before_first_request
|
|
@@ -506,8 +517,8 @@ async def amiDeviceChannel(device):
|
|
|
return message.channel
|
|
return message.channel
|
|
|
return None
|
|
return None
|
|
|
|
|
|
|
|
-async def setQueueStates(queues, user, device, state):
|
|
|
|
|
- for queue in [_q for _q, _ma in queues.items() for _m in _ma if _m.user == user]:
|
|
|
|
|
|
|
+async def setQueueStates(user, device, state):
|
|
|
|
|
+ for queue in [_q for _q, _ma in app.cache['queues'].items() for _m in _ma if _m.user == user]:
|
|
|
await amiSetVar('DEVICE_STATE(Custom:QUEUE{}*{})'.format(device, queue), state)
|
|
await amiSetVar('DEVICE_STATE(Custom:QUEUE{}*{})'.format(device, queue), state)
|
|
|
|
|
|
|
|
async def getDeviceUser(device):
|
|
async def getDeviceUser(device):
|
|
@@ -531,7 +542,7 @@ async def setUserDevice(user, device):
|
|
|
else:
|
|
else:
|
|
|
return await amiDBPut('AMPUSER', '{}/device'.format(user), device)
|
|
return await amiDBPut('AMPUSER', '{}/device'.format(user), device)
|
|
|
|
|
|
|
|
-async def unbindOtherDevices(user, newDevice, queues, ast):
|
|
|
|
|
|
|
+async def unbindOtherDevices(user, newDevice, ast):
|
|
|
'''Unbinds user from all devices except newDevice and sets
|
|
'''Unbinds user from all devices except newDevice and sets
|
|
|
all required device states.
|
|
all required device states.
|
|
|
'''
|
|
'''
|
|
@@ -542,20 +553,20 @@ async def unbindOtherDevices(user, newDevice, queues, ast):
|
|
|
if ast.FMDEVSTATE == 'TRUE':
|
|
if ast.FMDEVSTATE == 'TRUE':
|
|
|
await amiSetVar('DEVICE_STATE(Custom:FOLLOWME{})'.format(_device), 'INVALID')
|
|
await amiSetVar('DEVICE_STATE(Custom:FOLLOWME{})'.format(_device), 'INVALID')
|
|
|
if ast.QUEDEVSTATE == 'TRUE':
|
|
if ast.QUEDEVSTATE == 'TRUE':
|
|
|
- await setQueueStates(queues, user, _device, 'NOT_INUSE')
|
|
|
|
|
|
|
+ await setQueueStates(user, _device, 'NOT_INUSE')
|
|
|
if ast.DNDDEVSTATE:
|
|
if ast.DNDDEVSTATE:
|
|
|
await amiSetVar('DEVICE_STATE(Custom:DEVDND{})'.format(_device), 'NOT_INUSE')
|
|
await amiSetVar('DEVICE_STATE(Custom:DEVDND{})'.format(_device), 'NOT_INUSE')
|
|
|
if ast.CFDEVSTATE:
|
|
if ast.CFDEVSTATE:
|
|
|
await amiSetVar('DEVICE_STATE(Custom:DEVCF{})'.format(_device), 'NOT_INUSE')
|
|
await amiSetVar('DEVICE_STATE(Custom:DEVCF{})'.format(_device), 'NOT_INUSE')
|
|
|
await amiDBPut('DEVICE', '{}/user'.format(_device), 'none')
|
|
await amiDBPut('DEVICE', '{}/user'.format(_device), 'none')
|
|
|
|
|
|
|
|
-async def setUserDeviceStates(user, device, queues, ast):
|
|
|
|
|
|
|
+async def setUserDeviceStates(user, device, ast):
|
|
|
if ast.FMDEVSTATE == 'TRUE':
|
|
if ast.FMDEVSTATE == 'TRUE':
|
|
|
_followMe = await amiDBGet('AMPUSER', '{}/followme/ddial'.format(user))
|
|
_followMe = await amiDBGet('AMPUSER', '{}/followme/ddial'.format(user))
|
|
|
if _followMe is not None:
|
|
if _followMe is not None:
|
|
|
await amiSetVar('DEVICE_STATE(Custom:FOLLOWME{})'.format(device), followMe2DevState(_followMe))
|
|
await amiSetVar('DEVICE_STATE(Custom:FOLLOWME{})'.format(device), followMe2DevState(_followMe))
|
|
|
if ast.QUEDEVSTATE == 'TRUE':
|
|
if ast.QUEDEVSTATE == 'TRUE':
|
|
|
- await setQueueStates(queues, user, device, 'INUSE')
|
|
|
|
|
|
|
+ await setQueueStates(user, device, 'INUSE')
|
|
|
if ast.DNDDEVSTATE:
|
|
if ast.DNDDEVSTATE:
|
|
|
_dnd = await amiDBGet('DND', user)
|
|
_dnd = await amiDBGet('DND', user)
|
|
|
await amiSetVar('DEVICE_STATE(Custom:DEVDND{})'.format(device), 'INUSE' if _dnd == 'YES' else 'NOT_INUSE')
|
|
await amiSetVar('DEVICE_STATE(Custom:DEVDND{})'.format(device), 'INUSE' if _dnd == 'YES' else 'NOT_INUSE')
|
|
@@ -564,9 +575,18 @@ async def setUserDeviceStates(user, device, queues, ast):
|
|
|
await amiSetVar('DEVICE_STATE(Custom:DEVCF{})'.format(device), 'INUSE' if _cf != '' else 'NOT_INUSE')
|
|
await amiSetVar('DEVICE_STATE(Custom:DEVCF{})'.format(device), 'INUSE' if _cf != '' else 'NOT_INUSE')
|
|
|
|
|
|
|
|
async def refreshStatesCache():
|
|
async def refreshStatesCache():
|
|
|
- app.config['STATE_CACHE']['user'] = await amiExtensionStateList()
|
|
|
|
|
- app.config['STATE_CACHE']['presence'] = await amiPresenceStateList()
|
|
|
|
|
- return len(app.config['STATE_CACHE']['user'])
|
|
|
|
|
|
|
+ app.cache['ustates'] = await amiExtensionStateList()
|
|
|
|
|
+ app.cache['pstates'] = await amiPresenceStateList()
|
|
|
|
|
+ return len(app.cache['ustates'])
|
|
|
|
|
+
|
|
|
|
|
+async def refreshDevicesCache():
|
|
|
|
|
+ aors = await amiPJSIPShowAors()
|
|
|
|
|
+ app.cache['devices'] = list(aors.keys())
|
|
|
|
|
+ return len(app.cache['devices'])
|
|
|
|
|
+
|
|
|
|
|
+async def refreshQueuesCache():
|
|
|
|
|
+ app.cache['queues'] = await amiQueues()
|
|
|
|
|
+ return len(app.cache['queues'])
|
|
|
|
|
|
|
|
async def userStateChangeCallback(user, state, prevState = None):
|
|
async def userStateChangeCallback(user, state, prevState = None):
|
|
|
reply = None
|
|
reply = None
|
|
@@ -581,12 +601,12 @@ async def userStateChangeCallback(user, state, prevState = None):
|
|
|
return reply
|
|
return reply
|
|
|
|
|
|
|
|
def getUserStateCombined(user):
|
|
def getUserStateCombined(user):
|
|
|
- _uCache = app.config['STATE_CACHE']['user']
|
|
|
|
|
- _pCache = app.config['STATE_CACHE']['presence']
|
|
|
|
|
|
|
+ _uCache = app.cache['ustates']
|
|
|
|
|
+ _pCache = app.cache['pstates']
|
|
|
return combinedStates[_uCache.get(user, 'unavailable')][_pCache.get(user, 'not_set')]
|
|
return combinedStates[_uCache.get(user, 'unavailable')][_pCache.get(user, 'not_set')]
|
|
|
|
|
|
|
|
def getUsersStatesCombined():
|
|
def getUsersStatesCombined():
|
|
|
- return {user:getUserStateCombined(user) for user in app.config['STATE_CACHE']['user']}
|
|
|
|
|
|
|
+ return {user:getUserStateCombined(user) for user in app.cache['ustates']}
|
|
|
|
|
|
|
|
@app.route('/atxfer/<userA>/<userB>')
|
|
@app.route('/atxfer/<userA>/<userB>')
|
|
|
class AtXfer(Resource):
|
|
class AtXfer(Resource):
|
|
@@ -635,7 +655,7 @@ class UserState(Resource):
|
|
|
'''Returns user's combined state.
|
|
'''Returns user's combined state.
|
|
|
One of: available, away, dnd, inuse, busy, unavailable, ringing
|
|
One of: available, away, dnd, inuse, busy, unavailable, ringing
|
|
|
'''
|
|
'''
|
|
|
- if user not in app.config['STATE_CACHE']['user']:
|
|
|
|
|
|
|
+ if user not in app.cache['ustates']:
|
|
|
return noUser(user)
|
|
return noUser(user)
|
|
|
return successReply({'user':user,'state':getUserStateCombined(user)})
|
|
return successReply({'user':user,'state':getUserStateCombined(user)})
|
|
|
|
|
|
|
@@ -648,9 +668,9 @@ class PresenceState(Resource):
|
|
|
'''Returns user's presence state.
|
|
'''Returns user's presence state.
|
|
|
One of: not_set, unavailable, available, away, xa, chat, dnd
|
|
One of: not_set, unavailable, available, away, xa, chat, dnd
|
|
|
'''
|
|
'''
|
|
|
- if user not in app.config['STATE_CACHE']['user']:
|
|
|
|
|
|
|
+ if user not in app.cache['ustates']:
|
|
|
return noUser(user)
|
|
return noUser(user)
|
|
|
- return successReply({'user':user,'state':app.config['STATE_CACHE']['presence'].get(user, 'not_set')})
|
|
|
|
|
|
|
+ return successReply({'user':user,'state':app.cache['pstates'].get(user, 'not_set')})
|
|
|
|
|
|
|
|
@app.route('/user/<user>/presence/<state>')
|
|
@app.route('/user/<user>/presence/<state>')
|
|
|
class SetPresenceState(Resource):
|
|
class SetPresenceState(Resource):
|
|
@@ -666,7 +686,7 @@ class SetPresenceState(Resource):
|
|
|
'''
|
|
'''
|
|
|
if state not in presenceStates:
|
|
if state not in presenceStates:
|
|
|
return invalidState(state)
|
|
return invalidState(state)
|
|
|
- if user not in app.config['STATE_CACHE']['user']:
|
|
|
|
|
|
|
+ if user not in app.cache['ustates']:
|
|
|
return noUser(user)
|
|
return noUser(user)
|
|
|
result = await amiSetVar('PRESENCE_STATE(CustomPresence:{})'.format(user), state)
|
|
result = await amiSetVar('PRESENCE_STATE(CustomPresence:{})'.format(user), state)
|
|
|
if result is not None:
|
|
if result is not None:
|
|
@@ -682,7 +702,7 @@ class UsersDevices(Resource):
|
|
|
Possible states are: available, away, dnd, inuse, busy, unavailable, ringing
|
|
Possible states are: available, away, dnd, inuse, busy, unavailable, ringing
|
|
|
'''
|
|
'''
|
|
|
data = {}
|
|
data = {}
|
|
|
- for user in app.config['STATE_CACHE']['user']:
|
|
|
|
|
|
|
+ for user in app.cache['ustates']:
|
|
|
device = await getUserDevice(user)
|
|
device = await getUserDevice(user)
|
|
|
if device in NONEs:
|
|
if device in NONEs:
|
|
|
device = None
|
|
device = None
|
|
@@ -702,7 +722,7 @@ class UserDeviceBind(Resource):
|
|
|
Any device user was previously bound to, is unbound.
|
|
Any device user was previously bound to, is unbound.
|
|
|
Any user previously bound to device is unbound also.
|
|
Any user previously bound to device is unbound also.
|
|
|
'''
|
|
'''
|
|
|
- if user not in app.config['STATE_CACHE']['user']:
|
|
|
|
|
|
|
+ if user not in app.cache['ustates']:
|
|
|
return noUser(user)
|
|
return noUser(user)
|
|
|
dial = await getDeviceDial(device) # Check if device exists in astdb
|
|
dial = await getDeviceDial(device) # Check if device exists in astdb
|
|
|
if dial is None:
|
|
if dial is None:
|
|
@@ -711,19 +731,18 @@ class UserDeviceBind(Resource):
|
|
|
if currentUser == user:
|
|
if currentUser == user:
|
|
|
return alreadyBound(user, device)
|
|
return alreadyBound(user, device)
|
|
|
ast = await getGlobalVars()
|
|
ast = await getGlobalVars()
|
|
|
- queues = await amiQueues()
|
|
|
|
|
if currentUser not in NONEs: # If any other user is bound to device, unbind him,
|
|
if currentUser not in NONEs: # If any other user is bound to device, unbind him,
|
|
|
await setUserDevice(currentUser, None)
|
|
await setUserDevice(currentUser, None)
|
|
|
if ast.QUEDEVSTATE == 'TRUE': # set device states for previous user queues
|
|
if ast.QUEDEVSTATE == 'TRUE': # set device states for previous user queues
|
|
|
- await setQueueStates(queues, currentUser, device, 'NOT_INUSE')
|
|
|
|
|
|
|
+ await setQueueStates(currentUser, device, 'NOT_INUSE')
|
|
|
await setUserHint(currentUser, None, ast) # set hints for previous user
|
|
await setUserHint(currentUser, None, ast) # set hints for previous user
|
|
|
await setDeviceUser(device, user) # Bind user to device
|
|
await setDeviceUser(device, user) # Bind user to device
|
|
|
# If user is bound to some other devices, unbind him and set
|
|
# If user is bound to some other devices, unbind him and set
|
|
|
# device states for those devices
|
|
# device states for those devices
|
|
|
- await unbindOtherDevices(user, device, queues, ast)
|
|
|
|
|
|
|
+ await unbindOtherDevices(user, device, ast)
|
|
|
if not (await setUserHint(user, dial, ast)): # Set hints for user on new device
|
|
if not (await setUserHint(user, dial, ast)): # Set hints for user on new device
|
|
|
return hintError(user, device)
|
|
return hintError(user, device)
|
|
|
- await setUserDeviceStates(user, device, queues, ast) # Set device states for users new device
|
|
|
|
|
|
|
+ await setUserDeviceStates(user, device, ast) # Set device states for users new device
|
|
|
if not (await setUserDevice(user, device)): # Bind device to user
|
|
if not (await setUserDevice(user, device)): # Bind device to user
|
|
|
return bindError(user, device)
|
|
return bindError(user, device)
|
|
|
return successfullyBound(user, device)
|
|
return successfullyBound(user, device)
|
|
@@ -745,10 +764,9 @@ class DeviceUnBind(Resource):
|
|
|
return noUserBound(device)
|
|
return noUserBound(device)
|
|
|
else:
|
|
else:
|
|
|
ast = await getGlobalVars()
|
|
ast = await getGlobalVars()
|
|
|
- queues = await amiQueues()
|
|
|
|
|
await setUserDevice(currentUser, None) # Unbind device from current user
|
|
await setUserDevice(currentUser, None) # Unbind device from current user
|
|
|
if ast.QUEDEVSTATE == 'TRUE': # set device states for current user queues
|
|
if ast.QUEDEVSTATE == 'TRUE': # set device states for current user queues
|
|
|
- await setQueueStates(queues, currentUser, device, 'NOT_INUSE')
|
|
|
|
|
|
|
+ await setQueueStates(currentUser, device, 'NOT_INUSE')
|
|
|
await setUserHint(currentUser, None, ast) # set hints for current user
|
|
await setUserHint(currentUser, None, ast) # set hints for current user
|
|
|
await setDeviceUser(device, 'none') # Unbind user from device
|
|
await setDeviceUser(device, 'none') # Unbind user from device
|
|
|
return successfullyUnbound(currentUser, device)
|
|
return successfullyUnbound(currentUser, device)
|