Last active
September 3, 2018 11:59
-
-
Save satels/a0a0e5612588248f3659e94d75a6bf24 to your computer and use it in GitHub Desktop.
Языки программирования: Python, Ruby. Создание звонка через API calltools.ru и получение результата на PostBack url
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import requests | |
# Полная и актуальная документация по API: https://calltools.ru/guide_api | |
CALLTOOLS_PUBLIC_KEY = 'test_public_key' | |
CALLTOOLS_BASE_URL = 'https://calltools.ru' | |
CALLTOOLS_TIMEOUT = 30 | |
class CallToolsException(Exception): | |
pass | |
def create_call(campaign_id, phonenumber, text=None, speaker='Tatyana'): | |
''' | |
Создание звонка на прозвон с генерацией ролика | |
:type campaign_id: int | |
:type phonenumber: str | |
:type text: str|None | |
:type speaker: str | |
:rtype: dict | |
''' | |
resp = requests.get(CALLTOOLS_BASE_URL + '/lk/cabapi_external/api/v1/phones/call/', { | |
'public_key': CALLTOOLS_PUBLIC_KEY, | |
'phone': phonenumber, | |
'campaign_id': campaign_id, | |
'text': text, | |
'speaker': speaker, | |
}, timeout=CALLTOOLS_TIMEOUT) | |
ret = resp.json() | |
if ret['status'] == 'error': | |
raise CallToolsException(ret['data']) | |
return ret | |
def check_status(campaign_id, phonenumber=None, call_id=None, | |
from_created_date=None, to_created_date=None, | |
from_updated_date=None, to_updated_date=None): | |
''' | |
Получение статуса звонка по номеру телефона или по call_id (ID звонка) | |
:type campaign_id: int | |
:type phonenumber: str|None | |
:type call_id: int|None | |
:type from_created_date: str|None | |
:type to_created_date: str|None | |
:type from_updated_date: str|None | |
:type to_updated_date: str|None | |
:rtype: dict | |
''' | |
if phonenumber: | |
url = '/lk/cabapi_external/api/v1/phones/calls_by_phone/' | |
elif call_id: | |
url = '/lk/cabapi_external/api/v1/phones/call_by_id/' | |
else: | |
raise ValueError('check_status required call_id or phonenumber') | |
resp = requests.get(CALLTOOLS_BASE_URL + url, { | |
'public_key': CALLTOOLS_PUBLIC_KEY, | |
'phone': phonenumber, | |
'call_id': call_id, | |
'campaign_id': campaign_id, | |
'from_created_date': from_created_date, | |
'to_created_date': to_created_date, | |
'from_updated_date': from_updated_date, | |
'to_updated_date': to_updated_date, | |
}, timeout=CALLTOOLS_TIMEOUT) | |
ret = resp.json() | |
if ret['status'] == 'error': | |
raise CallToolsException(ret['data']) | |
return ret | |
def remove_call(campaign_id, phonenumber=None, call_id=None): | |
''' | |
Удаление номера из прозвона | |
:type campaign_id: int | |
:type phonenumber: str|None | |
:type call_id: int|None | |
:rtype: dict | |
''' | |
if not phonenumber and not call_id: | |
raise ValueError('remove_call required call_id or phonenumber') | |
resp = requests.get(CALLTOOLS_BASE_URL + '/lk/cabapi_external/api/v1/phones/remove_call/', { | |
'public_key': CALLTOOLS_PUBLIC_KEY, | |
'phone': phonenumber, | |
'call_id': call_id, | |
'campaign_id': campaign_id, | |
}, timeout=CALLTOOLS_TIMEOUT) | |
ret = resp.json() | |
if ret['status'] == 'error': | |
raise CallToolsException(ret['data']) | |
return ret | |
# Статусы звонка | |
ATTEMPTS_EXCESS_STATUS = 'attempts_exc' | |
USER_CUSTOM_STATUS = 'user' | |
NOVALID_BUTTON_STATUS = 'novalid_button' | |
COMPLETE_FINISHED_STATUS = 'compl_finished' | |
COMPLETE_NOFINISHED_STATUS = 'compl_nofinished' | |
DELETED_CALL_STATUS = 'deleted' | |
IN_PROCESS_STATUS = 'in_process' | |
HUMAN_STATUSES = [ | |
(IN_PROCESS_STATUS, 'В процессе'), | |
(USER_CUSTOM_STATUS, 'Пользовательский IVR'), | |
(ATTEMPTS_EXCESS_STATUS, 'Попытки закончились'), | |
(COMPLETE_NOFINISHED_STATUS, 'Некорректный ответ'), | |
(COMPLETE_FINISHED_STATUS, 'Закончен удачно'), | |
(NOVALID_BUTTON_STATUS, 'Невалидная кнопка'), | |
(DELETED_CALL_STATUS, 'Удалён из прозвона'), | |
] | |
# Статусы дозвона | |
DIAL_STATUS_WAIT = 0 | |
DIAL_STATUS_FAILED = 1 | |
DIAL_STATUS_HANGUP = 2 | |
DIAL_STATUS_RING_TIMEOUT = 3 | |
DIAL_STATUS_BUSY = 4 | |
DIAL_STATUS_ANSWER = 5 | |
DIAL_STATUS_ROBOT1 = 6 | |
DIAL_STATUS_ROBOT2 = 7 | |
DIAL_STATUS_NOVALID_BTN = 8 | |
DIAL_STATUS_UNKNOWN = 9 | |
DIAL_STATUS_WED = 10 | |
DIAL_STATUS_USERSTOPLIST = 11 | |
DIAL_STATUS_GLOBALSTOPLIST = 12 | |
DIAL_STATUS_WED_WAIT = 13 | |
DIAL_STATUS_ITSELF_EXC = 14 | |
DIAL_STATUS_REMOVE = 15 | |
DIAL_STATUSES = [ | |
(DIAL_STATUS_WAIT, 'Ожидание вызова (звонка еще нет)'), | |
(DIAL_STATUS_FAILED, 'Ошибка при вызове абонента'), | |
(DIAL_STATUS_HANGUP, 'Абонент сбросил звонок'), | |
(DIAL_STATUS_RING_TIMEOUT, 'Не дозвонились'), | |
(DIAL_STATUS_BUSY, 'Абонент занят'), | |
(DIAL_STATUS_ANSWER, 'Абонент ответил'), | |
(DIAL_STATUS_ROBOT1, 'Ответил автоответчик'), | |
(DIAL_STATUS_ROBOT2, 'Ответил автоответчик'), | |
(DIAL_STATUS_NOVALID_BTN, 'Невалидная кнопка'), | |
(DIAL_STATUS_WED, 'Завершен без действия клиента'), | |
(DIAL_STATUS_UNKNOWN, 'Неизвестный статус'), | |
(DIAL_STATUS_USERSTOPLIST, 'Пользовательский стоп-лист'), | |
(DIAL_STATUS_GLOBALSTOPLIST, 'Глобальный стоп-лист'), | |
(DIAL_STATUS_WED_WAIT, 'Абонент ответил, но продолжительности разговора не достаточно для фиксации результата в статистике'), | |
(DIAL_STATUS_ITSELF_EXC, 'Номер абонента совпадает с CallerID'), | |
(DIAL_STATUS_REMOVE, 'Номер удалён из прозвона'), | |
] | |
# Примеры использование postback в django: | |
class PostBackForm(forms.Form): | |
ct_campaign_id = forms.IntegerField() | |
ct_phone = PhoneNumberField() # See https://github.com/stefanfoulis/django-phonenumber-field | |
ct_call_id = forms.IntegerField() | |
ct_completed = forms.DateTimeField(required=False) | |
ct_status = forms.CharField(required=False) | |
ct_dial_status = forms.IntegerField() | |
ct_button_num = forms.IntegerField(required=False) | |
ct_duration = forms.FloatField(required=False) | |
def calltools_postback(request): | |
form = PostBackForm(request.GET or None) | |
if form.is_valid(): | |
CallToolsPostBackResult.objects.create(**form.cleaned_data) | |
return HttpResponse('OK') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# http-cookie need for Max-Age | |
# @see https://github.com/nahi/httpclient/issues/242#issuecomment-69020815 | |
require 'http-cookie' | |
require 'httpclient' | |
require 'json' | |
require 'symbolize_keys_recursively' | |
# CallTools | |
module CallTools | |
# CallTools API Client | |
# | |
# @author Sergey Blohin ([email protected]) | |
# | |
# @see https://calltools.ru/ | |
# @see https://calltools.ru/guide_api/ | |
# @see https://calltools.ru/lk/audioclips/all-speakers/ | |
# @see https://calltools.ru/lk/pages/synth-valid-text/ | |
# @see https://calltools.ru/lk/phones/all/ | |
# @see https://calltools.ru/lk/users/profile/ | |
# | |
# @example | |
# api_public_key = 'a1d7352426832e77c2a9f55e01106f1a' | |
# client = CallTools::Client.new api_public_key | |
# | |
# # add phone number to campaign | |
# # and return call_id | |
# campaign_id = 1_234_567_890 | |
# phone_number = '+79185274526' | |
# call = client.add_call campaign_id, phone_number | |
# call_id = call[:call_id] | |
# | |
# # get call status by call_id | |
# call_result = client.call_result_by_call_id call_id | |
# call_status = call_result[:status] | |
# | |
# # remove phone number form campaign | |
# # if call status is `in_process` | |
# if call_status == 'is_process' | |
# client.remove_call_by_call_id campaign_id, phone_number | |
# end | |
class Client | |
# API Version | |
API_VERSION = 'v1'.freeze | |
public_constant :API_VERSION | |
# Base URL | |
BASE_URL = "https://calltools.ru/lk/cabapi_external/api/#{API_VERSION}".freeze | |
private_constant :BASE_URL | |
# HTTP STATUS OK | |
HTTP_STATUS_OK = 200 | |
private_constant :HTTP_STATUS_OK | |
# Path Audio Upload | |
PATH_AUDIO_UPLOAD = '/audio/upload/'.freeze | |
private_constant :PATH_AUDIO_UPLOAD | |
# Path Def Codes By Phone | |
PATH_DEF_CODES_BY_PHONE = '/def_codes/by_phone/'.freeze | |
private_constant :PATH_DEF_CODES_BY_PHONE | |
# Path Phones Call | |
PATH_PHONES_CALL = '/phones/call/'.freeze | |
private_constant :PATH_PHONES_CALL | |
# Path Phones Call By ID | |
PATH_PHONES_CALL_BY_ID = '/phones/call_by_id/'.freeze | |
private_constant :PATH_PHONES_CALL_BY_ID | |
# Path Phones Calls By Phone | |
PATH_PHONES_CALLS_BY_PHONE = '/phones/calls_by_phone/'.freeze | |
private_constant :PATH_PHONES_CALLS_BY_PHONE | |
# Path Phones Remove Call | |
PATH_PHONES_REMOVE_CALL = '/phones/remove_call/'.freeze | |
private_constant :PATH_PHONES_REMOVE_CALL | |
# Path Users Balance | |
PATH_USERS_BALANCE = '/users/balance/'.freeze | |
private_constant :PATH_USERS_BALANCE | |
# @param [String] api_public_key | |
# @example | |
# .new(a1d7352426832e77c2a9f55e01106f1a) | |
def initialize(api_public_key) | |
@api_public_key = api_public_key | |
end | |
# Add Call To Campaign | |
# | |
# @param [Integer] campaign_id | |
# @param [String] phone_number phone_number in international format +79185274526 | |
# @return [Hash] :balance, :call_id, :created, :phone | |
# @example | |
# .add_call(1234567890, '+79185274526') | |
def add_call(campaign_id, phone_number) | |
parameters = { | |
public_key: @api_public_key, | |
campaign_id: campaign_id, | |
phone: phone_number | |
} | |
post PATH_PHONES_CALL, parameters | |
end | |
# Add Call With Clip Generation | |
# | |
# @param [Integer] campaign_id | |
# @param [String] phone_number phone_number in international format +79185274526 | |
# @param [String] text text generated by voice clip, you can also use special markup | |
# @param [String] speaker speaker voice, if not specified, use default from campaign settings | |
# @return [Hash] :call_id, :phone, :balance, :audioclip_id, :created, :callerid | |
# @example | |
# .add_call_with_clip_generation(1234567890, '+79185274526', 'Hello, Kitty', 'Tatyana') | |
def add_call_with_clip_generation(campaign_id, phone_number, text, speaker = nil) | |
parameters = { | |
public_key: @api_public_key, | |
campaign_id: campaign_id, | |
phone: phone_number, | |
text: text, | |
speaker: speaker | |
} | |
post PATH_PHONES_CALL, parameters | |
end | |
# Result by phone number | |
# | |
# @param [Integer] campaign_id | |
# @param [String] phone_number phone_number in international format +79185274526 | |
# @param [Hash] date format is YYYY-MM-DD HH:MM:SS | |
# @option data [String] from_date_created | |
# @option data [String] from_date_created | |
# @option data [String] to_date_created | |
# @option data [String] from_date_updated | |
# @option data [String] to_date_updated | |
# @return [Array <Hash>] | |
# @example | |
# .calls_result_by_phone(1234567890, '+79185274526') | |
def calls_result_by_phone(campaign_id, phone_number, date = {}) | |
parameters = { | |
public_key: @api_public_key, | |
campaign_id: campaign_id, | |
phone: phone_number, | |
from_created_date: date[:from_date_created], | |
to_created_date: date[:to_date_created], | |
from_updated_date: date[:from_date_updated], | |
to_updated_date: date[:to_date_updated] | |
} | |
get PATH_PHONES_CALLS_BY_PHONE, parameters | |
end | |
# Result By `call_id` | |
# | |
# @see #add_call | |
# @see #add_call_with_clip_generation | |
# @param [Integer] call_id | |
# @return [Hash] | |
# @example | |
# .call_result_by_call_id(1234567890) | |
def call_result_by_call_id(call_id) | |
parameters = { | |
public_key: @api_public_key, | |
call_id: call_id | |
} | |
response = get PATH_PHONES_CALL_BY_ID, parameters | |
response.first | |
end | |
# Remove Call By `phone_number` | |
# | |
# @param [Integer] campaign_id | |
# @param [String] phone_number phone_number in international format +79185274526 | |
# @return [Hash] :call_id, :completed, :phone | |
# @example | |
# .remove_call_by_phone_number(1234567890, '+79185274526') | |
def remove_call_by_phone_number(campaign_id, phone_number) | |
parameters = { | |
public_key: @api_public_key, | |
campaign_id: campaign_id, | |
phone: phone_number | |
} | |
post PATH_PHONES_REMOVE_CALL, parameters | |
end | |
# Remove Call By `call_id` | |
# | |
# @see #add_call | |
# @see #add_call_with_clip_generation | |
# @param [Integer] campaign_id | |
# @param [Integer] call_id | |
# @return [Hash] :call_id, :completed, :phone | |
# @example | |
# .remove_call_by_call_id(1234567890, 1234567890) | |
def remove_call_by_call_id(campaign_id, call_id) | |
parameters = { | |
public_key: @api_public_key, | |
campaign_id: campaign_id, | |
call_id: call_id | |
} | |
post PATH_PHONES_REMOVE_CALL, parameters | |
end | |
# Get User Balance | |
# | |
# @param [TrueClass, FalseClass] accuracy | |
# @return [Integer, Float] | |
# @example | |
# .balance(true) #=> 42.666 | |
# .balance(false) #=> 42 | |
def balance(accuracy = false) | |
parameters = { public_key: @api_public_key } | |
response = get PATH_USERS_BALANCE, parameters | |
balance = response[:balance] | |
accuracy ? balance.to_f : balance.to_i | |
end | |
# Upload Audiofile | |
# | |
# @param [String] clip_name base filename or clip title | |
# @param [String] clip_filename full path to filename | |
# @param [String] speaker speaker name | |
# @param [String] text clip description | |
# @return [Hash] :length, :audioclip_id, :path | |
# @example | |
# .upload_audio('welcome_to_hell', '/path/to/filename.mp3', 'Maria', 'Welcome To Hell') | |
def upload_audio(clip_name, clip_filename, speaker = nil, text = nil) | |
parameters = { | |
public_key: @api_public_key, | |
clip_name: clip_name, | |
clip_file: File.open(clip_filename), | |
speaker: speaker, | |
text: text | |
} | |
post PATH_AUDIO_UPLOAD, parameters | |
end | |
# Definition Of The Region And Time Zone (Timezone) by `phone_number` | |
# | |
# @param [String] phone_number phone_number in international format +79185274526 | |
# @return [Hash] | |
# @example | |
# .time_zone_by_phone_number('+79185274526') | |
def time_zone_by_phone_number(phone_number) | |
parameters = { phone: phone_number } | |
get PATH_DEF_CODES_BY_PHONE, parameters | |
end | |
private | |
# Call GET Request | |
# | |
# @param [String] path | |
# @param [Hash, NilClass] parameters | |
# @return [Hash] | |
# @example | |
# .get('/method/name/', {foo: 42, bar: 'bar'}) | |
def get(path, parameters = nil) | |
request :get, path, parameters | |
end | |
# Call POST Request | |
# | |
# @param [String] path | |
# @param [Hash, NilClass] parameters | |
# @return [Hash] | |
# @example | |
# .post('/method/name/', {foo: 42, bar: 'bar'}) | |
def post(path, parameters = nil) | |
request :post, path, parameters | |
end | |
# Call Request | |
# | |
# @param [Symbol] method | |
# @param [String] path | |
# @param [Hash] parameters | |
# @return [Hash] | |
# @example | |
# .request(:get, '/method/name/', {foo: 42, bar: 'bar'}) | |
def request(method, path, parameters) | |
url = "#{BASE_URL}#{path}" | |
case method | |
when :get | |
response_validator HTTPClient.get url, parameters | |
when :post | |
response_validator HTTPClient.post url, parameters | |
else | |
raise method.to_s | |
end | |
end | |
# Check http.code is HTTP_STATUS_OK and http.body is JSON | |
# | |
# @param [HTTP::Message] response | |
# @return [Hash] http.body as JSON | |
# @example | |
# .response_validator(HTTPClient.get('http://httpbin.org/get')) | |
def response_validator(response) | |
status = response.status | |
source_body = response.body | |
# raise code if http.code is not HTTP_STATUS_OK | |
raise status.to_s unless status == HTTP_STATUS_OK | |
# raise source http.body if http.body is not JSON | |
begin | |
body = JSON.parse source_body | |
# { 'foo' => 42 } => { foo: 42 } | |
body.symbolize_keys_recursively! | |
rescue JSON::ParserError | |
raise source_body | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment