bing-ads 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +280 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bing-ads.gemspec +31 -0
- data/lib/bing/ads.rb +23 -0
- data/lib/bing/ads/api/errors.rb +20 -0
- data/lib/bing/ads/api/soap_client.rb +67 -0
- data/lib/bing/ads/api/v11.rb +14 -0
- data/lib/bing/ads/api/v11/constants.rb +27 -0
- data/lib/bing/ads/api/v11/constants/campaign_management.yml +78 -0
- data/lib/bing/ads/api/v11/constants/languages.yml +12 -0
- data/lib/bing/ads/api/v11/constants/limits.yml +5 -0
- data/lib/bing/ads/api/v11/constants/time_zones.yml +75 -0
- data/lib/bing/ads/api/v11/constants/wsdl.yml +6 -0
- data/lib/bing/ads/api/v11/services.rb +2 -0
- data/lib/bing/ads/api/v11/services/base.rb +121 -0
- data/lib/bing/ads/api/v11/services/campaign_management.rb +360 -0
- data/lib/bing/ads/utils.rb +18 -0
- data/lib/bing/ads/version.rb +5 -0
- metadata +157 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
module Bing
|
2
|
+
module Ads
|
3
|
+
module API
|
4
|
+
# Bing::Ads::API::SOAPClient
|
5
|
+
class SOAPClient
|
6
|
+
attr_accessor :customer_id, :account_id, :developer_token, :wsdl_url, :client_settings
|
7
|
+
attr_accessor :authentication_token, :username, :password, :namespace_identifier
|
8
|
+
attr_accessor :savon_client
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@customer_id = options[:customer_id]
|
12
|
+
@account_id = options[:account_id]
|
13
|
+
@developer_token = options[:developer_token]
|
14
|
+
@wsdl_url = options[:wsdl_url]
|
15
|
+
@namespace_identifier = options[:namespace_identifier]
|
16
|
+
@authentication_token = options[:authentication_token]
|
17
|
+
@username = options[:username]
|
18
|
+
@password = options[:password]
|
19
|
+
@client_settings = options[:client_settings]
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(operation:, payload: {})
|
23
|
+
client(client_settings).call(operation, message: payload)
|
24
|
+
end
|
25
|
+
|
26
|
+
def client(settings)
|
27
|
+
return savon_client if savon_client
|
28
|
+
settings = {
|
29
|
+
convert_request_keys_to: :camelcase,
|
30
|
+
wsdl: wsdl_url,
|
31
|
+
namespace_identifier: namespace_identifier,
|
32
|
+
soap_header: soap_header,
|
33
|
+
log: true,
|
34
|
+
log_level: :debug,
|
35
|
+
pretty_print_xml: true
|
36
|
+
}
|
37
|
+
settings.merge!(client_settings) if client_settings
|
38
|
+
@savon_client = Savon.client(settings)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def soap_header
|
43
|
+
headers = {}
|
44
|
+
if authentication_token
|
45
|
+
headers[ns('AuthenticationToken')] = authentication_token
|
46
|
+
headers[ns('CustomerAccountId')] = account_id
|
47
|
+
headers[ns('CustomerId')] = customer_id
|
48
|
+
headers[ns('DeveloperToken')] = developer_token
|
49
|
+
elsif username && password
|
50
|
+
headers[ns('CustomerAccountId')] = account_id
|
51
|
+
headers[ns('CustomerId')] = customer_id
|
52
|
+
headers[ns('DeveloperToken')] = developer_token
|
53
|
+
headers[ns('UserName')] = username
|
54
|
+
headers[ns('Password')] = password
|
55
|
+
else
|
56
|
+
raise Errors::AuthenticationParamsMissing, 'no authentication params provided'
|
57
|
+
end
|
58
|
+
headers
|
59
|
+
end
|
60
|
+
|
61
|
+
def ns(string)
|
62
|
+
"#{namespace_identifier}:#{string}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bing
|
4
|
+
module Ads
|
5
|
+
module API
|
6
|
+
# Bing::Ads::API::V11::Constants
|
7
|
+
module Constants
|
8
|
+
root_v11_path = File.expand_path('../', __FILE__)
|
9
|
+
|
10
|
+
campaign_management_path = File.join(root_v11_path, 'constants', 'campaign_management.yml')
|
11
|
+
languages_path = File.join(root_v11_path, 'constants', 'languages.yml')
|
12
|
+
limits_path = File.join(root_v11_path, 'constants', 'limits.yml')
|
13
|
+
time_zones_path = File.join(root_v11_path, 'constants', 'time_zones.yml')
|
14
|
+
wsdl_path = File.join(root_v11_path, 'constants', 'wsdl.yml')
|
15
|
+
|
16
|
+
Persey.init(:default) do
|
17
|
+
source :yaml, campaign_management_path, :campaign_management
|
18
|
+
source :yaml, languages_path, :languages
|
19
|
+
source :yaml, limits_path, :limits
|
20
|
+
source :yaml, time_zones_path, :time_zones
|
21
|
+
source :yaml, wsdl_path, :wsdl
|
22
|
+
env :default
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
ad_distribution:
|
2
|
+
search: Search
|
3
|
+
content: Content
|
4
|
+
ad_rotation:
|
5
|
+
optimize_for_clicks: OptimizeForClicks
|
6
|
+
rotate_ads_evenly: RotateAdsEvenly
|
7
|
+
ad_group_status:
|
8
|
+
draft: Draft
|
9
|
+
active: Active
|
10
|
+
paused: Paused
|
11
|
+
deleted: Deleted
|
12
|
+
ad_editorial_status:
|
13
|
+
active: Active
|
14
|
+
disapproved: Disapproved
|
15
|
+
inactive: Inactive
|
16
|
+
active_limited: ActiveLimited
|
17
|
+
ad_status:
|
18
|
+
inactive: Inactive
|
19
|
+
active: Active
|
20
|
+
paused: Paused
|
21
|
+
deleted: Deleted
|
22
|
+
ad_types:
|
23
|
+
text_ad: TextAd
|
24
|
+
image_ad: ImageAd
|
25
|
+
product_ad: ProductAd
|
26
|
+
app_install_ad: AppInstallAd
|
27
|
+
expanded_text_ad: ExpandedTextAd
|
28
|
+
dynamic_search_ad: DynamicSearchAd
|
29
|
+
ad_types_for_get:
|
30
|
+
text: Text
|
31
|
+
image: Image
|
32
|
+
product: Product
|
33
|
+
app_install: AppInstall
|
34
|
+
expanded_text: ExpandedText
|
35
|
+
dynamic_search: DynamicSearch
|
36
|
+
bidding_model:
|
37
|
+
keyword: Keyword
|
38
|
+
site_placement: SitePlacement
|
39
|
+
bidding_scheme:
|
40
|
+
manual_cpc: ManualCpcBiddingScheme
|
41
|
+
enhanced_cpc: EnhancedCpcBiddingScheme
|
42
|
+
inherit_from_parent: InheritFromParentBiddingScheme
|
43
|
+
max_clicks: MaxClicksBiddingScheme
|
44
|
+
max_conversions: MaxConversionsBiddingScheme
|
45
|
+
target_cpa: TargetCpaBiddingScheme
|
46
|
+
budget_limit_type:
|
47
|
+
daily_budget_accelerated: DailyBudgetAccelerated
|
48
|
+
daily_budget_standard: DailyBudgetStandard
|
49
|
+
campaign_status:
|
50
|
+
active: Active
|
51
|
+
paused: Paused
|
52
|
+
budget_paused: BudgetPaused
|
53
|
+
budget_and_manual_paused: BudgetAndManualPaused
|
54
|
+
deleted: Deleted
|
55
|
+
pricing_model:
|
56
|
+
cpc: Cpc
|
57
|
+
cpm: Cpm
|
58
|
+
keyword_editorial_statuses:
|
59
|
+
active: Active
|
60
|
+
disapproved: Disapproved
|
61
|
+
inactive: Inactive
|
62
|
+
keyword_statuses:
|
63
|
+
active: Active
|
64
|
+
paused: Paused
|
65
|
+
deleted: Deleted
|
66
|
+
inactive: Inactive
|
67
|
+
match_types:
|
68
|
+
broad: Broad
|
69
|
+
content: Content
|
70
|
+
exact: Exact
|
71
|
+
phrase: Phrase
|
72
|
+
network:
|
73
|
+
owned_and_operated_and_syndicated_search: OwnedAndOperatedAndSyndicatedSearch
|
74
|
+
owned_and_operated_only: OwnedAndOperatedOnly
|
75
|
+
syndicated_search_only: SyndicatedSearchOnly
|
76
|
+
remarketing_target_setting:
|
77
|
+
bid_only: BidOnly
|
78
|
+
target_and_bid: TargetAndBid
|
@@ -0,0 +1,12 @@
|
|
1
|
+
danish: 'Danish'
|
2
|
+
dutch: 'Dutch'
|
3
|
+
english: 'English'
|
4
|
+
finnish: 'Finnish'
|
5
|
+
french: 'French'
|
6
|
+
german: 'German'
|
7
|
+
italian: 'Italian'
|
8
|
+
norwegian: 'Norwegian'
|
9
|
+
portuguese: 'Portuguese'
|
10
|
+
spanish: 'Spanish'
|
11
|
+
swedish: 'Swedish'
|
12
|
+
traditional_chinese: 'TraditionalChinese'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
abu_dhabi_muscat: 'AbuDhabiMuscat'
|
2
|
+
adelaide: 'Adelaide'
|
3
|
+
alaska: 'Alaska'
|
4
|
+
almaty_novosibirsk: 'Almaty_Novosibirsk'
|
5
|
+
amsterdam_berlin_bern_rome_stockholm_vienna: 'AmsterdamBerlinBernRomeStockholmVienna'
|
6
|
+
arizona: 'Arizona'
|
7
|
+
astana_dhaka: 'AstanaDhaka'
|
8
|
+
athens_buckarest_istanbul: 'AthensBuckarestIstanbul'
|
9
|
+
atlantic_time_canada: 'AtlanticTimeCanada'
|
10
|
+
auckland_wellington: 'AucklandWellington'
|
11
|
+
azores: 'Azores'
|
12
|
+
baghdad: 'Baghdad'
|
13
|
+
baku_tbilisi_yerevan: 'BakuTbilisiYerevan'
|
14
|
+
bangkok_hanoi_jakarta: 'BangkokHanoiJakarta'
|
15
|
+
beijing_chongqing_hong_kong_urumqi: 'BeijingChongqingHongKongUrumqi'
|
16
|
+
belgrade_bratislava_budapest_ljubljana_prague: 'BelgradeBratislavaBudapestLjubljanaPrague'
|
17
|
+
bogota_lima_quito: 'BogotaLimaQuito'
|
18
|
+
brasilia: 'Brasilia'
|
19
|
+
brisbane: 'Brisbane'
|
20
|
+
brussels_copenhagen_madrid_paris: 'BrusselsCopenhagenMadridParis'
|
21
|
+
bucharest: 'Bucharest'
|
22
|
+
buenos_aires_georgetown: 'BuenosAiresGeorgetown'
|
23
|
+
cairo: 'Cairo'
|
24
|
+
canberra_melbourne_sydney: 'CanberraMelbourneSydney'
|
25
|
+
cape_verde_island: 'CapeVerdeIsland'
|
26
|
+
caracas_la_paz: 'CaracasLaPaz'
|
27
|
+
casablanca_monrovia: 'CasablancaMonrovia'
|
28
|
+
central_america: 'CentralAmerica'
|
29
|
+
central_time_u_s_canada: 'CentralTimeUSCanada'
|
30
|
+
chennai_kolkata_mumbai_new_delhi: 'ChennaiKolkataMumbaiNewDelhi'
|
31
|
+
chihuahua_la_paz_mazatlan: 'ChihuahuaLaPazMazatlan'
|
32
|
+
darwin: 'Darwin'
|
33
|
+
eastern_time_u_s_canada: 'EasternTimeUSCanada'
|
34
|
+
ekaterinburg: 'Ekaterinburg'
|
35
|
+
fiji_kamchatka_marshall_island: 'FijiKamchatkaMarshallIsland'
|
36
|
+
greenland: 'Greenland'
|
37
|
+
greenwich_mean_time_dublin_edinburgh_lisbon_london: 'GreenwichMeanTimeDublinEdinburghLisbonLondon'
|
38
|
+
guadalajara_mexico_city_monterrey: 'GuadalajaraMexicoCityMonterrey'
|
39
|
+
guam_port_moresby: 'GuamPortMoresby'
|
40
|
+
harare_pretoria: 'HararePretoria'
|
41
|
+
hawaii: 'Hawaii'
|
42
|
+
helsinki_kyiv_riga_sofia_tallinn_vilnius: 'HelsinkiKyivRigaSofiaTallinnVilnius'
|
43
|
+
hobart: 'Hobart'
|
44
|
+
indiana_east: 'IndianaEast'
|
45
|
+
international_date_line_west: 'InternationalDateLineWest'
|
46
|
+
irkutsk_ulaan_bataar: 'IrkutskUlaanBataar'
|
47
|
+
islandamabad_karachi_tashkent: 'IslandamabadKarachiTashkent'
|
48
|
+
jerusalem: 'Jerusalem'
|
49
|
+
kabul: 'Kabul'
|
50
|
+
kathmandu: 'Kathmandu'
|
51
|
+
krasnoyarsk: 'Krasnoyarsk'
|
52
|
+
kuala_lumpur_singapore: 'KualaLumpurSingapore'
|
53
|
+
kuwait_riyadh: 'KuwaitRiyadh'
|
54
|
+
magadan_solomon_island_new_caledonia: 'MagadanSolomonIslandNewCaledonia'
|
55
|
+
mid_atlantic: 'MidAtlantic'
|
56
|
+
midway_islandand_samoa: 'MidwayIslandand_Samoa'
|
57
|
+
moscow_st_petersburg_volgograd: 'MoscowStPetersburgVolgograd'
|
58
|
+
mountain_time_u_s_canada: 'MountainTime_US_Canada'
|
59
|
+
nairobi: 'Nairobi'
|
60
|
+
newfoundland: 'Newfoundland'
|
61
|
+
nukualofa: 'Nukualofa'
|
62
|
+
osaka_sapporo_tokyo: 'OsakaSapporoTokyo'
|
63
|
+
pacific_time_u_s_canada_tijuana: 'PacificTimeUSCanadaTijuana'
|
64
|
+
perth: 'Perth'
|
65
|
+
rangoon: 'Rangoon'
|
66
|
+
santiago: 'Santiago'
|
67
|
+
sarajevo_skopje_warsaw_zagreb: 'SarajevoSkopjeWarsawZagreb'
|
68
|
+
saskatchewan: 'Saskatchewan'
|
69
|
+
seoul: 'Seoul'
|
70
|
+
sri_jayawardenepura: 'SriJayawardenepura'
|
71
|
+
taipei: 'Taipei'
|
72
|
+
tehran: 'Tehran'
|
73
|
+
vladivostok: 'Vladivostok'
|
74
|
+
west_central_africa: 'WestCentralAfrica'
|
75
|
+
yakutsk: 'Yakutsk'
|
@@ -0,0 +1,6 @@
|
|
1
|
+
sandbox:
|
2
|
+
campaign_management: "https://campaign.api.sandbox.bingads.microsoft.com/Api/Advertiser/CampaignManagement/V11/CampaignManagementService.svc?singleWsdl"
|
3
|
+
reporting: "https://api.sandbox.bingads.microsoft.com/Api/Advertiser/Reporting/V11/ReportingService.svc?singleWsdl"
|
4
|
+
production:
|
5
|
+
campaign_management: "https://campaign.api.bingads.microsoft.com/Api/Advertiser/CampaignManagement/V11/CampaignManagementService.svc?singleWsdl"
|
6
|
+
reporting: "https://api.bingads.microsoft.com/Api/Advertiser/Reporting/V11/ReportingService.svc?singleWsdl"
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Bing
|
2
|
+
module Ads
|
3
|
+
module API
|
4
|
+
module V11
|
5
|
+
module Services
|
6
|
+
# Bing::Ads::API::V11::Base
|
7
|
+
class Base
|
8
|
+
attr_accessor :soap_client, :environment, :retry_attempts
|
9
|
+
|
10
|
+
# @param options - Hash with autentication and environment settings
|
11
|
+
# * environment - +:production+ or +:sandbox+
|
12
|
+
# * developer_token - client application's developer access token
|
13
|
+
# * customer_id - identifier for the customer that owns the account
|
14
|
+
# * account_id - identifier of the account that own the entities in the request
|
15
|
+
# * client_settings - Hash with any Client additional options (such as header, logger or enconding)
|
16
|
+
# * retry_attempts - Number of times the service must retry on failure
|
17
|
+
# (EITHER)
|
18
|
+
# * authentication_token - OAuth2 token
|
19
|
+
# (OR)
|
20
|
+
# * username - Bing Ads username
|
21
|
+
# * password - Bing Ads password
|
22
|
+
def initialize(options = {})
|
23
|
+
@environment = options.delete(:environment)
|
24
|
+
@retry_attempts = options.delete(:retry_attempts) || 0
|
25
|
+
@account_id = options[:account_id]
|
26
|
+
raise 'You must set the service environment' unless @environment
|
27
|
+
options[:wsdl_url] = service_wsdl_url
|
28
|
+
options[:namespace_identifier] = Bing::Ads::API::V11::NAMESPACE_IDENTIFIER
|
29
|
+
@soap_client = Bing::Ads::API::SOAPClient.new(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
# This is a utility wrapper for calling services into the
|
33
|
+
# SOAPClient. This methods handle the Savon::Client Exceptions
|
34
|
+
# and returns a Hash with the call response
|
35
|
+
#
|
36
|
+
# @param operation - name of the operation to be called
|
37
|
+
# @param payload - hash with the parameters to the operation
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# service.call(:some_operation, { key: value })
|
41
|
+
# # => <Hash>
|
42
|
+
#
|
43
|
+
# @return Hash with the result of the service call
|
44
|
+
# @raise ServiceError if the SOAP call fails or the response is invalid
|
45
|
+
def call(operation, payload)
|
46
|
+
retries_made = 0
|
47
|
+
raise 'You must provide an operation' if operation.nil?
|
48
|
+
begin
|
49
|
+
response = soap_client.call(operation: operation.to_sym, payload: payload)
|
50
|
+
return response.hash
|
51
|
+
rescue Savon::SOAPFault => error
|
52
|
+
fault_detail = error.to_hash[:fault][:detail]
|
53
|
+
if fault_detail.key?(:api_fault_detail)
|
54
|
+
handle_soap_fault(operation, fault_detail, :api_fault_detail)
|
55
|
+
elsif fault_detail.key?(:ad_api_fault_detail)
|
56
|
+
handle_soap_fault(operation, fault_detail, :ad_api_fault_detail)
|
57
|
+
else
|
58
|
+
raise
|
59
|
+
end
|
60
|
+
rescue Savon::HTTPError => error
|
61
|
+
raise
|
62
|
+
rescue Savon::InvalidResponseError => error
|
63
|
+
raise
|
64
|
+
rescue
|
65
|
+
if retries_made < retry_attempts
|
66
|
+
sleep(2**retries_made)
|
67
|
+
retries_made += 1
|
68
|
+
retry
|
69
|
+
else
|
70
|
+
raise
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Extracts the actual response from the entire response hash.
|
76
|
+
#
|
77
|
+
# @param response - The complete response hash received from a Operation call
|
78
|
+
# @param method - Name of the method of with the 'reponse' tag is require
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# service.response_body(Hash, 'add_campaigns')
|
82
|
+
# # => Hash
|
83
|
+
#
|
84
|
+
# @return Hash with the content of the called method response hash
|
85
|
+
def response_body(response, method)
|
86
|
+
response[:envelope][:body]["#{method}_response".to_sym]
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Returns service name. This method must be overriden by specific services.
|
92
|
+
#
|
93
|
+
# @return String with the service name
|
94
|
+
# @raise exception if the specific Service class hasn't overriden this method
|
95
|
+
def service_name
|
96
|
+
raise 'Should return the a service name from config.wsdl keys'
|
97
|
+
end
|
98
|
+
|
99
|
+
# Gets the service WSDL URL based on the service name and environment
|
100
|
+
#
|
101
|
+
# @return String with the Service url
|
102
|
+
def service_wsdl_url
|
103
|
+
Bing::Ads::API::V11.constants.wsdl.send(environment).send(service_name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_soap_fault(operation, fault_detail, key)
|
107
|
+
if fault_detail[key][:errors] &&
|
108
|
+
fault_detail[key][:errors][:ad_api_error] &&
|
109
|
+
fault_detail[key][:errors][:ad_api_error][:error_code] == 'AuthenticationTokenExpired'
|
110
|
+
raise Bing::Ads::API::Errors::AuthenticationTokenExpired,
|
111
|
+
'renew authentication token or obtain a new one.'
|
112
|
+
else
|
113
|
+
raise "SOAP error while calling #{operation}, #{fault_detail[key]}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
module Bing
|
2
|
+
module Ads
|
3
|
+
module API
|
4
|
+
module V11
|
5
|
+
module Services
|
6
|
+
# Bing::Ads::API::V11::Services::CampaignManagement
|
7
|
+
class CampaignManagement < Base
|
8
|
+
def initialize(options = {})
|
9
|
+
super(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_campaigns_by_account_id(account_id=nil)
|
13
|
+
account_id ||= @account_id
|
14
|
+
response = call(:get_campaigns_by_account_id, account_id: account_id)
|
15
|
+
response_body = response_body(response, __method__)
|
16
|
+
[response_body[:campaigns][:campaign]].flatten.compact
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_campaigns(account_id, campaigns)
|
20
|
+
validate_limits!(:campaign, :add, campaigns)
|
21
|
+
campaigns = campaigns.map { |campaign| prepare_campaign(campaign) }
|
22
|
+
payload = {
|
23
|
+
account_id: account_id,
|
24
|
+
campaigns: { campaign: campaigns }
|
25
|
+
}
|
26
|
+
response = call(:add_campaigns, payload)
|
27
|
+
response_body(response, __method__)
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_campaigns(account_id, campaigns)
|
31
|
+
validate_limits!(:campaign, :update, campaigns)
|
32
|
+
campaigns = campaigns.map { |campaign| prepare_campaign(campaign) }
|
33
|
+
payload = {
|
34
|
+
account_id: account_id,
|
35
|
+
campaigns: { campaign: campaigns }
|
36
|
+
}
|
37
|
+
response = call(:update_campaigns, payload)
|
38
|
+
response_body(response, __method__)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_campaigns(account_id, campaign_ids)
|
42
|
+
payload = {
|
43
|
+
account_id: account_id,
|
44
|
+
campaign_ids: { 'ins1:long' => campaign_ids }
|
45
|
+
}
|
46
|
+
response = call(:delete_campaigns, payload)
|
47
|
+
response_body(response, __method__)
|
48
|
+
end
|
49
|
+
|
50
|
+
def get_ad_groups_by_campaign_id(campaign_id)
|
51
|
+
response = call(:get_ad_groups_by_campaign_id,
|
52
|
+
campaign_id: campaign_id)
|
53
|
+
response_body = response_body(response, __method__)
|
54
|
+
[response_body[:ad_groups][:ad_group]].flatten.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_ad_groups_by_ids(campaign_id, ad_groups_ids)
|
58
|
+
payload = {
|
59
|
+
campaign_id: campaign_id,
|
60
|
+
ad_group_ids: { 'ins1:long' => ad_groups_ids }
|
61
|
+
}
|
62
|
+
response = call(:get_ad_groups_by_ids, payload)
|
63
|
+
response_body = response_body(response, __method__)
|
64
|
+
[response_body[:ad_groups][:ad_group]].flatten
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_ad_groups(campaign_id, ad_groups)
|
68
|
+
validate_limits!(:ad_group, :add, ad_groups)
|
69
|
+
ad_groups = ad_groups.map { |ad_group| prepare_ad_group(ad_group) }
|
70
|
+
payload = {
|
71
|
+
campaign_id: campaign_id,
|
72
|
+
ad_groups: { ad_group: ad_groups }
|
73
|
+
}
|
74
|
+
response = call(:add_ad_groups, payload)
|
75
|
+
response_body(response, __method__)
|
76
|
+
end
|
77
|
+
|
78
|
+
def update_ad_groups(campaign_id, ad_groups)
|
79
|
+
validate_limits!(:ad_group, :update, ad_groups)
|
80
|
+
ad_groups = ad_groups.map { |ad_group| prepare_ad_group(ad_group) }
|
81
|
+
payload = {
|
82
|
+
campaign_id: campaign_id,
|
83
|
+
ad_groups: { ad_group: ad_groups }
|
84
|
+
}
|
85
|
+
response = call(:update_ad_groups, payload)
|
86
|
+
response_body(response, __method__)
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_ads_by_ad_group_id(ad_group_id)
|
90
|
+
payload = {
|
91
|
+
ad_group_id: ad_group_id,
|
92
|
+
ad_types: all_ad_types
|
93
|
+
}
|
94
|
+
response = call(:get_ads_by_ad_group_id, payload)
|
95
|
+
response_body = response_body(response, __method__)
|
96
|
+
response_ads = [response_body[:ads][:ad]].flatten.compact
|
97
|
+
response_ads.each_with_object({}) do |ad, obj|
|
98
|
+
type = ad['@i:type'.to_sym]
|
99
|
+
obj[type] ||= []
|
100
|
+
obj[type] << ad
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def get_ads_by_ids(ad_group_id, ad_ids)
|
105
|
+
# Order matters
|
106
|
+
payload = {
|
107
|
+
ad_group_id: ad_group_id,
|
108
|
+
ad_ids: { 'ins1:long' => ad_ids },
|
109
|
+
ad_types: all_ad_types
|
110
|
+
}
|
111
|
+
response = call(:get_ads_by_ids, payload)
|
112
|
+
response_body = response_body(response, __method__)
|
113
|
+
response_ads = [response_body[:ads][:ad]].flatten
|
114
|
+
response_ads.each_with_object({}) do |ad, obj|
|
115
|
+
type = ad['@i:type'.to_sym]
|
116
|
+
obj[type] ||= []
|
117
|
+
obj[type] << ad
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_ads(ad_group_id, ads)
|
122
|
+
validate_limits!(:ad, :add, ads)
|
123
|
+
ads = ads.map { |ad| prepare_ad(ad) }
|
124
|
+
payload = {
|
125
|
+
ad_group_id: ad_group_id,
|
126
|
+
ads: { ad: ads }
|
127
|
+
}
|
128
|
+
response = call(:add_ads, payload)
|
129
|
+
response_body(response, __method__)
|
130
|
+
end
|
131
|
+
|
132
|
+
def update_ads(ad_group_id, ads)
|
133
|
+
validate_limits!(:ad, :update, ads)
|
134
|
+
ads = ads.map { |ad| prepare_ad(ad) }
|
135
|
+
payload = {
|
136
|
+
ad_group_id: ad_group_id,
|
137
|
+
ads: { ad: ads }
|
138
|
+
}
|
139
|
+
response = call(:update_ads, payload)
|
140
|
+
response_body(response, __method__)
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_keywords_by_ad_group_id(ad_group_id)
|
144
|
+
response = call(:get_keywords_by_ad_group_id, ad_group_id: ad_group_id)
|
145
|
+
response_body = response_body(response, __method__)
|
146
|
+
[response_body[:keywords][:keyword]].flatten.compact
|
147
|
+
end
|
148
|
+
|
149
|
+
def get_keywords_by_ids(ad_group_id, keyword_ids)
|
150
|
+
payload = {
|
151
|
+
ad_group_id: ad_group_id,
|
152
|
+
keyword_ids: { 'ins1:long' => keyword_ids }
|
153
|
+
}
|
154
|
+
response = call(:get_keywords_by_ids, payload)
|
155
|
+
response_body = response_body(response, __method__)
|
156
|
+
[response_body[:keywords][:keyword]].flatten
|
157
|
+
end
|
158
|
+
|
159
|
+
def add_keywords(ad_group_id, keywords)
|
160
|
+
validate_limits!(:keyword, :add, keywords)
|
161
|
+
keywords = keywords.map { |keyword| prepare_keyword(keyword) }
|
162
|
+
payload = {
|
163
|
+
ad_group_id: ad_group_id,
|
164
|
+
keywords: { keyword: keywords }
|
165
|
+
}
|
166
|
+
response = call(:add_keywords, payload)
|
167
|
+
response_body(response, __method__)
|
168
|
+
end
|
169
|
+
|
170
|
+
def update_keywords(ad_group_id, keywords)
|
171
|
+
validate_limits!(:keyword, :update, keywords)
|
172
|
+
keywords = keywords.map { |keyword| prepare_keyword(keyword) }
|
173
|
+
payload = {
|
174
|
+
ad_group_id: ad_group_id,
|
175
|
+
keywords: { keyword: keywords }
|
176
|
+
}
|
177
|
+
response = call(:update_keywords, payload)
|
178
|
+
response_body(response, __method__)
|
179
|
+
end
|
180
|
+
|
181
|
+
# TODO add_ad_extensions
|
182
|
+
# TODO add_ad_group_criterions
|
183
|
+
# TODO add_audiences
|
184
|
+
# TODO add_budgets
|
185
|
+
# TODO add_campaign_criterions
|
186
|
+
# TODO add_conversion_goals
|
187
|
+
# TODO add_labels
|
188
|
+
# TODO add_list_items_to_shared_list
|
189
|
+
# TODO add_media
|
190
|
+
# TODO add_negative_keywords_to_entities
|
191
|
+
# TODO add_shared_entity
|
192
|
+
# TODO add_uet_tags
|
193
|
+
# TODO appeal_editorial_rejections
|
194
|
+
# TODO apply_offline_conversions
|
195
|
+
# TODO apply_product_partition_actions
|
196
|
+
# TODO delete_ad_extensions
|
197
|
+
# TODO delete_ad_group_criterions
|
198
|
+
# TODO delete_ad_groups
|
199
|
+
# TODO delete_ads
|
200
|
+
# TODO delete_audiences
|
201
|
+
# TODO delete_budgets
|
202
|
+
# TODO delete_campaign_criterions
|
203
|
+
# TODO delete_conversion_goals
|
204
|
+
# TODO delete_keywords
|
205
|
+
# TODO delete_labels
|
206
|
+
# TODO delete_list_items_to_shared_list
|
207
|
+
# TODO delete_media
|
208
|
+
# TODO delete_negative_keywords_to_entities
|
209
|
+
# TODO delete_shared_entity
|
210
|
+
# TODO delete_shared_entity_associations
|
211
|
+
# TODO get_account_migration_statuses
|
212
|
+
# TODO get_account_properties
|
213
|
+
# TODO get_ad_extension_ids_by_account_id
|
214
|
+
# TODO get_ad_extensions_associations
|
215
|
+
# TODO get_ad_extensions_by_ids
|
216
|
+
# TODO get_ad_extensions_editorial_reasons
|
217
|
+
# TODO get_ad_group_criterions_by_ids
|
218
|
+
# TODO get_ads_by_editorial_status
|
219
|
+
# TODO get_audiences_by_ids
|
220
|
+
# TODO get_bmc_stores_by_customer_id
|
221
|
+
# TODO get_bsc_countries
|
222
|
+
# TODO get_budgets_by_ids
|
223
|
+
# TODO get_campaign_criterions_by_ids
|
224
|
+
# TODO get_campaign_ids_by_budget_ids
|
225
|
+
# TODO get_campaigns_by_ids
|
226
|
+
# TODO get_campaign_sizes_by_account_id
|
227
|
+
# TODO get_config_value
|
228
|
+
# TODO get_conversion_goals_by_ids
|
229
|
+
# TODO get_conversion_goals_by_tag_ids
|
230
|
+
# TODO get_editorial_reasons_by_ids
|
231
|
+
# TODO get_geo_locations_file_url
|
232
|
+
# TODO get_keywords_by_editorial_status
|
233
|
+
# TODO get_label_associations_by_entity_ids
|
234
|
+
# TODO get_label_associations_by_label_ids
|
235
|
+
# TODO get_labels_by_ids
|
236
|
+
# TODO get_list_items_by_shared_list
|
237
|
+
# TODO get_media_associations
|
238
|
+
# TODO get_media_by_ids
|
239
|
+
# TODO get_media_meta_data_by_account_id
|
240
|
+
# TODO get_media_meta_data_by_ids
|
241
|
+
# TODO get_negative_keywords_by_entity_ids
|
242
|
+
# TODO get_negative_sites_by_ad_group_ids
|
243
|
+
# TODO get_negative_sites_by_campaign_ids
|
244
|
+
# TODO get_shared_entities_by_account_id
|
245
|
+
# TODO get_shared_entity_associations_by_entity_ids
|
246
|
+
# TODO get_shared_entity_associations_by_shared_entity_ids
|
247
|
+
# TODO get_uet_tags_by_ids
|
248
|
+
# TODO set_account_properties
|
249
|
+
# TODO set_ad_extensions_associations
|
250
|
+
# TODO set_label_associations
|
251
|
+
# TODO set_negative_sites_to_ad_groups
|
252
|
+
# TODO set_negative_sites_to_campaigns
|
253
|
+
# TODO set_shared_entity_associations
|
254
|
+
# TODO update_ad_extensions
|
255
|
+
# TODO update_ad_group_criterions
|
256
|
+
# TODO update_audiences
|
257
|
+
# TODO update_budgets
|
258
|
+
# TODO update_campaign_criterions
|
259
|
+
# TODO update_conversion_goals
|
260
|
+
# TODO update_labels
|
261
|
+
# TODO update_list_items_to_shared_list
|
262
|
+
# TODO update_media
|
263
|
+
# TODO update_negative_keywords_to_entities
|
264
|
+
# TODO update_shared_entity
|
265
|
+
# TODO update_uet_tags
|
266
|
+
|
267
|
+
private
|
268
|
+
|
269
|
+
def service_name
|
270
|
+
'campaign_management'
|
271
|
+
end
|
272
|
+
|
273
|
+
def prepare_campaign(campaign)
|
274
|
+
campaign = Bing::Ads::Utils.sort_keys(campaign)
|
275
|
+
if campaign[:bidding_scheme]
|
276
|
+
campaign[:bidding_scheme] = {
|
277
|
+
# TODO support MaxClicksBiddingScheme, MaxConversionsBiddingScheme and TargetCpaBiddingScheme
|
278
|
+
type: campaign[:bidding_scheme],
|
279
|
+
'@xsi:type' => "#{Bing::Ads::API::V11::NAMESPACE_IDENTIFIER}:#{campaign[:bidding_scheme]}"
|
280
|
+
}
|
281
|
+
end
|
282
|
+
campaign[:languages] = { 'ins1:string' => campaign[:languages] } if campaign[:languages]
|
283
|
+
# TODO UrlCustomParameters
|
284
|
+
# TODO Settings
|
285
|
+
Bing::Ads::Utils.camelcase_keys(campaign)
|
286
|
+
end
|
287
|
+
|
288
|
+
def prepare_ad_group(ad_group)
|
289
|
+
ad_group = Bing::Ads::Utils.sort_keys(ad_group)
|
290
|
+
ad_group[:ad_rotation] = { type: ad_group[:ad_rotation] } if ad_group[:ad_rotation]
|
291
|
+
if ad_group[:bidding_scheme]
|
292
|
+
# TODO support MaxClicksBiddingScheme, MaxConversionsBiddingScheme and TargetCpaBiddingScheme
|
293
|
+
ad_group[:bidding_scheme] = {
|
294
|
+
type: ad_group[:bidding_scheme],
|
295
|
+
'@xsi:type' => "#{Bing::Ads::API::V11::NAMESPACE_IDENTIFIER}:#{ad_group[:bidding_scheme]}"
|
296
|
+
}
|
297
|
+
end
|
298
|
+
ad_group[:content_match_bid] = { amount: ad_group[:content_match_bid] } if ad_group[:content_match_bid]
|
299
|
+
ad_group[:end_date] = date_hash(ad_group[:end_date]) if ad_group[:end_date]
|
300
|
+
ad_group[:search_bid] = { amount: ad_group[:search_bid] } if ad_group[:search_bid]
|
301
|
+
ad_group[:start_date] = date_hash(ad_group[:start_date]) if ad_group[:start_date]
|
302
|
+
# TODO UrlCustomParameters
|
303
|
+
Bing::Ads::Utils.camelcase_keys(ad_group)
|
304
|
+
end
|
305
|
+
|
306
|
+
def prepare_ad(ad)
|
307
|
+
ad = Bing::Ads::Utils.sort_keys(ad)
|
308
|
+
ad['@xsi:type'] = "#{Bing::Ads::API::V11::NAMESPACE_IDENTIFIER}:#{ad[:type]}"
|
309
|
+
ad[:final_mobile_urls] = { 'ins1:string' => ad[:final_mobile_urls] } if ad[:final_mobile_urls]
|
310
|
+
ad[:final_urls] = { 'ins1:string' => ad[:final_urls] } if ad[:final_urls]
|
311
|
+
# TODO FinalAppUrls
|
312
|
+
Bing::Ads::Utils.camelcase_keys(ad)
|
313
|
+
end
|
314
|
+
|
315
|
+
def prepare_keyword(keyword)
|
316
|
+
keyword = Bing::Ads::Utils.sort_keys(keyword)
|
317
|
+
keyword[:bid] = { amount: keyword[:bid] } if keyword[:bid]
|
318
|
+
if keyword[:bidding_scheme]
|
319
|
+
# TODO support MaxClicksBiddingScheme, MaxConversionsBiddingScheme and TargetCpaBiddingScheme
|
320
|
+
keyword[:bidding_scheme] = {
|
321
|
+
type: keyword[:bidding_scheme],
|
322
|
+
'@xsi:type' => "#{Bing::Ads::API::V11::NAMESPACE_IDENTIFIER}:#{keyword[:bidding_scheme]}"
|
323
|
+
}
|
324
|
+
end
|
325
|
+
keyword[:final_mobile_urls] = { 'ins1:string' => keyword[:final_mobile_urls] } if keyword[:final_mobile_urls]
|
326
|
+
keyword[:final_urls] = { 'ins1:string' => keyword[:final_urls] } if keyword[:final_urls]
|
327
|
+
# TODO FinalAppUrls
|
328
|
+
# TODO UrlCustomParameters
|
329
|
+
Bing::Ads::Utils.camelcase_keys(keyword)
|
330
|
+
end
|
331
|
+
|
332
|
+
def date_hash(date)
|
333
|
+
date = Date.parse(date) if date.is_a?(String)
|
334
|
+
{ day: date.day, month: date.month, year: date.year }
|
335
|
+
end
|
336
|
+
|
337
|
+
def validate_limits!(type, operation, array)
|
338
|
+
limit = Bing::Ads::API::V11.constants.limits.per_call.send(type)
|
339
|
+
if array.size > limit
|
340
|
+
raise Bing::Ads::API::Errors::LimitError.new(operation, limit, type)
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
def all_ad_types
|
345
|
+
{
|
346
|
+
ad_type: [
|
347
|
+
Bing::Ads::API::V11.constants.campaign_management.ad_types_for_get.text,
|
348
|
+
Bing::Ads::API::V11.constants.campaign_management.ad_types_for_get.expanded_text,
|
349
|
+
Bing::Ads::API::V11.constants.campaign_management.ad_types_for_get.image,
|
350
|
+
Bing::Ads::API::V11.constants.campaign_management.ad_types_for_get.product,
|
351
|
+
Bing::Ads::API::V11.constants.campaign_management.ad_types_for_get.app_install
|
352
|
+
]
|
353
|
+
}
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|