bing-ads 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|