-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
116 changed files
with
10,402 additions
and
18,772 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ tests/.coverage | |
build/ | ||
tests/report/ | ||
cover/ | ||
htmlcov/ | ||
|
||
.coverage | ||
coverage.xml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ | |
- Luit | ||
- JorisE | ||
- HansAdema | ||
- hafkensite |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,18 @@ | ||
# Cacofonisk | ||
|
||
Cacofonisk is a framework that connects to the Asterisk PBX, listens to events | ||
on the Asterisk Management Interface (AMI) and tracks the status of calls | ||
Cacofonisk is a framework that connects to the Asterisk PBX, listens to events | ||
on the Asterisk Management Interface (AMI) and tracks the status of calls | ||
currently in progress in Asterisk. | ||
|
||
Cacofonisk takes a stream of AMI events as input and uses these to keep track | ||
of the channels currently active in Asterisk and how they are related. When | ||
Cacofonisk takes a stream of AMI events as input and uses these to keep track | ||
of the channels currently active in Asterisk and how they are related. When | ||
something interesting happens to one of the channels, it will call a method on | ||
a call state Reporter with interesting information about the call, like who is | ||
a call state Reporter with interesting information about the call, like who is | ||
in the call, and a unique identifier. | ||
|
||
This data can then be used to send webhooks regarding a call, to notify a | ||
This data can then be used to send webhooks regarding a call, to notify a | ||
person who is being called, or to log calls being performed. | ||
|
||
## Status | ||
|
||
This product is actively being developed and used at VoIPGRID. | ||
|
@@ -23,6 +23,7 @@ This product is actively being developed and used at VoIPGRID. | |
|
||
- Python >= 3.4 | ||
- Panoramisk 1.x | ||
- Asterisk >= 12 | ||
|
||
### Installation | ||
|
||
|
@@ -42,54 +43,56 @@ $ python3 setup.py install | |
|
||
To run Cacofonisk, you will need two things: a Runner and a Reporter. | ||
|
||
A Runner is a class which is responsible for passing AMI events to the Cacofonisk. Two runners are included: an AmiRunner (which connects to the Asterisk Management Interface) and a FileRunner (which imports AMI events from a JSON file). | ||
A Runner is a class which is responsible for passing AMI events to the | ||
Cacofonisk. Two runners are included: an AmiRunner (which connects to the | ||
Asterisk Management Interface) and a FileRunner (which imports AMI events from | ||
a JSON file). | ||
|
||
A Reporter is a class which takes the interesting data from Cacofonisk and does awesome things with it. Two reports have been included: a DebugReporter (which just dumps the data to stdout) and a JsonReporter (which creates JSON files for the FileRunner). | ||
A Reporter is a class which takes the interesting data from Cacofonisk and does | ||
awesome things with it. You can find various Reporters in the `examples` | ||
folder. | ||
|
||
To create your own reporter, you can extend the BaseReport class and implement your own event handlers, like so: | ||
To create your own reporter, you can extend the BaseReporter class and | ||
implement your own event handlers, like so: | ||
|
||
```python | ||
from cacofonisk import AmiRunner, BaseReporter | ||
|
||
|
||
class ReportAllTheThings(BaseReporter): | ||
|
||
def on_b_dial(self, call_id, caller, to_number, targets): | ||
callee_codes = [target.code for target in targets] | ||
caller_number = caller.number | ||
print("{} is now ringing {} on number {}".format( | ||
caller_number, ', '.join(callee_codes), to_number, | ||
def on_b_dial(self, caller, targets): | ||
target_channels = [target.name for target in targets] | ||
caller_number = caller.caller_id.num | ||
print("{} is now calling {}".format( | ||
caller_number, ', '.join(target_channels), | ||
)) | ||
|
||
def on_up(self, call_id, caller, to_number, callee): | ||
callee_account_code = callee.code | ||
caller_number = caller.number | ||
print("{} is now in conversation with {}".format(caller_number, callee_account_code)) | ||
|
||
def on_warm_transfer(self, call_id, merged_id, redirector, caller, destination): | ||
print('{} is now calling with {} (was calling {})'.format(caller, destination, redirector)) | ||
|
||
def on_cold_transfer(self, call_id, merged_id, redirector, caller, to_number, targets): | ||
print('{} tried to transfer the call from {} to number {} (ringing {})'.format( | ||
redirector, caller, to_number, ', '.join(targets), | ||
)) | ||
def on_up(self, caller, target): | ||
target_number = target.caller_id.num | ||
caller_number = caller.caller_id.num | ||
print("{} is now in conversation with {}".format(caller_number, target_number)) | ||
|
||
def on_hangup(self, caller, reason): | ||
caller_number = caller.caller_id.num | ||
print("{} is no longer calling (reason: {})".format(caller_number, reason)) | ||
|
||
def on_hangup(self, call_id, caller, to_number, reason): | ||
print("{} is no longer calling number {} (reason: {})".format(caller, to_number, reason)) | ||
|
||
|
||
reporter = ReportAllTheThings() | ||
runner = AmiRunner([ | ||
{'host': '127.0.0.1', 'username': 'cacofonisk', 'password': 'bard', 'port': 5038}, | ||
], reporter) | ||
runner = AmiRunner(['tcp://username:[email protected]:5038'], reporter) | ||
runner.run() | ||
``` | ||
|
||
This reporter can then be passed to a Runner of your choice to process AMI events. | ||
This reporter can then be passed to a Runner of your choice to process AMI | ||
events. | ||
|
||
For more information about the parameters of the reporter, please see the docs in BaseReporter. | ||
For more information about the parameters of the reporter, please see the docs | ||
in BaseReporter. | ||
|
||
You can also listen for [UserEvents](https://wiki.asterisk.org/wiki/display/AST/Asterisk+11+Application_UserEvent) using the `on_user_event` function. This can be used to pass additional data from Asterisk to your Cacofonisk application. | ||
You can also listen for | ||
[UserEvents](https://wiki.asterisk.org/wiki/display/AST/Asterisk+13+Application_UserEvent) | ||
using the `on_user_event` function. This can be used to pass additional data | ||
from Asterisk to your Cacofonisk application. | ||
|
||
#### Running the tests | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,4 @@ | ||
from .handlers import EventHandler | ||
from .reporters import BaseReporter, LoggingReporter | ||
from .runners.ami_runner import AmiRunner | ||
from .runners.file_runner import FileRunner | ||
|
||
from .reporters.base_reporter import BaseReporter | ||
from .reporters.debug_reporter import DebugReporter | ||
from .reporters.json_reporter import JsonReporter | ||
|
||
from .channel import ChannelManager |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
class MissingBridgeUniqueid(KeyError): | ||
pass | ||
|
||
|
||
class Bridge(object): | ||
""" | ||
The Bridge object represents a Bridge in Asterisk. | ||
With Asterisk 12+, Asterisk creates bridges and puts channels into them to | ||
make audio flow between the channels. This class is a Python representation | ||
of such bridges. | ||
""" | ||
|
||
def __init__(self, event): | ||
""" | ||
Create a new bridge object. | ||
Args: | ||
event (Event): A BridgeCreate event. | ||
""" | ||
self.uniqueid = event['BridgeUniqueid'] | ||
self.type = event['BridgeType'] | ||
self.technology = event['BridgeTechnology'] | ||
self.creator = event['BridgeCreator'] | ||
self.video_source_mode = event['BridgeVideoSourceMode'] | ||
|
||
self.peers = set() | ||
|
||
def __len__(self): | ||
""" | ||
Get the number of channels in this bridge. | ||
Returns: | ||
int: The number of channels in this bridge. | ||
""" | ||
return len(self.peers) | ||
|
||
def __repr__(self): | ||
""" | ||
Get a textual representation of this bridge. | ||
Returns: | ||
str: A representation of this bridge. | ||
""" | ||
return '<Bridge(id={self.uniqueid},peers={peers})>'.format( | ||
self=self, | ||
peers=','.join([chan.name for chan in self.peers]), | ||
) | ||
|
||
|
||
class BridgeDict(dict): | ||
""" | ||
A dict which raises a MissingBridgeUniqueid exception if a key is missing. | ||
""" | ||
|
||
def __getitem__(self, item): | ||
try: | ||
return super(BridgeDict, self).__getitem__(item) | ||
except KeyError: | ||
raise MissingBridgeUniqueid(item) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,43 @@ | ||
""" | ||
CallerId holds information about one end of a call. | ||
The users of this application are interested in tuples of caller ID | ||
number, caller ID name and sometimes an account ID (accountcode), and | ||
it's privacy settings, both for call initiators and for call recipients. | ||
""" | ||
from collections import namedtuple | ||
|
||
|
||
class CallerId(namedtuple('CallerIdBase', 'code name number is_public')): | ||
class CallerId(namedtuple('CallerIdBase', 'name num')): | ||
""" | ||
An immutable CallerId class. | ||
CallerId holds immutable information about one end of a call. | ||
The users of this application are interested in tuples of caller ID number, | ||
caller ID name and sometimes an account ID (accountcode), and it's privacy | ||
settings, both for call initiators and for call recipients. | ||
Usage:: | ||
caller = CallerId(name='My name', number='+311234567', is_public=True) | ||
caller = caller.replace(code=123456789) | ||
""" | ||
def __new__(cls, code=0, name='', number=None, is_public=None): | ||
return super().__new__(cls, code, name, number, is_public) | ||
|
||
def __new__(cls, name='', num=''): | ||
if name == '<unknown>': | ||
name = '' | ||
|
||
if num == '<unknown>': | ||
num = '' | ||
|
||
return super().__new__(cls, name, str(num)) | ||
|
||
def replace(self, **kwargs): | ||
""" | ||
Return a new CallerId instance replacing specified fields with | ||
new values. | ||
Create a copy of this CallerId with specified changes. | ||
Args: | ||
**kwargs: One or more of code, name, number, is_public. | ||
Returns: | ||
CallerId: A new instance with replaced values. | ||
""" | ||
# The method already exists on the namedtuple. We simply make it | ||
# public. | ||
return self._replace(**kwargs) | ||
if 'name' in kwargs and kwargs['name'] == '<unknown>': | ||
kwargs['name'] = '' | ||
|
||
def _is_public_tag(self): | ||
if self.is_public is None: | ||
return '' | ||
elif self.is_public: | ||
return ';pub' | ||
else: | ||
return ';priv' | ||
|
||
def __str__(self): | ||
return '"{}" <{}{};code={}>'.format( | ||
self.name.replace('\\', '\\\\').replace('"', '\\"'), | ||
self.number, self._is_public_tag(), self.code) | ||
if 'number' in kwargs and kwargs['number'] == '<unknown>': | ||
kwargs['number'] = '' | ||
|
||
return self._replace(**kwargs) |
Oops, something went wrong.