transpub/cgi/sipgate.py

238 lines
9.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- encoding: UTF8 -*-
# author: Philipp Klaus, philipp.klaus →AT→ gmail.com
# This file is part of python-sipgate-xmlrpc.
#
# python-sipgate-xmlrpc is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# python-sipgate-xmlrpc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with python-sipgate-xmlrpc. If not, see <http://www.gnu.org/licenses/>.
#####################################################################
###### This the most important file of the project: #######
###### It contains the classe api, which #######
###### implements the XML-RPC communication with the #######
###### Sipgate API. #######
#from time import time
from sys import stderr
from xmlrpclib import ServerProxy, Fault, ProtocolError, ResponseError
from exceptions import TypeError
from socket import error as socket_error
import re
VERSION = "0.9.2"
NAME = "%s - python-sipgate-xmlrpc/sipgate.py"
VENDOR = "https://github.com/pklaus/python-sipgate-xmlrpc"
### ------- Here comes the most important piece of code: the api class with magic methods -----
class api (ServerProxy):
def __init__ (self, username=False, password=False, prog_name=False, verbose=False):
if not (username and password and prog_name):
raise SipgateAPIException('To use the class sipgate.api you must provide, username, password and a program name.')
address = SIPGATE_API_URL % {'username':username, 'password':password}
### The super() call would be more modern but it doesn't work with the current Python version yet.
#super(api, self).__init__(address, verbose=debug)
ServerProxy.__init__(self, address,verbose=verbose)
### It is considered good practice to Identify the client talking to the server:
self.ClientIdentify({ "ClientName" : NAME % prog_name, "ClientVersion" : VERSION, "ClientVendor" : VENDOR })
def __getattr__(self,name):
return _Method(self.__request, name)
def __request (self, methodname, params):
if methodname.replace(API_PREFIX,'') not in VALID_METHODS:
stderr.write( UNKNOWN_METHOD_MESSAGE % {
'method': methodname.replace(API_PREFIX,''), 'api_prefix': API_PREFIX,
'api_version': SIPGATE_API_DOC_V, 'api_date': SIPGATE_API_DOC_D } )
if len(params)>0 and not type(params[0]) is dict:
raise TypeError(DICT_AS_PARAM_MESSAGE % methodname.replace(API_PREFIX,''))
method_function = ServerProxy.__getattr__(self,methodname)
try:
result = method_function(params[0] if len(params)>0 and type(params[0]) is dict else dict())
# cast the result dictionary to a SipgateResponse (custom dictionary):
result = SipgateResponse(result)
except Fault, e:
raise SipgateAPIFault(e.faultCode, e.faultString)
except ProtocolError, e:
raise SipgateAPIProtocolError(e.url, e.errcode, e.errmsg, e.headers)
except socket_error, (value,message):
raise SipgateAPISocketError(value, message)
return result
## <http://stackoverflow.com/questions/2390827/how-to-properly-subclass-dict-and-override-get-set>
class SipgateResponse(dict):
def __init__(self, response_dict):
try:
self.StatusCode, self.StatusString = int(response_dict['StatusCode']), response_dict['StatusString']
self.success = self.StatusCode == 200
except:
raise TypeError(RESPONSE_NOT_A_DICTIONARY % response_dict)
dict.__init__(self, response_dict)
class _Method:
# With the help of this class the api class does not
# need to state explicitly the possible XML-RPC calls.
def __init__(self, send, name):
self.__send = send
self.__name = API_PREFIX+name
def __call__(self, *args):
return self.__send(self.__name, args)
### ------ now we define the exceptions that could occur ------
class SipgateAPIException(Exception):
pass
class SipgateAPIFault(Fault, SipgateAPIException):
# As this inherits from xmlrpclib.Fault it also has the
# attributes faultCode and faultString.
pass
class SipgateAPIProtocolError(ProtocolError, SipgateAPIException):
# As this inherits from xmlrpclib.ProtocolError it also has the
# attributes errcode and errmsg.
pass
class SipgateAPISocketError(socket_error, SipgateAPIException):
# As this inherits from socket.error it also has the
# attributes .
pass
### ------ This section contains message strings -------
UNKNOWN_METHOD_MESSAGE = "The method '%(method)s' for the API prefix '%(api_prefix)s' " + \
"was called. This method, however, is currently not documented for the Sipgate API " + \
"v%(api_version)s (%(api_date)s). Let's try but I've warned you.\n"
DICT_AS_PARAM_MESSAGE = 'Please specify a dictionary as function call parameter for api.%s().'
RESPONSE_NOT_A_DICTIONARY = 'The response "%s" does not seem to be a response from the ' + \
'Sipgate XML-RPC API.'
### ------ This section contains constants of the Sipgate XML-RPC API -------
# This constant represents the version of the currently implemented Sipgate API
# ans is taken from the API description PDF:
SIPGATE_API_DOC_V = '1.06'
SIPGATE_API_DOC_D = 'August 21, 2007'
# Sipgate basic and plus accounts must use this API URL:
SIPGATE_API_URL = "https://%(username)s:%(password)s@samurai.sipgate.net/RPC2"
# Sipgate one and team have a different URL: api.sipgate.net.
# see <http://groups.google.com/group/sipgate-api/msg/51a3535b6d61241f>
API_PREFIX = 'samurai.'
VALID_METHODS = [
'AccountStatementGet',
'BalanceGet',
'ClientIdentify',
'HistoryGetByDate',
'ItemizedEntriesGet',
'OwnUriListGet',
'PhonebookEntryGet',
'PhonebookListGet',
'RecommendedIntervalGet',
'ServerdataGet',
'SessionClose',
'SessionInitiate',
'SessionInitiateMulti',
'SessionStatusGet',
'TosListGet',
'TosListGet',
'UmSummaryGet',
'UserdataGreetingGet',
'UserdataSipGet',
]
SERVER_STATUS_CODES = {
### From Table A.1 and A.2 of the API docu: general server status codes
200: 'Method success',
400: 'Method not supported',
401: 'Request denied (no reason specified)',
402: 'Internal error',
403: 'Invalid arguments',
404: 'Resources exceeded (this MUST not be used to indicate parameters in error)',
405: 'Invalid parameter name',
406: 'Invalid parameter type',
407: 'Invalid parameter value',
408: 'Attempt to set a non-writable parameter',
409: 'Notification request rejected.',
410: 'Parameter exceeds maximum size.',
411: 'Missing parameter.',
412: 'Too many requests.',
500: 'Date out of range.',
501: 'Uri does not belong to user.',
502: 'Unknown type of service.',
503: 'Selected payment method failed.',
504: 'Selected currency not supported.',
505: 'Amount exceeds limit.',
506: 'Malformed SIP URI.',
507: 'URI not in list.',
508: 'Format is not valid E.164.',
509: 'Unknown status.',
510: 'Unknown ID.',
511: 'Invalid timevalue.',
512: 'Referenced session not found.',
513: 'Only single default per TOS allowed.',
514: 'Malformed VCARD format.',
515: 'Malformed PID format.',
516: 'Presence information not available.',
517: 'Invalid label name.',
518: 'Label not assigned.',
519: 'Label doesnt exist.',
520: 'Parameter includes invalid characters.',
521: 'Bad password. (Rejected due to security concerns.)',
522: 'Malformed timezone format.',
523: 'Delay exceeds limit.',
524: 'Requested VPN type not available.',
525: 'Requested TOS not available.',
526: 'Unified messaging not available.',
527: 'URI not available for registration.',
}
TYPE_OF_SERVICE = {
'fax': 'pages', # fax transmission
'text': 'characters', # text message (e.g. "SMS")
'video': 'seconds', # video communication
'voice': 'seconds', # voice communication
}
class helpers (object):
@staticmethod
def FQTN(phone_number, default_country_code):
"""
Assures phone numbers are in the form of a E164 Fully Qualified Telephone Number
without the leading + sign.
The alternative would be the Python port of Google's libphonenumber:
https://github.com/daviddrysdale/python-phonenumbers
"""
phone_number = phone_number.replace(' ','').replace('-','').replace('+','').replace('/','')
## number starting with 00 (so it's an international format)
if re.compile("^00[1-9][0-9]*$").match(phone_number):
return phone_number[2:]
## number starting with your country code (so it was already a FQTN):
if re.compile("^"+default_country_code+"[1-9][0-9]*$").match(phone_number):
return phone_number
if re.compile("^0[1-9]*$").match(phone_number):
return default_country_code+phone_number[1:]
if re.compile("^[1-9]*$").match(phone_number):
return phone_number
raise TypeError("Couldn't parse this phone number: "+phone_number)