The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]



Индекс форумов
Составление сообщения

Исходное сообщение
"Раздел полезных советов: Русскоговорящий будильник/напоминал..."
Отправлено auto_tips, 04-Ноя-10 23:21 
В Asterisk есть штатный будильник - напоминаловка, если вы загляните в директорию /var/lib/asterisk/agi-bin, то увидите что-то похожее на wakeup.php. Вещь не плохая, но хочу предложить Вашему вниманию, более продвинутую версию русскоговорящего будильника. В нашем случае, Вы можете отправлять голосовые сообщения как сами себе, так и на любые другие номера. Причем задавать дату можно как в четком виде ГГММДД ЧЧММ, так и в неявном виде, через 10 минут от текущего времени, или через 3-и дня от текущей даты.

Концепция будильника следующая, пользователь вводит со своего телефона комбинацию цифр вида *0*X*Y*Z, где

X - дата, когда необходимо озвучить сообщение
Y - время, когда необходимо озвучить сообщение
Z - номер, на который необходимо позвонить для проигрывания сообщения


Возможные варианты значения X (дата, когда проигрывать напоминание)

0 - Звонить сегодня
От 1 до 99 - Позвонить через 1-99 дней
ГГГГММДД - Позвонить в конкретную дату

Возможные варианты значения Y (время, когда проигрывать напоминание)

0 - Звонить в это время (не работает с текущим днем)
От 1 до 999 - Звонить через 1-999 минут
ЧЧММ - Звонить в конкретное время

Возможные варианты значения Z (номер куда будем отправлять напоминание)

0 - Номер с которого звонят
zzz -Локальный номер (у меня 3-значные номера)
zzzzzzzzzzzzzzzzz - Внешний номер, может быть любым!

Примеры:

*0*0*120*0  - позвонить через 2-а часа  на свой телефон, и озвучить надиктованное сообщение (не забыть выключить суп!).

*0*20101231*2300*777 - позвонить 31 декабря 2010 года в 23.00 на номер 777 и озвучить поздравление с новым годом!

*0*30*1500*78123090607 - позвонить через 30 дней  в 15.00 на номер 78123090607 и озвучить сообщение (пора оплачивать счета)!

*0*20110308*0*1234567 - позвонить 8 марта 2011 года в текущее время на номер 1234567 и озвучить поздравление с праздником!

Думаю, концепция ввода даты, времени  и номера, куда необходимо позвонить для напоминания вам ясна. Теперь перейдем к технической реализации нашей задачи.


В файле extensions.conf мы должны описать наш экстеншен!

   exten => _*0*X.,1,Answer
   exten => _*0*X.,n,AGI(reminder.agi)
   exten => _*0*X.,n,Hangup

Таким образом, все набранные номера, которые начинаются с *0* попадают в наш контекст и включают скрипт reminder.agi

Далее опишем контекст go - он запускается call файлом в момент когда происходит дозвон до абонента с целью озвучить надиктованное сообщение.

   exten => go,1,NoOp("Будильничег")
   exten => go,n,AGI(reminder_listen.agi)
   exten => go,n,HagnUp

ну и теперь самое интересное, это наши скрипты

/var/lib/asterisk/agi-bin/reminder.agi

   #!/usr/bin/perl
   use Asterisk::AGI;
   use POSIX;
   use File::Copy;
   use Time::Local;
   use Date::Calc qw (Add_Delta_Days);

   $AGI = new Asterisk::AGI;
   my %input = $AGI->ReadParse();

   $trunk="megatrank";
   # начало отсчета с 1900 года, значение year - смещение от 1900 года
   $datesec=time;
   #chomp($datesec);
   # получаем значение exten и заносим в переменную $digit -
   # это то что набрал пользователь на своем телефоне, далее мы
   # разберем по частям эти цифры с целью понять что хотел сказать
   # пользователь (когда и куда звонить)
   $digit = $AGI->get_variable('EXTEN');
   # на всякий случай, выдергиваем номер звонящего
   $src = $AGI->get_variable('CALLERID(num)');
   #регулярным выражением выдергиваем значения даты времени и номера звонящего
   $digit =~ /^\*0\*(\d+)\*(\d+)\*(\d+)/;
   $wdate=$1;
   $wtime=$2;
   $wnum=$3;

   # вводим маленькую функцию для озвучки ошибки набора, что бы
   # несколько раз не писать один и тот же код!
   # обратите внимание, я взял звуковые файлы с http://ivrvoice.ru/downloader
   # и перекинул их  в Asterisk в папку /var/lib/asterisk/sounds/ru11 !
   sub digit_error {
      $AGI->exec('Wait',"1");
      $AGI->exec('Playback',"ru11/an-error-has-occured");
      $AGI->exec('Playback',"ru11/check-number-dial-again");
      exit 0;
   }
   # звуковые файлы в директории digits мною были заменены с
   # английского на русский аналог, все с того же сайта. На самом
   # деле это не совсем верно, по идее Астериск должен цеплять
   # русские файлы после переопределения глобальной переменной LANG,
   # однако сходу у меня не получилось, потому исправил ситуацию
   # простым копированием файлов.
   # Данная функция, корректно для нашего чисто русского слуха, озвучивает день!
   sub say_day {
      $sayday=$_[0];
      if ("$sayday" < 20) {
         $AGI->exec('Playback',"digits/h-$sayday\\n");
      } elsif ( 20 < "$sayday" and  "$sayday" < 30 ) {
         $sayday=~/\d(\d)/;
         $AGI->exec('Playback',"digits/20");
         $AGI->exec('Playback',"digits/h-$1\\n");
      } elsif ( "$sayday" == 20 ) {
         $AGI->exec('Playback',"digits/h-20n");
      } elsif ( "$sayday" == 30 ) {
         $AGI->exec('Playback',"digits/h-30n");
      } else {
         $AGI->exec('Playback',"digits/30");
         $AGI->exec('Playback',"digits/h-1\\n");
      }
   }

   # берем локальное время и разбиваем на переменные
   ($sec2,$min2,$hour2,$mday2,$mon2,$year2,$wday2,$yday2,$isdst2)=localtime($datesec);
   # не забываем к году прибавить 1900 а к месяцу единичку
   $tmpyear2=$year2+1900;
   $tmpmon2=$mon2+1;
   # для проверки работы скрипта выводим на экран информацию, когда у нас задание стартовало.
   $AGI->exec('NoOp',"Старт_задания_$tmpyear2.$tmpmon2.$mday2\_в_$hour2:$min2");
   #####
   # если пользователь ввел в поле дата 8 цифр, значит он имел ввиду четкую дату!
   if (length($wdate) eq "8") {
      $wdate =~ /(\d{4})(\d{2})(\d{2})/;
      $timestamp1 = timelocal($sec2,$min2,$hour2,$3,$2-1,$1-1900);
      # если ввел 0 – значит имел ввиду что запустить будильник сегодня
   } elsif ($wdate eq "0") {
      $timestamp1=$datesec;
      # если ввел 2-х значное число, значит имел ввиду что пускть задание через сколько то дней.
   } elsif (length($wdate) ge "1" and length($wdate) le "2" and $wdate ne "0") {
      $timestamp1=$datesec + $wdate*86400;
      # ну а если ерунду ввел, значит говорим ему об этом в мягкой форме и отключаемся
   } else {
      &digit_error;
   }
   # обратите внимание, я не делал проверку на правильность ввода
   # даты, т.е. система проверяет, кол-во дней в месяце, месяцев в
   # году, что не есть хорошо, но на мой взгляд это не критично!
   ####
   # Та же операция с временем
   if (length($wtime) eq "4") {
      $wtime =~ /(\d{2})(\d{2})/;
      $timestamp=$timestamp1-(localtime($datesec))[2]*3600-(localtime($datesec))[1]*60+($1*3600)+($2*60);
   } elsif ($wtime eq "0") {
      $timestamp=$timestamp1+60;
   } elsif (length($wtime) ge "1" and length($wtime) le "3") {
      $timestamp=$timestamp1+$wtime*60;
   } else {
      $AGI->exec('NoOp',"Не_верно_задано_время");
      &digit_error;
   }
   ####

   ($sec1,$min1,$hour1,$mday1,$mon1,$year1,$wday1,$yday1,$isdst1)=localtime($timestamp);
   $mon_sound="mon-$mon1";
   $mon1++;
   $year1=1900+$year1;

   # В результате хитрых и не очень операций, получаем время, когда
   # необходимо позвонить нашему абоненту!
   $AGI->exec('NoOp',"Выполнение_задачи_$year1.$mon1.$mday1\_в_$hour1:$min1");

   ####
   # если же дата выполнения задачи ранее текущей даты, значит
   # пользователь ошибся при вводе даты и времени
   if ($timestamp le $datesec) {
      $AGI->exec('NoOp',"Дата_задания_меньше_текущего_времени!");
      &digit_error;
   }
   ####
   # проделываем похожую операцию с номером куда будем звонить, при 0
   # - звоним сами себе, если длина введенных цыфр от 3-х до 4-х,
   # значит это локальный звонок,  если ни то не другое, пользователь
   # имел ввиду что звоним на внешний номер!
   if ( $wnum eq "0" ) {
      $ch="SIP/$src";
      $dst="$src";
   } elsif ( length($wnum) eq "4" or length($wnum) eq "3") {
      $ch="SIP/$wnum";
      $dst="$wnum";
   } else {
      $ch="SIP/$wnum\@$trunk";
      $dst="$wnum";
   }

   #формируем имя файла для звонка
   $records="$year1$mon1$mday1\-$hour1$min1-$dst";
   $filename="/var/lib/asterisk/sounds/records/$records.sln";

   CICLE3:
   # Приятный женский голос говорит - оставьте сообщение после
   # сигнала, затем нажмите решетку или повесьте трубку. Вешать
   # трубку не следует, т.к. нам необходимо будет подтвердить запись,
   # потому необходимо после надиктованного сообщения нажать решетку
   $AGI->exec('Playback',"ru11/vm-intro");
   $AGI->exec('Record',"records/$records.sln||10");

   CICLE2:
   # Тут мы озвучиваем само сообщение и куда и когда оно будет отправлено
   $AGI->exec('Playback',"ru11/vm-soobshenie");
   $AGI->exec('Playback',"ru11/na-nomer");
   $AGI->exec('SayDigits',"$dst");
   $AGI->exec('Playback',"digits/at");
   $AGI->exec('Playback',"digits/day-$wday1");
   &say_day($mday1);
   $AGI->exec('Playback',"digits/$mon_sound");
   $AGI->exec('Playback',"digits/at");
   $AGI->exec('SayNumber',"$hour1");
   $AGI->exec('Playback',"ru11/hours");
   $AGI->exec('SayNumber',"$min1");
   $AGI->exec('Playback',"ru11/minutes");
   $AGI->exec('Playback',"records/$records");

   # ввели счетчик возварата к прослушки или записи, что бы пользователь не заигрывался.
   $count++;
   if ($count eq "5") {  $AGI->exec('Playback',"ru11/goodbye");  
   unlink($filename); exit 0; }

   # подтверждаем запись (нажмите 1-н что бы принять сообщение 2-а
   # что бы прослушать, 3-и что бы записать его заново)
   $AGI->exec('Read',"rep|ru11/vm-review|1||1|5");
   $rep = $AGI->get_variable("rep");

   if ( $rep eq "2" ){
      goto CICLE2;
   } elsif ( $rep eq "3" ) {
      unlink($filename);
      goto CICLE3;
      # если пользователь вводит 1-н, тем самым подтверждая запись,
      # ему говорить что сообщение записано и формируется call файл в
      # контексте wakeup экстеншен go, максимальное количество
      # попыток дозвона 3, время ожидания на проводе 60 сек. Так же
      # говорим что CallID у нас подменяется на NOTE (напоминание),
      # это нужно для локальных телефонов с дисплеем, что бы можно
      # было понять от кого звонок и передаем переменную в астериск
      # date, говорящую когда была сделана запись, а так же
      # переменную с именем файла, который будем слушать.
   } elsif ( $rep eq "1" ) {
      $AGI->exec('Playback',"ru11/vm-msgsaved");
      open (CALL,  "> /tmp/$records");
      print CALL  "Channel:$ch\nContext:wakeup\nExtension:go
        \nPriority:1\nMaxRetries:3\nRetryTime:60\nWaitTime:60
        \nCallerID:NOTE<$src>\nSet:date=$datesec\nSet:src=$src
        \nSet:records=$records\n";
      close (CALL);

      # меняем атрибуты файла, что бы астериск при чтении директории
      # outgoing не запускал сразу после перемещения нашего call файла процесс дозвона.
      utime($timestamp,$timestamp,"/tmp/$records");
      move("/tmp/$records","/var/spool/asterisk/outgoing/$records");
      $AGI->exec('Playback',"ru11/goodbye");
      exit 0;
   }
   # попрощались и удалили временный файл.
   $AGI->exec('Playback',"ru11/demo-moreinfo");
   unlink($filename);
   exit 0;


Скрипт рабочий, у Вас могут возникнуть сложности только с модулями, в таком случае идем на http://search.cpan.org/  и качаем необходимые модули.

Обратите внимание все записанные файлы хранятся в директории /var/lib/asterisk/sounds/records в формате sln - при создании директории поменяйте правильно права, иначе работать не будет!

Сообщение записали, теперь нужно бы его прослушать.

Итак скрипт ./reminder_listen.agi

   #!/usr/bin/perl
   use Asterisk::AGI;
   use POSIX;
   use File::Copy;
   use Time::Local;

   $AGI = new Asterisk::AGI;
   my %input = $AGI->ReadParse();
   $count=0;
   sub say_day {
      $sayday=$_[0];
      if ("$sayday" < 20) {
         $AGI->exec('Playback',"digits/h-$sayday\\n");
      } elsif ( 20 < "$sayday" and  "$sayday" < 30 ) {
         $sayday=~/\d(\d)/;
         $AGI->exec('Playback',"digits/20");
         $AGI->exec('Playback',"digits/h-$1\\n");
      } elsif ( "$sayday" == 20 ) {
         $AGI->exec('Playback',"digits/h-20n");
      } elsif ( "$sayday" == 30 ) {
         $AGI->exec('Playback',"digits/h-30n");
      } else {
         $AGI->exec('Playback',"digits/30");
         $AGI->exec('Playback',"digits/h-1\\n");
      }
   }

   # Получаем от Asterisk данные о том, откуда поступило напоминание,
   # имя звукового файла и дата когда была сделана запись.

   $src = $AGI->get_variable('src');
   $records = $AGI->get_variable('records');
   $filename="/var/lib/asterisk/sounds/records/$records";
   $date = $AGI->get_variable('date');

   ($sec1,$min1,$hour1,$mday1,$mon1,$year1,$wday1,$yday1,$isdst1)=localtime($date);
   $mon_sound="mon-$mon1";

   CICLE1:
   # Начинаем грузить нашего абонента информацией откуда был сделан
   # звонок, когда и что собственно от него хотели!
   $AGI->exec('Playback',"ru11/vm-from-phonenumber");
   $AGI->exec('SayDigits',"$src");

   $AGI->exec('Playback',"digits/at");
   $AGI->exec('Playback',"digits/day-$wday1");
   &say_day($mday1);
   $AGI->exec('Playback',"digits/$mon_sound");
   $AGI->exec('Playback',"digits/at");
   $AGI->exec('SayNumber',"$hour1");
   $AGI->exec('Playback',"ru11/hours");
   $AGI->exec('SayNumber',"$min1");
   $AGI->exec('Playback',"ru11/minutes");

   $AGI->exec('Playback',"records/$records");
   # В конце концов ошарашенного таким напором абонента мы
   # переспрашиваем, хочет ли он еще раз прослушать наше сообщение,
   # если да то у него еще в запасе 4-ре раза прослушки, далее цикл
   # прервется и система ему скажет goodbye, удалит файл и повесит трубку.
   $AGI->exec('Read',"rep|ru11/vm-repeat|1||1|5");
   $rep = $AGI->get_variable("rep");
   if ( $rep eq "5" ){
      $count++;
      $AGI->exec('NoOp',"$count");
      if ($count eq "3") {
        $AGI->exec('Playback',"ru11/goodbye");  
        unlink($filename); exit 0;
      }
      goto CICLE1;
   }
   unlink($filename);
   exit 0;

Вот собственно и все. Как мог подробно изложил информацию. Конечно во многих местах код можно более изящно написать, но данная система работает довольно стабильно.


URL: http://3090607.ru/note/28-create-wakeup
Обсуждается: http://www.opennet.ru/tips/info/2472.shtml

 

Ваше сообщение
Имя*:
EMail:
Для отправки ответов на email укажите знак ! перед адресом, например, !user@host.ru (!! - не показывать email).
Более тонкая настройка отправки ответов производится в профиле зарегистрированного участника форума.
Заголовок*:
Сообщение*:
 
При общении не допускается: неуважительное отношение к собеседнику, хамство, унизительное обращение, ненормативная лексика, переход на личности, агрессивное поведение, обесценивание собеседника, провоцирование флейма голословными и заведомо ложными заявлениями. Не отвечайте на сообщения, явно нарушающие правила - удаляются не только сами нарушения, но и все ответы на них. Лог модерирования.



Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру