From 3dda3d2c4743cf2c058303fd4c0a1cbc7e1a8584 Mon Sep 17 00:00:00 2001 From: David Maitland Date: Wed, 12 Aug 2015 17:56:34 +0100 Subject: [PATCH] v0.1.1 --- .gitignore | 3 + LICENSE | 3 +- README.md | 93 ++++++++++++- nimvelo/__init__.py | 259 +++++++++++++++++++++++++++++++++++++ nimvelo/stream/__init__.py | 115 ++++++++++++++++ setup.py | 23 ++++ 6 files changed, 492 insertions(+), 4 deletions(-) create mode 100644 nimvelo/__init__.py create mode 100644 nimvelo/stream/__init__.py create mode 100755 setup.py diff --git a/.gitignore b/.gitignore index ba74660..4f641b0 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ docs/_build/ # PyBuilder target/ + +# Nimvelo +example.py diff --git a/LICENSE b/LICENSE index 9b7c6f6..b9ffee6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2015 Nimvelo +Copyright (c) 2015 Sipcentric Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md index 6f7338e..833a041 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,91 @@ -# python-client -Nimvelo client library for Python 2.7 +# Nimvelo Python Client + +Python 2.7 client library for the Nimvelo/Sipcentric API + +```python +from nimvelo import Nimvelo +nimvelo = Nimvelo(username="myusername", password="mypassword") +print nimvelo.sms.post(_from="0123", to="03301201200", body="Hello World!") +``` + +## Install + +### Best method + +``` +sudo pip install nimvelo +``` + +### Manual method + +``` +git clone git@github.com:Nimvelo/python-client.git && cd python-client +sudo python setup.py install +``` + +## Getting started + +### Examples + +**Get account details** + +```python +from nimvelo import Nimvelo + +nimvelo = Nimvelo(username="myusername", password="mypassword") + +print nimvelo.account.get() +``` + +**Connect to the streaming api** + +```python +from nimvelo import Nimvelo + +nimvelo = Nimvelo(username="myusername", password="mypassword") +stream = nimvelo.Stream + +def callHandler(call): + print 'Incoming call from ' + call['callerIdName'] + ' (' + call['callerIdNumber'] + ')' + +def smsHandler(sms): + print sms['excerpt'] + ' from: ' + sms['from'] + +stream.register(type='incomingcall', callback=callHandler) +stream.register(type='smsreceived', callback=smsHandler) + +stream.connect() +``` + +## Reference + +- nimvelo.Nimvelo(username, password, base='https://pbx.sipcentric.com/api/v1', customer='me') + - account + - get() + - callBundles + - get() + - recordings + - get() + - phoneBook + - get() + - timeIntervals + - get() + - endpoints + - get() + - phoneNumbers + - get() + - sms + - get() + - post(to, _from, body) + - creditStatus + - get() + - calls + - get() + - sounds + - get() + - outgoingCallerIds + - get() + - Stream + - register(type, callback) + - connect() + - disconnect() diff --git a/nimvelo/__init__.py b/nimvelo/__init__.py new file mode 100644 index 0000000..f0c76bb --- /dev/null +++ b/nimvelo/__init__.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- + +# nimvelo/__init__.py +# Python 2.7 client library for the Nimvelo/Sipcentric API +# Copyright (c) 2015 Sipcentric Ltd. Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +import sys +import math +import requests +import json +import time +import logging + +from stream import Stream + +logger = logging.getLogger(__name__) + + +class Nimvelo(object): + + def __init__(self, username, password, base='https://pbx.sipcentric.com/api/v1', customer='me'): + + self.username = username # Account username + self.password = password # Account password + self.base = base # Base API URL (default: https://pbx.sipcentric.com/api/v1) + self.customer = customer # Customer (default: me) + + # Resources + self.account = Account(self) + self.callBundles = CallBundles(self) + self.recordings = Recordings(self) + self.phoneBook = PhoneBook(self) + self.timeIntervals = TimeIntervals(self) + self.endpoints = Endpoints(self) + self.phoneNumbers = PhoneNumbers(self) + self.sms = Sms(self) + self.creditStatus = CreditStatus(self) + self.calls = Calls(self) + self.sounds = Sounds(self) + self.outgoingCallerIds = OutgoingCallerIds(self) + + self.Stream = Stream(self) + + def _request(self, uri, method='GET', data=None, params=None): + + url = self.base + '/customers/' + self.customer + '/' + uri + + auth = requests.auth.HTTPBasicAuth(self.username, self.password) # Basic auth + + if method == 'GET': + + if params: + r = requests.get(url, auth=auth, params=params, verify=True, timeout=3.000) + else: + r = requests.get(url, auth=auth, verify=True, timeout=3.000) + + elif method == 'POST': + + headers = {'content-type': 'application/json'} + + if params: + r = requests.post(url, auth=auth, headers=headers, data=json.dumps(data), params=params, verify=True, timeout=3.000) + else: + r = requests.post(url, auth=auth, headers=headers, data=json.dumps(data), verify=True, timeout=3.000) + + if (r.status_code == 200) or (r.status_code == 201): + + try: + + response = r.json() + return response + + except: + + return True + + elif r.status_code == 401: + + raise AuthenticationException('We couldn\'t authenticate you with the API. Make sure you are using the correct credentials from the \'Web Users\' section of the control panel. If you dont have an account, sign up for one at https://my.nimvelo.com/signup') + + return False + + else: + + if r.json(): + + raise Exception('HTTP Error ' + str(r.status_code), r.json()) + + else: + + raise Exception('HTTP Error ' + str(r.status_code), 'Something went wrong with the request') + + return False + + +class Account(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = '' # Not needed for the base of the customer + + def get(self): + + return self.parent._request(self.uri) + + +class CallBundles(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'callbundles' + + def get(self): + + return self.parent._request(self.uri) + + +class Recordings(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'recordings' + + def get(self): + + return self.parent._request(self.uri) + + +class PhoneBook(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'phonebook' + + def get(self): + + return self.parent._request(self.uri) + + +class TimeIntervals(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'timeintervals' + + def get(self): + + return self.parent._request(self.uri) + + +class Endpoints(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'endpoints' + + def get(self): + + return self.parent._request(self.uri) + + +class PhoneNumbers(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'phonenumbers' + + def get(self): + + return self.parent._request(self.uri) + + +class Sms(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'sms' + + def get(self): + + return self.parent._request(self.uri) + + def post(self, to=None, _from=None, body=None): + + data = { + 'type': 'smsmessage', + 'to': to, + 'from': _from, + 'body': body + } + + return self.parent._request(self.uri, method='POST', data=data) + + +class CreditStatus(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'creditstatus' + + def get(self): + + return self.parent._request(self.uri) + + +class Calls(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'calls' + + def get(self): + + return self.parent._request(self.uri) + + +class Sounds(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'sounds' + + def get(self): + + return self.parent._request(self.uri) + + +class OutgoingCallerIds(object): + + def __init__(self, parent): + + self.parent = parent + self.uri = 'outgoingcallerids' + + def get(self): + + return self.parent._request(self.uri) + + +class AuthenticationException(Exception): + + pass + + +if __name__ == '__main__': + logging.error('Do not run directly, import module first!') + sys.exit() diff --git a/nimvelo/stream/__init__.py b/nimvelo/stream/__init__.py new file mode 100644 index 0000000..ffb8962 --- /dev/null +++ b/nimvelo/stream/__init__.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- + +# nimvelo/stream/__init__.py +# Python 2.7 client library for the Nimvelo/Sipcentric API +# Copyright (c) 2015 Sipcentric Ltd. Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +import multiprocessing +import requests +import json +import time +import logging + +logger = logging.getLogger(__name__) + + +class Stream(object): + '''Allows you to connect to the Nimvelo (Sipcentric) streaming API + and register callbacks to your own functions. + ''' + + def __init__(self, parent): + + self.parent = parent + + self.process = multiprocessing.Process(target=self.__run) + + self.username = self.parent.username # Account username + self.password = self.parent.password # Account password + self.base = self.parent.base + '/stream' # Base streaming URL (default: https://pbx.sipcentric.com/api/v1/stream) + + self.heartbeat = None + + self.eventsCallback = None + self.incomingcallCallback = None + self.smsreceivedCallback = None + + def __proccess(self, event): + + event = json.loads(event) + + logger.info('Processing event') + logger.debug(event) + + if event['event'] == 'heartbeat': + + self.heartbeat = time.time() + return True + + elif event['event'] == 'incomingcall': + + if self.incomingcallCallback: + self.incomingcallCallback(event['values']) + return True + + elif event['event'] == 'smsreceived': + + if self.smsreceivedCallback: + self.smsreceivedCallback(event['values']) + return True + + if self.eventsCallback: + + self.eventsCallback(event) + return True + + def __run(self): + + stream = '' # Used as a buffer for the stream data + data = False # Data is not JSON until we detect it + level = 0 # JSON object depth + + r = requests.get(self.base, verify=True, auth=(self.username, self.password), stream=True) + + for i in r.iter_content(): + if i == '{': + stream += i + level += 1 + data = True + + elif i == '}': + stream += i + data = False + level -= 1 + + if level <= 0: + self.__proccess(stream) + stream = '' + + elif data is True: + stream += i + + def register(self, type, callback): + + # Register a function to a callback in the class + if type == 'incomingcall': + self.incomingcallCallback = callback + elif type == 'smsreceived': + self.smsreceivedCallback = callback + elif type == 'events': + self.eventsCallback = callback + + logger.info('Callback registered') + + def connect(self): + + # Start multiprocessing thread + self.process.start() + logger.info('Connected') + + def disconnect(self): + + # Terminate multiprocessing thread + self.process.terminate() + logger.info('Disconnected') diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..f844ac5 --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python2.7 +# -*- coding: utf-8 -*- + +# setup.py +# Python 2.7 client library for the Nimvelo/Sipcentric API +# Copyright (c) 2015 Sipcentric Ltd. Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +import os +from setuptools import setup + +setup( + name='nimvelo', + description='Python 2.7 client library for the Nimvelo/Sipcentric API', + version='0.1.1', + url='https://github.com/Nimvelo/python-client', + author='David Maitland', + author_email='david.maitland@nimvelo.com', + license='MIT', + keywords='nimvelo sipcentric voip pbx sms sip', + packages=['nimvelo', 'nimvelo/stream'], + requires=['math', 'json', 'logging', 'time'], + install_requires=['requests', 'multiprocessing'] +)