Last active
          September 3, 2018 11:59 
        
      - 
      
- 
        Save satels/a0a0e5612588248f3659e94d75a6bf24 to your computer and use it in GitHub Desktop. 
Revisions
- 
        satels revised this gist Sep 3, 2018 . 1 changed file with 178 additions and 98 deletions.There are no files selected for viewingThis file contains hidden or 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 charactersOriginal 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' # 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, @@ -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] } 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 @@ -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, :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 
- 
        satels revised this gist Aug 28, 2018 . No changes.There are no files selected for viewing
- 
        satels revised this gist Aug 28, 2018 . No changes.There are no files selected for viewing
- 
        satels revised this gist Aug 28, 2018 . 1 changed file with 235 additions and 0 deletions.There are no files selected for viewingThis file contains hidden or 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 charactersOriginal 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 
- 
        satels revised this gist Jul 12, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewingThis file contains hidden or 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 charactersOriginal 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) if form.is_valid(): CallToolsPostBackResult.objects.create(**form.cleaned_data) 
- 
        satels revised this gist Dec 26, 2017 . 1 changed file with 1 addition and 24 deletions.There are no files selected for viewingThis file contains hidden or 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 charactersOriginal 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') 
- 
        satels revised this gist Dec 25, 2017 . 1 changed file with 24 additions and 1 deletion.There are no files selected for viewingThis file contains hidden or 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 charactersOriginal 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') 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() 
- 
        satels created this gist Nov 21, 2017 .There are no files selected for viewingThis file contains hidden or 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 charactersOriginal 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')