Commit c796e765 authored by okhin's avatar okhin 🚴

Everything things is plugged i now, need to do the application work

parent f89712dc
......@@ -4,13 +4,34 @@ import sqlite3
import datetime
import uuid
import json
import logging
import concurrent.futures
import asyncio
from wsgiref.simple_server import make_server
from bottle import route, run, request, abort, install, get, post
from bottle import request, abort, Bottle, ServerAdapter, JSONPlugin
from bottle_sqlite import SQLitePlugin
import jwt
import Asterisk
import websockets
import requests
install(SQLitePlugin(dbfile='call.db'))
logging.basicConfig(filename='app.log', level=logging.DEBUG)
app = Bottle(autojson=False)
class PiphoneJSONEncoder(json.JSONEncoder):
def default(self, obj):
"""
We need to implement this to be able to JSONEncode
"""
if isinstance(obj, Call):
return { 'caller': obj.caller
, 'callee': obj.callee
, 'callid': obj.id
, 'url': obj.url()
, 'history': obj.history
, 'owner': obj.owner }
else:
return json.JSONEncoder.default(self, o)
def sanitize_phonenumber(number):
"""
......@@ -26,34 +47,29 @@ def sanitize_phonenumber(number):
raise TypeError('{} is not a valid international number, it should start with 00')
return number
class Call(json.JSONEncoder):
class Call(object):
"""
This Class is used to manage operatiosn on a call, to print it and dump it.
"""
history = {}
def __init__(self, caller, callee, owner):
history = []
actions = {'Created': 'call_caller'}
def __init__(self, caller, callee, owner, callid=None, db=None):
try:
self.caller = caller
self.callee = callee
self.owner = owner
self.id = uuid.uuid4()
if callid == None:
self.id = uuid.uuid4()
else:
self.id = callid
self.db = db
self.change(('Created', datetime.datetime.now().isoformat(),))
except Exception as e:
raise e
def url(self):
return ''.join(['/calls/', self.id])
def default(self, o):
"""
We need to implement this to be able to JSONEncode
"""
return { 'caller': self.caller
, 'callee': self.callee
, 'callid': self.callee
, 'url': self.url()
, 'history': self.history
, 'owner': self.owner }
@classmethod
def load(cls, callid, db):
results = db.execute('SELECT caller, callee, owner, callid, history FROM calls WHERE callid = ?;', (callid,))
......@@ -61,18 +77,47 @@ class Call(json.JSONEncoder):
result = results.fetchone()
object = cls(result[0], result[1], result[2])
object.id = result[3]
object.db = db
object.history = json.loads(result[4])
return object
except:
return None
def save(self, db):
def change(self, new_state):
'''
Let's change the state of the call. new_state is a tuple in the form (newstate, timestamp,)
'''
logging.debug("Got a new state: {}".format(new_state,))
self.history.append(new_state)
self.save()
state = new_state[0]
if state in self.actions:
getattr(self, self.actions[state])()
def call_caller(self):
'''
Let's do a request on the ARI system
'''
results = self.db.execute('SELECT login_ari, pass_ari FROM users WHERE api = ?', (self.owner,))
try:
login_ari, token_ari = results.fetchone()
payload = {}
payload['app'] = 'piphone'
payload['api_key'] = ':'.join([login_ari, token_ari])
payload['endpoint'] = 'SIP/' + sanitize_phonenumber(self.caller) + '@forfait-kwaoo'
logging.debug('Preparing to send a request to the ARI with payload {}'.format(payload,))
r = requests.post('http://185.34.33.12:8088/ari/channels', data=payload)
logging.debug('Requests sent, got response: {}'.format(r,))
except Exception as e:
raise e
def save(self):
'''
Save the Call to database.
'''
db.execute('''INSERT OR REPLACE INTO calls (caller, callee, owner, callid, history)
self.db.execute('''INSERT OR REPLACE INTO calls (caller, callee, owner, callid, history)
VALUES (?, ?, ?, ?, ?)
''', (self.caller, self.callee, self.owner, self.callid, json.dumps(self.history)))
''', (self.caller, self.callee, self.owner, self.id, json.dumps(self.history)))
# We need a decorator to check if our query is authenticated.
# We will store an API key and SECRET in ur database, the client
......@@ -104,18 +149,105 @@ def authenticated(f):
return f(db, *args, **kwargs)
return wrapped
@get('/calls/')
class Server(ServerAdapter):
"""
This class manage all that is needed to run the REST App of the piphone. It is in charge of
launching an Executor, start the event loop in its own thread and run the bottle app.
"""
server = None
running = False
options = None
callbacks = {}
def __init__(self, debug=False, *args, **kwargs):
"""
We're starting the logger subsytem, create the Thread Pool and stuff
"""
self.logger = logging.getLogger(__name__)
if debug:
self.logger.setLevel(logging.DEBUG)
self.log_handler = logging.FileHandler('app.log')
self.logger.addHandler(self.log_handler)
self.threads = concurrent.futures.ThreadPoolExecutor()
self.loop = asyncio.get_event_loop()
self.db = sqlite3.connect('call.db')
super(Server, self).__init__(*args, **kwargs)
async def __listen(self):
logger = logging.getLogger('websockets')
logger.setLevel(logging.DEBUG)
logger.addHandler(self.log_handler)
async with websockets.connect('ws://185.34.33.12:8088/ari/events?app=piphone&api_key=piphone:passpiphone') as websocket:
while self.running == True:
logger.debug("Waiting for events")
event = await websocket.recv()
# Let's call the applications function
await self.dispatch(json.loads(event))
# This is now where I want to create callbacks from events
async def dispatch(self, event):
"""
Let's work on our events. Parse them and do request on the ARI API. Event is
a dict loaded from JSON.
"""
self.logger.debug('Event received: {}'.format(event,))
# Let's get the call ID
if 'channel' not in event:
self.logger.debug('Not a channel event, skip')
return
call = Call.load(event['channel']['id'], self.db)
await call.change((event['channel']['state'], event['channel']['creationtime'],))
def run(self, handler):
"""
We're starting the REST application, and the launch applications
"""
self.running = True
self.server = make_server('127.0.0.1', 8080, handler, **self.options)
self.threads.submit(self.server.serve_forever)
self.loop.run_until_complete(self.__listen())
def stop(self):
self.running = False
self.loop.close()
self.server.shutdown()
self.threads.shutdown(wait=False)
@app.get('/calls/')
@authenticated
def calls(db):
"""
Return the list of calls associated to the connected user.
The call has a status, caller, callee and history (status change+timestamp)
"""
logging.debug("/calls/ called")
results = db.execute('SELECT callid FROM calls WHERE owner = ?;', (request.params['api'],))
calls = []
for call in results.fetchall():
calls.append(Calls.load(call[0], db))
head = {'call': '/calls/', 'user': request.params['api'], 'hits': len(calls)}
calls.append(Call.load(call[0], db))
head = {'call': request.fullpath, 'user': request.params['api'], 'hits': len(calls)}
return {'head': head, 'data': calls}
run(host='localhost', port=8080, debug=True)
@app.post('/calls/<callid>/caller/<caller>/callee/<callee>/')
@authenticated
def originate(db, callid, caller, callee):
call = Call(caller, callee, request.params['api'], callid, db)
logging.debug("Originate a call: {}".format(json.dumps(call, cls=PiphoneJSONEncoder)))
head = {'call': call.url()
, 'user': request.params['api']
, 'hits': 1}
return {'header': head, 'data': call}
if __name__ == '__main__':
app.install(SQLitePlugin(dbfile='call.db'))
app.install(JSONPlugin(json_dumps=lambda s: json.dumps(s, cls=PiphoneJSONEncoder)))
server=Server()
try:
app.run(server=server)
except Exception as e:
print(e)
logging.error(e)
server.stop()
raise e
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment