[ previous ] [ next ] [ threads ]
 To :  yate@v...
 From :  Maciek Kaminski <maciejka@t...>
 Subject :  newest yaypm
 Date :  Tue, 28 Mar 2006 21:33:32 +0200
Enjoy.

mk



#!/usr/bin/python
# -*- coding: iso-8859-2; -*-
"""
 yaypm.py

 YAYPM - Yet Another Yate(http://yate.null.ro/) Python Module,
 uses Twisted 2.0 (http://twistedmatrix.com/projects/core/).
 
 Copyright (C) 2005 Maciek Kaminski
 
 This program 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 2 of the License, or
 (at your option) any later version.

 This program 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 this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""

from twisted.protocols.basic import LineReceiver
from twisted.internet.protocol import Protocol, ClientFactory
from twisted.internet import reactor, protocol, defer

from twisted.python import log, failure
import sys, logging, random, time, types, traceback

logger = logging.getLogger('yaypm')
logger_messages = logging.getLogger('yaypm.messages')

triggeredUntilCondition = False
handledUntilCondition = False

def escape(str, extra = ":"):
    str = str + ""
    s = ""
    n = len(str)
    i = 0
    while i < n:
        c = str[i]
        if( ord(c) < 32 ) or (c == extra):
            c = chr(ord(c) + 64)
            s = s + "%"
        elif( c == "%" ):
            s = s + c
        s = s + c
        i = i + 1
    return s

def unescape(str):
    s = ""
    n = len(str)
    i = 0
    while i < n:
        c = str[i]
        if c == "%":
            i = i + 1
            c = str[i]
            if c != "%":
                c = chr(ord(c) - 64)
        s = s + c
        i = i + 1
    return s

def parse_attrs(values):
    values = values.split(":")
    if values:
        attrs = {}
        for key, value in [x.split("=", 1) for x in values]:
            attrs[key] = unescape(value)
        return attrs
    else:
        return None


class AbandonedException(Exception):
    def __init__(self, cause):
        Exception.__init__(self)
        self.cause = cause

class CancelledError(Exception):
     pass

class DisconnectedException(Exception):
    pass

class CancellableDeferred(defer.Deferred):
    def __init__(self, canceller=None):
        defer.Deferred.__init__(self)
        self.canceller = canceller
        
    def cancel(self, *args, **kwargs):
        canceller=self.canceller
        if not self.called:
            if canceller:
                canceller(self, *args, **kwargs)
            if not self.called:
               self.errback(CancelledError())
        elif isinstance(self.result, CancellableDeferred):
            # Waiting for another deferred -- cancel it instead
            self.result.cancel()
        #else:
            # Called and not waiting for another deferred
            #raise defer.AlreadyCalledError
            

class Message:   
    def __init__(self, type = 'message', **arguments):
        self.type = type       
        for k, v in arguments.iteritems():
            self.__dict__[k] = v
        if not self.__dict__.has_key("time"):
            self.time = long(time.time())
        if not self.__dict__.has_key("id"):            
            self.id = str(time.time()) + str(random.randrange(1, 10000, 1))
        self.returned = False
    def __str__(self):
        result = self.name
        for k, v in self.__dict__.iteritems():
            if k != "name":
                result = result + ", %s: '%s'" % (k, str(v))
        return result

    def format_attrs(self):
        if self.__dict__.has_key("attrs") and self.attrs != None:
            result = ""
            for name, value in self.attrs.iteritems():
                if type(value) == type(u""):
                    v = value.encode('raw_unicode_escape')
                elif type(value) != type(""):
                    v = str(value)
                else:
                    v = value
                result = result + ":" + name + "=" + escape(v)
            return result
        else:
            return ""

      
    def format_message_response(self, processed):
        result = "%%%%message::

#!/usr/bin/python
# -*- coding: iso-8859-2; -*-
"""
 flow.py

 Flow submodule for YAYPM.
 
 Copyright (C) 2005 Maciek Kaminski 

 This program 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 2 of the License, or
 (at your option) any later version.

 This program 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 this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""

from twisted.internet import reactor, defer
import types

import sys, logging, random, time, types, traceback, yaypm

logger_flow = logging.getLogger('yaypm.flow')

current_result = None

def logFailure(f):
    if f.type!=yaypm.AbandonedException:
        if logger_flow.isEnabledFor(logging.WARN):
            logger_flow.warn("Exception in flow: %s" % str(f))
    return f

class Result:
    def __init__(self, d):
        self.deferred = d
        self.abandoned = None
        self.failure = None
    
    def getResult(self):
        current_result = None                    
        if self.failure:
            if self.failure.value:
                raise self.failure.value
            else:
                raise Exception(self.failure)
        else:
            return self.result

def getResult():
    global current_result    
    if current_result:
        return current_result.getResult()
    else:
        None
        

class Flow:
    def __init__(self, fun_todo, return_with):
        self.fun_todo = fun_todo
        self.return_with = return_with
        try:
            global current_result
            current_result = None
            self.result = Result(fun_todo.next())
            if isinstance(self.result.deferred, defer.Deferred):
                self.result.deferred.addCallbacks(self.callback, self.errback)
                self.result.deferred.addErrback(logFailure)
                self.result.deferred.addErrback(self.errback)
            else:
                self.return_with.callback(self.result.deferred)
        except StopIteration:
            if current_result:
                self.return_with.callback(current_result.result)
            else:
                self.return_with.callback(None)

    def callback(self, m):
        self.result.result = m
        try:
            global current_result
            current_result = self.result
            self.result = Result(self.fun_todo.next())
            if isinstance(self.result.deferred, defer.Deferred):
                self.result.deferred.addCallbacks(self.callback, self.errback)
                self.result.deferred.addErrback(logFailure)
                self.result.deferred.addErrback(self.exception)                
            else:
                self.return_with.callback(self.result.deferred)
        except StopIteration:
            if current_result:
                self.return_with.callback(current_result.result)
            else:
                self.return_with.callback(None)            

    def errback(self, failure):
        self.result.failure = failure
        self.callback(None)

    def exception(self, failure):
        self.return_with.errback(failure)

def go(fun_todo):
    return_with = defer.Deferred()
    if isinstance(fun_todo, types.GeneratorType):
        Flow(fun_todo, return_with)
    else:
        reactor.callLater(0, return_with.callback, fun_todo)
    return return_with

def sleep(time):
    d = defer.Deferred()
    reactor.callLater(time, d.callback, None)
    return d


def build_start(fun, *fun_args):
    def start(yate):
        go(fun(yate, *fun_args))
    return start



#!/usr/bin/python
# -*- coding: iso-8859-2; -*-

"""
 examples.py

 Examples for YAYPM.
 
 Copyright (C) 2005 Maciek Kaminski 

 This program 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 2 of the License, or
 (at your option) any later version.

 This program 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 this program; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""


from yaypm import YateClientProtocolFactory, Message, AbandonedException, DisconnectedException, logger, logger_messages, RestrictedYateClient
from yaypm.flow import sleep, build_start, go, logger_flow, getResult
from twisted.internet import reactor, defer

import sys, logging, random, time

logger_examples = logging.getLogger('yaypm.examples')

beepresource = "wave/play/./sounds/beep.gsm"
digitresource = "wave/play/./sounds/digits/pl/%s.gsm"

level_colors  = {
  "DEBUG": "3[22;32m", "INFO": "3[01;34m",
  "WARNING": "3[22;35m", "ERROR": "3[22;31m",
  "CRITICAL": "3[01;31m"
 };

class ConsoleFormater(logging.Formatter) :    
    def __init__(self, fmt = '%(message)s', datefmt=None):
        logging.Formatter.__init__(self, fmt, datefmt)
        
    def format(self, record):
        if(level_colors.has_key(record.levelname)):
            record.levelname = "%s%s3[0;0m" % (level_colors[record.levelname], record.levelname)
        record.name = "3[37m3[1m%s3[0;0m" % record.name
        return logging.Formatter.format(self, record)    

def voip_client(yate, callto):
    
    yield defer.DeferredList(
        [yate.install("call.execute", 50),
         yate.watch("call.execute"),
         yate.install("chan.dtmf"),
         yate.install("chan.notify"),
         yate.install("chan.hangup")])

    getResult()

    while True:
        yield yate.onmsg("call.execute", lambda m : m.attrs["callto"] == callto)
        execute = getResult()

        execute.attrs["callto"] = "dumb/"

        yate.ret(execute, False)        

        yield yate.onwatch(
            "call.execute",
            lambda m : m.attrs["id"] == execute.attrs["id"])

        execute = getResult()

        yield sleep(1)
        getResult()

        yate.dispatch(Message(
                name = "call.ringing",
               attrs = {"id": execute.attrs["id"],
                        "peerid": execute.attrs["peerid"]}))

        logger_examples.info("sleeping for 10s ...")
        yield sleep(10)
        getResult()

        logger_examples.info("answering...")

        yield yate.dispatch(Message(
                name = "call.answered",
               attrs = {"id": execute.attrs["id"],
                        "peerid": execute.attrs["peerid"]}))
        
        logger_examples.info("answered")
      
        yield yate.dispatch(Message(
                name = "chan.attach",
               attrs = {"userData": execute.attrs["peerid"],
                        "source": "alsa/",
                        "consumer": "alsa/"}))
        attach = getResult()        

        logger_examples.info("chan oss attached")
    

def keyecho(yate, callid, targetid):
    try:
        logger_examples.info("keyecho started: %s -> %s" % (callid, targetid))

        yield yate.dispatch(Message(
            name = "chan.masquerade",
            attrs = {"message" : "chan.attach",
                     "id" : targetid,
                     "source": beepresource}))
        attach = getResult()

        while True:
            yield yate.onmsg("chan.dtmf",
                             lambda m : m.attrs["id"] == callid,
                             autoreturn = True)
            dtmf = getResult()

            logger_examples.info(
                "keyecho: dtmf %s from %s " % (dtmf.attrs["text"], callid))
            yield yate.dispatch(Message(
                     name = "chan.masquerade",
                    attrs = {"message" : "chan.attach",
                             "id" : targetid,
                             "source": digitresource % dtmf.attrs["text"]}))
            print getResult()
            #No yield here, to not to loose dtmfs
            
    except AbandonedException, e:
        logger_examples.info("keyecho abandoned by: %s\n" % e.cause)


def dropcore(yate, callid, targetid):
    try:
        logger_examples.info("drop core started: %s -> %s" % (callid, targetid))

        while True:
            yield yate.dispatch(Message(
                name = "chan.masquerade",
                attrs = {"message" : "chan.attach",
                         "id" : targetid,
                         "source": beepresource}))
            attach = getResult()
            time.sleep(0.01)
                      
    except AbandonedException, e:
        logger_examples.info("dropcore abandoned by: %s\n" % e.cause)
    except DisconnectedException, e:
        logger_examples.info("Disconnected. Core droped?")
        
def route(yate, called, target, handleConn = None):

    logger_examples.info("Will route %s to %s" % (called, target))
    logger_examples.warn("RestrictedYateClient will be used as a wrapper!")
    
    yate.install("call.route", 50)
    yate.watch("call.execute")
    yate.watch("call.answered")
    yate.install("chan.dtmf", 50)
    yate.install("chan.hangup")

    try:

        while True:
            yield yate.onmsg("call.route", lambda m : m.attrs["called"] == called)

            route = getResult()

            callid = route.attrs["id"]
            yate.ret(route, True, target)

            end = yate.onmsg(
                "chan.hangup",
                lambda m : m.attrs["id"] == callid, autoreturn = True)

            yield yate.onwatch(
                "call.execute",
                lambda m : m.attrs["id"] == callid, until = end)
            execute = getResult()

            if target.startswith("dumb"):
                peerid = execute.attrs["peerid"]
                yate.dispatch(Message(
                    name = "call.answered",
                    attrs = {"id": execute.attrs["peerid"],
                             "peerid": execute.attrs["id"]}))            
            else:
                yield yate.onwatch(
                    "call.answered",
                    lambda m : m.attrs["peerid"] ==  execute.attrs["id"],
                    until = end)
                answered = getResult()
                peerid = answered.attrs["id"]

            if handleConn:            
                go(handleConn(
                    RestrictedYateClient(yate, end),
                    callid, peerid))

    except DisconnectedException, e:
        logger_examples.info("Route disconnected.")

def incallrecording(yate, callid, targetid):

    yate.install("chan.notify")
    
    end = yate.onmsg(
        "chan.hangup",
        lambda m : m.attrs["id"] == targetid, autoreturn = True)

    try:

        yield yate.dispatch(Message(
                name = "chan.masquerade",
               attrs = {"message": "chan.attach",
                        "id": targetid,
                        "source": beepresource,
                        "notify": targetid}))
        getResult()

        yield yate.onmsg(
            "chan.notify",
            lambda m : m.attrs["targetid"] == targetid,
            autoreturn = True, until = end)

        yield yate.dispatch(Message(
                name = "chan.masquerade",
               attrs = {"message": "chan.attach",
                        "id": targetid,
                        "consumer": "wave/record//tmp/recording.gsm",
                        "maxlen": 5000,
                        "notify": targetid}))
        getResult()

        yield yate.onmsg(
            "chan.notify",
            lambda m : m.attrs["targetid"] == targetid,
            autoreturn = True, until = end)
        getResult()

        logger_examples.info("recording")

        #yield sleep(1)
        #getResult()

        yield yate.dispatch(Message(
                name = "chan.masquerade",
               attrs = {"message": "chan.attach",
                        "id": targetid,
                        "source": beepresource,
                        "notify": targetid}))
        getResult()

        logger_examples.info("replaying")

        yield yate.onmsg(
            "chan.notify",
            lambda m : m.attrs["targetid"] == targetid,
            autoreturn = True, until = end)
        getResult()

        yield yate.dispatch(Message(
                name = "chan.masquerade",
               attrs = {"message": "chan.attach",
                        "id": targetid,
                        "source": "wave/play//tmp/recording.gsm",
                        "notify": targetid}))
        getResult()

        yield yate.onmsg(
            "chan.notify",
            lambda m : m.attrs["targetid"] == targetid,
            autoreturn = True, until = end)
        getResult()    

        yield yate.dispatch(Message(
                name = "call.drop", attrs = {"id": callid}))

        getResult()       

    except AbandonedException, e:
        logger_examples.info("recording abandoned")
                

def dumb_proxy(yate, callto, handler):
    
    yield defer.DeferredList(
        [yate.install("call.execute", 50),
         yate.watch("call.execute"),
         yate.install("chan.dtmf"),
         yate.install("chan.notify"),
         yate.install("chan.hangup")])

    getResult()

    print callto

    while True:

        yield yate.onmsg(
            "call.execute",
            lambda m : m.attrs["callto"] == callto)
        
        execute = getResult()

        execute.attrs["callto"] = "dumb/"

        yate.ret(execute, False)        

        yield yate.onwatch(
            "call.execute",
            lambda m : m.attrs["id"] == execute.attrs["id"])

        execute = getResult()

        yield yate.dispatch(Message(
                name = "call.answered",
               attrs = {"id": execute.attrs["id"],
                        "peerid": execute.attrs["peerid"]}))
        
        go(handler(yate, execute.attrs["id"], execute.attrs["peerid"]))


def record(yate, pattern):
  
    logger_examples.info("record: %s" % pattern)
    
    yield defer.DeferredList(
        [yate.install("call.execute", 50),
         yate.watch("call.answered"),
         yate.watch("chan.hangup")])
    getResult()   

    while True:
        yield yate.onmsg("call.execute", lambda m : m.attrs["called"] == pattern)
        execute = getResult()

        yate.ret(execute, False)

        end = yate.onwatch(
            "chan.hangup",
            lambda m : m.attrs["id"] == execute.attrs["id"])

        yield yate.onwatch(
            "call.answered",
            lambda m : m.attrs["peerid"] == execute.attrs["id"],
            until = end)
        answered = getResult()

        yate.dispatch(Message(
            name = "chan.masquerade",
            attrs = {"message": "chan.record",
                     "id": execute.attrs["id"],        
                      "call": "wave/record//tmp/rec_call.slin",
                      "peer": "wave/record//tmp/rec_peer.slin"}))

def outgoing_call(yate, target, handler, times, *args):

    yield defer.DeferredList(
        [
         #yate.install("call.route", 50),
         yate.watch("call.execute"),
         yate.watch("call.answered"),
         yate.install("chan.dtmf", 50),
         yate.install("chan.hangup")])

    getResult()

    for i in range(1,int(times)+1):
        yield yate.dispatch(Message(
                        name = "call.execute",
#                       attrs = {"callto": "dumb/",
                       attrs = {"callto": "alsa/default",
                                "target": target}))
        execute = getResult()

        return

        logger_examples.info(
            "outgoing call number: %d to  %s" % (i, execute.attrs["id"]))

        end = yate.onmsg(
            "chan.hangup",
            lambda m : m.attrs["id"] == execute.attrs["id"],
            autoreturn = True)

        try:            
            yield yate.onwatch(
                "call.answered",
                lambda m : m.attrs["peerid"] ==  execute.attrs["id"],
                until = end)
            answered = getResult()
            #go(handler(yate, answered.attrs["id"], execute.attrs["id"], *args))

        except AbandonedException, e:
            logger_examples.info("outgoing call number: %d to %s abandoned" % (i, execute.attrs["id"]))
            

def send_dtmfs(yate, targetid, callid, dtmfs):

    end = yate.onmsg("chan.hangup", lambda m : m.attrs["id"] == callid, autoreturn = True)

    yield yate.dispatch(Message(
            name = "chan.masquerade",
           attrs = {"message": "chan.attach",
                    "id": callid,        
                    "consumer": "wave/record//tmp/recording%s.slin" % callid.replace("/", "-"),
                    "maxlen": 0}), until = end)
    try:
        for text in dtmfs.split(","):
            if text != "_":            
                logger_examples.info("Sending dtmf: %s to %s" % (text, targetid))
                yield yate.dispatch(Message(
                        name = "chan.dtmf",
                       attrs = {"targetid": targetid,
                                "text": text}))
                dtmf = getResult()
            else:
                logger_examples.info("Sleeping for 5 sec.")
                yield sleep(5)
                getResult()
        yield sleep(10)
        getResult()    
        yield yate.dispatch(Message(
                name = "call.drop", attrs = {"id": callid}))
        getResult()
    except AbandonedException, e:
        logger_examples.info("call %s abandoned" % callid)       

    logger_examples.info("call %s finished" % callid)          

def transfer(yate, callid, targetid, handleConn = None):
    
    end = yate.onmsg(
        "chan.hangup",
        lambda m : m.attrs["id"] in [callid, targetid], autoreturn = True)

    try:
        yield yate.onmsg(
            "chan.dtmf",
            lambda m : m.attrs["id"] == targetid and m.attrs["text"] == "#",
            end, autoreturn = True)
        dtmf = getResult()

        yate.dispatch(Message(
                 name = "call.transfer",
                attrs = {"id": callid,
                         "to": "dumb/",
                         "peerto": "moh/default"}))

        print "waiting for:", callid
        yield yate.onwatch(
            "call.execute",
            lambda m : m.attrs["id"] == callid , end)
        execute = getResult()
        
        go(keyecho(yate, callid, execute.attrs["peerid"]))

        yield yate.onmsg(
            "chan.dtmf",
            lambda m : m.attrs["id"] == targetid and m.attrs["text"] == "#",
            end, autoreturn = True)
        dtmf = getResult()

        yield yate.dispatch(Message(
                     name = "chan.connect",
                    attrs = {"id": callid, "targetid": targetid}))
        getResult()
    except AbandonedException, e:
        logger_examples.info("transfer abandoned by: %s\n" % e.cause)

if __name__ == '__main__':
   examples = {"outgoing":
               (lambda yate, target: outgoing_call(yate, target, keyecho, 1), 1,
                    "Connects outgoing call to keyecho.",
                    "\t\tparam1 - outgoing extension"),
               "pseudochannel":
               (lambda yate, callto: dumb_proxy(yate, callto, keyecho), 2,
                    "Pretends keyecho to be a channel.",
                    "\t\tparam1 - channel descriptor"),
               "incallrecording":
               (lambda yate: dumb_proxy(yate, "record/", incallrecording), 1,
                    "Records and plays back.",
                    ""),
               "route":
               (lambda yate, called: route(yate, called, "dumb/", keyecho), 1,
                    "Routes calls to keyecho.",
                    "\t\tparam1 - extension to route from"),
               "dropcore":
               (lambda yate: route(yate, "dropcore", "dumb/", dropcore), 0,
                    "Routes calls to dropcore.", ""),               
               "transfer":
               (lambda yate, called, target: route(yate, called, target, transfer), 1,
                    "Route calls to simple transfer application.",
                    "\t\tparam1 - extension to route from\n\t\tparam2 - target channel"),
               "record":
               (lambda yate, callto: record(yate, callto), 1,
                    "Record calls.",
                    "\t\tparam1 - extension for which record calls"),
               "senddtmfs":
               (lambda yate, target, times, dtmfs: outgoing_call(yate, target, send_dtmfs, times, dtmfs), 1,
                    "Send dtmfs",
                    "\t\tparam1 - extension to send dtmfs to\n\t\tparam2 - dtmfs to send in format delimited with colon e.g.: 1,2,3,_,4. Underscore means 1 sec break"),
               "voipclient":
               (lambda yate, callto: voip_client(yate, callto), 2,
                    "Makes voiop client of of yate",
                    "\t\tparam1 - channel descriptor")}

   
   if len(sys.argv) <= 1 or not examples.has_key(sys.argv[1]):
       print "Usage:\n\t%s examplename param1 param2 ...\nWhere examplename is one of:" % sys.argv[0]
       for key, (_, count, desc, params) in examples.iteritems():
           print "\t%s - %s" % (key, desc)
           print params
   else:

       #hdlr = logging.FileHandler('examples.log')
       hdlr = logging.StreamHandler()
       #formatter = logging.Formatter('%(name)s %(levelname)s %(message)s')
       #formatter = logging.Formatter()
       formatter = ConsoleFormater('%(name)s %(levelname)s %(message)s')
       hdlr.setFormatter(formatter)

       logger.setLevel(logging.INFO)
       logger.addHandler(hdlr)

#       logger_messages.setLevel(logging.INFO)
       logger_messages.setLevel(logging.DEBUG)
       start = build_start(examples[sys.argv[1]][0], *sys.argv[2:])

       f = YateClientProtocolFactory(start)       
       reactor.connectTCP("localhost", 5038, f)
#       reactor.connectTCP("konopia", 5038, f)
       reactor.run()