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.

Revisions

  1. satels revised this gist Sep 3, 2018. 1 changed file with 178 additions and 98 deletions.
    276 changes: 178 additions & 98 deletions calltoolsru_ruby.rb
    Original file line number Diff line number Diff line change
    @@ -1,93 +1,141 @@
    # 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'

    # CallToolsApi
    #
    # @author Sergey Blohin
    # @email [email protected]
    # @see https://calltools.ru/
    module CallToolsApi
    # Client
    # CallTools
    module CallTools
    # CallTools API Client
    #
    # @see https://calltools.ru/guide_api
    # @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
    # @return [String] CallTools API Version
    attr_reader :api_version
    # 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

    # Create CallTools API Client Object
    #
    # @see https://calltools.ru/lk/users/profile/
    #
    # @param [String] api_public_key
    # @example
    # .new(a1d7352426832e77c2a9f55e01106f1a)
    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
    # @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)
    url = "#{@base_url}/phones/call/".freeze
    post_parameters = {
    parameters = {
    public_key: @api_public_key,
    campaign_id: campaign_id,
    phone: phone_number
    }.freeze
    response = HTTPClient.post url, post_parameters
    response_validator response
    }
    post PATH_PHONES_CALL, parameters
    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
    #
    # @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)
    url = "#{@base_url}/phones/call/".freeze
    post_parameters = {
    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
    }
    post PATH_PHONES_CALL, parameters
    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 [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 = {})
    url = "#{@base_url}/phones/calls_by_phone/".freeze
    parameters = {
    public_key: @api_public_key,
    campaign_id: campaign_id,
    @@ -97,77 +145,72 @@ def calls_result_by_phone(campaign_id, phone_number, date = {})
    from_updated_date: date[:from_date_updated],
    to_updated_date: date[:to_date_updated]
    }
    response = HTTPClient.get url, parameters
    response_validator response
    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 [Array <Hash>]
    def calls_result_by_call_id(call_id)
    url = "#{@base_url}/phones/call_by_id/".freeze
    # @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
    }.freeze
    response = HTTPClient.get url, parameters
    response_validator response
    }
    response = get PATH_PHONES_CALL_BY_ID, parameters
    response.first
    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
    #
    # @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)
    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
    }
    post PATH_PHONES_REMOVE_CALL, parameters
    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
    # @example
    # .remove_call_by_call_id(1234567890, 1234567890)
    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
    }
    post PATH_PHONES_REMOVE_CALL, parameters
    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
    # @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
    @@ -176,52 +219,89 @@ def balance
    # @param [String] clip_filename full path to filename
    # @param [String] speaker speaker name
    # @param [String] text clip description
    #
    # @return [Hash] :length, :audioclip_id, :url
    # @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)
    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
    }
    post PATH_AUDIO_UPLOAD, parameters
    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
    #
    # @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)
    url = "#{@base_url}/def_codes/by_phone/".freeze
    parameters = { phone: phone_number }.freeze
    response = HTTPClient.get url, parameters
    response_validator response
    parameters = { phone: phone_number }
    get PATH_DEF_CODES_BY_PHONE, parameters
    end

    private

    # Check http.code is 200 and http.body is JSON
    # Call GET Request
    #
    # @param [HTTP::Message] response
    # @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

    # if http.code is not 200
    # raise code
    raise status.to_s unless status == 200
    # raise code if http.code is not HTTP_STATUS_OK
    raise status.to_s unless status == HTTP_STATUS_OK

    # if http.body is not JSON
    # raise source http.body
    # raise source http.body if http.body is not JSON
    begin
    body = JSON.parse source_body

  2. satels revised this gist Aug 28, 2018. No changes.
  3. satels revised this gist Aug 28, 2018. No changes.
  4. satels revised this gist Aug 28, 2018. 1 changed file with 235 additions and 0 deletions.
    235 changes: 235 additions & 0 deletions calltoolsru_ruby.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,235 @@
    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
  5. satels revised this gist Jul 12, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion calltoolsru_python.py
    Original file line number Diff line number Diff line change
    @@ -173,7 +173,7 @@ class PostBackForm(forms.Form):

    def calltools_postback(request):

    form = PostBackForm(request.GET or None):
    form = PostBackForm(request.GET or None)

    if form.is_valid():
    CallToolsPostBackResult.objects.create(**form.cleaned_data)
  6. satels revised this gist Dec 26, 2017. 1 changed file with 1 addition and 24 deletions.
    25 changes: 1 addition & 24 deletions calltoolsru_python.py
    Original file line number Diff line number Diff line change
    @@ -178,27 +178,4 @@ def calltools_postback(request):
    if form.is_valid():
    CallToolsPostBackResult.objects.create(**form.cleaned_data)

    return HttpResponse('OK')


    from phones.models import AtsPhoneCall, PhoneCall


    ats_calls = AtsPhoneCall.objects.filter(campaign_id=1180642991, campaign__user_id=292445898)


    uniq_phones = list(set(ats_calls.values_list('phone_id', flat=True)))

    to_remove = []
    already = set()
    for atscall in ats_calls.order_by('-ats_complete_date'):
    if atscall.phone_id in already:
    to_remove.append(atscall.id)
    continue
    already.add(atscall.phone_id)


    print(len(to_remove))

    PhoneCall.objects.filter(campaign_id=1180642991, campaign__user_id=292445898, ats_call_id__in=to_remove).delete()
    AtsPhoneCall.objects.filter(campaign_id=1180642991, campaign__user_id=292445898, id__in=to_remove).delete()
    return HttpResponse('OK')
  7. satels revised this gist Dec 25, 2017. 1 changed file with 24 additions and 1 deletion.
    25 changes: 24 additions & 1 deletion calltoolsru_python.py
    Original file line number Diff line number Diff line change
    @@ -178,4 +178,27 @@ def calltools_postback(request):
    if form.is_valid():
    CallToolsPostBackResult.objects.create(**form.cleaned_data)

    return HttpResponse('OK')
    return HttpResponse('OK')


    from phones.models import AtsPhoneCall, PhoneCall


    ats_calls = AtsPhoneCall.objects.filter(campaign_id=1180642991, campaign__user_id=292445898)


    uniq_phones = list(set(ats_calls.values_list('phone_id', flat=True)))

    to_remove = []
    already = set()
    for atscall in ats_calls.order_by('-ats_complete_date'):
    if atscall.phone_id in already:
    to_remove.append(atscall.id)
    continue
    already.add(atscall.phone_id)


    print(len(to_remove))

    PhoneCall.objects.filter(campaign_id=1180642991, campaign__user_id=292445898, ats_call_id__in=to_remove).delete()
    AtsPhoneCall.objects.filter(campaign_id=1180642991, campaign__user_id=292445898, id__in=to_remove).delete()
  8. satels created this gist Nov 21, 2017.
    181 changes: 181 additions & 0 deletions calltoolsru_python.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,181 @@
    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')