py-Asterisk-0.5.3/0000755000175000017500000000000012224537335015010 5ustar jeanschjeansch00000000000000py-Asterisk-0.5.3/Asterisk/0000755000175000017500000000000012224537335016575 5ustar jeanschjeansch00000000000000py-Asterisk-0.5.3/Asterisk/CLI.py0000644000175000017500000000740312224536642017562 0ustar jeanschjeansch00000000000000#!/usr/bin/env python ''' Asterisk/CLI.py: Command-line wrapper around the Asterisk Manager API. ''' __author__ = 'David M. Wilson ' __id__ = '$Id$' import sys, os, inspect from Asterisk import Manager, BaseException, Config import Asterisk.Util class ArgumentsError(BaseException): _prefix = 'bad arguments' def usage(argv0, out_file): ''' Print command-line program usage. ''' argv0 = os.path.basename(argv0) usage = ''' %(argv0)s actions Show available actions and their arguments. %(argv0)s action [ [ ..]] Execute the specified action. For named arguments "--name=" syntax may also be used. %(argv0)s command "" Execute the specified Asterisk console command. %(argv0)s usage Display this message. %(argv0)s help Display usage message for the given . ''' % locals() out_file.writelines([ line[6:] + '\n' for line in usage.splitlines() ]) def show_actions(action = None): if action is None: print print 'Supported actions and their arguments.' print '======================================' print class AllActions(Manager.CoreActions, Manager.ZapataActions): pass methods = [ (name, obj) for (name, obj) in inspect.getmembers(AllActions) \ if inspect.ismethod(obj) and name[0] != '_' ] if action is not None: methods = [ x for x in methods if x[0].lower() == action.lower() ] for name, method in methods: arg_spec = inspect.getargspec(method) arg_spec[0].pop(0) print ' Action:', name fmt = inspect.formatargspec(*arg_spec)[1:-1] if fmt: print 'Arguments:', fmt foo = [ x.strip() for x in method.__doc__.strip().splitlines() ] print ' ' + '\n '.join(foo) print def execute_action(manager, argv): method_name = argv.pop(0).lower() method_dict = dict(\ [ (k.lower(), v) for (k, v) in inspect.getmembers(manager) \ if inspect.ismethod(v) ]) try: method = method_dict[method_name] except KeyError, e: raise ArgumentsError('%r is not a valid action.' % (method_name,)) pos_args = [] kw_args = {} process_kw = True for arg in argv: if process_kw and arg == '--': process_kw = False # stop -- processing. elif process_kw and arg[:2] == '--' and '=' in arg: key, val = arg[2:].split('=', 2) kw_args[key] = val else: pos_args.append(arg) Asterisk.Util.dump_human(method(*pos_args, **kw_args)) def command_line(argv): ''' Act as a command-line tool. ''' commands = [ 'actions', 'action', 'command', 'usage', 'help' ] if len(argv) < 2: raise ArgumentsError('please specify at least one argument.') command = argv[1] if command not in commands: raise ArgumentsError('invalid arguments.') if command == 'usage': return usage(argv[0], sys.stdout) manager = Manager.Manager(*Config.Config().get_connection()) if command == 'actions': show_actions() if command == 'help': if len(argv) < 3: raise ArgumentsError('please specify an action.') show_actions(argv[2]) elif command == 'action': if len(argv) < 3: raise ArgumentsError('please specify an action.') try: execute_action(manager, argv[2:]) except TypeError, e: print "Bad arguments specified. Help for %s:" % (argv[2],) show_actions(argv[2]) elif command == 'command': execute_action('command', argv[2]) py-Asterisk-0.5.3/Asterisk/Config.py0000644000175000017500000000541712224536642020363 0ustar jeanschjeansch00000000000000''' Asterisk/Config.py: filesystem configuration reader. ''' import os, ConfigParser import Asterisk # Default configuration file search path: CONFIG_FILENAME = 'py-asterisk.conf' CONFIG_PATHNAMES = [ os.environ.get('PYASTERISK_CONF', ''), os.path.join(os.environ.get('HOME', ''), '.py-asterisk.conf'), os.path.join(os.environ.get('USERPROFILE', ''), 'py-asterisk.conf'), 'py-asterisk.conf', '/etc/py-asterisk.conf', '/etc/asterisk/py-asterisk.conf', ] class ConfigurationError(Asterisk.BaseException): 'This exception is raised when there is a problem with the configuration.' _prefix = 'configuration error' class Config(object): def _find_config(self, config_pathname): ''' Search the filesystem paths listed in CONFIG_PATHNAMES for a regular file. Return the name of the first one found, or , if it is not None. ''' if config_pathname is None: for pathname in CONFIG_PATHNAMES: if os.path.exists(pathname): config_pathname = pathname break if config_pathname is None: raise ConfigurationError('cannot find a suitable configuration file.') return config_pathname def refresh(self): 'Read py-Asterisk configuration data from the filesystem.' try: self.conf = ConfigParser.SafeConfigParser() self.conf.readfp(file(self.config_pathname)) except ConfigParser.Error, e: raise ConfigurationError('%r contains invalid data at line %r' %\ (self.config_pathname, e.lineno)) def __init__(self, config_pathname = None): config_pathname = self._find_config(config_pathname) if config_pathname is None: raise ConfigurationError('could not find a configuration file.') self.config_pathname = config_pathname self.refresh() def get_connection(self, connection = None): ''' Return an (address, username, secret) argument tuple, suitable for initialising a Manager instance. If is specified, use the named instead of the configuration default. ''' conf = self.conf try: if connection is None: connection = conf.get('py-asterisk', 'default connection') items = dict(conf.items('connection: ' + connection)) except ConfigParser.Error, e: raise ConfigurationError(str(e)) try: address = (items['hostname'], int(items['port'])) except ValueError: raise ConfigurationError('The port number specified in profile %r is not valid.' % profile) return ( address, items['username'], items['secret']) py-Asterisk-0.5.3/Asterisk/Logging.py0000644000175000017500000000331312224536642020535 0ustar jeanschjeansch00000000000000''' Asterisk/Logging.py: extensions to the Python 2.3 logging module. ''' __author__ = 'David Wilson' __Id__ = '$Id$' import logging # Add new levels. logging.STATE = logging.INFO - 1 logging.PACKET = logging.DEBUG - 1 logging.IO = logging.PACKET - 1 logging.addLevelName(logging.STATE, 'STATE') logging.addLevelName(logging.PACKET, 'PACKET') logging.addLevelName(logging.IO, 'IO') # Attempt to find the parent logger class using the Python 2.4 API. if hasattr(logging, 'getLoggerClass'): loggerClass = logging.getLoggerClass() else: loggerClass = logging.Logger # Provide a new logger class that supports our new levels. class AsteriskLogger(loggerClass): def state(self, msg, *args, **kwargs): "Log a message with severity 'STATE' on this logger." return self.log(logging.STATE, msg, *args, **kwargs) def packet(self, msg, *args, **kwargs): "Log a message with severity 'PACKET' on this logger." return self.log(logging.PACKET, msg, *args, **kwargs) def io(self, msg, *args, **kwargs): "Log a message with severity 'IO' on this logger." return self.log(logging.IO, msg, *args, **kwargs) # Install the new system-wide logger class. logging.setLoggerClass(AsteriskLogger) # Per-instance logging mix-in. class InstanceLogger(object): def getLoggerName(self): ''' Return the name where log messages for this instance is sent. ''' return '%s.%s' % (self.__module__, self.__class__.__name__) def getLogger(self): ''' Return the Logger instance which receives debug messages for this class instance. ''' return logging.getLogger(self.getLoggerName()) py-Asterisk-0.5.3/Asterisk/Manager.py0000644000175000017500000010252212224537006020516 0ustar jeanschjeansch00000000000000''' Asterisk Manager and Channel objects. ''' __author__ = 'David Wilson' __id__ = '$Id$' import socket, time, logging, errno, os, re, datetime from new import instancemethod import Asterisk, Asterisk.Util, Asterisk.Logging # Your ParentBaseException class should provide a __str__ method that combined # _prefix and _error as ('%s: %s' % (_prefix, _error) or similar. class BaseException(Asterisk.BaseException): 'Base class for all Asterisk Manager API exceptions.' _prefix = 'Asterisk' class AuthenticationFailure(BaseException): 'This exception is raised when authentication to the PBX instance fails.' _error = 'Authentication failed.' class CommunicationError(BaseException): 'This exception is raised when the PBX responds in an unexpected manner.' def __init__(self, packet, msg = None): e = 'Unexpected response from PBX: %r\n' % (msg,) self._error = e + ': %r' % (packet,) class GoneAwayError(BaseException): 'This exception is raised when the Manager connection becomes closed.' class InternalError(BaseException): 'This exception is raised when an error occurs within a Manager object.' _prefix = 'py-Asterisk internal error' class ActionFailed(BaseException): 'This exception is raised when a PBX action fails.' _prefix = 'py-Asterisk action failed' class PermissionDenied(BaseException): ''' This exception is raised when our connection is not permitted to perform a requested action. ''' _error = 'Permission denied' class BaseChannel(Asterisk.Logging.InstanceLogger): ''' Represents a living Asterisk channel, with shortcut methods for operating on it. The object acts as a mapping, ie. you may get and set items of it. This translates to Getvar and Setvar actions on the channel. ''' def __init__(self, manager, id): ''' Initialise a new Channel object belonging to reachable via BaseManager . ''' self.manager = manager self.id = id self.log = self.getLogger() def __eq__(self, other): 'Return truth if is equal to this object.' return (self.id == other.id) and (self.manager is other.manager) def __hash__(self): 'Return the hash value of this channel.' return hash(self.id) ^ id(self.manager) def __str__(self): return self.id def __repr__(self): return '<%s.%s referencing channel %r of %r>' %\ (self.__class__.__module__, self.__class__.__name__, self.id, self.manager) def AbsoluteTimeout(self, timeout): 'Set the absolute timeout of this channel to .' return self.manager.AbsoluteTimeout(self, timeout) def ChangeMonitor(self, pathname): 'Change the monitor filename of this channel to .' return self.manager.ChangeMonitor(self, pathname) def Getvar(self, variable, default = Asterisk.Util.Unspecified): ''' Return the value of this channel's , or if variable is not set. ''' if default is Asterisk.Util.Unspecified: return self.manager.Getvar(self, variable) return self.manager.Getvar(self, variable, default) def Hangup(self): 'Hangup this channel.' return self.manager.Hangup(self) def Monitor(self, pathname, format, mix): 'Begin monitoring of this channel into using .' return self.manager.Monitor(self, pathname, format, mix) def Redirect(self, context, extension = 's', priority = 1, channel2 = None): ''' Redirect this channel to of in , optionally bridging with . ''' return self.manager.Redirect(self, context, extension, priority, channel2) def SetCDRUserField(self, data, append = False): "Append or replace this channel's CDR user field with ." return self.manager.SetCDRUserField(self, data, append) def Setvar(self, variable, value): 'Set the in this channel to .' return self.manager.Setvar(self, variable, value) def Status(self): 'Return the Status() dict for this channel (wasteful!).' return self.manager.Status()[self] def StopMonitor(self): 'Stop monitoring of this channel.' return self.manager.StopMonitor(self) def __getitem__(self, key): 'Fetch as a variable from this channel.' return self.Getvar(key) def __setitem__(self, key, value): 'Set as a variable on this channel.' return self.Setvar(key, value) class ZapChannel(BaseChannel): def ZapDNDoff(self): 'Disable DND status on this Zapata driver channel.' return self.manager.ZapDNDoff(self) def ZapDNDon(self): 'Enable DND status on this Zapata driver channel.' return self.manager.ZapDNDon(self) def ZapDialOffhook(self, number): 'Off-hook dial on this Zapata driver channel.' return self.manager.ZapDialOffhook(self, number) def ZapHangup(self): 'Hangup this Zapata driver channel.' return self.manager.ZapHangup(self) def ZapTransfer(self): 'Transfer this Zapata driver channel.' return self.manager.ZapTransfer(self) class BaseManager(Asterisk.Logging.InstanceLogger): 'Base protocol implementation for the Asterisk Manager API.' _AST_BANNERS = [ 'Asterisk Call Manager/1.0\r\n', 'Asterisk Call Manager/1.1\r\n', 'Asterisk Call Manager/1.2\r\n', 'Asterisk Call Manager/1.3\r\n', ] def __init__(self, address, username, secret, listen_events=True, timeout=None): ''' Provide communication methods for the PBX instance running at
. Authenticate using and . Receive event information from the Manager API if is True. ''' self.address = address self.username = username self.secret = secret self.listen_events = listen_events self.events = Asterisk.Util.EventCollection() self.timeout = timeout # Configure logging: self.log = self.getLogger() self.log.debug('Initialising.') sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(self.timeout) sock.connect(address) self.file = sock.makefile('r+', 0) # line buffered. self.fileno = self.file.fileno self.response_buffer = [] self._authenticate() def get_channel(self, channel_id): 'Return a channel object for the given .' if channel_id[:3].lower() == 'zap': return ZapChannel(self, channel_id) return BaseChannel(self, channel_id) def _authenticate(self): 'Read the server banner and attempt to authenticate.' banner = self.file.readline() if banner not in self._AST_BANNERS: raise Exception('banner incorrect; got %r, expected one of %r' %\ (banner, self._AST_BANNERS)) action = { 'Username': self.username, 'Secret': self.secret } if not self.listen_events: action['Events'] = 'off' self.log.debug('Authenticating as %r/%r.', self.username, self.secret) self._write_action('Login', action) if self._read_packet().Response == 'Error': raise AuthenticationFailure('authentication failed.') self.log.debug('Authenticated as %r.', self.username) def __repr__(self): 'Return a string representation of this object.' return '<%s.%s connected as %s to %s:%d>' %\ ((self.__module__, self.__class__.__name__, self.username) + self.address) def _write_action(self, action, data = None): ''' Write an request to the Manager API, sending header keys and values from the mapping . Return the (string) action identifier on success. Values from are omitted if they are None. ''' id = str(time.time()) # Assumes microsecond precision for reliability. lines = [ 'Action: ' + action, 'ActionID: ' + id ] if data is not None: for item in data.iteritems(): if item[1] is not None: if not isinstance(item[1], list): lines.append('%s: %s' % item) elif isinstance(item[1], list): for param in item[1]: lines.append('%s: %s' % (item[0], param)) self.log.packet('write_action: %r', lines) for line in lines: self.file.write(line + '\r\n') self.log.io('_write_action: send %r', line + '\r\n') self.file.write('\r\n') self.log.io('_write_action: send: %r', '\r\n') return id def _read_response_follows(self): ''' Continue reading the remainder of this packet, in the format sent by the "command" action. ''' self.log.debug('In _read_response_follows().') lines = [] packet = Asterisk.Util.AttributeDict({ 'Response': 'Follows', 'Lines': lines }) line_nr = 0 empty_line_ts = None while True: line = self.file.readline().rstrip() self.log.io('_read_response_follows: recv %r', line) line_nr += 1 # In some case, ActionID is the line 2 the first starting with # 'Privilege:' if line_nr in [1, 2] and line.startswith('ActionID: '): # Asterisk is a pile of shite!!!!!!!!! packet.ActionID = line[10:] elif line == '--END COMMAND--': self.file.readline() self.log.debug('Completed _read_response_follows().') return packet elif not line: if self.timeout: now = datetime.datetime.now() if empty_line_ts is None: empty_line_ts = now else: if (now - empty_line_ts).seconds > self.timeout: self.log.debug("Bogus asterisk 'Command' answer.'") raise CommunicationError(packet, 'expected --END COMMAND--') self.log.debug('Empty line encountered.') else: lines.append(line) def _read_packet(self, discard_events = False): ''' Read a set of packet from the Manager API, stopping when a "\r\n\r\n" sequence is read. Return the packet as a mapping. If is True, discard all Event packets and wait for a Response packet, this is used while closing down the channel. ''' packet = Asterisk.Util.AttributeDict() self.log.debug('In _read_packet().') while True: line = self.file.readline().rstrip() self.log.io('_read_packet: recv %r', line) if not line: if not packet: raise GoneAwayError('Asterisk Manager connection has gone away.') self.log.packet('_read_packet: %r', packet) if discard_events and 'Event' in packet: self.log.debug('_read_packet() discarding: %r.', packet) packet.clear() continue self.log.debug('_read_packet() completed.') return packet val = None if line.count(':') == 1 and line[-1] == ':': # Empty field: key, val = line[:-1], '' elif line.count(',') == 1 and line[0] == ' ': # ChannelVariable key, val = line[1:].split(',', 1) else: # Some asterisk features like 'XMPP' presence # send bogus packets with empty lines in the datas # We should properly fail on those packets. try: key, val = line.split(': ', 1) except: raise InternalError('Malformed packet detected: %r' % (packet,)) if key == 'Response' and val == 'Follows': return self._read_response_follows() packet[key] = val def _dispatch_packet(self, packet): 'Feed a single packet to an event handler.' if 'Response' in packet: self.log.debug('_dispatch_packet() placed response in buffer.') self.response_buffer.append(packet) elif 'Event' in packet: self._translate_event(packet) self.log.debug('_dispatch_packet() passing event to on_Event.') self.on_Event(packet) else: raise InternalError('Unknown packet type detected: %r' % (packet,)) def _translate_response(self, packet, success = None): ''' Raise an error if the reponse packet reports failure. Convert any channel identifiers to their equivalent objects using get_channel(). ''' for key in ('Channel', 'Channel1', 'Channel2'): if key in packet: packet[key] = self.get_channel(packet[key]) if packet.Response in ('Success', 'Follows', 'Pong'): return packet if packet.Message == 'Permission denied': raise PermissionDenied raise ActionFailed(packet.Message) def _translate_event(self, event): ''' Translate any objects discovered in to Python types. ''' for key in ('Channel', 'Channel1', 'Channel2'): if key in event: event[key] = self.get_channel(event[key]) def close(self): 'Log off and close the connection to the PBX.' self.log.debug('Closing down.') self._write_action('Logoff') packet = self._read_packet(discard_events = True) if packet.Response != 'Goodbye': raise CommunicationError(packet, 'expected goodbye') self.file.close() def read(self): 'Called by the parent code when activity is detected on our fd.' self.log.io('read(): Activity detected on our fd.') packet = self._read_packet() self._dispatch_packet(packet) def read_response(self, id): 'Return the response packet found for the given action .' buffer = self.response_buffer while True: if buffer: for idx, packet in enumerate(buffer): # It is an error if no ActionID is sent. This is intentional. if packet.ActionID == id: buffer.pop(idx) packet.pop('ActionID') return packet packet = self._read_packet() if 'Event' in packet: self._dispatch_packet(packet) elif not packet.has_key('ActionID'): raise CommunicationError(packet, 'no ActionID') elif packet.ActionID == id: packet.pop('ActionID') return packet else: buffer.append(packet) def on_Event(self, event): 'Triggered when an event is received from the Manager.' self.events.fire(event.Event, self, event) def responses_waiting(self): 'Return truth if there are unprocessed buffered responses.' return bool(self.response_buffer) def serve_forever(self): 'Handle one event at a time until doomsday.' while True: packet = self._read_packet() self._dispatch_packet(packet) def strip_evinfo(self, event): ''' Given an event, remove it's ActionID and Event members. ''' new = event.copy() del new['ActionID'], new['Event'] return new class CoreActions(object): ''' Provide methods for Manager API actions exposed by the core Asterisk engine. ''' def AbsoluteTimeout(self, channel, timeout): 'Set the absolute timeout of to .' id = self._write_action('AbsoluteTimeout', { 'Channel': channel, 'Timeout': int(timeout) }) self._translate_response(self.read_response(id)) def ChangeMonitor(self, channel, pathname): 'Change the monitor filename of to .' id = self._write_action('ChangeMonitor', { 'Channel': channel, 'File': pathname }) self._translate_response(self.read_response(id)) def Command(self, command): 'Execute console command and return its output lines.' id = self._write_action('Command', {'Command': command}) return self._translate_response(self.read_response(id))['Lines'] def DBGet(self, family, key): 'Retrieve a key from the Asterisk database' id = self._write_action('DBGet', {'Family': family, 'Key': key}) try: response = self._translate_response(self.read_response(id)) except Asterisk.Manager.ActionFailed as e: return str(e) if response.get('Response') == 'Success': packet = self._read_packet() return packet.get('Value') def Events(self, categories): 'Filter received events to only those in the list .' id = self._write_action('Events', { 'EventMask': ','.join(categories) }) return self._translate_response(self.read_response(id)) def ExtensionStates(self): 'Return nested dictionary of contexts, extensions and their state' hint_re = re.compile(r'\s+(\d+)@(\S+).+State:(\S+)') state_dict = dict() for line in self.Command('core show hints'): match = hint_re.search(line) if match: extension, context, state = match.groups() if context in state_dict: state_dict[context][extension] = state else: state_dict[context] = {extension: state} return state_dict def Getvar(self, channel, variable, default = Asterisk.Util.Unspecified): ''' Return the value of 's , or if is not set. ''' self.log.debug('Getvar(%r, %r, default=%r)', channel, variable, default) id = self._write_action('Getvar', { 'Channel': channel, 'Variable': variable }) response = self._translate_response(self.read_response(id)) if response.has_key(variable): value = response[variable] else: value = response['Value'] if value == '(null)': if default is Asterisk.Util.Unspecified: raise KeyError(variable) else: self.log.debug('Getvar() returning %r', default) return default self.log.debug('Getvar() returning %r', value) return value def Hangup(self, channel): 'Hangup .' id = self._write_action('Hangup', { 'Channel': channel }) return self._translate_response(self.read_response(id)) def ListCommands(self): 'Return a dict of all available => items.' id = self._write_action('ListCommands') commands = self._translate_response(self.read_response(id)) del commands['Response'] return commands def Logoff(self): 'Close the connection to the PBX.' return self.close() def MailboxCount(self, mailbox): 'Return a (, ) tuple for the given .' # TODO: this can sum multiple mailboxes too. id = self._write_action('MailboxCount', { 'Mailbox': mailbox }) result = self._translate_response(self.read_response(id)) return int(result.NewMessages), int(result.OldMessages) def MailboxStatus(self, mailbox): 'Return the number of messages in .' id = self._write_action('MailboxStatus', { 'Mailbox': mailbox }) return int(self._translate_response(self.read_response(id))['Waiting']) def MeetMe(self): ''' Return list of dictionaries containing confnum, parties, marked, activity and creation. Returns empty list if no conferences active. ''' resp = self.Command('meetme') meetme_list = list() if resp[1] == 'No active MeetMe conferences.': return meetme_list else: meetme_re = re.compile(r'^(?P\d+)\s+(?P\d+)\s+(?P\S+)\s+(?P\S+)\s+(?P\S+)') for line in resp: match = meetme_re.search(line) if match: meetme_list.append(match.groupdict()) return meetme_list def MeetMeList(self, confnum): 'Lists users in conferences' resp = self.Command('meetme list %s' % confnum) caller_list = list() if (resp[1] == 'No active conferences.') or ('No such conference' in resp[1]): return caller_list else: meetme_re = re.compile(r'^User #: (?P\d+)\s+(?P.+)\s+Channel: (?P\S+)\s+\((?P.+)\)\s+(?P\S+)') for line in resp: match = meetme_re.search(line) if match: caller_list.append(match.groupdict()) return caller_list def Monitor(self, channel, pathname, format, mix): 'Begin monitoring of into using .' id = self._write_action('Monitor', { 'Channel': channel, 'File': pathname, 'Format': format, 'Mix': mix and 'yes' or 'no' }) return self._translate_response(self.read_response(id)) def Originate(self, channel, context = None, extension = None, priority = None, application = None, data = None, timeout = None, caller_id = None, variable = None, account = None, async = None): ''' Originate(channel, context = .., extension = .., priority = ..[, ...]) Originate(channel, application = ..[, data = ..[, ...]]) Originate a call on , bridging it to the specified dialplan extension (format 1) or application (format 2). Dialplan context to bridge with. Context extension to bridge with. Context priority to bridge with. Application to bridge with. Application parameters. Answer timeout for in milliseconds. Outgoing channel Caller ID. channel variable to set (K=V[|K2=V2[|..]]). CDR account code. Return successfully immediately. ''' # Since channel is a required parameter, no need including it here. # As a matter of fact, including it here, generates an AttributeError # because 'None' does not have an 'id' attribute which is required in # checking equality with an object like channel has_dialplan = None not in (context, extension) has_application = application is not None if has_dialplan and has_application: raise ActionFailed('Originate: dialplan and application calling style are mutually exclusive.') if not (has_dialplan or has_application): raise ActionFailed('Originate: neither dialplan or application calling style used. Refer to documentation.') if not channel: raise ActionFailed('Originate: you must specify a channel.') data = { 'Channel': channel, 'Context': context, 'Exten': extension, 'Priority': priority, 'Application': application, 'Data': data, 'Timeout': timeout, 'CallerID': caller_id, 'Variable': variable, 'Account': account, 'Async': int(bool(async)) } id = self._write_action('Originate', data) return self._translate_response(self.read_response(id)) def Originate2(self, channel, parameters): ''' Originate a call, using parameters in the mapping . Provided for compatibility with RPC bridges that do not support keyword arguments. ''' return self.Originate(channel, **parameters) def ParkedCalls(self): 'Return a nested dict describing currently parked calls.' id = self._write_action('ParkedCalls') self._translate_response(self.read_response(id)) parked = {} def ParkedCall(self, event): event = self.strip_evinfo(event) parked[event.pop('Exten')] = event def ParkedCallsComplete(self, event): stop_flag[0] = True events = Asterisk.Util.EventCollection([ ParkedCall, ParkedCallsComplete ]) self.events += events try: stop_flag = [ False ] while stop_flag[0] == False: packet = self._read_packet() self._dispatch_packet(packet) finally: self.events -= events return parked def Ping(self): 'No-op to ensure the PBX is still there and keep the connection alive.' id = self._write_action('Ping') return self._translate_response(self.read_response(id)) def QueueAdd(self, queue, interface, penalty = 0): 'Add to with optional .' id = self._write_action('QueueAdd', { 'Queue': queue, 'Interface': interface, 'Penalty': str(int(penalty)) }) return self._translate_response(self.read_response(id)) def QueuePause(self, queue, interface, paused): 'Pause in .' id = self._write_action('QueuePause', { 'Queue': queue, 'Interface': interface, 'Paused': paused and 'true' or 'false' }) return self._translate_response(self.read_response(id)) def QueueRemove(self, queue, interface): 'Remove from .' id = self._write_action('QueueRemove', { 'Queue': queue, 'Interface': interface }) return self._translate_response(self.read_response(id)) def QueueStatus(self): 'Return a complex nested dict describing queue statii.' id = self._write_action('QueueStatus') self._translate_response(self.read_response(id)) queues = {} def QueueParams(self, event): queue = self.strip_evinfo(event) queue['members'] = {} queue['entries'] = {} queues[queue.pop('Queue')] = queue def QueueMember(self, event): member = self.strip_evinfo(event) queues[member.pop('Queue')]['members'][member.pop('Location')] = member def QueueEntry(self, event): entry = self.strip_evinfo(event) queues[entry.pop('Queue')]['entries'][event.pop('Channel')] = entry def QueueStatusComplete(self, event): stop_flag[0] = True events = Asterisk.Util.EventCollection([ QueueParams, QueueMember, QueueEntry, QueueStatusComplete]) self.events += events try: stop_flag = [False] while stop_flag[0] == False: packet = self._read_packet() self._dispatch_packet(packet) finally: self.events -= events return queues Queues = QueueStatus def Redirect(self, channel, context, extension = 's', priority = 1, channel2 = None): ''' Redirect to of in , optionally bridging with ''' id = self._write_action('Redirect', { 'Channel': channel, 'Context': context, 'Exten': extension, 'Priority': priority, 'ExtraChannel': channel2 }) return self._translate_response(self.read_response(id)) def SetCDRUserField(self, channel, data, append = False): "Append or replace 's CDR user field with '." id = self._write_action('SetCDRUserField', { 'Channel': channel, 'UserField': data, 'Append': append and 'yes' or 'no' }) return self._translate_response(self.read_response(id)) def Setvar(self, channel, variable, value): 'Set of to .' id = self._write_action('Setvar', { 'Channel': channel, 'Variable': variable, 'Value': value }) return self._translate_response(self.read_response(id)) def SipShowPeer(self, peer): 'Fetch the status of SIP peer .' id = self._write_action('SIPshowpeer', {'Peer': peer}) showpeer = self._translate_response(self.read_response(id)) del showpeer['Response'] return showpeer def SipShowRegistry(self): 'Return a nested dict of SIP registry.' id = self._write_action('SIPshowregistry') self._translate_response(self.read_response(id)) registry = {} def RegistryEntry(self, event): event = self.strip_evinfo(event) name = event.pop('Host') registry[name] = event def RegistrationsComplete(self, event): stop_flag[0] = True events = Asterisk.Util.EventCollection([ RegistryEntry, RegistrationsComplete ]) self.events += events try: stop_flag = [ False ] while stop_flag[0] == False: packet = self._read_packet() self._dispatch_packet(packet) finally: self.events -= events return registry def Status(self): 'Return a nested dict of channel statii.' id = self._write_action('Status') self._translate_response(self.read_response(id)) channels = {} def Status(self, event): event = self.strip_evinfo(event) name = event.pop('Channel') channels[name] = event def StatusComplete(self, event): stop_flag[0] = True events = Asterisk.Util.EventCollection([ Status, StatusComplete ]) self.events += events try: stop_flag = [ False ] while stop_flag[0] == False: packet = self._read_packet() self._dispatch_packet(packet) finally: self.events -= events return channels def StopMonitor(self, channel): 'Stop monitoring of .' id = self._write_action('StopMonitor', { 'Channel': channel }) return self._translate_response(self.read_response(id)) class ZapataActions(object): 'Provide methods for Manager API actions exposed by the Zapata driver.' def ZapDialOffhook(self, channel, number): 'Off-hook dial on Zapata driver .' id = self._write_action('ZapDialOffhook', { 'ZapChannel': channel, 'Number': number }) return self._translate_response(self.read_response(id)) def ZapDNDoff(self, channel): 'Disable DND status on Zapata driver .' id = self._write_action('ZapDNDoff', { 'ZapChannel': str(int(channel)) }) return self._translate_response(self.read_response(id)) def ZapDNDon(self, channel): 'Enable DND status on Zapata driver .' id = self._write_action('ZapDNDon', { 'ZapChannel': str(int(channel)) }) return self._translate_response(self.read_response(id)) def ZapHangup(self, channel): 'Hangup Zapata driver .' id = self._write_action('ZapHangup', { 'ZapChannel': str(int(channel)) }) return self._translate_response(self.read_response(id)) def ZapShowChannels(self): 'Return a nested dict of Zapata driver channel statii.' id = self._write_action('ZapShowChannels') self._translate_response(self.read_response(id)) channels = {} def ZapShowChannels(self, event): event = self.strip_evinfo(event) number = int(event.pop('Channel')) channels[number] = event def ZapShowChannelsComplete(self, event): stop_flag[0] = True events = Asterisk.Util.EventCollection([ ZapShowChannels, ZapShowChannelsComplete ]) self.events += events try: stop_flag = [ False ] while stop_flag[0] == False: packet = self._read_packet() self._dispatch_packet(packet) finally: self.events -= events return channels def ZapTransfer(self, channel): 'Transfer Zapata driver .' # TODO: Does nothing on X100P. What is this for? id = self._write_action('ZapTransfer', { 'ZapChannel': channel }) return self._translate_response(self.read_response(id)) class CoreManager(BaseManager, CoreActions, ZapataActions): ''' Asterisk Manager API protocol implementation and core actions, but without event handlers. ''' pass class Manager(BaseManager, CoreActions, ZapataActions): ''' Asterisk Manager API protocol implementation, core event handler placeholders, and core actions. ''' py-Asterisk-0.5.3/Asterisk/Util.py0000644000175000017500000001211612224536642020065 0ustar jeanschjeansch00000000000000''' Asterisk/Util.py: utility classes. ''' __author__ = 'David Wilson' __Id__ = '$Id$' import sys, copy import Asterisk from Asterisk import Logging class SubscriptionError(Asterisk.BaseException): ''' This exception is raised when an attempt to register the same (event, handler) tuple twice is detected. ''' # This special unique object is used to indicate that an argument has not been # specified. It is used where None may be a valid argument value. class Unspecified(object): 'A class to represent an unspecified value that cannot be None.' def __repr__(self): return '' Unspecified = Unspecified() class AttributeDict(dict): def __getattr__(self, key): return self[key] def __setattr__(self, key, value): self[key] = value def copy(self): return AttributeDict(self.iteritems()) class EventCollection(Logging.InstanceLogger): ''' Utility class to allow grouping and automatic registration of event. ''' def __init__(self, initial = None): ''' If is not None, register functions from the list waiting for events with the same name as the function. ''' self.subscriptions = {} self.log = self.getLogger() if initial is not None: for func in initial: self.subscribe(func.__name__, func) def subscribe(self, name, handler): ''' Subscribe callable to event named . ''' if name not in self.subscriptions: subscriptions = self.subscriptions[name] = [] else: subscriptions = self.subscriptions[name] if handler in subscriptions: raise SubscriptionError subscriptions.append(handler) def unsubscribe(self, name, handler): 'Unsubscribe callable to event named .' self.subscriptions[name].remove(handler) def clear(self): 'Destroy all present subscriptions.' self.subscriptions.clear() def fire(self, name, *args, **kwargs): ''' Fire event passing * and ** to subscribers, returning the return value of the last called subscriber. ''' if name not in self.subscriptions: return return_value = None for subscription in self.subscriptions[name]: self.log.debug('calling %r(*%r, **%r)', subscription, args, kwargs) return_value = subscription(*args, **kwargs) return return_value def copy(self): new = self.__class__() for name, subscriptions in self.subscriptions.iteritems(): new.subscriptions[name] = [] for subscription in subscriptions: new.subscriptions[name].append(subscription) def __iadd__(self, collection): 'Add all the events in to our collection.' if not isinstance(collection, EventCollection): raise TypeError new = self.copy() try: for name, handlers in collection.subscriptions.iteritems(): for handler in handlers: self.subscribe(name, handler) except Exception, e: self.subscriptions = new.subscriptions raise return self def __isub__(self, collection): 'Remove all the events in from our collection.' if not isinstance(collection, EventCollection): raise TypeError new = self.copy() try: for name, handlers in collection.subscriptions.iteritems(): for handler in handlers: self.unsubscribe(name, handler) except Exception, e: self.subscriptions = new.subscriptions raise return self def dump_packet(packet, file = sys.stdout): ''' Dump a packet in human readable form to file-like object . ''' packet = dict(packet) if 'Event' in packet: file.write('-- %s\n' % packet.pop('Event')) else: file.write('-- Response: %s\n' % packet.pop('Response')) packet = packet.items() packet.sort() for tuple in packet: file.write(' %s: %s\n' % tuple) file.write('\n') def dump_human(data, file = sys.stdout, _indent = 0): scalars = (str, int, float) recursive = (dict, list, tuple, AttributeDict) indent = lambda a = 0, i = _indent: (' ' * (a + i)) Type = type(data) if Type in (dict, AttributeDict): items = data.items() items.sort() for key, val in items: file.write(indent() + str(key) + ': ') if type(val) in recursive: file.write('\n') dump_human(val, file, _indent + 1) else: dump_human(val, file, 0) elif Type in (list, tuple): for val in data: dump_human(val, file, _indent + 1) elif Type in (int, float): file.write(indent() + '%r\n' % data) elif Type is str: file.write(indent() + data + '\n') py-Asterisk-0.5.3/Asterisk/__init__.py0000644000175000017500000000225712224536642020714 0ustar jeanschjeansch00000000000000''' Asterisk Manager API Python package. ''' __author__ = 'David Wilson' __id__ = '$Id$' try: __revision__ = int('$Rev$'.split()[1]) except: __revision__ = None __version__ = '0.1' __all__ = [ 'CLI', 'Config', 'Logging', 'Manager', 'Util' ] cause_codes = { 0: ( 0, 'UNKNOWN', 'Unkown' ), 1: ( 1, 'UNALLOCATED', 'Unallocated number' ), 16: ( 16, 'CLEAR', 'Normal call clearing' ), 17: ( 17, 'BUSY', 'User busy' ), 18: ( 18, 'NOUSER', 'No user responding' ), 21: ( 21, 'REJECTED', 'Call rejected' ), 22: ( 22, 'CHANGED', 'Number changed' ), 27: ( 27, 'DESTFAIL', 'Destination out of order' ), 28: ( 28, 'NETFAIL', 'Network out of order' ), 41: ( 41, 'TEMPFAIL', 'Temporary failure' ) } class BaseException(Exception): ''' Base class for all py-Asterisk exceptions. ''' _prefix = '(Base Exception)' _error = '(no error)' def __init__(self, error): self._error = error def __str__(self): return '%s: %s' % (self._prefix, self._error) py-Asterisk-0.5.3/asterisk-dump0000755000175000017500000000236312224536642017532 0ustar jeanschjeansch00000000000000#!/usr/bin/env python2.3 ''' Dump events from the Manager interface to stdout. ''' __author__ = 'David Wilson' __id__ = '$Id$' import sys, time, socket from Asterisk.Config import Config from Asterisk.Manager import CoreManager import Asterisk.Manager, Asterisk.Util class MyManager(CoreManager): ''' Print events to stdout. ''' def on_Event(self, event): Asterisk.Util.dump_packet(event) def main2(): manager = MyManager(*Config().get_connection()) try: print '#', repr(manager) print manager.serve_forever() except KeyboardInterrupt, e: raise SystemExit def main(argv): max_reconnects = 100 reconnect_delay = 2 while True: try: main2() except Asterisk.Manager.GoneAwayError, e: print '#', str(e) except socket.error, e: print print '# Connect error:', e[1] reconnect_delay *= 2 print '# Waiting', reconnect_delay, 'seconds before reconnect.' print '# Will try', max_reconnects, 'more times before exit..' max_reconnects -= 1 time.sleep(reconnect_delay) print '# Reconnecting...' if __name__ == '__main__': main(sys.argv[1:]) py-Asterisk-0.5.3/asterisk-recover0000755000175000017500000000344112224536642020230 0ustar jeanschjeansch00000000000000#!/usr/bin/env python2.3 ''' Recover a channel lost in asterisk. Usage: asterisk-recover ''' __author__ = 'David Wilson' __id__ = '$Id: asterisk-dump 3 2004-09-03 01:41:42Z dw $' import sys from Asterisk.Config import Config from Asterisk.Manager import Manager def main(argv): manager = Manager(*Config().get_connection()) statii = manager.Status() if not len(statii): print >> sys.stderr, 'No channels active.' print raise SystemExit, 0 if len(sys.argv) == 5: manager.Redirect(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]) else: chans = [ x for x in statii.items() if not x[1].has_key('Extension') ] for nr, (channel, status) in enumerate(chans): print 'Channel #%d:' % (nr+1, ), channel if len(status['CallerID']): print ' Caller ID:', status['CallerID'] if status.has_key('Link') and len(status['Link']): print ' Link:', status['Link'] print try: chan_nr = input('Channel number [default: 1]: ') - 1 except SyntaxError, e: print 'Using channel #1.' chan_nr = 1 if len(chans) < chan_nr: print 'Using channel #1.' chan_nr = 0 else: chan_nr -= 1 context = raw_input('Context [default: local_extensions]: ') or 'local_extensions' exten = raw_input('Extension [default: 101]: ') or '101' priority = raw_input('Priority [default: 1]: ') or '1' print 'Redirecting', chans[chan_nr][0], 'to', '%s:%s@%s' % (exten, priority, context) manager.Redirect(chans[chan_nr][0], context, exten, priority) if __name__ == '__main__': main(sys.argv[1:]) py-Asterisk-0.5.3/py-asterisk0000755000175000017500000000145112224536642017212 0ustar jeanschjeansch00000000000000#!/usr/bin/env python2.3 ''' py-asterisk: User interface to Asterisk.CLI. ''' __author__ = 'David Wilson' __id__ = '$Id$' import Asterisk, Asterisk.Manager, Asterisk.CLI import sys, os, getopt progname = os.path.basename(sys.argv[0]) try: sys.exit(Asterisk.CLI.command_line(sys.argv)) except Asterisk.CLI.ArgumentsError, error: print >> sys.stderr, progname, 'error:', str(error) Asterisk.CLI.usage(progname, sys.stderr) except Asterisk.BaseException, error: print >> sys.stderr, progname, 'error:', str(error) except IOError, error: print >> sys.stderr, '%s: %s: %s' %\ ( progname, error.filename, error.strerror ) except getopt.GetoptError, error: print >> sys.stderr, '%s: %s' %\ ( progname, error.msg ) Asterisk.CLI.usage(progname, sys.stderr) py-Asterisk-0.5.3/setup.py0000644000175000017500000000102412224537174016520 0ustar jeanschjeansch00000000000000#!/usr/bin/env python ''' py-Asterisk distutils script. ''' __author__ = 'David Wilson' __id__ = '$Id$' from distutils.core import setup setup( name = 'py-Asterisk', version = '0.5.3', description = 'Asterisk Manager API Python interface.', author = 'David Wilson', author_email = 'dw@botanicus.net', license = 'MIT', url = 'http://code.google.com/p/py-asterisk/', packages = [ 'Asterisk' ], scripts = [ 'asterisk-dump', 'py-asterisk' ] ) py-Asterisk-0.5.3/PKG-INFO0000644000175000017500000000040012224537335016077 0ustar jeanschjeansch00000000000000Metadata-Version: 1.0 Name: py-Asterisk Version: 0.5.3 Summary: Asterisk Manager API Python interface. Home-page: http://code.google.com/p/py-asterisk/ Author: David Wilson Author-email: dw@botanicus.net License: MIT Description: UNKNOWN Platform: UNKNOWN