Capisuite: Unterschied zwischen den Versionen
Zur Navigation springen
Zur Suche springen
Keine Bearbeitungszusammenfassung |
Keine Bearbeitungszusammenfassung |
||
Zeile 68: | Zeile 68: | ||
Karl=12345,017134566789 | Karl=12345,017134566789 | ||
Peter=0170123456 | Peter=0170123456 | ||
</pre> | |||
== Erweitertes Incoming.py == | |||
File: ''/usr/lib/capisuite/incoming_adv.py'' | |||
<pre> | |||
# incoming_adv.py - advanced incoming script for capisuite | |||
# -------------------------------------------------------- | |||
# copyright : (c) 2007 by Neobiker | |||
# | |||
# original script: | |||
# copyright : (C) 2002 by Gernot Hillier | |||
# email : gernot@hillier.de | |||
# version : $Revision: 1.9.2.1 $ | |||
# | |||
# 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. | |||
# | |||
# general imports | |||
import datetime,time,os,re,string,pwd | |||
# CapiSuite imports | |||
import capisuite,cs_helpers | |||
# @brief check if user program date fits actual date | |||
# | |||
# @param pdate user program date | |||
# @param adate actual date | |||
def check_date (pdate,adate): | |||
"""check_date() check if actual date fits user program date""" | |||
if pdate == '*': | |||
return True | |||
# set day/month/year from actual date if field is not defined | |||
idate=pdate.split('.') | |||
pdate=adate[:] | |||
# set pdate fields day/month/year as defined in user program | |||
for i in range(len(idate)): | |||
if idate[i] and idate[i] != '*': pdate[i] = int(idate[i]) | |||
if pdate == adate: | |||
if adv_debug: | |||
print "check_date(): " , pdate , " / " , adate | |||
return True | |||
return False | |||
# @brief check if date is listed in date section | |||
# | |||
# @param dsect date section list of dates | |||
def check_date_section (dsect,adate): | |||
"""check_date_section () check if date is listed in date section""" | |||
for i in range(len(config.items(dsect))): | |||
for pdate in config.items(dsect)[i][1].split(','): | |||
if check_date(pdate,adate): | |||
if adv_debug: | |||
print "check_date_Section(): found" | |||
return True | |||
return False | |||
# @brief check if actual time fits user program time interval | |||
# | |||
# @param ptime program time intervall | |||
# @param atime actual time | |||
def prog_time (ptime,atime): | |||
"""prog_time() check if actual time fits user program time interval""" | |||
if ptime == '*': | |||
return True | |||
# From: time intervall start | |||
f0=ptime.split('-')[0] + ':00' | |||
f=[int(f0.split(':')[0]), int(f0.split(':')[1])] | |||
# To: time intervall end | |||
t0=ptime.split('-')[1] + ':00' | |||
t=[int(t0.split(':')[0]), int(t0.split(':')[1])] | |||
# actual time is between From - To Intervall? | |||
if adv_debug: | |||
print "Time test: ", f, " <= ", atime, " <= ", t, " ?" | |||
# 1.st test: f < t (means: t < 23:59) | |||
# 2.nd test: f > t (means: t >= 00:00 -> on next day!) | |||
if f < t: | |||
return f <= atime <= t | |||
else: | |||
return not (t <= atime <= f) | |||
# @brief check if weekday is listed in user program | |||
# | |||
# @param pwday program weekday | |||
# @param wday actual weekday | |||
def prog_wday (pwday,wday): | |||
"""prog_wday() check if weekday is listed in user program""" | |||
if pwday == '*': | |||
return True | |||
wd={'MO': 0, 'DI': 1, 'MI': 2, 'DO': 3, 'FR': 4, 'SA': 5, 'SO': 6, \ | |||
'TU': 1, 'WE': 2, 'TH': 3, 'SU': 6} | |||
return wday==wd[pwday.upper()] | |||
# @brief check if call_from is listed in callers section | |||
# | |||
# @param callers list of numbers or sections | |||
def check_caller_section (csect): | |||
"""check_caller_section () check if call_from is listed in callers section""" | |||
for i in range(len(config.items(csect))): | |||
for j in config.items(csect)[i][1].split(','): | |||
if call_from == re.sub('[\s+()-]','',j): | |||
return True | |||
return False | |||
# @brief check if call_from is listed in callers list/section | |||
# | |||
# @param callers list of numbers or sections | |||
def check_caller (callers): | |||
"""prog_caller () check if caller_from matches caller entries/section""" | |||
if callers == '*': | |||
return True | |||
for i in range(len(callers.split(','))): | |||
pcaller=re.sub('[\s+()-]','',callers.split(',')[i]) | |||
if pcaller in config.sections(): | |||
if check_caller_section(pcaller): | |||
return True | |||
elif call_from == pcaller: | |||
return True | |||
return False | |||
# @brief check if given user program is active | |||
# | |||
# Check if Date/Time/Weekday fits actual date/time | |||
# to see if user program is activ | |||
# | |||
# @param dates program dates | |||
# @param times program time intervals | |||
# @param wdays program week days | |||
# @param d actual date/time | |||
def prog_active (dates, times, wdays, d): | |||
"""prog_active() check if given user program is active""" | |||
# | |||
# check the dates in user program | |||
found=False | |||
for i in range(len(dates.split(','))): | |||
pdate=dates.split(',')[i] | |||
if pdate in config.sections(): | |||
if check_date_section(pdate,[d.day, d.month, d.year]): | |||
found=True | |||
break | |||
elif check_date(pdate,[d.day, d.month, d.year]): | |||
found=True | |||
break | |||
# date didn't fit | |||
if not found: return False | |||
# | |||
# check the times in user program | |||
found=False | |||
for i in range(len(times.split(','))): | |||
ptime=times.split(',')[i] | |||
if prog_time(ptime,[d.hour, d.minute]): | |||
found=True | |||
break | |||
# time intervall didn't fit | |||
if not found: return False | |||
# | |||
# check the weekdays in user program | |||
found=False | |||
for i in range(len(wdays.split(','))): | |||
pwday=wdays.split(',')[i] | |||
if prog_wday(pwday,d.weekday()): | |||
return True | |||
# | |||
# weekdays didn't fit | |||
return False | |||
# @brief read user specific program for vbox | |||
# --- prog[dates, times, weekdays, message, delay, length, callers] --- | |||
# | |||
# It will search for a valid user program (prog#) in the user section | |||
# and reads corresponding definitions for | |||
# - delay the delay of vbox activation | |||
# - message the specific message to play | |||
# | |||
# @param (user-)section in config file to read | |||
# @return True/False if valid programm found or not | |||
# @user_prog['delay': xx, 'message': yy] | |||
def read_prog (section): | |||
"""read_prog(section) read user specific program for vbox""" | |||
prog="prog1" | |||
d=datetime.datetime.now() | |||
i=1 | |||
while config.has_option(section,prog): | |||
prog="prog"+str(i) | |||
# p[dates, times, weekdays, message, delay, length, callers] | |||
p=config.get(section,prog).split() | |||
# check program dates/times/weekdays | |||
if prog_active(p[0], p[1], p[2] ,d): | |||
if adv_debug: | |||
print "prog active: "+prog+" ("+section+")" | |||
if check_caller(re.sub('[\s+()-]','',p[6])): | |||
user_prog['user']=section | |||
user_prog['prog']=i | |||
user_prog['dates']=p[0] | |||
user_prog['times']=p[1] | |||
user_prog['wdays']=p[2] | |||
user_prog['message']=p[3] | |||
user_prog['delay']=p[4] | |||
user_prog['length']=p[5] | |||
user_prog['callers']=p[6] | |||
if adv_debug: | |||
print "checked: caller("+call_from+") in ", p[6] | |||
print user_prog | |||
return True | |||
i=i+1 | |||
return False | |||
# @brief main function called by CapiSuite when an incoming call is received | |||
# | |||
# It will decide if this call should be accepted, with which service and for | |||
# which user. The real call handling is done in faxIncoming and voiceIncoming. | |||
# | |||
# @param call reference to the call. Needed by all capisuite functions | |||
# @param service one of SERVICE_FAXG3, SERVICE_VOICE, SERVICE_OTHER | |||
# @param call_from string containing the number of the calling party | |||
# @param call_to string containing the number of the called party | |||
def callIncoming(call,service,call_from,call_to): | |||
# read sections in config file | |||
try: | |||
config=cs_helpers.readConfig() | |||
userlist=config.sections() | |||
userlist.remove('GLOBAL') | |||
user_prog={} | |||
# debug messages for adv. features | |||
adv_debug=False | |||
# search for call_to in the user sections | |||
curr_user="" | |||
for u in userlist: | |||
if config.has_option(u,'voice_numbers'): | |||
numbers=config.get(u,'voice_numbers') | |||
if (call_to in numbers.split(',') or numbers=="*"): | |||
if (service==capisuite.SERVICE_VOICE): | |||
curr_user=u | |||
curr_service=capisuite.SERVICE_VOICE | |||
break | |||
if (service==capisuite.SERVICE_FAXG3): | |||
curr_user=u | |||
curr_service=capisuite.SERVICE_FAXG3 | |||
break | |||
if config.has_option(u,'fax_numbers'): | |||
numbers=config.get(u,'fax_numbers') | |||
if (call_to in numbers.split(',') or numbers=="*"): | |||
if (service in (capisuite.SERVICE_FAXG3,capisuite.SERVICE_VOICE)): | |||
curr_user=u | |||
curr_service=capisuite.SERVICE_FAXG3 | |||
break | |||
except IOError,e: | |||
capisuite.error("Error occured during config file reading: "+e+" Disconnecting...") | |||
capisuite.reject(call,0x34A9) | |||
return | |||
# answer the call with the right service | |||
if (curr_user==""): | |||
capisuite.log("call from "+call_from+" to "+call_to+" ignoring",1,call) | |||
capisuite.reject(call,1) | |||
return | |||
try: | |||
if (curr_service==capisuite.SERVICE_VOICE): | |||
delay=cs_helpers.getOption(config,curr_user,"voice_delay") | |||
active_user_prog=read_prog(curr_user) | |||
if active_user_prog: | |||
delay=user_prog['delay'] | |||
if (delay==None): | |||
capisuite.error("voice_delay not found for user "+curr_user+"! -> rejecting call") | |||
capisuite.reject(call,0x34A9) | |||
return | |||
capisuite.log("call from "+call_from+" to "+call_to+" for "+curr_user+" connecting with voice",1,call) | |||
capisuite.connect_voice(call,int(delay)) | |||
voiceIncoming(call,call_from,call_to,curr_user,config) | |||
elif (curr_service==capisuite.SERVICE_FAXG3): | |||
faxIncoming(call,call_from,call_to,curr_user,config,0) | |||
except capisuite.CallGoneError: # catch exceptions from connect_* | |||
(cause,causeB3)=capisuite.disconnect(call) | |||
capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) | |||
# @brief called by callIncoming when an incoming fax call is received | |||
# | |||
# @param call reference to the call. Needed by all capisuite functions | |||
# @param call_from string containing the number of the calling party | |||
# @param call_to string containing the number of the called party | |||
# @param curr_user name of the user who is responsible for this | |||
# @param config ConfigParser instance holding the config data | |||
# @param already_connected 1 if we're already connected (that means we must switch to fax mode) | |||
def faxIncoming(call,call_from,call_to,curr_user,config,already_connected): | |||
try: | |||
udir=cs_helpers.getOption(config,"","fax_user_dir") | |||
if (udir==None): | |||
capisuite.error("global option fax_user_dir not found! -> rejecting call") | |||
capisuite.reject(call,0x34A9) | |||
return | |||
udir=os.path.join(udir,curr_user)+"/" | |||
if (not os.access(udir,os.F_OK)): | |||
userdata=pwd.getpwnam(curr_user) | |||
os.mkdir(udir,0700) | |||
os.chown(udir,userdata[2],userdata[3]) | |||
if (not os.access(udir+"received/",os.F_OK)): | |||
userdata=pwd.getpwnam(curr_user) | |||
os.mkdir(udir+"received/",0700) | |||
os.chown(udir+"received/",userdata[2],userdata[3]) | |||
except KeyError: | |||
capisuite.error("user "+curr_user+" is not a valid system user. Disconnecting",call) | |||
capisuite.reject(call,0x34A9) | |||
return | |||
filename="" # assure the variable is defined... | |||
try: | |||
stationID=cs_helpers.getOption(config,curr_user,"fax_stationID") | |||
if (stationID==None): | |||
capisuite.error("Warning: fax_stationID not found for user "+curr_user+" -> using empty string") | |||
stationID="" | |||
headline=cs_helpers.getOption(config,curr_user,"fax_headline","") # empty string is no problem here | |||
capisuite.log("call from "+call_from+" to "+call_to+" for "+curr_user+" connecting with fax",1,call) | |||
if (already_connected): | |||
faxInfo=capisuite.switch_to_faxG3(call,stationID,headline) | |||
else: | |||
faxInfo=capisuite.connect_faxG3(call,stationID,headline,0) | |||
if (faxInfo!=None and faxInfo[3]==1): | |||
faxFormat="cff" # color fax | |||
else: | |||
faxFormat="sff" # normal b&w fax | |||
filename=cs_helpers.uniqueName(udir+"received/","fax",faxFormat) | |||
capisuite.fax_receive(call,filename) | |||
(cause,causeB3)=capisuite.disconnect(call) | |||
capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call) | |||
except capisuite.CallGoneError: # catch this here to get the cause info in the mail | |||
(cause,causeB3)=capisuite.disconnect(call) | |||
capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) | |||
if (os.access(filename,os.R_OK)): | |||
cs_helpers.writeDescription(filename, | |||
"call_from=\""+call_from+"\"\ncall_to=\""+call_to+"\"\ntime=\"" | |||
+time.ctime()+"\"\ncause=\"0x%x/0x%x\"\n" % (cause,causeB3)) | |||
userdata=pwd.getpwnam(curr_user) | |||
os.chmod(filename,0600) | |||
os.chown(filename,userdata[2],userdata[3]) | |||
os.chmod(filename[:-3]+"txt",0600) | |||
os.chown(filename[:-3]+"txt",userdata[2],userdata[3]) | |||
fromaddress=cs_helpers.getOption(config,curr_user,"fax_email_from","") | |||
if (fromaddress==""): | |||
fromaddress=curr_user | |||
mailaddress=cs_helpers.getOption(config,curr_user,"fax_email","") | |||
if (mailaddress==""): | |||
mailaddress=curr_user | |||
action=cs_helpers.getOption(config,curr_user,"fax_action","").lower() | |||
if (action not in ("mailandsave","saveonly")): | |||
capisuite.error("Warning: No valid fax_action definition found for user "+curr_user+" -> assuming SaveOnly") | |||
action="saveonly" | |||
if (action=="mailandsave"): | |||
cs_helpers.sendMIMEMail(fromaddress, mailaddress, "Fax received from "+call_from+" to "+call_to, faxFormat, | |||
"You got a fax from "+call_from+" to "+call_to+"\nDate: "+time.ctime()+"\n\n" | |||
+"See attached file.\nThe original file was saved to file://"+filename+"\n\n", filename) | |||
# @brief called by callIncoming when an incoming voice call is received | |||
# | |||
# @param call reference to the call. Needed by all capisuite functions | |||
# @param call_from string containing the number of the calling party | |||
# @param call_to string containing the number of the called party | |||
# @param curr_user name of the user who is responsible for this | |||
# @param config ConfigParser instance holding the config data | |||
def voiceIncoming(call,call_from,call_to,curr_user,config): | |||
try: | |||
udir=cs_helpers.getOption(config,"","voice_user_dir") | |||
if (udir==None): | |||
capisuite.error("global option voice_user_dir not found! -> rejecting call") | |||
capisuite.reject(call,0x34A9) | |||
return | |||
udir=os.path.join(udir,curr_user)+"/" | |||
if (not os.access(udir,os.F_OK)): | |||
userdata=pwd.getpwnam(curr_user) | |||
os.mkdir(udir,0700) | |||
os.chown(udir,userdata[2],userdata[3]) | |||
if (not os.access(udir+"received/",os.F_OK)): | |||
userdata=pwd.getpwnam(curr_user) | |||
os.mkdir(udir+"received/",0700) | |||
os.chown(udir+"received/",userdata[2],userdata[3]) | |||
except KeyError: | |||
capisuite.error("user "+curr_user+" is not a valid system user. Disconnecting",call) | |||
capisuite.reject(call,0x34A9) | |||
return | |||
filename=cs_helpers.uniqueName(udir+"received/","voice","la") | |||
action=cs_helpers.getOption(config,curr_user,"voice_action","").lower() | |||
if (action not in ("mailandsave","saveonly","none")): | |||
capisuite.error("Warning: No valid voice_action definition found for user "+curr_user+" -> assuming SaveOnly") | |||
action="saveonly" | |||
try: | |||
capisuite.enable_DTMF(call) | |||
if not active_user_prog: | |||
userannouncement=udir+cs_helpers.getOption(config,curr_user,"announcement","announcement.la") | |||
else: | |||
userannouncement=udir+user_prog['message'] | |||
pin=cs_helpers.getOption(config,curr_user,"pin","") | |||
if (os.access(userannouncement,os.R_OK)): | |||
capisuite.audio_send(call,userannouncement,1) | |||
else: | |||
if (call_to!="-"): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"anrufbeantworter-von.la"),1) | |||
cs_helpers.sayNumber(call,call_to,curr_user,config) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-nachricht.la"),1) | |||
if (action!="none"): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la"),1) | |||
length=cs_helpers.getOption(config,curr_user,"record_length","60") | |||
silence_timeout=cs_helpers.getOption(config,curr_user,"record_silence_timeout","5") | |||
capisuite.audio_receive(call,filename,int(length), int(silence_timeout),1) | |||
dtmf_list=capisuite.read_DTMF(call,0) | |||
if (dtmf_list=="X"): | |||
if (os.access(filename,os.R_OK)): | |||
os.unlink(filename) | |||
faxIncoming(call,call_from,call_to,curr_user,config,1) | |||
elif (dtmf_list!="" and pin!=""): | |||
dtmf_list+=capisuite.read_DTMF(call,3) # wait 5 seconds for input | |||
count=1 | |||
while (count<3 and pin!=dtmf_list): # try again if input was wrong | |||
capisuite.log("wrong PIN entered...",1,call) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) | |||
dtmf_list=capisuite.read_DTMF(call,3) | |||
count+=1 | |||
if (pin==dtmf_list): | |||
if (os.access(filename,os.R_OK)): | |||
os.unlink(filename) | |||
capisuite.log("Starting remote inquiry...",1,call) | |||
remoteInquiry(call,udir,curr_user,config) | |||
(cause,causeB3)=capisuite.disconnect(call) | |||
capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call) | |||
except capisuite.CallGoneError: # catch this here to get the cause info in the mail | |||
(cause,causeB3)=capisuite.disconnect(call) | |||
capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) | |||
if (os.access(filename,os.R_OK)): | |||
cs_helpers.writeDescription(filename, | |||
"call_from=\""+call_from+"\"\ncall_to=\""+call_to+"\"\ntime=\"" | |||
+time.ctime()+"\"\ncause=\"0x%x/0x%x\"\n" % (cause,causeB3)) | |||
userdata=pwd.getpwnam(curr_user) | |||
os.chmod(filename,0600) | |||
os.chown(filename,userdata[2],userdata[3]) | |||
os.chmod(filename[:-2]+"txt",0600) | |||
os.chown(filename[:-2]+"txt",userdata[2],userdata[3]) | |||
fromaddress=cs_helpers.getOption(config,curr_user,"voice_email_from","") | |||
if (fromaddress==""): | |||
fromaddress=curr_user | |||
mailaddress=cs_helpers.getOption(config,curr_user,"voice_email","") | |||
if (mailaddress==""): | |||
mailaddress=curr_user | |||
if (action=="mailandsave"): | |||
cs_helpers.sendMIMEMail(fromaddress, mailaddress, "Voice call received from "+call_from+" to "+call_to, "la", | |||
"You got a voice call from "+call_from+" to "+call_to+"\nDate: "+time.ctime()+"\n\n" | |||
+"See attached file.\nThe original file was saved to file://"+filename+"\n\n", filename) | |||
# @brief remote inquiry function (uses german wave snippets!) | |||
# | |||
# commands for remote inquiry | |||
# delete message - 1 | |||
# next message - 4 | |||
# last message - 5 | |||
# repeat current message - 6 | |||
# | |||
# @param call reference to the call. Needed by all capisuite functions | |||
# @param userdir spool_dir of the current_user | |||
# @param curr_user name of the user who is responsible for this | |||
# @param config ConfigParser instance holding the config data | |||
def remoteInquiry(call,userdir,curr_user,config): | |||
import time,fcntl,errno,os | |||
# acquire lock | |||
lockfile=open(userdir+"received/inquiry_lock","w") | |||
try: | |||
fcntl.lockf(lockfile,fcntl.LOCK_EX | fcntl.LOCK_NB) # only one inquiry at a time! | |||
except IOError,err: # can't get the lock | |||
if (err.errno in (errno.EACCES,errno.EAGAIN)): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fernabfrage-aktiv.la")) | |||
lockfile.close() | |||
return | |||
try: | |||
# read directory contents | |||
messages=os.listdir(userdir+"received/") | |||
messages=filter (lambda s: re.match("voice-.*\.la",s),messages) # only use voice-* files | |||
messages=map(lambda s: int(re.match("voice-([0-9]+)\.la",s).group(1)),messages) # filter out numbers | |||
messages.sort() | |||
# read the number of the message heard last at the last inquiry | |||
lastinquiry=-1 | |||
if (os.access(userdir+"received/last_inquiry",os.W_OK)): | |||
lastfile=open(userdir+"received/last_inquiry","r") | |||
lastinquiry=int(lastfile.readline()) | |||
lastfile.close() | |||
# sort out old messages | |||
oldmessages=[] | |||
i=0 | |||
while (i<len(messages)): | |||
if (messages[i]<=lastinquiry): | |||
oldmessages.append(messages[i]) | |||
del messages[i] | |||
else: | |||
i+=1 | |||
cs_helpers.sayNumber(call,str(len(messages)),curr_user,config) | |||
if (len(messages)==1): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachricht.la"),1) | |||
else: | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachrichten.la"),1) | |||
# menu for record new announcement | |||
cmd="" | |||
while (cmd not in ("1","9")): | |||
if (len(messages)+len(oldmessages)): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"zum-abhoeren-1.la"),1) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fuer-neue-ansage-9.la"),1) | |||
cmd=capisuite.read_DTMF(call,0,1) | |||
if (cmd=="9"): | |||
newAnnouncement(call,userdir,curr_user,config) | |||
return | |||
# start inquiry | |||
for curr_msgs in (messages,oldmessages): | |||
cs_helpers.sayNumber(call,str(len(curr_msgs)),curr_user,config) | |||
if (curr_msgs==messages): | |||
if (len(curr_msgs)==1): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachricht.la"),1) | |||
else: | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachrichten.la"),1) | |||
else: | |||
if (len(curr_msgs)==1): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht.la"),1) | |||
else: | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachrichten.la"),1) | |||
i=0 | |||
while (i<len(curr_msgs)): | |||
filename=userdir+"received/voice-"+str(curr_msgs[i])+".la" | |||
descr=cs_helpers.readConfig(filename[:-2]+"txt") | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht.la"),1) | |||
cs_helpers.sayNumber(call,str(i+1),curr_user,config) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"von.la"),1) | |||
cs_helpers.sayNumber(call,descr.get('GLOBAL','call_from'),curr_user,config) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fuer.la"),1) | |||
cs_helpers.sayNumber(call,descr.get('GLOBAL','call_to'),curr_user,config) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"am.la"),1) | |||
calltime=time.strptime(descr.get('GLOBAL','time')) | |||
cs_helpers.sayNumber(call,str(calltime[2]),curr_user,config) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"..la"),1) | |||
cs_helpers.sayNumber(call,str(calltime[1]),curr_user,config) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"..la"),1) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"um.la"),1) | |||
cs_helpers.sayNumber(call,str(calltime[3]),curr_user,config) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"uhr.la"),1) | |||
cs_helpers.sayNumber(call,str(calltime[4]),curr_user,config) | |||
capisuite.audio_send(call,filename,1) | |||
cmd="" | |||
while (cmd not in ("1","4","5","6")): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"erklaerung.la"),1) | |||
cmd=capisuite.read_DTMF(call,0,1) | |||
if (cmd=="1"): | |||
os.remove(filename) | |||
os.remove(filename[:-2]+"txt") | |||
del curr_msgs[i] | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht-geloescht.la")) | |||
elif (cmd=="4"): | |||
if (curr_msgs[i]>lastinquiry): | |||
lastinquiry=curr_msgs[i] | |||
lastfile=open(userdir+"received/last_inquiry","w") | |||
lastfile.write(str(curr_msgs[i])+"\n") | |||
lastfile.close() | |||
i+=1 | |||
elif (cmd=="5"): | |||
i-=1 | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"keine-weiteren-nachrichten.la")) | |||
finally: | |||
# unlock | |||
fcntl.lockf(lockfile,fcntl.LOCK_UN) | |||
lockfile.close() | |||
os.unlink(userdir+"received/inquiry_lock") | |||
# @brief remote inquiry: record new announcement (uses german wave snippets!) | |||
# | |||
# @param call reference to the call. Needed by all capisuite functions | |||
# @param userdir spool_dir of the current_user | |||
# @param curr_user name of the user who is responsible for this | |||
# @param config ConfigParser instance holding the config data | |||
def newAnnouncement(call,userdir,curr_user,config): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-komplett.la")) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) | |||
cmd="" | |||
while (cmd!="1"): | |||
capisuite.audio_receive(call,userdir+"announcement-tmp.la",60,3) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-ansage-lautet.la")) | |||
capisuite.audio_send(call,userdir+"announcement-tmp.la") | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"wenn-einverstanden-1.la")) | |||
cmd=capisuite.read_DTMF(call,0,1) | |||
if (cmd!="1"): | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-kurz.la")) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) | |||
userannouncement=userdir+cs_helpers.getOption(config,curr_user,"announcement","announcement.la") | |||
os.rename(userdir+"announcement-tmp.la",userannouncement) | |||
userdata=pwd.getpwnam(curr_user) | |||
os.chown(userannouncement,userdata[2],userdata[3]) | |||
capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"ansage-gespeichert.la")) | |||
# | |||
# History: | |||
# | |||
# $Log: incoming.py,v $ | |||
# Revision 1.9.2.1 2003/08/24 12:47:19 gernot | |||
# - faxIncoming tried to reconnect when it was called after a switch from | |||
# voice to fax mode, which lead to a call abort. Thx to Harald Jansen & | |||
# Andreas Scholz for reporting! | |||
# | |||
# Revision 1.9 2003/06/27 07:51:09 gernot | |||
# - replaced german umlaut in filename "nachricht-gelscht.la", can cause | |||
# problems on Redhat, thx to Herbert Hübner for reporting | |||
# | |||
# Revision 1.8 2003/06/16 10:21:05 gernot | |||
# - define filename in any case (thx to Axel Schneck for reporting and | |||
# analyzing...) | |||
# | |||
# Revision 1.7 2003/05/25 13:38:30 gernot | |||
# - support reception of color fax documents | |||
# | |||
# Revision 1.6 2003/04/10 21:29:51 gernot | |||
# - support empty destination number for incoming calls correctly (austrian | |||
# telecom does this (sic)) | |||
# - core now returns "-" instead of "??" for "no number available" (much nicer | |||
# in my eyes) | |||
# - new wave file used in remote inquiry for "unknown number" | |||
# | |||
# Revision 1.5 2003/03/20 09:12:42 gernot | |||
# - error checking for reading of configuration improved, many options got | |||
# optional, others produce senseful error messages now if not found, | |||
# fixes bug# 531, thx to Dieter Pelzel for reporting | |||
# | |||
# Revision 1.4 2003/03/13 11:08:06 gernot | |||
# - fix remote inquiry locking (should fix bug #534, but doesn't - anyway, | |||
# this fix is definitely necessary) | |||
# - stricter permissions of saved files and created dirs, fixes #544 | |||
# - add "file://" prefix to the path shown in the mails to the user | |||
# | |||
# Revision 1.3 2003/02/21 13:13:34 gernot | |||
# - removed some debug output (oops...) | |||
# | |||
# Revision 1.2 2003/02/21 11:02:17 gernot | |||
# - removed os.setuid() from incoming script | |||
# -> fixes Bug #527 | |||
# | |||
# Revision 1.1.1.1 2003/02/19 08:19:54 gernot | |||
# initial checkin of 0.4 | |||
# | |||
# Revision 1.11 2003/02/17 11:13:43 ghillie | |||
# - remoteinquiry supports new and old messages now | |||
# | |||
# Revision 1.10 2003/02/03 14:50:08 ghillie | |||
# - fixed small typo | |||
# | |||
# Revision 1.9 2003/01/31 16:32:41 ghillie | |||
# - support "*" for "all numbers" | |||
# - automatic switch voice->fax when SI says fax | |||
# | |||
# Revision 1.8 2003/01/31 11:24:41 ghillie | |||
# - wrong user handling for more than one users fixed | |||
# - creates user_dir/user and user_dir/user/received now separately as | |||
# idle.py can also create user_dir/user now | |||
# | |||
# Revision 1.7 2003/01/27 21:57:54 ghillie | |||
# - fax_numbers and voice_numbers may not exist (no fatal error any more) | |||
# - accept missing email option | |||
# - fixed typo | |||
# | |||
# Revision 1.6 2003/01/27 19:24:29 ghillie | |||
# - updated to use new configuration files for fax & answering machine | |||
# | |||
# Revision 1.5 2003/01/19 12:03:27 ghillie | |||
# - use capisuite log functions instead of stdout/stderr | |||
# | |||
# Revision 1.4 2003/01/17 15:09:49 ghillie | |||
# - cs_helpers.sendMail was renamed to sendMIMEMail | |||
# | |||
# Revision 1.3 2003/01/16 12:58:34 ghillie | |||
# - changed DTMF timeout for pin to 3 seconds | |||
# - delete recorded wave if fax or remote inquiry is recognized | |||
# - updates in remoteInquiry: added menu for recording own announcement | |||
# - fixed some typos | |||
# - remoteInquiry: delete description file together with call if requested | |||
# - new function: newAnnouncement | |||
# | |||
# Revision 1.2 2003/01/15 15:55:12 ghillie | |||
# - added exception handler in callIncoming | |||
# - faxIncoming: small typo corrected | |||
# - voiceIncoming & remoteInquiry: updated to new config file system | |||
# | |||
# Revision 1.1 2003/01/13 16:12:58 ghillie | |||
# - renamed from incoming.pyin to incoming.py as all previously processed | |||
# variables are moved to config and cs_helpers.pyin | |||
# | |||
# Revision 1.4 2002/12/18 14:34:56 ghillie | |||
# - added some informational prints | |||
# - accept voice calls to fax nr | |||
# | |||
# Revision 1.3 2002/12/16 15:04:51 ghillie | |||
# - added missing path prefix to delete routing in remote inquiry | |||
# | |||
# Revision 1.2 2002/12/16 13:09:25 ghillie | |||
# - added some comments about the conf_* vars | |||
# - added conf_wavedir | |||
# - added support for B3 cause now returned by disconnect() | |||
# - corrected some dir entries to work in installed system | |||
# | |||
# Revision 1.1 2002/12/14 13:53:18 ghillie | |||
# - idle.py and incoming.py are now auto-created from *.pyin | |||
# | |||
# Revision 1.4 2002/12/11 12:58:05 ghillie | |||
# - read return value from disconnect() | |||
# - added disconnect() to exception handler | |||
# | |||
# Revision 1.3 2002/12/09 15:18:35 ghillie | |||
# - added disconnect() in exception handler | |||
# | |||
# Revision 1.2 2002/12/02 21:30:42 ghillie | |||
# fixed some minor typos | |||
# | |||
# Revision 1.1 2002/12/02 21:15:55 ghillie | |||
# - moved scripts to own directory | |||
# - added remote-connect script to repository | |||
# | |||
# Revision 1.20 2002/12/02 20:59:44 ghillie | |||
# another typo :-| | |||
# | |||
# Revision 1.19 2002/12/02 20:54:07 ghillie | |||
# fixed small typo | |||
# | |||
# Revision 1.18 2002/12/02 16:51:32 ghillie | |||
# nearly complete new script, supports answering machine, fax receiving and remote inquiry now | |||
# | |||
# Revision 1.17 2002/11/29 16:28:43 ghillie | |||
# - updated syntax (connect_telephony -> connect_voice) | |||
# | |||
# Revision 1.16 2002/11/29 11:09:04 ghillie | |||
# renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) | |||
# | |||
# Revision 1.15 2002/11/25 11:43:43 ghillie | |||
# updated to new syntax | |||
# | |||
# Revision 1.14 2002/11/23 16:16:17 ghillie | |||
# moved switch2fax after audio_receive() | |||
# | |||
# Revision 1.13 2002/11/22 15:48:58 ghillie | |||
# renamed pcallcontrol module to capicom | |||
# | |||
# Revision 1.12 2002/11/22 15:02:39 ghillie | |||
# - added automatic switch between speech and fax | |||
# - some comments added | |||
# | |||
# Revision 1.11 2002/11/19 15:57:18 ghillie | |||
# - Added missing throw() declarations | |||
# - phew. Added error handling. All exceptions are caught now. | |||
# | |||
# Revision 1.10 2002/11/18 12:32:36 ghillie | |||
# - callIncoming lives now in __main__, not necessarily in pcallcontrol any more | |||
# - added some comments and header | |||
# | |||
</pre> | </pre> |
Version vom 3. Januar 2007, 14:13 Uhr
Capisuite Erweiterung
Bei der Umstellung von VBox unter Debian Sarge auf Capisuite unter Etch habe ich schmerzlich die Möglichkeit der individuellen Programmierung der Anrufbeantworter vermisst. Nachdem ich keine verbesserten Incoming.py Files im Web gefunden habe, habe ich auf die schnelle Python gelernt und die fehlende Funktion selbst implementiert.
Programmierung
- Die Programme werden fortlaufend (!) nummeriert, beginnend bei prog1
- Das erste Programm prog1 ... progX das match'ed wird verwendet, deshalb ist die Reihenfolge relevant!
- Die Einträge dates und group können als eigene Section definiert werden (siehe holiday, family, friends)
- Die Telefonnummern müssen so definiert sein, wie sie im Logfile (/var/log/capisuite.log) erscheinen, d.h. üblicherweise 0911 12345678 (nicht: +49 (911) 12345678).
Mein Konfig-File /etc/capisuite/answering_machine.conf sieht in etwa wie folgt aus und sollte weitestgehend selbsterklärend sein.
[GLOBAL] audio_dir="/usr/share/capisuite/" voice_user_dir="/var/spool/capisuite/users/" user_audio_files="1" voice_delay="15" announcement="ab_buis.la" record_length="60" record_silence_timeout="5" voice_email_from="Anrufbeantworter <ab@neobiker.de>" [priv] voice_numbers="20" announcement="ab_priv.la" voice_action="MailAndSave" voice_delay="10" record_length="90" voice_email_from="ab@neobiker.de" voice_email="Anrufbeantworter <ab@neobiker.de>" pin="1111" # ----- vbox programming: # ----- dates time frame week days file to play delay record group prog1= * 22:00-07:00 * ab_priv.la 5 120 family,friends prog2= * * * ab_priv.la 15 120 family,friends prog3= * 22:00-07:00 * ab_priv.la 5 120 * prog4= * * * ab_priv.la 10 120 * [buis] voice_numbers="23" announcement="ab_buis.la" voice_action="MailAndSave" voice_delay="10" record_length="60" voice_email_from="Anrufbeantworter <dr@friedrichnet.de>" voice_email="ab@friedrichnet.de" pin="2222" # ----- vbox programming: # ----- dates time frame week days file to play delay record group prog1= * 22:00-07:00 * ab_priv.la 5 120 family,friends prog2= * * * ab_priv.la 15 120 family,friends prog3= holiday * * ab_buis.la 1 90 * prog4= * * SA,SO ab_buis.la 1 90 * prog5= * 17-08:29 * ab_buis.la 1 90 * prog6= * * * ab_buis.la 10 90 * [holiday] Weihnachten=24.12.,25.12.,26.12. Ostern=6.4.2007,9.4.2007,17.5.2007,28.5.2007.,7.6.2007 Sonstige=1.1.,6.1.,1.5.,3.10.,1.11. Birthdays=19.5. [family] Eltern=01234 12345, 0175123456,0172 234567 Oma=123456789 [friends] Karl=12345,017134566789 Peter=0170123456
Erweitertes Incoming.py
File: /usr/lib/capisuite/incoming_adv.py
# incoming_adv.py - advanced incoming script for capisuite # -------------------------------------------------------- # copyright : (c) 2007 by Neobiker # # original script: # copyright : (C) 2002 by Gernot Hillier # email : gernot@hillier.de # version : $Revision: 1.9.2.1 $ # # 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. # # general imports import datetime,time,os,re,string,pwd # CapiSuite imports import capisuite,cs_helpers # @brief check if user program date fits actual date # # @param pdate user program date # @param adate actual date def check_date (pdate,adate): """check_date() check if actual date fits user program date""" if pdate == '*': return True # set day/month/year from actual date if field is not defined idate=pdate.split('.') pdate=adate[:] # set pdate fields day/month/year as defined in user program for i in range(len(idate)): if idate[i] and idate[i] != '*': pdate[i] = int(idate[i]) if pdate == adate: if adv_debug: print "check_date(): " , pdate , " / " , adate return True return False # @brief check if date is listed in date section # # @param dsect date section list of dates def check_date_section (dsect,adate): """check_date_section () check if date is listed in date section""" for i in range(len(config.items(dsect))): for pdate in config.items(dsect)[i][1].split(','): if check_date(pdate,adate): if adv_debug: print "check_date_Section(): found" return True return False # @brief check if actual time fits user program time interval # # @param ptime program time intervall # @param atime actual time def prog_time (ptime,atime): """prog_time() check if actual time fits user program time interval""" if ptime == '*': return True # From: time intervall start f0=ptime.split('-')[0] + ':00' f=[int(f0.split(':')[0]), int(f0.split(':')[1])] # To: time intervall end t0=ptime.split('-')[1] + ':00' t=[int(t0.split(':')[0]), int(t0.split(':')[1])] # actual time is between From - To Intervall? if adv_debug: print "Time test: ", f, " <= ", atime, " <= ", t, " ?" # 1.st test: f < t (means: t < 23:59) # 2.nd test: f > t (means: t >= 00:00 -> on next day!) if f < t: return f <= atime <= t else: return not (t <= atime <= f) # @brief check if weekday is listed in user program # # @param pwday program weekday # @param wday actual weekday def prog_wday (pwday,wday): """prog_wday() check if weekday is listed in user program""" if pwday == '*': return True wd={'MO': 0, 'DI': 1, 'MI': 2, 'DO': 3, 'FR': 4, 'SA': 5, 'SO': 6, \ 'TU': 1, 'WE': 2, 'TH': 3, 'SU': 6} return wday==wd[pwday.upper()] # @brief check if call_from is listed in callers section # # @param callers list of numbers or sections def check_caller_section (csect): """check_caller_section () check if call_from is listed in callers section""" for i in range(len(config.items(csect))): for j in config.items(csect)[i][1].split(','): if call_from == re.sub('[\s+()-]','',j): return True return False # @brief check if call_from is listed in callers list/section # # @param callers list of numbers or sections def check_caller (callers): """prog_caller () check if caller_from matches caller entries/section""" if callers == '*': return True for i in range(len(callers.split(','))): pcaller=re.sub('[\s+()-]','',callers.split(',')[i]) if pcaller in config.sections(): if check_caller_section(pcaller): return True elif call_from == pcaller: return True return False # @brief check if given user program is active # # Check if Date/Time/Weekday fits actual date/time # to see if user program is activ # # @param dates program dates # @param times program time intervals # @param wdays program week days # @param d actual date/time def prog_active (dates, times, wdays, d): """prog_active() check if given user program is active""" # # check the dates in user program found=False for i in range(len(dates.split(','))): pdate=dates.split(',')[i] if pdate in config.sections(): if check_date_section(pdate,[d.day, d.month, d.year]): found=True break elif check_date(pdate,[d.day, d.month, d.year]): found=True break # date didn't fit if not found: return False # # check the times in user program found=False for i in range(len(times.split(','))): ptime=times.split(',')[i] if prog_time(ptime,[d.hour, d.minute]): found=True break # time intervall didn't fit if not found: return False # # check the weekdays in user program found=False for i in range(len(wdays.split(','))): pwday=wdays.split(',')[i] if prog_wday(pwday,d.weekday()): return True # # weekdays didn't fit return False # @brief read user specific program for vbox # --- prog[dates, times, weekdays, message, delay, length, callers] --- # # It will search for a valid user program (prog#) in the user section # and reads corresponding definitions for # - delay the delay of vbox activation # - message the specific message to play # # @param (user-)section in config file to read # @return True/False if valid programm found or not # @user_prog['delay': xx, 'message': yy] def read_prog (section): """read_prog(section) read user specific program for vbox""" prog="prog1" d=datetime.datetime.now() i=1 while config.has_option(section,prog): prog="prog"+str(i) # p[dates, times, weekdays, message, delay, length, callers] p=config.get(section,prog).split() # check program dates/times/weekdays if prog_active(p[0], p[1], p[2] ,d): if adv_debug: print "prog active: "+prog+" ("+section+")" if check_caller(re.sub('[\s+()-]','',p[6])): user_prog['user']=section user_prog['prog']=i user_prog['dates']=p[0] user_prog['times']=p[1] user_prog['wdays']=p[2] user_prog['message']=p[3] user_prog['delay']=p[4] user_prog['length']=p[5] user_prog['callers']=p[6] if adv_debug: print "checked: caller("+call_from+") in ", p[6] print user_prog return True i=i+1 return False # @brief main function called by CapiSuite when an incoming call is received # # It will decide if this call should be accepted, with which service and for # which user. The real call handling is done in faxIncoming and voiceIncoming. # # @param call reference to the call. Needed by all capisuite functions # @param service one of SERVICE_FAXG3, SERVICE_VOICE, SERVICE_OTHER # @param call_from string containing the number of the calling party # @param call_to string containing the number of the called party def callIncoming(call,service,call_from,call_to): # read sections in config file try: config=cs_helpers.readConfig() userlist=config.sections() userlist.remove('GLOBAL') user_prog={} # debug messages for adv. features adv_debug=False # search for call_to in the user sections curr_user="" for u in userlist: if config.has_option(u,'voice_numbers'): numbers=config.get(u,'voice_numbers') if (call_to in numbers.split(',') or numbers=="*"): if (service==capisuite.SERVICE_VOICE): curr_user=u curr_service=capisuite.SERVICE_VOICE break if (service==capisuite.SERVICE_FAXG3): curr_user=u curr_service=capisuite.SERVICE_FAXG3 break if config.has_option(u,'fax_numbers'): numbers=config.get(u,'fax_numbers') if (call_to in numbers.split(',') or numbers=="*"): if (service in (capisuite.SERVICE_FAXG3,capisuite.SERVICE_VOICE)): curr_user=u curr_service=capisuite.SERVICE_FAXG3 break except IOError,e: capisuite.error("Error occured during config file reading: "+e+" Disconnecting...") capisuite.reject(call,0x34A9) return # answer the call with the right service if (curr_user==""): capisuite.log("call from "+call_from+" to "+call_to+" ignoring",1,call) capisuite.reject(call,1) return try: if (curr_service==capisuite.SERVICE_VOICE): delay=cs_helpers.getOption(config,curr_user,"voice_delay") active_user_prog=read_prog(curr_user) if active_user_prog: delay=user_prog['delay'] if (delay==None): capisuite.error("voice_delay not found for user "+curr_user+"! -> rejecting call") capisuite.reject(call,0x34A9) return capisuite.log("call from "+call_from+" to "+call_to+" for "+curr_user+" connecting with voice",1,call) capisuite.connect_voice(call,int(delay)) voiceIncoming(call,call_from,call_to,curr_user,config) elif (curr_service==capisuite.SERVICE_FAXG3): faxIncoming(call,call_from,call_to,curr_user,config,0) except capisuite.CallGoneError: # catch exceptions from connect_* (cause,causeB3)=capisuite.disconnect(call) capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) # @brief called by callIncoming when an incoming fax call is received # # @param call reference to the call. Needed by all capisuite functions # @param call_from string containing the number of the calling party # @param call_to string containing the number of the called party # @param curr_user name of the user who is responsible for this # @param config ConfigParser instance holding the config data # @param already_connected 1 if we're already connected (that means we must switch to fax mode) def faxIncoming(call,call_from,call_to,curr_user,config,already_connected): try: udir=cs_helpers.getOption(config,"","fax_user_dir") if (udir==None): capisuite.error("global option fax_user_dir not found! -> rejecting call") capisuite.reject(call,0x34A9) return udir=os.path.join(udir,curr_user)+"/" if (not os.access(udir,os.F_OK)): userdata=pwd.getpwnam(curr_user) os.mkdir(udir,0700) os.chown(udir,userdata[2],userdata[3]) if (not os.access(udir+"received/",os.F_OK)): userdata=pwd.getpwnam(curr_user) os.mkdir(udir+"received/",0700) os.chown(udir+"received/",userdata[2],userdata[3]) except KeyError: capisuite.error("user "+curr_user+" is not a valid system user. Disconnecting",call) capisuite.reject(call,0x34A9) return filename="" # assure the variable is defined... try: stationID=cs_helpers.getOption(config,curr_user,"fax_stationID") if (stationID==None): capisuite.error("Warning: fax_stationID not found for user "+curr_user+" -> using empty string") stationID="" headline=cs_helpers.getOption(config,curr_user,"fax_headline","") # empty string is no problem here capisuite.log("call from "+call_from+" to "+call_to+" for "+curr_user+" connecting with fax",1,call) if (already_connected): faxInfo=capisuite.switch_to_faxG3(call,stationID,headline) else: faxInfo=capisuite.connect_faxG3(call,stationID,headline,0) if (faxInfo!=None and faxInfo[3]==1): faxFormat="cff" # color fax else: faxFormat="sff" # normal b&w fax filename=cs_helpers.uniqueName(udir+"received/","fax",faxFormat) capisuite.fax_receive(call,filename) (cause,causeB3)=capisuite.disconnect(call) capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call) except capisuite.CallGoneError: # catch this here to get the cause info in the mail (cause,causeB3)=capisuite.disconnect(call) capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) if (os.access(filename,os.R_OK)): cs_helpers.writeDescription(filename, "call_from=\""+call_from+"\"\ncall_to=\""+call_to+"\"\ntime=\"" +time.ctime()+"\"\ncause=\"0x%x/0x%x\"\n" % (cause,causeB3)) userdata=pwd.getpwnam(curr_user) os.chmod(filename,0600) os.chown(filename,userdata[2],userdata[3]) os.chmod(filename[:-3]+"txt",0600) os.chown(filename[:-3]+"txt",userdata[2],userdata[3]) fromaddress=cs_helpers.getOption(config,curr_user,"fax_email_from","") if (fromaddress==""): fromaddress=curr_user mailaddress=cs_helpers.getOption(config,curr_user,"fax_email","") if (mailaddress==""): mailaddress=curr_user action=cs_helpers.getOption(config,curr_user,"fax_action","").lower() if (action not in ("mailandsave","saveonly")): capisuite.error("Warning: No valid fax_action definition found for user "+curr_user+" -> assuming SaveOnly") action="saveonly" if (action=="mailandsave"): cs_helpers.sendMIMEMail(fromaddress, mailaddress, "Fax received from "+call_from+" to "+call_to, faxFormat, "You got a fax from "+call_from+" to "+call_to+"\nDate: "+time.ctime()+"\n\n" +"See attached file.\nThe original file was saved to file://"+filename+"\n\n", filename) # @brief called by callIncoming when an incoming voice call is received # # @param call reference to the call. Needed by all capisuite functions # @param call_from string containing the number of the calling party # @param call_to string containing the number of the called party # @param curr_user name of the user who is responsible for this # @param config ConfigParser instance holding the config data def voiceIncoming(call,call_from,call_to,curr_user,config): try: udir=cs_helpers.getOption(config,"","voice_user_dir") if (udir==None): capisuite.error("global option voice_user_dir not found! -> rejecting call") capisuite.reject(call,0x34A9) return udir=os.path.join(udir,curr_user)+"/" if (not os.access(udir,os.F_OK)): userdata=pwd.getpwnam(curr_user) os.mkdir(udir,0700) os.chown(udir,userdata[2],userdata[3]) if (not os.access(udir+"received/",os.F_OK)): userdata=pwd.getpwnam(curr_user) os.mkdir(udir+"received/",0700) os.chown(udir+"received/",userdata[2],userdata[3]) except KeyError: capisuite.error("user "+curr_user+" is not a valid system user. Disconnecting",call) capisuite.reject(call,0x34A9) return filename=cs_helpers.uniqueName(udir+"received/","voice","la") action=cs_helpers.getOption(config,curr_user,"voice_action","").lower() if (action not in ("mailandsave","saveonly","none")): capisuite.error("Warning: No valid voice_action definition found for user "+curr_user+" -> assuming SaveOnly") action="saveonly" try: capisuite.enable_DTMF(call) if not active_user_prog: userannouncement=udir+cs_helpers.getOption(config,curr_user,"announcement","announcement.la") else: userannouncement=udir+user_prog['message'] pin=cs_helpers.getOption(config,curr_user,"pin","") if (os.access(userannouncement,os.R_OK)): capisuite.audio_send(call,userannouncement,1) else: if (call_to!="-"): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"anrufbeantworter-von.la"),1) cs_helpers.sayNumber(call,call_to,curr_user,config) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-nachricht.la"),1) if (action!="none"): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la"),1) length=cs_helpers.getOption(config,curr_user,"record_length","60") silence_timeout=cs_helpers.getOption(config,curr_user,"record_silence_timeout","5") capisuite.audio_receive(call,filename,int(length), int(silence_timeout),1) dtmf_list=capisuite.read_DTMF(call,0) if (dtmf_list=="X"): if (os.access(filename,os.R_OK)): os.unlink(filename) faxIncoming(call,call_from,call_to,curr_user,config,1) elif (dtmf_list!="" and pin!=""): dtmf_list+=capisuite.read_DTMF(call,3) # wait 5 seconds for input count=1 while (count<3 and pin!=dtmf_list): # try again if input was wrong capisuite.log("wrong PIN entered...",1,call) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) dtmf_list=capisuite.read_DTMF(call,3) count+=1 if (pin==dtmf_list): if (os.access(filename,os.R_OK)): os.unlink(filename) capisuite.log("Starting remote inquiry...",1,call) remoteInquiry(call,udir,curr_user,config) (cause,causeB3)=capisuite.disconnect(call) capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call) except capisuite.CallGoneError: # catch this here to get the cause info in the mail (cause,causeB3)=capisuite.disconnect(call) capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) if (os.access(filename,os.R_OK)): cs_helpers.writeDescription(filename, "call_from=\""+call_from+"\"\ncall_to=\""+call_to+"\"\ntime=\"" +time.ctime()+"\"\ncause=\"0x%x/0x%x\"\n" % (cause,causeB3)) userdata=pwd.getpwnam(curr_user) os.chmod(filename,0600) os.chown(filename,userdata[2],userdata[3]) os.chmod(filename[:-2]+"txt",0600) os.chown(filename[:-2]+"txt",userdata[2],userdata[3]) fromaddress=cs_helpers.getOption(config,curr_user,"voice_email_from","") if (fromaddress==""): fromaddress=curr_user mailaddress=cs_helpers.getOption(config,curr_user,"voice_email","") if (mailaddress==""): mailaddress=curr_user if (action=="mailandsave"): cs_helpers.sendMIMEMail(fromaddress, mailaddress, "Voice call received from "+call_from+" to "+call_to, "la", "You got a voice call from "+call_from+" to "+call_to+"\nDate: "+time.ctime()+"\n\n" +"See attached file.\nThe original file was saved to file://"+filename+"\n\n", filename) # @brief remote inquiry function (uses german wave snippets!) # # commands for remote inquiry # delete message - 1 # next message - 4 # last message - 5 # repeat current message - 6 # # @param call reference to the call. Needed by all capisuite functions # @param userdir spool_dir of the current_user # @param curr_user name of the user who is responsible for this # @param config ConfigParser instance holding the config data def remoteInquiry(call,userdir,curr_user,config): import time,fcntl,errno,os # acquire lock lockfile=open(userdir+"received/inquiry_lock","w") try: fcntl.lockf(lockfile,fcntl.LOCK_EX | fcntl.LOCK_NB) # only one inquiry at a time! except IOError,err: # can't get the lock if (err.errno in (errno.EACCES,errno.EAGAIN)): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fernabfrage-aktiv.la")) lockfile.close() return try: # read directory contents messages=os.listdir(userdir+"received/") messages=filter (lambda s: re.match("voice-.*\.la",s),messages) # only use voice-* files messages=map(lambda s: int(re.match("voice-([0-9]+)\.la",s).group(1)),messages) # filter out numbers messages.sort() # read the number of the message heard last at the last inquiry lastinquiry=-1 if (os.access(userdir+"received/last_inquiry",os.W_OK)): lastfile=open(userdir+"received/last_inquiry","r") lastinquiry=int(lastfile.readline()) lastfile.close() # sort out old messages oldmessages=[] i=0 while (i<len(messages)): if (messages[i]<=lastinquiry): oldmessages.append(messages[i]) del messages[i] else: i+=1 cs_helpers.sayNumber(call,str(len(messages)),curr_user,config) if (len(messages)==1): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachricht.la"),1) else: capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachrichten.la"),1) # menu for record new announcement cmd="" while (cmd not in ("1","9")): if (len(messages)+len(oldmessages)): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"zum-abhoeren-1.la"),1) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fuer-neue-ansage-9.la"),1) cmd=capisuite.read_DTMF(call,0,1) if (cmd=="9"): newAnnouncement(call,userdir,curr_user,config) return # start inquiry for curr_msgs in (messages,oldmessages): cs_helpers.sayNumber(call,str(len(curr_msgs)),curr_user,config) if (curr_msgs==messages): if (len(curr_msgs)==1): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachricht.la"),1) else: capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-nachrichten.la"),1) else: if (len(curr_msgs)==1): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht.la"),1) else: capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachrichten.la"),1) i=0 while (i<len(curr_msgs)): filename=userdir+"received/voice-"+str(curr_msgs[i])+".la" descr=cs_helpers.readConfig(filename[:-2]+"txt") capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht.la"),1) cs_helpers.sayNumber(call,str(i+1),curr_user,config) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"von.la"),1) cs_helpers.sayNumber(call,descr.get('GLOBAL','call_from'),curr_user,config) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fuer.la"),1) cs_helpers.sayNumber(call,descr.get('GLOBAL','call_to'),curr_user,config) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"am.la"),1) calltime=time.strptime(descr.get('GLOBAL','time')) cs_helpers.sayNumber(call,str(calltime[2]),curr_user,config) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"..la"),1) cs_helpers.sayNumber(call,str(calltime[1]),curr_user,config) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"..la"),1) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"um.la"),1) cs_helpers.sayNumber(call,str(calltime[3]),curr_user,config) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"uhr.la"),1) cs_helpers.sayNumber(call,str(calltime[4]),curr_user,config) capisuite.audio_send(call,filename,1) cmd="" while (cmd not in ("1","4","5","6")): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"erklaerung.la"),1) cmd=capisuite.read_DTMF(call,0,1) if (cmd=="1"): os.remove(filename) os.remove(filename[:-2]+"txt") del curr_msgs[i] capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"nachricht-geloescht.la")) elif (cmd=="4"): if (curr_msgs[i]>lastinquiry): lastinquiry=curr_msgs[i] lastfile=open(userdir+"received/last_inquiry","w") lastfile.write(str(curr_msgs[i])+"\n") lastfile.close() i+=1 elif (cmd=="5"): i-=1 capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"keine-weiteren-nachrichten.la")) finally: # unlock fcntl.lockf(lockfile,fcntl.LOCK_UN) lockfile.close() os.unlink(userdir+"received/inquiry_lock") # @brief remote inquiry: record new announcement (uses german wave snippets!) # # @param call reference to the call. Needed by all capisuite functions # @param userdir spool_dir of the current_user # @param curr_user name of the user who is responsible for this # @param config ConfigParser instance holding the config data def newAnnouncement(call,userdir,curr_user,config): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-komplett.la")) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) cmd="" while (cmd!="1"): capisuite.audio_receive(call,userdir+"announcement-tmp.la",60,3) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-ansage-lautet.la")) capisuite.audio_send(call,userdir+"announcement-tmp.la") capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"wenn-einverstanden-1.la")) cmd=capisuite.read_DTMF(call,0,1) if (cmd!="1"): capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-kurz.la")) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) userannouncement=userdir+cs_helpers.getOption(config,curr_user,"announcement","announcement.la") os.rename(userdir+"announcement-tmp.la",userannouncement) userdata=pwd.getpwnam(curr_user) os.chown(userannouncement,userdata[2],userdata[3]) capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"ansage-gespeichert.la")) # # History: # # $Log: incoming.py,v $ # Revision 1.9.2.1 2003/08/24 12:47:19 gernot # - faxIncoming tried to reconnect when it was called after a switch from # voice to fax mode, which lead to a call abort. Thx to Harald Jansen & # Andreas Scholz for reporting! # # Revision 1.9 2003/06/27 07:51:09 gernot # - replaced german umlaut in filename "nachricht-gelscht.la", can cause # problems on Redhat, thx to Herbert Hübner for reporting # # Revision 1.8 2003/06/16 10:21:05 gernot # - define filename in any case (thx to Axel Schneck for reporting and # analyzing...) # # Revision 1.7 2003/05/25 13:38:30 gernot # - support reception of color fax documents # # Revision 1.6 2003/04/10 21:29:51 gernot # - support empty destination number for incoming calls correctly (austrian # telecom does this (sic)) # - core now returns "-" instead of "??" for "no number available" (much nicer # in my eyes) # - new wave file used in remote inquiry for "unknown number" # # Revision 1.5 2003/03/20 09:12:42 gernot # - error checking for reading of configuration improved, many options got # optional, others produce senseful error messages now if not found, # fixes bug# 531, thx to Dieter Pelzel for reporting # # Revision 1.4 2003/03/13 11:08:06 gernot # - fix remote inquiry locking (should fix bug #534, but doesn't - anyway, # this fix is definitely necessary) # - stricter permissions of saved files and created dirs, fixes #544 # - add "file://" prefix to the path shown in the mails to the user # # Revision 1.3 2003/02/21 13:13:34 gernot # - removed some debug output (oops...) # # Revision 1.2 2003/02/21 11:02:17 gernot # - removed os.setuid() from incoming script # -> fixes Bug #527 # # Revision 1.1.1.1 2003/02/19 08:19:54 gernot # initial checkin of 0.4 # # Revision 1.11 2003/02/17 11:13:43 ghillie # - remoteinquiry supports new and old messages now # # Revision 1.10 2003/02/03 14:50:08 ghillie # - fixed small typo # # Revision 1.9 2003/01/31 16:32:41 ghillie # - support "*" for "all numbers" # - automatic switch voice->fax when SI says fax # # Revision 1.8 2003/01/31 11:24:41 ghillie # - wrong user handling for more than one users fixed # - creates user_dir/user and user_dir/user/received now separately as # idle.py can also create user_dir/user now # # Revision 1.7 2003/01/27 21:57:54 ghillie # - fax_numbers and voice_numbers may not exist (no fatal error any more) # - accept missing email option # - fixed typo # # Revision 1.6 2003/01/27 19:24:29 ghillie # - updated to use new configuration files for fax & answering machine # # Revision 1.5 2003/01/19 12:03:27 ghillie # - use capisuite log functions instead of stdout/stderr # # Revision 1.4 2003/01/17 15:09:49 ghillie # - cs_helpers.sendMail was renamed to sendMIMEMail # # Revision 1.3 2003/01/16 12:58:34 ghillie # - changed DTMF timeout for pin to 3 seconds # - delete recorded wave if fax or remote inquiry is recognized # - updates in remoteInquiry: added menu for recording own announcement # - fixed some typos # - remoteInquiry: delete description file together with call if requested # - new function: newAnnouncement # # Revision 1.2 2003/01/15 15:55:12 ghillie # - added exception handler in callIncoming # - faxIncoming: small typo corrected # - voiceIncoming & remoteInquiry: updated to new config file system # # Revision 1.1 2003/01/13 16:12:58 ghillie # - renamed from incoming.pyin to incoming.py as all previously processed # variables are moved to config and cs_helpers.pyin # # Revision 1.4 2002/12/18 14:34:56 ghillie # - added some informational prints # - accept voice calls to fax nr # # Revision 1.3 2002/12/16 15:04:51 ghillie # - added missing path prefix to delete routing in remote inquiry # # Revision 1.2 2002/12/16 13:09:25 ghillie # - added some comments about the conf_* vars # - added conf_wavedir # - added support for B3 cause now returned by disconnect() # - corrected some dir entries to work in installed system # # Revision 1.1 2002/12/14 13:53:18 ghillie # - idle.py and incoming.py are now auto-created from *.pyin # # Revision 1.4 2002/12/11 12:58:05 ghillie # - read return value from disconnect() # - added disconnect() to exception handler # # Revision 1.3 2002/12/09 15:18:35 ghillie # - added disconnect() in exception handler # # Revision 1.2 2002/12/02 21:30:42 ghillie # fixed some minor typos # # Revision 1.1 2002/12/02 21:15:55 ghillie # - moved scripts to own directory # - added remote-connect script to repository # # Revision 1.20 2002/12/02 20:59:44 ghillie # another typo :-| # # Revision 1.19 2002/12/02 20:54:07 ghillie # fixed small typo # # Revision 1.18 2002/12/02 16:51:32 ghillie # nearly complete new script, supports answering machine, fax receiving and remote inquiry now # # Revision 1.17 2002/11/29 16:28:43 ghillie # - updated syntax (connect_telephony -> connect_voice) # # Revision 1.16 2002/11/29 11:09:04 ghillie # renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) # # Revision 1.15 2002/11/25 11:43:43 ghillie # updated to new syntax # # Revision 1.14 2002/11/23 16:16:17 ghillie # moved switch2fax after audio_receive() # # Revision 1.13 2002/11/22 15:48:58 ghillie # renamed pcallcontrol module to capicom # # Revision 1.12 2002/11/22 15:02:39 ghillie # - added automatic switch between speech and fax # - some comments added # # Revision 1.11 2002/11/19 15:57:18 ghillie # - Added missing throw() declarations # - phew. Added error handling. All exceptions are caught now. # # Revision 1.10 2002/11/18 12:32:36 ghillie # - callIncoming lives now in __main__, not necessarily in pcallcontrol any more # - added some comments and header #