diff --git a/docs/ats/ss-dialog.agi b/docs/ats/ss-dialog.agi new file mode 100644 index 0000000..eb98b6a --- /dev/null +++ b/docs/ats/ss-dialog.agi @@ -0,0 +1,211 @@ +#!/bin/bash +# AGI-сценарий автодозвона SuperSam. +# Аргументы: $1 = сценарий (A|B|C), $2 = тип (dlv|pkp) +# A выбор дня + времени (звоним в день готовности) +# B день фиксирован (завтра), только время +# C слот уже выбран — подтверждение (молчание = подтвердил) +# После подтверждения выбора предлагается изменить его (кнопка 2). +# Результат: /tmp/ss-result-.txt. Конфиги Asterisk не меняет. + +RES=/tmp/ss-result +LOG=/tmp/ss-dialog.log + +UNIQ="" +while read -r line; do + line=${line%$'\r'} + [ -z "$line" ] && break + case "$line" in + agi_uniqueid:*) UNIQ=$(echo "$line" | cut -d' ' -f2) ;; + agi_arg_1:*) SCENARIO=$(echo "$line" | cut -d' ' -f2) ;; + agi_arg_2:*) TYPE=$(echo "$line" | cut -d' ' -f2) ;; + esac +done +SCENARIO=${SCENARIO:-A} +TYPE=${TYPE:-dlv} +OUT="${RES}-${UNIQ}.txt" + +say () { echo "$1"; read -r resp; echo "$(date +%T) $1 -> $resp" >> "$LOG"; RESP="$resp"; } +digit () { # $1=озвучка, $2=таймаут мс -> DIGIT + say "GET DATA $1 ${2:-9000} 1" + DIGIT=$(echo "$RESP" | sed -n 's/.*result=\([0-9]*\).*/\1/p') +} +# Чтение channel-переменной из call-файла (Setvar) через AGI. +getvar () { # $1=имя -> VAR + say "GET VARIABLE $1" + VAR=$(echo "$RESP" | sed -n 's/^200 result=1 (\(.*\))$/\1/p') +} +play () { say "STREAM FILE $1 \"\""; } + +# Channel-переменные из call-файла (Setvar). Читаем ДО диалога, т.к. AGI +# не видит их как bash-переменные — только через GET VARIABLE. +getvar API_URL; API_URL=$VAR +getvar API_KEY; API_KEY=$VAR +getvar GROUP_ID; GROUP_ID=$VAR +getvar ATTEMPT; ATTEMPT=$VAR +getvar TEST_MODE; TEST_MODE=$VAR +getvar CUSTOMER_PHONE; CUSTOMER_PHONE=$VAR + +# Пишем состояние ПОСЛЕ КАЖДОГО шага: если клиент положит трубку, +# выбор всё равно сохранён (AGI умирает молча, финальной записи не будет). +save () { + { + echo "uniqueid=$UNIQ" + echo "scenario=$SCENARIO" + echo "outcome=${OUTCOME:-in_progress}" + echo "dtmf_day=$CONF_DAY" + echo "dtmf_half=$CONF_HALF" + echo "in_progress_day=$DAY" + echo "in_progress_half=$HALF" + echo "changed=$CHANGED" + echo "edits=$EDITS" + echo "updated_at=$(date '+%F %T')" + } > "$OUT" + # Отправляем в БД сразу при финальном исходе (клиент может бросить трубку + # в любой момент — результат должен успеть уйти). Идемпотентность по uniqueid. + case "$OUTCOME" in + confirmed|confirmed_kept_previous|confirmed_silent|no_answer|no_input_day|no_input_half|operator|opt_out|invalid_number|error) + send_result ;; + esac +} + +# Отправка результата в БД (edge function autocall-result). +# Переменные приходят из call-файла (Setvar) и прочитаны через getvar выше. +# При потере сети звонок не валится: curl с таймаутом, результат в $OUT. +send_result () { + [ -n "$API_URL" ] && [ -n "$API_KEY" ] || return 0 + curl -s --max-time 5 -X POST "$API_URL" \ + -H "Content-Type: application/json" \ + -H "x-webhook-secret: $API_KEY" \ + -d "{ + \"asterisk_uniqueid\":\"$UNIQ\", + \"order_group_id\":\"${GROUP_ID:-}\", + \"campaign_type\":\"autocall_slot\", + \"customer_phone\":\"${CUSTOMER_PHONE:-}\", + \"attempt_no\":${ATTEMPT:-1}, + \"scenario\":\"$SCENARIO\", + \"call_started_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", + \"answered_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", + \"duration_sec\":0, + \"sip_code\":200, + \"channel_state\":\"Up\", + \"dtmf_day\":${CONF_DAY:-null}, + \"dtmf_half\":${CONF_HALF:-null}, + \"changed\":\"$CHANGED\", + \"outcome\":\"$OUTCOME\", + \"was_test_mode\":${TEST_MODE:-0} + }" >/dev/null 2>&1 || echo "curl result failed (network?)" >> "$LOG" +} + +echo "=== $(date) uniq=$UNIQ scenario=$SCENARIO type=$TYPE ===" >> "$LOG" +DAY=""; HALF=""; OUTCOME=""; CHANGED=no; EDITS=0 +CONF_DAY=""; CONF_HALF="" # согласованный слот: только он идёт в БД +save + +say "ANSWER" +say "EXEC Wait 1" + +# ─── C: подтверждение уже выбранного слота ─────────────────────────── +if [ "$SCENARIO" = "C" ]; then + play "ru/ss_c_intro_${TYPE}" + play "ru/ss_c_day_tomorrow" + play "ru/ss_ok_first" + digit "ru/ss_c_ask" 8000 + if [ "$DIGIT" = "1" ]; then + play "ru/ss_c_change" + CHANGED=yes + SCENARIO=A + else + play "ru/ss_c_confirmed" + OUTCOME=confirmed_silent + DAY=1; HALF=1 + fi +fi + +# ─── выбор дня и времени, с возможностью переделать ────────────────── +while [ -z "$OUTCOME" ]; do + + # шаг 1: день + if [ "$SCENARIO" = "A" ]; then + digit "ru/ss_a_day_${TYPE}" + DAY=$DIGIT + if [ "$DAY" != "1" ] && [ "$DAY" != "2" ] && [ "$DAY" != "0" ] && [ "$DAY" != "9" ]; then + digit "ru/ss_a_day_${TYPE}" + DAY=$DIGIT + fi + else + DAY=1 # сценарий B: день фиксирован = завтра + fi + save + + if [ "$DAY" = "9" ]; then + play "ru/ss_optout"; OUTCOME=opt_out; break + elif [ "$DAY" = "0" ]; then + play "ru/ss_operator"; OUTCOME=operator; break + elif [ "$DAY" != "1" ] && [ "$DAY" != "2" ]; then + play "ru/ss_no_input"; OUTCOME=no_input_day; break + fi + + # шаг 2: время + if [ "$SCENARIO" = "B" ]; then + digit "ru/ss_b_half_${TYPE}" + else + digit "ru/ss_half_${TYPE}" + fi + HALF=$DIGIT + if [ "$HALF" != "1" ] && [ "$HALF" != "2" ]; then + if [ "$SCENARIO" = "B" ]; then + digit "ru/ss_b_half_${TYPE}" + else + digit "ru/ss_half_${TYPE}" + fi + HALF=$DIGIT + fi + + if [ "$HALF" != "1" ] && [ "$HALF" != "2" ]; then + play "ru/ss_no_input"; OUTCOME=no_input_half; break + fi + + # ЕДИНАЯ ТОЧКА ФИКСАЦИИ: оба шага пройдены — только теперь слот становится + # согласованным. Обрыв на любом шаге раньше не портит уже согласованный слот. + CONF_DAY=$DAY; CONF_HALF=$HALF + OUTCOME=confirmed + save + + # шаг 3: озвучиваем выбор + [ "$DAY" = "1" ] && play "ru/ss_ok_tomorrow" || play "ru/ss_ok_dayafter" + [ "$HALF" = "1" ] && play "ru/ss_ok_first" || play "ru/ss_ok_second" + + # шаг 4: предложить изменить (один раз) + if [ "$EDITS" -lt 1 ]; then + digit "ru/ss_change_hint" 6000 + if [ "$DIGIT" = "2" ]; then + # CONF_* НЕ трогаем: пока новый выбор не сделан целиком, в силе прежний. + # Рабочие DAY/HALF обнуляем, чтобы не склеить новый день со старым временем. + # ВАЖНО: OUTCOME к этому моменту = confirmed (выставлен на шаге фиксации). + # Сбрасываем его в пусто, иначе цикл while [ -z OUTCOME ] завершится и день + # не переспросит. Контракт: пустой OUTCOME = диалог ещё идёт. + DAY=""; HALF=""; OUTCOME="" + EDITS=$((EDITS+1)); CHANGED=yes; SCENARIO=A + save + continue # заново день и время + fi + fi + + # подтверждено + if [ "$EDITS" -gt 0 ]; then + play "ru/ss_tail_final_${TYPE}" + else + play "ru/ss_tail_${TYPE}" + fi + OUTCOME=confirmed +done + +# Правка начата, но не доведена (тишина/сброс): согласованный слот остаётся в силе +if [ -n "$CONF_DAY" ] && [ "$OUTCOME" != "confirmed" ]; then + OUTCOME=confirmed_kept_previous +fi +save +echo "finished_at=$(date '+%F %T')" >> "$OUT" + +say "HANGUP" +exit 0