В одном из моих любимых фильмов «О чем говорят мужчины», персонаж А. Демидова периодически произносит фразу «Вот поэтому я и не женюсь». И я как человек неженатый тоже иногда её произношу с отсылкой на фильм.

Но плох тот системный аналитик, который не подстелил себе соломки на будущее. Вдруг однажды и мне придется связать себя узами Гименея.

Поэтому я решил пофантазировать, как бы я мог оригинально пригласить гостей на свадьбу. Да так, чтобы сделать оригинальное гостям приглашение своими руками, а не просто отдать на откуп какой-то конторе.

Для решения такой благородной задачи я решил проверить кейс с отправкой приглашений в виде голосовых сообщений. Для этого нам понадобится бесплатный пробный аккаунт в МТС Exolve и самые базовые знания языка программирования Python.

Если вы не сильны в программировании или совсем не знакомы с Python, не переживайте, в статье есть необходимые примеры и скриншоты. Уверен, что вы сможете разобраться и поэкспериментировать самостоятельно.

Как всегда дисклеймер: я не программист. Поэтому не стоит принимать материалы статьи как истину в последней инстанции. Я старался упростить код, чтобы он был понятнее для начинающих.

Подготовимся

План работ

Вот что мы должны сделать:

  1. зарегистрировать ЛК в МТС Exolve и получить 300 рублей для тестов;

  2. создать приложение;

  3. арендовать номер на тестовые рубли;

  4. загрузить аудиофайл с приглашением на праздник.

  5. написать скрипт который будет:

    1. считывать из csv список гостей для приглашения;

    2. отправлять гостям голосовое сообщение;

    3. ждать, пока люди слушают сообщение;

    4. проверять, кто не послушал;

    5. тем, кто не послушал, отправлять SMS с приглашением;

    6. выводить итоговую статистику.

Дополним наш план наглядной иллюстрацией:

Загрузка аудиосообщения

Прежде чем загрузить аудиосообщение, нам необходимо зарегистрироваться.
Сделать это очень просто. На сайте exolve.ru нажать кнопку «Попробовать бесплатно», зарегистрироваться и подтвердить почту.

Сразу после подтверждения почты нас порадуют начислением 300 бонусных рублей и предложат создать приложение.

Не пугайтесь, в нашем случае писать код или арендовать хостинг для приложения не придется. Приложение позволяет нам получить ключ от API.

Окно создания приложения
Окно создания приложения

Если вы вдруг случайно закрыли это окно не переживайте, вы всегда успеете создать приложение, более подробно об этом я рассказал в статье про автоматизацию отправки SMS в Github Actions.

Чтобы полноценно протестировать API, подтвердите свой номер телефона. Форму подтверждения можно найти на главной странице ЛК или в разделе «Профиль» . Необходимо вводить настоящий номер телефона: тот, который используете. На него будет отправлено SMS c кодом подтверждения. Во время тестового периода отправлять голосовые сообщения, SMS, звонки и получать другие услуги можно будет только на этот номер. 

Для того, чтобы отправлять голосовые сообщения, покупаем виртуальный номер в системе. Сделать это можно в соседней форме на главной странице ЛК. Плата за номер будет списываться с тестового баланса раз в месяц, пока он не исчерпается.

Если при покупке номера вы не видите доступных вариантов, попробуйте установить другой регион в фильтрах и повторно нажать кнопку «Найти номера».

Окно покупки номера
Окно покупки номера

Теперь когда все формальности улажены, мы можем загрузить аудиоприглашение.

Для начала загрузим сам аудиофайл. Откройте приложение и перейдите в раздел «Аудиофайлы» и загрузите файл .mp3 или .wav, размером не больше 5 Мб.

Некоторые «битые» файлы .mp3 могут некорректно обрабатываться системой. Если видите ошибку загрузки, не переживайте, а просто попробуйте другой файл.

Теперь привяжем загруженный файл к аудиосообщению. Для этого перейдите в раздел «Голосовые SMS» и нажмите кнопку «Создать голосовое SMS»

Получится как на картинке ниже:

Создание голосового сообщения
Создание голосового сообщения

Важно помнить, что в данном контексте аудиосообщение — это не сам звонок абоненту, а ресурс с определенным идентификатором, к которому мы будем дальше обращаться при работе с API.

После создания голосового сообщения мы перейдем на страницу с информацией о нём, с которой всегда можно скопировать идентификатор. 

Фрагмент id сообщения в ЛК
Фрагмент id сообщения в ЛК

Проверяем работу API

Прежде чем перейти к разработке, давайте проверим, что у нас все верно настроено для работы с API. Я буду работать с Postman, но в принципе можно использовать любой другой клиент для запросов к API, например, cURL.

Не забудьте скопировать ключ для API из раздела «API-ключи».

Ключ  (token) для API
Ключ  (token) для API

Начнем проверку с «бесплатного» метода /voice-message/v1/GetList (документация этого метода). Он выводит список голосовых сообщений и никак не влияет на состояние тестового баланса.

Создадим новую коллекцию, а в ней новый запрос.

На вкладке «Authorization» выбираем тип «Bearer token».
В поле «Token» введите токен, который получили при создании приложения.

После отправки запроса должно получится как на картинке ниже:

Пример отправки запроса на получение списка голосовых сообщений
Пример отправки запроса на получение списка голосовых сообщений

Нам вернулись те же данные, что и немного раньше на странице создания голосового сообщения в ЛК.

Теперь попробуем отправить себе голосовое сообщение, с помощью метода /call/v1/MakeVoiceMessage (документация). Помните, что за этот запрос с тестового баланса спишут стоимость услуги голосового сообщения. А голосовые сообщения тарифицируется как исходящий звонок. Оплату спишут, только после того как абонент примет вызов. За «гудки» денег не возьмут.
Поэтому отправляйте запросы осознанно, чтобы раньше времени не израсходовать тестовый баланс.

Создадим по аналогии следующий запрос в Postman. В теле запроса укажем:

  • source – номер, арендованный в ЛК;

  • destination –  наш номер, привязанный к ЛК;

  • service_id –  id голосового сообщения. 

Получится примерно так:

Пример запроса на отправку голосового сообщения
Пример запроса на отправку голосового сообщения

Проверим статус отправки голосового сообщения с помощью метода

/call/v1/GetInfo (документация).

Для этого используем call_id, который получили на прошлом этапе.

Пример запроса на проверку статуса голосового сообщения
Пример запроса на проверку статуса голосового сообщения

Настало время финальной проверки. 

Отправим SMS-сообщение, с помощью метода /messaging/v1/SendSMS (документация).
Делаем все по аналогии:

Пример запроса на отправку SMS
Пример запроса на отправку SMS

Напишем скрипт на Python

Для запуска скрипта я использовал Python 3.10.9 и менеджер окружений Conda. Но скорее всего вам подойдет практически любая версия Python 3.X и менеджер пакетов. Также для запуска скрипта понадобится библиотека requests.

Если вы совсем не знакомы с Python, то под спойлером краткая инструкция по настройке окружения.

Как настроить окружение

Для начала установите MiniConda или более тяжеловесную Anaconda, если вам нужен GUI и пакеты для Data Science.

Затем откройте командную строку и введите команду создания окружения для проекта wedding c версией Python 3.10

conda create --name wedding python=3.10

Когда вас спросят, согласны ли вы установить пакеты, напечатайте “y” и нажмите Enter

Чтобы активировать окружение, введите команду

conda activate wedding

Установим библиотеку requests, для отправки запросов к API.

conda install requests

Чтобы деактивировать окружение, введите команду

conda deactivate

Алгоритм и скелет скрипта

Еще раз вспомним общий план работ.

Мы постараемся сделать всё максимально просто, поэтому не будем чертить сложные диаграммы и ограничимся диаграммой с алгоритмом. 

Алгоритм большой, поэтому спрячу его под спойлер

Алгоритм

Как видно из алгоритма, в скрипте есть три циклических блока, их мы вынесем в отдельные функции и будем последовательно вызывать в корне скрипта.

Мы будем разбирать код по частям. 

Если вы опытный разработчик, можете посмотреть листинг целиком на GitHub. Также для удобства в конце раздела я приведу полный листинг и повторю ссылку на GitHub.

import requests
import csv
import argparse
…

Для начала импортируем необходимые библиотеки.

# Set variables
# They uses only in send_request function and placed here for easier setup
# Don't forget to paste your MTS EXolve API token and arended phone bellow
token = "Type your API token here"
arended_phone = "Type here arended MTS Exolve phone"
message_id="Type here voice message ID"


# change this vars if API path will changed
base_url = "https://api.exolve.ru"
urls={
   "send_voice":f"{base_url}/voice-message/v1/MakeVoiceMessage",
   "get_info":f"{base_url}/call/v1/GetInfo",
   "send_sms":f"{base_url}/SendSMS"
   }
head = f"Authorization: Bearer {token}"

Затем определим глобальные переменные.

На самом деле мы используем эти переменные всего в одной функции, send_request.

И вполне разумно разместить данные переменные в ней. Но я решил для простоты использования вынести их в начало скрипта.

В принципе, чтобы проверить свое приложение МТС Exolve, вам нужно только вставить в поле token вместо текста «Type your API token here» ваш API-токен. В поле «arended_phone» —  арендованный номер телефона. И напоследок, аналогично id голосового сообщения. Остальные настройки оставить без изменений.

Далее мы разобьем код по функциям, которые будут выполнять один из ключевых этапов:

  • read_csv – чтение файла со списком телефонов;

  • send_request – вспомогательная функция для отправки запросов;

  • send_voice_messages отправка голосовых сообщений по списку;

  • check_listened – проверка статуса сообщений по списку;

  • send_sms  отправка SMS по списку.

В языке Python функции должны быть определены до их вызова, однако финальный вызов цепочки функций мы разместим в конце скрипта. Поэтому в листинге мы пока пропустим часть кода, обозначив её как  “...”.

# …
if __name__ == '__main__':
   # parse path to csv file (deault - guest_list.csv.csv)
   parser = argparse.ArgumentParser()
   parser.add_argument('--inpath', type=str, required=False, default='guest_list.csv')   
   print ("start")
   args = parser.parse_args()
   # read csv
   guests_list= read_csv(args.inpath)
   # send voice
   success_voice_list,error_voice_list=send_voice_messages(guests_list)
   # Pause 30 seconds
   time.sleep(30)   
   # Checking who listened voice message
   listen_list, need_sms_list = check_listened (success_voice_list)
   # send SMS for guests whom not listened voice message
   success_sms_list, error_sms_list = send_sms(guests_list, need_sms_list)
   # print results
   print (f"Всего приглашено {(len(success_sms_list)+len(listen_list))} гостей")
   print (f"Не смогли пригласить:", [*error_voice_list, *error_sms_list] )

В данном фрагменте, мы сначала обрабатываем параметры вызова скрипта.
Мы можем изменить название файла по умолчанию, прописав при запуске путь в параметре inpath.

python3 send_voice.py --inpath=guest_list2.csv

Затем мы последовательно вызываем функции:

  • чтения списка гостей –  read_csv(args.inpath);

  • отправки голосовых сообщений – end_voice_messages(guests_list);

  • задержки – делаем паузу на 30 секунд, чтобы сообщение прослушали;

  • проверки гостей, прослушавших сообщение – send_sms(guests_list, need_sms_list)

  • отправки SMS тем, кто не послушал – send_sms(guests_list, need_sms_list);

  • вывода результатов на экран распечатки результатов –  функцией print.

Обрабатываем список рассылки

def read_csv(in_filepath):
   """
   Read data from csv with columns [id, name, phone,] \r \n
   in_filepath - path to csv \r \n
   """
   guests_list =[]
   with open(in_filepath, 'r', encoding='utf-8-sig', newline='') as infile:
       reader = csv.reader(infile)
       next(reader, None)  # skip the headers
       for item in reader:
           if (item[0], item[2]):
               guests_list.append(item)
           else:
               print(["Error: not correct data", item ] )
   return guests_list

Эта функция достаточно проста. Мы открываем для чтения файл с помощью open.  Затем считываем csv-файл. Пропускаем строку заголовков next(reader, None).

Затем циклически считываем остальные строки. Если в строке есть id и номер телефона, записываем её в список guests_list. Если нет, то выводим сообщение об ошибке.

Отправляем голосовые сообщения

Прежде чем перейти к функции отправки сообщений, рассмотрим вспомогательную функцию send_request.

def send_request(type,body):
   """
   Sending request to MTS Exolve Api \r \n
   type - api method for request (see urls keys above) \r \n
   body - payload for POST method
   """
   result="";
   if type in urls:
       url = urls[type]
       payload = json.dumps(body)
       response = requests.request("POST", url, headers=headers, data=payload)
       result = response.json()
   else:
       print("Unknown request type")
   return result

Функция принимает на вход тип запроса к API (равен ключу в словаре urls) и непосредственно тело запроса (как в документации). 

В блоке if мы проверяем, есть ли такой type в словаре urls.

Если все хорошо, то в итоге мы отправим POST-запрос к API. Затем получим ответ и запишем его как объект в переменную result

Если тип запроса указан неверно, то выведем на экран ошибку. Вспомогательную функцию send_request мы будем использовать во всех трех наших основных функциях.

Хоть я и не жую жвачку, но…

Поэтому пора переходить к самому «вкусному».

def send_voice_messages(guests_list):
   """
   Sending voice messages by MTS Exolve Api \r \n
   guests_list - list with next structure [id, name, phone] \r \n
   """
   success_voice_list = [];
   error_voice_list = [];
   for guest in guests_list:
       body = {
           "source": f"{arended_phone}",
           "destination": f"{guest[2]}",
           "service_id": f"{message_id}"
       }
       result= send_request("send_voice",body)
       if ( "call_id" in result):
           success_voice_list.append([guest[0],result["call_id"]])
       else:
           error_voice_list.append(guest)
   return success_voice_list, error_voice_list

В этой функции мы пробежимся по каждой записи в списке с гостями и с помощью функции send_request отправляем запрос к методу API – MakeVoiceMessage. Прямо как ранее в Postman. Если все прошло успешно, то пользователю поступит звонок, а нам вернется в ответ идентификатор голосового сообщения. Мы добавим его в список success_voice_list. Если идентификатор не вернется, мы занесем запись о госте в список ошибок.

Проверяем кто прослушал, а кто нет

Дадим нашим гостям, 10 секунд вызвав time.sleep(10) перед функцией check_listened.

А потом проверим, кто из наших гостей не очень сильно ждет звонка-приглашения.

def check_listened(success_voice_list):
   """
   Checking status of voice messages by MTS Exolve Api \r \n
   success_voice_list - list of previous success sended voice messages \r \n
   """
   listen_list  = [];
   need_sms_list = [];
   for voice in success_voice_list:
       body = {
           "call_id": f"{voice[1]}"
       }
       result= send_request("send_voice",body)
       if ( "status" in result):
           if  send_request["status"]=="completed":
               listen_list.append(voice[0])
       else:
           need_sms_list.append(voice[0])
   return listen_list, need_sms_list

В целом код функции похож на то, что мы рассмотрели раньше. 

Для всех успешных звонков мы проверяем вначале сам факт успешного ответа метода GetInfo. Если нам пришло в ответе поле status, значит все отработало успешно. Затем мы проверяем, что статус равен «completed», в таком случае гость точно прослушал приглашение. Во всех остальных случаях мы записываем id гостя в список тех, кому надо отправить SMS.

Отправляем SMS тем, кто не послушал

Осталось разослать SMS тем, кто не отвечает на незнакомые номера.

def send_sms(guests_list, need_sms_list):
   """
   Sending SMS by MTS Exolve Api \r \n
   guests_list - list with next structure [id, name, phone] \r \n
   need_sms_list - list with ids guests whom we will sent SMS \r \n
   """
   success_sms_list = [];
   error_sms_list = [];
   for guest in guests_list:
       for id in need_sms_list:
           if int(guest[0]) == int(id):
               body = {
                   "number": f"{arended_phone}",
                   "destination": f"{guest[2]}",
                   "text": f"{guest[1]} ждем на нашу свадьбу! Таня + Володя"
               }
               result= send_request("send_sms",body)
               if ( "message_id" in result):
                   success_sms_list.append(guest)
               else:
                   error_sms_list.append(guest)
               break
   return success_sms_list, error_sms_list

В данном методе код немного сложнее. Но общая логика вам уже знакома. 

В этот раз у нас в одном методе присутствует цикл внутри цикла.

Вначале мы проходим по всему списку гостей, который получили из файла .csv.

Затем сопоставляем id гостя из списка и id, который не послушал голосовое сообщение. Если id совпадают, вызываем метод для отправки SMS.

Как и обещал, дублирую в конце полный листинг скрипта, а также ссылку на GitHub.

Весь код
# send_voice.py - script for sending voice message from MTS EXOLVE


import requests
import csv
import argparse
import json
import time




# Set variables
# They uses only in send_request function and placed here for easier setup
# Don't forget to paste your MTS EXolve API token, arended phone, and message id bellow
token = "Type your API token here"
arended_phone = "Type here arended MTS Exolve phone"
message_id="Type here voice message ID"
# change this vars if API path will changed
base_url = "https://api.exolve.ru"
urls={
   "send_voice":f"{base_url}/call/v1/MakeVoiceMessage",
   "get_info":f"{base_url}/call/v1/GetInfo",
   "send_sms":f"{base_url}/messaging/v1/SendSMS"
   }
headers = {
   "Content-Type": "application/json",
   "Authorization": f"Bearer {token}"
}


def read_csv(in_filepath):
   """
   Read data from csv with columns [id, name, phone] \r \n
   in_filepath - path to csv \r \n
   """
   guests_list =[]
   with open(in_filepath, 'r', encoding='utf-8-sig', newline='') as infile:
       reader = csv.reader(infile)
       next(reader, None)  # skip the headers
       for item in reader:
           if (item[0], item[2]):
               guests_list.append(item)
           else:
               print(["Error: not correct data", item ] )
   return guests_list




def send_request(type,body):
   """
   Sending request to MTS Exolve Api \r \n
   type - api method for request (see urls keys above) \r \n
   body - payload for POST method
   """
   result="";
   if type in urls:
       url = urls[type]
       payload = json.dumps(body)
       response = requests.request("POST", url, headers=headers, data=payload)
       result = response.json()
   else:
       print("Unknown request type")
   return result


def send_voice_messages(guests_list):
   """
   Sending voice messages by MTS Exolve Api \r \n
   guests_list - list with next structure [id, name, phone] \r \n
   """
   success_voice_list = [];
   error_voice_list = [];
   for guest in guests_list:
       body = {
           "source": f"{arended_phone}",
           "destination": f"{guest[2]}",
           "service_id": f"{message_id}"
       }
       result= send_request("send_voice",body)
       if ( "call_id" in result):
           success_voice_list.append([guest[0],result["call_id"]])
       else:
           error_voice_list.append(guest)
   return success_voice_list, error_voice_list


def check_listened(success_voice_list):
   """
   Checking status of voice messages by MTS Exolve Api \r \n
   success_voice_list - list of previous success sended voice messages \r \n
   """
   listen_list  = [];
   need_sms_list = [];
   for voice in success_voice_list:
       body = {
           "call_id": f"{voice[1]}"
       }
       result= send_request("send_voice",body)
       if ( "status" in result):
           if  send_request["status"]=="completed":
               listen_list.append(voice[0])
       else:
           need_sms_list.append(voice[0])
   return listen_list, need_sms_list


def send_sms(guests_list, need_sms_list):
   """
   Sending SMS by MTS Exolve Api \r \n
   guests_list - list with next structure [id, name, phone] \r \n
   need_sms_list - list with ids guests whom we will sent SMS \r \n
   """
   success_sms_list = [];
   error_sms_list = [];
   for guest in guests_list:
       for id in need_sms_list:
           if int(guest[0]) == int(id):
               body = {
                   "number": f"{arended_phone}",
                   "destination": f"{guest[2]}",
                   "text": f"{guest[1]} ждем на нашу свадьбу! Таня + Володя"
               }
               result= send_request("send_sms",body)
               if ( "message_id" in result):
                   success_sms_list.append(guest)
               else:
                   error_sms_list.append(guest)
               break
   return success_sms_list, error_sms_list


if __name__ == '__main__':
   # parse path to csv file (deault - guest_list.csv.csv)
   parser = argparse.ArgumentParser()
   parser.add_argument('--inpath', type=str, required=False, default='guest_list.csv')   
   print ("start")
   args = parser.parse_args()
   # read csv
   guests_list= read_csv(args.inpath)
   # send voice
   success_voice_list,error_voice_list=send_voice_messages(guests_list)
   # Pause 10 seconds
   time.sleep(30)   
   # Checking who listened voice message
   listen_list, need_sms_list = check_listened (success_voice_list)
   # send SMS for guests whom not listened voice message
   success_sms_list, error_sms_list = send_sms(guests_list, need_sms_list)
   # print results
   print (f"Всего приглашено {(len(success_sms_list)+len(listen_list))} гостей")
   print (f"Не смогли пригласить:", [*error_voice_list, *error_sms_list] )

Проверяем работу скрипта

Я использовал следующий csv-файл:

Пример csv-файла
Пример csv-файла

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

Имена и номера в примере само собой выдуманные (все совпадения случайны).
Но для наглядности, чтобы получить результат как в данной статье, я советую в п.п. 1 и 4 указать свой номер, чтобы на него точно поступили звонок и SMS. А вот в п.п. 2 и 3 указать фиктивные номера, чтобы проверить вариант неудачной отправки.

Открываем консоль, переходим в папку, в которой лежит скрипт, и запускаем его командой python3 send_voice.py.

Ждем звонка с голосовым сообщением.

Экран приложения для звонков
Экран приложения для звонков

.

А вот второй звонок мне поступить уже не успеет, поэтому по нему мне придёт SMS.

SMS поступило
SMS поступило

Проверяем итоговый результат:

Вывод итогового результата в консоль
Вывод итогового результата в консоль

Ура! Всё работает как запланировано.

Заключение

Сегодня мы успешно и с пользой попробовали функции SMS и голосовых сообщений МТС Exolve. Я специально упрощал скрипт, чтобы его понимали люди, начинающие свой путь в освоении языков программирования. Вы безусловно можете улучшить код, например добавив в него сохранение промежуточных или итоговых отчетов в csv, обработку ошибок, а также разнообразив шаблон SMS-сообщения в зависимости от имени гостя, например, распознавая несколько имен в одном поле.

Позже хочется попробовать перевод текста в речь (TTS), чтобы реализовать генерацию голосовых сообщений «на лету».

Спасибо, что уделили время материалу! Буду рад прочитать в комментариях обратную связь, а также ваши идеи о том, как еще можно применить голосовые сообщения.

Комментарии (0)