|
@@ -90,22 +90,22 @@ async def extensionStatusCallback(mngr: Manager, msg: Message):
|
|
|
user = msg.exten #hint = msg.hint
|
|
user = msg.exten #hint = msg.hint
|
|
|
state = msg.statustext.lower()
|
|
state = msg.statustext.lower()
|
|
|
if user in app.config['STATE_CACHE']['user']:
|
|
if user in app.config['STATE_CACHE']['user']:
|
|
|
- _combinedState = getUserStateCombined(user)
|
|
|
|
|
|
|
+ prevState = getUserStateCombined(user)
|
|
|
app.config['STATE_CACHE']['user'][user] = state
|
|
app.config['STATE_CACHE']['user'][user] = state
|
|
|
combinedState = getUserStateCombined(user)
|
|
combinedState = getUserStateCombined(user)
|
|
|
- if combinedState != _combinedState:
|
|
|
|
|
- await userStateChangeCallback(user, combinedState)
|
|
|
|
|
|
|
+ if combinedState != prevState:
|
|
|
|
|
+ await userStateChangeCallback(user, combinedState, prevState)
|
|
|
|
|
|
|
|
@manager.register_event('PresenceStatus')
|
|
@manager.register_event('PresenceStatus')
|
|
|
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.config['STATE_CACHE']['user']:
|
|
|
- _combinedState = getUserStateCombined(user)
|
|
|
|
|
|
|
+ prevState = getUserStateCombined(user)
|
|
|
app.config['STATE_CACHE']['presence'][user] = state
|
|
app.config['STATE_CACHE']['presence'][user] = state
|
|
|
combinedState = getUserStateCombined(user)
|
|
combinedState = getUserStateCombined(user)
|
|
|
- if combinedState != _combinedState:
|
|
|
|
|
- await userStateChangeCallback(user, combinedState)
|
|
|
|
|
|
|
+ if combinedState != prevState:
|
|
|
|
|
+ await userStateChangeCallback(user, combinedState, prevState)
|
|
|
|
|
|
|
|
@app.before_first_request
|
|
@app.before_first_request
|
|
|
async def initHttpClient():
|
|
async def initHttpClient():
|
|
@@ -158,6 +158,30 @@ async def amiGetVar(variable):
|
|
|
app.logger.warning('GetVar({})->{}'.format(variable, reply.value))
|
|
app.logger.warning('GetVar({})->{}'.format(variable, reply.value))
|
|
|
return reply.value
|
|
return reply.value
|
|
|
|
|
|
|
|
|
|
+@app.route('/ami/auths')
|
|
|
|
|
+async def amiPJSIPShowAuths():
|
|
|
|
|
+ auths = {}
|
|
|
|
|
+ reply = await manager.send_action({'Action':'PJSIPShowAuths'})
|
|
|
|
|
+ if len(reply) >= 2:
|
|
|
|
|
+ for message in reply:
|
|
|
|
|
+ if ((message.event == 'AuthList') and
|
|
|
|
|
+ ('objecttype' in message) and
|
|
|
|
|
+ (message.objecttype == 'auth')):
|
|
|
|
|
+ auths[message.username] = message.password
|
|
|
|
|
+ return successReply(auths)
|
|
|
|
|
+
|
|
|
|
|
+@app.route('/ami/aors')
|
|
|
|
|
+async def amiPJSIPShowAors():
|
|
|
|
|
+ aors = {}
|
|
|
|
|
+ reply = await manager.send_action({'Action':'PJSIPShowAors'})
|
|
|
|
|
+ if len(reply) >= 2:
|
|
|
|
|
+ for message in reply:
|
|
|
|
|
+ if ((message.event == 'AorList') and
|
|
|
|
|
+ ('objecttype' in message) and
|
|
|
|
|
+ (message.objecttype == 'aor')):
|
|
|
|
|
+ aors[message.objectname] = message.contacts
|
|
|
|
|
+ return successReply(aors)
|
|
|
|
|
+
|
|
|
async def amiSetVar(variable, value):
|
|
async def amiSetVar(variable, value):
|
|
|
'''AMI SetVar
|
|
'''AMI SetVar
|
|
|
Sets variable using AMI action SetVar to value in background.
|
|
Sets variable using AMI action SetVar to value in background.
|
|
@@ -167,15 +191,18 @@ async def amiSetVar(variable, value):
|
|
|
value (string): Value to set for variable
|
|
value (string): Value to set for variable
|
|
|
|
|
|
|
|
Returns:
|
|
Returns:
|
|
|
- boolean: True if DBPut action was successfull, False overwise
|
|
|
|
|
|
|
+ string: None if SetVar was successfull, error message overwise
|
|
|
'''
|
|
'''
|
|
|
reply = await manager.send_action({'Action': 'SetVar',
|
|
reply = await manager.send_action({'Action': 'SetVar',
|
|
|
'Variable': variable,
|
|
'Variable': variable,
|
|
|
'Value': value})
|
|
'Value': value})
|
|
|
app.logger.warning('SetVar({}, {})'.format(variable, value))
|
|
app.logger.warning('SetVar({}, {})'.format(variable, value))
|
|
|
- if (isinstance(reply, Message) and reply.success):
|
|
|
|
|
- return True
|
|
|
|
|
- return False
|
|
|
|
|
|
|
+ if isinstance(reply, Message):
|
|
|
|
|
+ if reply.success:
|
|
|
|
|
+ return None
|
|
|
|
|
+ else:
|
|
|
|
|
+ return reply.message
|
|
|
|
|
+ return 'AMI error'
|
|
|
|
|
|
|
|
async def amiDBGet(family, key):
|
|
async def amiDBGet(family, key):
|
|
|
'''AMI DBGet
|
|
'''AMI DBGet
|
|
@@ -270,14 +297,17 @@ async def amiPresenceState(user):
|
|
|
user (string): user
|
|
user (string): user
|
|
|
|
|
|
|
|
Returns:
|
|
Returns:
|
|
|
- string: One of: not_set, unavailable, available, away, xa, chat or dnd
|
|
|
|
|
|
|
+ boolean, string: True and state or False and error message
|
|
|
'''
|
|
'''
|
|
|
reply = await manager.send_action({'Action': 'PresenceState',
|
|
reply = await manager.send_action({'Action': 'PresenceState',
|
|
|
'Provider': 'CustomPresence:{}'.format(user)})
|
|
'Provider': 'CustomPresence:{}'.format(user)})
|
|
|
app.logger.warning('PresenceState({})'.format(user))
|
|
app.logger.warning('PresenceState({})'.format(user))
|
|
|
- if (isinstance(reply, Message) and reply.success):
|
|
|
|
|
- return reply.state
|
|
|
|
|
- return None
|
|
|
|
|
|
|
+ if isinstance(reply, Message):
|
|
|
|
|
+ if reply.success:
|
|
|
|
|
+ return True, reply.state
|
|
|
|
|
+ else:
|
|
|
|
|
+ return False, reply.message
|
|
|
|
|
+ return False, 'AMI error'
|
|
|
|
|
|
|
|
async def amiPresenceStateList():
|
|
async def amiPresenceStateList():
|
|
|
states = {}
|
|
states = {}
|
|
@@ -438,34 +468,22 @@ async def refreshStatesCache():
|
|
|
app.config['STATE_CACHE']['presence'] = await amiPresenceStateList()
|
|
app.config['STATE_CACHE']['presence'] = await amiPresenceStateList()
|
|
|
return len(app.config['STATE_CACHE']['user'])
|
|
return len(app.config['STATE_CACHE']['user'])
|
|
|
|
|
|
|
|
-async def userStateChangeCallback(user, state):
|
|
|
|
|
|
|
+async def userStateChangeCallback(user, state, prevState = None):
|
|
|
reply = None
|
|
reply = None
|
|
|
if ((app.config['STATE_CALLBACK_URL'] not in NONEs) and
|
|
if ((app.config['STATE_CALLBACK_URL'] not in NONEs) and
|
|
|
('HTTP_CLIENT' in app.config)):
|
|
('HTTP_CLIENT' in app.config)):
|
|
|
reply = await app.config['HTTP_CLIENT'].post(app.config['STATE_CALLBACK_URL'],
|
|
reply = await app.config['HTTP_CLIENT'].post(app.config['STATE_CALLBACK_URL'],
|
|
|
json={'user': user,
|
|
json={'user': user,
|
|
|
- 'state': state})
|
|
|
|
|
|
|
+ 'state': state,
|
|
|
|
|
+ 'prev_state':prevState})
|
|
|
else:
|
|
else:
|
|
|
app.logger.warning('{} changed state to: {}'.format(user, state))
|
|
app.logger.warning('{} changed state to: {}'.format(user, state))
|
|
|
return reply
|
|
return reply
|
|
|
|
|
|
|
|
def getUserStateCombined(user):
|
|
def getUserStateCombined(user):
|
|
|
- if user not in app.config['STATE_CACHE']['user']:
|
|
|
|
|
- return None
|
|
|
|
|
- _state = app.config['STATE_CACHE']['user'][user]
|
|
|
|
|
- if (_state == 'idle'):
|
|
|
|
|
- if (user in app.config['STATE_CACHE']['presence']):
|
|
|
|
|
- if app.config['STATE_CACHE']['presence'][user] in ('not_set','available', 'xa', 'chat'):
|
|
|
|
|
- return 'available'
|
|
|
|
|
- else:
|
|
|
|
|
- return app.config['STATE_CACHE']['presence'][user]
|
|
|
|
|
- else:
|
|
|
|
|
- return 'available'
|
|
|
|
|
- else:
|
|
|
|
|
- if _state in ('unavailable', 'ringing'):
|
|
|
|
|
- return _state
|
|
|
|
|
- else:
|
|
|
|
|
- return 'busy'
|
|
|
|
|
|
|
+ _uCache = app.config['STATE_CACHE']['user']
|
|
|
|
|
+ _pCache = app.config['STATE_CACHE']['presence']
|
|
|
|
|
+ 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.config['STATE_CACHE']['user']}
|
|
@@ -474,80 +492,102 @@ def getUsersStatesCombined():
|
|
|
class AtXfer(Resource):
|
|
class AtXfer(Resource):
|
|
|
@app.param('userA', 'User initiating the attended transfer', 'path')
|
|
@app.param('userA', 'User initiating the attended transfer', 'path')
|
|
|
@app.param('userB', 'Transfer destination user', 'path')
|
|
@app.param('userB', 'Transfer destination user', 'path')
|
|
|
- @app.response(HTTPStatus.OK, 'Successfuly transfered the call')
|
|
|
|
|
|
|
+ @app.response(HTTPStatus.OK, 'Json reply')
|
|
|
@app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
@app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
|
- @app.response(HTTPStatus.NOT_FOUND, 'User does not exist, or user is not bound to device, \
|
|
|
|
|
- or device is offline, or no current call for user')
|
|
|
|
|
- @app.response(HTTPStatus.BAD_REQUEST, 'Transfer failed somehow or other AMI error')
|
|
|
|
|
async def get(self, userA, userB):
|
|
async def get(self, userA, userB):
|
|
|
'''Attended call transfer
|
|
'''Attended call transfer
|
|
|
'''
|
|
'''
|
|
|
device = await getUserDevice(userA)
|
|
device = await getUserDevice(userA)
|
|
|
if device in NONEs:
|
|
if device in NONEs:
|
|
|
- abort(HTTPStatus.NOT_FOUND)
|
|
|
|
|
|
|
+ return noUserDevice(userA)
|
|
|
channel = await amiDeviceChannel(device)
|
|
channel = await amiDeviceChannel(device)
|
|
|
if channel in NONEs:
|
|
if channel in NONEs:
|
|
|
- abort(HTTPStatus.NOT_FOUND)
|
|
|
|
|
|
|
+ return noUserChannel(userA)
|
|
|
reply = await manager.send_action({'Action':'Atxfer',
|
|
reply = await manager.send_action({'Action':'Atxfer',
|
|
|
'Channel':channel,
|
|
'Channel':channel,
|
|
|
'async':'false',
|
|
'async':'false',
|
|
|
'Exten':userB})
|
|
'Exten':userB})
|
|
|
- if (isinstance(reply, Message) and reply.success):
|
|
|
|
|
- return '', HTTPStatus.OK
|
|
|
|
|
- abort(HTTPStatus.BAD_REQUEST)
|
|
|
|
|
|
|
+ if isinstance(reply, Message):
|
|
|
|
|
+ if reply.success:
|
|
|
|
|
+ return successfullyTransfered(userA, userB)
|
|
|
|
|
+ else:
|
|
|
|
|
+ return errorReply(reply.message)
|
|
|
|
|
|
|
|
@app.route('/users/states')
|
|
@app.route('/users/states')
|
|
|
class UsersStates(Resource):
|
|
class UsersStates(Resource):
|
|
|
- @app.response(HTTPStatus.OK, 'JSON map of form {user1:state1, user2:state2, ...}')
|
|
|
|
|
|
|
+ @app.response(HTTPStatus.OK, 'JSON reply with user:state map or error message')
|
|
|
@app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
@app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
|
async def get(self):
|
|
async def get(self):
|
|
|
- '''Returns all users with their states.
|
|
|
|
|
|
|
+ '''Returns all users with their combined states.
|
|
|
Possible states are: available, away, dnd, inuse, busy, unavailable, ringing
|
|
Possible states are: available, away, dnd, inuse, busy, unavailable, ringing
|
|
|
'''
|
|
'''
|
|
|
- await refreshStatesCache()
|
|
|
|
|
- return getUsersStatesCombined()
|
|
|
|
|
|
|
+ usersCount = await refreshStatesCache()
|
|
|
|
|
+ if usersCount == 0:
|
|
|
|
|
+ return stateCacheEmpty()
|
|
|
|
|
+ return successReply(getUsersStatesCombined())
|
|
|
|
|
+
|
|
|
|
|
+@app.route('/user/<user>/state')
|
|
|
|
|
+class UserState(Resource):
|
|
|
|
|
+ @app.param('user', 'User to query for combined state', 'path')
|
|
|
|
|
+ @app.response(HTTPStatus.OK, 'JSON data {"user":user,"state":state}')
|
|
|
|
|
+ @app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
|
|
|
+ async def get(self, user):
|
|
|
|
|
+ '''Returns user's combined state.
|
|
|
|
|
+ One of: available, away, dnd, inuse, busy, unavailable, ringing
|
|
|
|
|
+ '''
|
|
|
|
|
+ if user not in app.config['STATE_CACHE']['user']:
|
|
|
|
|
+ return noUser(user)
|
|
|
|
|
+ return successReply({'user':user,'state':getUserStateCombined(user)})
|
|
|
|
|
|
|
|
@app.route('/user/<user>/presence')
|
|
@app.route('/user/<user>/presence')
|
|
|
-class UserPresenceState(Resource):
|
|
|
|
|
|
|
+class PresenceState(Resource):
|
|
|
@app.param('user', 'User to query for presence state', 'path')
|
|
@app.param('user', 'User to query for presence state', 'path')
|
|
|
- @app.response(HTTPStatus.OK, 'Presence state string')
|
|
|
|
|
|
|
+ @app.response(HTTPStatus.OK, 'JSON data {"user":user,"state":state}')
|
|
|
@app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
@app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
|
- @app.response(HTTPStatus.NOT_FOUND, 'User does not exist')
|
|
|
|
|
- @app.response(HTTPStatus.BAD_REQUEST, 'AMI error')
|
|
|
|
|
async def get(self, user):
|
|
async def get(self, user):
|
|
|
'''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
|
|
|
'''
|
|
'''
|
|
|
- cidnum = await getUserCID(user) # Check if user exists in astdb
|
|
|
|
|
- if cidnum is None:
|
|
|
|
|
- abort(HTTPStatus.NOT_FOUND)
|
|
|
|
|
- state = await amiPresenceState(user)
|
|
|
|
|
- if state is None:
|
|
|
|
|
- abort(HTTPStatus.BAD_REQUEST)
|
|
|
|
|
- return state
|
|
|
|
|
|
|
+ if user not in app.config['STATE_CACHE']['user']:
|
|
|
|
|
+ return noUser(user)
|
|
|
|
|
+ return successReply({'user':user,'state':app.config['STATE_CACHE']['presence'].get(user, 'not_set')})
|
|
|
|
|
|
|
|
@app.route('/user/<user>/presence/<state>')
|
|
@app.route('/user/<user>/presence/<state>')
|
|
|
-class SetUserPresenceState(Resource):
|
|
|
|
|
|
|
+class SetPresenceState(Resource):
|
|
|
@app.param('user', 'Target user to set the presence state', 'path')
|
|
@app.param('user', 'Target user to set the presence state', 'path')
|
|
|
@app.param('state',
|
|
@app.param('state',
|
|
|
- 'The presence state to set for user, one of: not_set, unavailable, available, away, xa, chat or dnd',
|
|
|
|
|
|
|
+ 'The presence state for user, one of: not_set, unavailable, available, away, xa, chat or dnd',
|
|
|
'path')
|
|
'path')
|
|
|
- @app.response(HTTPStatus.OK, 'Successfuly set the presence state')
|
|
|
|
|
|
|
+ @app.response(HTTPStatus.OK, 'Json reply')
|
|
|
@app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
@app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
|
- @app.response(HTTPStatus.NOT_FOUND, 'User does not exist')
|
|
|
|
|
- @app.response(HTTPStatus.BAD_REQUEST, 'Wrong state string ot other AMI error')
|
|
|
|
|
async def get(self, user, state):
|
|
async def get(self, user, state):
|
|
|
'''Sets user's presence state.
|
|
'''Sets user's presence state.
|
|
|
Allowed states: not_set | unavailable | available | away | xa | chat | dnd
|
|
Allowed states: not_set | unavailable | available | away | xa | chat | dnd
|
|
|
'''
|
|
'''
|
|
|
- cidnum = await getUserCID(user) # Check if user exists in astdb
|
|
|
|
|
- if cidnum is None:
|
|
|
|
|
- abort(HTTPStatus.NOT_FOUND)
|
|
|
|
|
- if await amiSetVar('PRESENCE_STATE(CustomPresence:{})'.format(user),
|
|
|
|
|
- state):
|
|
|
|
|
- return '', HTTPStatus.OK
|
|
|
|
|
- else:
|
|
|
|
|
- abort(HTTPStatus.BAD_REQUEST)
|
|
|
|
|
|
|
+ if state not in _pstates:
|
|
|
|
|
+ return invalidState(state)
|
|
|
|
|
+ if user not in app.config['STATE_CACHE']['user']:
|
|
|
|
|
+ return noUser(user)
|
|
|
|
|
+ result = await amiSetVar('PRESENCE_STATE(CustomPresence:{})'.format(user), state)
|
|
|
|
|
+ if result is not None:
|
|
|
|
|
+ return errorReply(result)
|
|
|
|
|
+ return successfullySetState(user, state)
|
|
|
|
|
+
|
|
|
|
|
+@app.route('/users/devices')
|
|
|
|
|
+class UsersDevices(Resource):
|
|
|
|
|
+ @app.response(HTTPStatus.OK, 'JSON reply with user:device map or error message')
|
|
|
|
|
+ @app.response(HTTPStatus.UNAUTHORIZED, 'Authorization required')
|
|
|
|
|
+ async def get(self):
|
|
|
|
|
+ '''Returns all users with their combined states.
|
|
|
|
|
+ Possible states are: available, away, dnd, inuse, busy, unavailable, ringing
|
|
|
|
|
+ '''
|
|
|
|
|
+ data = {}
|
|
|
|
|
+ for user in app.config['STATE_CACHE']['user']:
|
|
|
|
|
+ device = await getUserDevice(user)
|
|
|
|
|
+ if device in NONEs:
|
|
|
|
|
+ device = None
|
|
|
|
|
+ data[user]=device
|
|
|
|
|
+ return successReply(data)
|
|
|
|
|
|
|
|
@app.route('/device/<device>/<user>/on')
|
|
@app.route('/device/<device>/<user>/on')
|
|
|
@app.route('/user/<user>/<device>/on')
|
|
@app.route('/user/<user>/<device>/on')
|
|
@@ -562,15 +602,14 @@ 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.
|
|
|
'''
|
|
'''
|
|
|
- cidnum = await getUserCID(user) # Check if user exists in astdb
|
|
|
|
|
- if cidnum is None:
|
|
|
|
|
|
|
+ if user not in app.config['STATE_CACHE']['user']:
|
|
|
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:
|
|
|
return noDevice(device)
|
|
return noDevice(device)
|
|
|
currentUser = await getDeviceUser(device) # Check if any user is already bound to device
|
|
currentUser = await getDeviceUser(device) # Check if any user is already bound to device
|
|
|
if currentUser == user:
|
|
if currentUser == user:
|
|
|
- return beenBound(user, device)
|
|
|
|
|
|
|
+ return alreadyBound(user, device)
|
|
|
ast = await getGlobalVars()
|
|
ast = await getGlobalVars()
|
|
|
queues = await amiQueues()
|
|
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,
|
|
@@ -587,7 +626,7 @@ class UserDeviceBind(Resource):
|
|
|
await setUserDeviceStates(user, device, queues, ast) # Set device states for users new device
|
|
await setUserDeviceStates(user, device, queues, 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 beenBound(user, device)
|
|
|
|
|
|
|
+ return successfullyBound(user, device)
|
|
|
|
|
|
|
|
@app.route('/device/<device>/off')
|
|
@app.route('/device/<device>/off')
|
|
|
class DeviceUnBind(Resource):
|
|
class DeviceUnBind(Resource):
|
|
@@ -612,7 +651,7 @@ class DeviceUnBind(Resource):
|
|
|
await setQueueStates(queues, currentUser, device, 'NOT_INUSE')
|
|
await setQueueStates(queues, 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 beenUnbound(currentUser, device)
|
|
|
|
|
|
|
+ return successfullyUnbound(currentUser, device)
|
|
|
|
|
|
|
|
manager.connect()
|
|
manager.connect()
|
|
|
app.run(loop=main_loop, host='0.0.0.0', port=app.config['PORT'])
|
|
app.run(loop=main_loop, host='0.0.0.0', port=app.config['PORT'])
|