#!/usr/bin/perl -w # # # 说明: # 用来更新机器人外呼状态 # 如果是第一次的时候,获取开场白 # sub robot_request_update() { use strict; use warnings; use Redis; use LWP::UserAgent; use JSON; use utf8; use Encode; my $self = shift; my $AGI = $self->{server}{agi}; #Asterisk::AGI object my $INPUT = $self->{server}{input}; #Extension input my $PARAM = $self->{server}{params}; #Call params my $callid = $PARAM->{'callid'}; my $caller = $PARAM->{'caller'}; my $requestid = $PARAM->{'requestid'}; my $status = $PARAM->{'status'}; my $hupinfo; if ("1" eq $status) { #通话结束时,删除临时彩铃 my $oldfile = $AGI->get_variable("PROMRTFILE"); if ( -e $oldfile) { unlink $oldfile; $AGI->verbose("delete PROMRTFILE $oldfile", 1); } $hupinfo = $self->notify_robot_hangup($callid, $caller); } elsif ("0" eq $status){ my $recordUrl = $AGI->get_variable("recordUrl"); if ($recordUrl) { $self->savefile($recordUrl, $callid); } } my $CFG='/data/justcall/pbx/etc/redis_db.conf'; my $cfg_info=$self->read_conf($CFG); if(!$cfg_info) { $AGI->verbose("$CFG open failed", 3); exit; } my $redis_ip = $cfg_info->{'global'}{'bing_ip'}; my $redis_port = $cfg_info->{'global'}{'bing_port'}; my $redis_passwd = $cfg_info->{'global'}{'password'}; if(!$redis_ip || !$redis_port) { $AGI->verbose("get config of redis failed!", 3); exit; } if (!$requestid) { $AGI->verbose("requestid is null!", 3); exit; } #snprintf(rediscmdList, sizeof(rediscmdList), "LPUSH agent:status %s.idle.%d.%ld", p, calltype, now); my $redisconn; if ($redis_passwd && "" ne "$redis_passwd") { $redisconn = Redis->new( server => "$redis_ip:$redis_port",reconnect => 1, every=>60, password => "$redis_passwd"); } else { $redisconn = Redis->new( server => "$redis_ip:$redis_port",reconnect => 1, every=>60); } if(!$redisconn) { $AGI->verbose("connect redis failed", 3); exit; } $redisconn->SELECT(9); $redisconn->HMSET("ss_robot_request:$requestid", "request_id", "$requestid", "call_id", "$callid", "status", "$status"); if ($hupinfo) { $redisconn->HSET("ss_robot_request:$requestid", "hupinfo", "$hupinfo"); } $redisconn->EXPIRE("ss_robot_request:$requestid", 259200); # 3 days $redisconn->quit; } sub savefile() { my $self = shift; #my $wavurl = "https://hx-robot-call.oss-cn-shanghai.aliyuncs.com/robot-call/record_wav/ttsRecord/16253_2024-04-19_7d2765327f3c41f2a640a5ab82683801.wav"; my $wavurl = shift; my $callid = shift; my $AGI=$self->{server}{agi}; #my $filename = `basename $wavurl`; #$filename =~ s/^\s+|\s+$//g; my $filedir = "/data/justcall/pbx/lib/sounds/mrcpvoice/"; my $filename = $filedir.$callid.time.".wav"; my $ua = LWP::UserAgent->new(env_proxy => 1, keep_alive => 1, timeout => 10); my $response = $ua->get($wavurl); if ($response->is_success) { my $content_type = $response->content_type; my $content_length = $response->content_length; if ($content_type =~ /audio/ || $content_type =~ /wav/) { $AGI->verbose("new $filename => $content_type $content_length", 1); if ($content_length && $content_length > 0) { my $WAVFILE; open($WAVFILE, "+>", $filename) or return; print $WAVFILE $response->decoded_content; $WAVFILE->flush(); close($WAVFILE); if ( -e $filename ) { my $oldfile = $AGI->get_variable("PROMRTFILE"); if ( -e $oldfile) { unlink $oldfile; $AGI->verbose("replace PROMRTFILE from $oldfile to $filename", 1); } else { $AGI->verbose("set PROMRTFILE $filename", 1); } $AGI->set_variable("PROMRTFILE","$filename"); } } } } else { $AGI->verbose("$wavurl failed: ".$response->status_line, 1); } return; } #入参 #callId string 通话唯一id true - #tel string 被叫号码 true - #customName string 被叫姓名 true - #callResult int32 呼叫结果 取值说明:1已接通, 2未接通, 7未拨打, 10执行中 true - #ringTime int32 响铃时长 true - #spNumber string 中继号码 true - #callStartTime string 呼叫开始时间 (yyyy-MM-dd HH:mm:ss) true - #answerTime string 接通时间 (yyyy-MM-dd HH:mm:ss) true - #callEndTime string 呼叫结束时间 (yyyy-MM-dd HH:mm:ss) true - #recordUrl string 通话录音 true - #duration int32 通话时长 true - #hangupReason string 挂机原因 true - #talkTurn int32 对话轮次 true - #dropSide string 通话挂断方 (1客户,2座席,3系统,4ivr) true - #transToAgent int32 转座席 (0 未转, 1 转) true - #extraParams string 自定义参数(透传) true #requestUniqueId string webcall请求唯一标识 true - # # # 返回值 #code string 响应码 ‘00’代表成功,其他失败 - #message string 响应信息 - #data object 响应体 # sub notify_robot_hangup { my $self = shift; my $callid = shift; my $caller = shift; my $AGI = $self->{server}{agi}; my $did = $AGI->get_variable("FROM_DID"); #Noop(${CDR(start,u)} ${CDR(start,f)} ${CDR(start)}) my $start = $AGI->get_variable("CDR(start)"); my $answer = $AGI->get_variable("CDR(answer)"); my $end = $AGI->get_variable("CDR(end)"); my $ringsec = 0; my $duration = 0; my $hangupReason = "normal"; my $dropSide = 1; my $talktimes = $AGI->get_variable("talk_times"); my $extraParams = $AGI->get_variable("extraParams"); #赋值 my $lstart = $AGI->get_variable("CDR(start,u)"); my $lanswer = $AGI->get_variable("CDR(answer,u)"); my $lend = $AGI->get_variable("CDR(end,u)"); if ($lanswer > 0) { $ringsec = int($lanswer - $lstart); } if ($lend > 0 && $lanswer > 0) { $duration = int($lend - $lanswer); } my $recogstatus = $AGI->get_variable('RECOGSTATUS'); my $noinput_times = $AGI->get_variable('noinput_times'); my $ttsapi_err_times = $AGI->get_variable('ttsapi_err_times'); my $robothangup = $AGI->get_variable('robothangup'); if ($recogstatus eq "ERROR") { $hangupReason = "asr server error"; } elsif ($noinput_times >= 2) { $hangupReason = "customer no input 2 times"; } elsif ($ttsapi_err_times >= 3) { $hangupReason = "ttsapi err 3 times"; } if ($robothangup) { $dropSide = $robothangup; } my $curTime = int(time()*1000); my $url = "https://service-gateway.houxikeji.com/robotcall/callback/hangup/mingyiyun"; #yyyy-MM-dd HH:mm:ss my %postjson = ( "callId" => "$callid", "tel" => "$caller", "callResult" => 1, "spNumber" => "$did", "callStartTime" => "$start", "answerTime" => "$answer", "callEndTime" => "$end", "ringTime" => $ringsec, "recordUrl" => "$callid", "duration" => $duration, "hangupReason" => "$hangupReason", "talkTurn" => $talktimes, "dropSide" => "$dropSide", "transToAgent" => 0, "extraParams" => "$extraParams", "requestUniqueId" => "$callid" ); my $content_string = encode_json(\%postjson); #调用app指定的url my $ua = LWP::UserAgent->new(env_proxy => 1, keep_alive => 1, timeout => 10); $ua->agent('perl/5.16'); my $req = HTTP::Request->new('POST' => $url); #$req->header('x-request-time' => $curTime); $req->content_type('application/json;charset=utf-8'); $req->content($content_string); $content_string =~ s/[\s+\r+\n+]//g; $AGI->verbose("$content_string"); my $response = $ua->request($req); if (!$response-> is_success){ $AGI->verbose("$url Post failed: ".$response->status_line, 1); return; } my $resl = $response->content(); $resl =~ s/[\s+\r+\n+]//g; $AGI->verbose("---------resl=[$resl]", 1); return $content_string; } sub read_conf() { my $self = shift; my $this = shift; my %ret=(); open FH, $this or return 0; my $cat="global"; #默认类别 [global] my $key=""; while (my $myline = ) { chomp $myline; next if $myline eq ""; if( $myline =~ /^\s*\[\s*([^=]+)\s*\]/ ) { $cat=$1; next; #类别 } elsif ($myline =~ /^\s*#/) { next; #注释; } elsif ($myline =~ /^\s*=/) { next; #无键 } elsif($myline =~ /^([^=]+)=\s+#/ || $myline =~ /^([^=]+)=\s*$/ || $myline !~ /=/) { next; #无值 } elsif($myline =~ /=/) { my ($key, $value) = $myline =~ /^([^=]+)=(.*)$/; $key =~ s/\s//g; #去注释与前后空格 if($value =~ /^(.*)\s#(.*)$/) { my $tmp = $1; my $note = $2; $tmp =~ s/^\s+|\s+$//g; $ret{$cat}{$key}=$tmp; } else { $value =~ s/^\s+|\s+$//g; $ret{$cat}{$key}=$value; } } } close FH; return \%ret; #返回引用 }