Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
piphone-sip
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
4
Issues
4
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
La Quadrature du Net
piphone
piphone-sip
Commits
c796e765
Commit
c796e765
authored
Apr 26, 2016
by
okhin
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Everything things is plugged i now, need to do the application work
parent
f89712dc
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
158 additions
and
26 deletions
+158
-26
app.py
app.py
+158
-26
No files found.
app.py
View file @
c796e765
...
...
@@ -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
r
oute
,
run
,
request
,
abort
,
install
,
get
,
post
from
bottle
import
r
equest
,
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
.
call
id
,
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
(
Call
s
.
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment