Skip to content

Instantly share code, notes, and snippets.

@satels
Last active September 3, 2018 11:59
Show Gist options
  • Save satels/a0a0e5612588248f3659e94d75a6bf24 to your computer and use it in GitHub Desktop.
Save satels/a0a0e5612588248f3659e94d75a6bf24 to your computer and use it in GitHub Desktop.
Языки программирования: Python, Ruby. Создание звонка через API calltools.ru и получение результата на PostBack url
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')
require 'httpclient'
require 'json'
require 'symbolize_keys_recursively'
# CallToolsApi
#
# @author Sergey Blohin
# @email [email protected]
# @see https://calltools.ru/
module CallToolsApi
# Client
#
# @see https://calltools.ru/guide_api
class Client
# @return [String] CallTools API Version
attr_reader :api_version
# Create CallTools API Client Object
#
# @see https://calltools.ru/lk/users/profile/
#
# @param [String] api_public_key
def initialize(api_public_key)
@api_version = 'v1'.freeze
@base_url = "https://calltools.ru/lk/cabapi_external/api/#{api_version}".freeze
@api_public_key = api_public_key
end
# Add Call To Campaign
#
# @see https://calltools.ru/lk/phones/all/
# @see https://calltools.ru/lk/users/profile/
#
# @param [Integer] campaign_id
# @param [String] phone_number international format +79185274526
# @return [Hash] :balance, :call_id, :created, :phone
def add_call(campaign_id, phone_number)
url = "#{@base_url}/phones/call/".freeze
post_parameters = {
public_key: @api_public_key,
campaign_id: campaign_id,
phone: phone_number
}.freeze
response = HTTPClient.post url, post_parameters
response_validator response
end
# Add Call With Clip Generation
#
# @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/
#
# @param [Integer] campaign_id
# @param [String] phone_number international format +79185274526
# @param [String] text generated by voice clip, you can also use special markup
# @param [String] speaker voice, if not specified, use default from campaign settings
#
# @return [Hash] :call_id, :phone, :balance, :audioclip_id, :created, :callerid
def add_call_with_clip_generation(campaign_id, phone_number, text, speaker = nil)
url = "#{@base_url}/phones/call/".freeze
post_parameters = {
public_key: @api_public_key,
campaign_id: campaign_id,
phone: phone_number,
text: text,
speaker: speaker
}.freeze
response = HTTPClient.post url, post_parameters
response_validator response
end
# Result by phone number
#
# @see https://calltools.ru/lk/phones/all/
# @see https://calltools.ru/lk/users/profile/
#
# @param [Integer] campaign_id
# @param [String] phone_number 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>]
def calls_result_by_phone(campaign_id, phone_number, date = {})
url = "#{@base_url}/phones/calls_by_phone/".freeze
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]
}
response = HTTPClient.get url, parameters
response_validator response
end
# Result By `call_id`
#
# @see #add_call
# @see #add_call_with_clip_generation
#
# @param [Integer] call_id
#
# @return [Array <Hash>]
def calls_result_by_call_id(call_id)
url = "#{@base_url}/phones/call_by_id/".freeze
parameters = {
public_key: @api_public_key,
call_id: call_id
}.freeze
response = HTTPClient.get url, parameters
response_validator response
end
# Remove Call By `phone_number`
#
# @see https://calltools.ru/lk/phones/all/
# @see https://calltools.ru/lk/users/profile/
#
# @param [Integer] campaign_id
# @param [String] phone_number international format +79185274526
#
# @return [Hash] :call_id, :completed, :phone
def remove_call_by_phone_number(campaign_id, phone_number)
url = "#{@base_url}/phones/remove_call/".freeze
parameters = {
public_key: @api_public_key,
campaign_id: campaign_id,
phone: phone_number
}.freeze
response = HTTPClient.post url, parameters
response_validator response
end
# Remove Call By `call_id`
#
# @see https://calltools.ru/lk/phones/all/
# @see https://calltools.ru/lk/users/profile/
# @see #add_call
# @see #add_call_with_clip_generation
#
# @param [Integer] campaign_id
# @param [Integer] call_id
# @return [Hash] :call_id, :completed, :phone
def remove_call_by_call_id(campaign_id, call_id)
url = "#{@base_url}/phones/remove_call/".freeze
parameters = {
public_key: @api_public_key,
campaign_id: campaign_id,
call_id: call_id
}.freeze
response = HTTPClient.post url, parameters
response_validator response
end
# Get User Balance
#
# @return [Hash] :balance
def balance
get_url = "#{@base_url}/users/balance/".freeze
get_parameters = { public_key: @api_public_key }.freeze
response = HTTPClient.get get_url, get_parameters
response_validator response
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, :url
def upload_audio(clip_name, clip_filename, speaker = nil, text = nil)
url = "#{@base_url}/audio/upload/".freeze
parameters = {
public_key: @api_public_key,
clip_name: clip_name,
clip_file: File.open(clip_filename),
speaker: speaker,
text: text
}.freeze
response = HTTPClient.post url, parameters
response_validator response
end
# Definition Of The Region And Time Zone (Timezone) by `phone_number`
#
# @see https://calltools.ru/lk/phones/all/
#
# @param [String] phone_number international format +79185274526
#
# @return [Hash]
def time_zone_by_phone_number(phone_number)
url = "#{@base_url}/def_codes/by_phone/".freeze
parameters = { phone: phone_number }.freeze
response = HTTPClient.get url, parameters
response_validator response
end
private
# Check http.code is 200 and http.body is JSON
#
# @param [HTTP::Message] response
#
# @return [Hash] http.body as JSON
def response_validator(response)
status = response.status
source_body = response.body
# if http.code is not 200
# raise code
raise status.to_s unless status == 200
# if http.body is not JSON
# raise source http.body
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