adapi 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/GUIDELINES.markdown +61 -0
- data/README.markdown +79 -45
- data/Rakefile +31 -3
- data/adapi.gemspec +10 -3
- data/examples/add_ad_group.rb +1 -1
- data/examples/add_bare_ad_group.rb +1 -4
- data/examples/add_campaign.rb +1 -0
- data/examples/add_campaign_criteria.rb +27 -0
- data/examples/add_invalid_ad_group.rb +3 -1
- data/examples/add_invalid_text_ad.rb +1 -1
- data/examples/add_keywords.rb +1 -1
- data/examples/add_negative_campaign_criteria.rb +23 -0
- data/examples/add_text_ad.rb +1 -1
- data/examples/customize_configuration.rb +1 -2
- data/examples/delete_keyword.rb +1 -1
- data/examples/find_campaign.rb +5 -5
- data/examples/find_campaign_ad_groups.rb +1 -1
- data/examples/find_campaign_criteria.rb +103 -0
- data/examples/find_locations.rb +21 -0
- data/examples/rollback_campaign.rb +7 -6
- data/examples/update_campaign.rb +1 -1
- data/examples/update_campaign_status.rb +1 -1
- data/lib/adapi.rb +11 -9
- data/lib/adapi/ad/text_ad.rb +2 -1
- data/lib/adapi/ad_group.rb +13 -9
- data/lib/adapi/ad_param.rb +89 -0
- data/lib/adapi/api.rb +8 -0
- data/lib/adapi/campaign.rb +27 -18
- data/lib/adapi/campaign_criterion.rb +278 -0
- data/lib/adapi/campaign_target.rb +5 -123
- data/lib/adapi/config.rb +36 -31
- data/lib/adapi/constant_data.rb +13 -0
- data/lib/adapi/constant_data/language.rb +45 -0
- data/lib/adapi/keyword.rb +15 -5
- data/lib/adapi/location.rb +91 -0
- data/lib/adapi/version.rb +8 -1
- data/lib/httpi_request_monkeypatch.rb +4 -0
- data/test/config/adapi.yml.template +21 -0
- data/test/config/adwords_api.yml.template +10 -0
- data/test/integration/create_campaign_test.rb +54 -0
- data/test/test_helper.rb +2 -3
- data/test/unit/ad_group_test.rb +3 -4
- data/test/unit/ad_test.rb +1 -1
- data/test/unit/campaign_criterion_test.rb +23 -0
- data/test/unit/config_test.rb +52 -0
- metadata +48 -35
- data/examples/add_campaign_targets.rb +0 -26
- data/test/unit/campaign_target_test.rb +0 -51
data/examples/add_text_ad.rb
CHANGED
@@ -5,7 +5,7 @@ require 'adapi'
|
|
5
5
|
# this example shows how to load custom settings
|
6
6
|
|
7
7
|
Adapi::Config.load_settings(
|
8
|
-
:
|
8
|
+
:dir => File.expand_path(File.dirname(__FILE__)),
|
9
9
|
:filename => 'custom_settings.yml'
|
10
10
|
)
|
11
11
|
|
@@ -16,4 +16,3 @@ p Adapi::Config.read[:authentication][:email]
|
|
16
16
|
Adapi::Config.set(:sandbox)
|
17
17
|
p "Set :sandbox account:"
|
18
18
|
p Adapi::Config.read[:authentication][:email]
|
19
|
-
|
data/examples/delete_keyword.rb
CHANGED
data/examples/find_campaign.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'adapi'
|
4
4
|
|
5
5
|
# create campaign
|
6
|
-
|
6
|
+
require_relative 'add_campaign'
|
7
7
|
|
8
8
|
$campaign = Adapi::Campaign.find_complete($campaign.id).to_hash
|
9
9
|
|
@@ -18,10 +18,10 @@ puts "Budget period: %s" % $campaign[:budget][:period]
|
|
18
18
|
puts "\nBidding strategy type: %s" % $campaign[:bidding_strategy][:xsi_type]
|
19
19
|
# TODO bidding_strategy.bid_ceiling
|
20
20
|
|
21
|
-
puts "\
|
22
|
-
$campaign[:
|
23
|
-
p
|
24
|
-
p
|
21
|
+
puts "\nCriteria:"
|
22
|
+
$campaign[:criteria].each do |criterion|
|
23
|
+
p criterion[:xsi_type]
|
24
|
+
p criterion
|
25
25
|
end
|
26
26
|
|
27
27
|
puts "\nAd groups (#{$campaign[:ad_groups].size} in total):"
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Encoding: utf-8
|
3
|
+
#
|
4
|
+
# Author:: api.dklimkin@gmail.com (Danial Klimkin)
|
5
|
+
#
|
6
|
+
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
|
7
|
+
#
|
8
|
+
# License:: Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
17
|
+
# implied.
|
18
|
+
# See the License for the specific language governing permissions and
|
19
|
+
# limitations under the License.
|
20
|
+
#
|
21
|
+
# This example illustrates how to retrieve all the campaign targets. To set
|
22
|
+
# campaign targets, run add_campaign_targeting_criteria.rb.
|
23
|
+
#
|
24
|
+
# Tags: CampaignCriterionService.get
|
25
|
+
|
26
|
+
require 'adwords_api'
|
27
|
+
|
28
|
+
API_VERSION = :v201109
|
29
|
+
PAGE_SIZE = 500
|
30
|
+
|
31
|
+
def get_campaign_targeting_criteria()
|
32
|
+
# AdwordsApi::Api will read a config file from ENV['HOME']/adwords_api.yml
|
33
|
+
# when called without parameters.
|
34
|
+
adwords = AdwordsApi::Api.new
|
35
|
+
|
36
|
+
# To enable logging of SOAP requests, set the log_level value to 'DEBUG' in
|
37
|
+
# the configuration file or provide your own logger:
|
38
|
+
# adwords.logger = Logger.new('adwords_xml.log')
|
39
|
+
|
40
|
+
campaign_criterion_srv =
|
41
|
+
adwords.service(:CampaignCriterionService, API_VERSION)
|
42
|
+
|
43
|
+
# Specify campaign ID to get targeting for.
|
44
|
+
campaign_id = 'INSERT_CAMPAIGN_ID_HERE'.to_i
|
45
|
+
|
46
|
+
# Selector to get all the targeting for this campaign.
|
47
|
+
selector = {
|
48
|
+
:fields => ['Id', 'CriteriaType', 'KeywordText'],
|
49
|
+
:predicates => [
|
50
|
+
{:field => 'CampaignId', :operator => 'EQUALS', :values => [campaign_id]}
|
51
|
+
],
|
52
|
+
:paging => {
|
53
|
+
:start_index => 0,
|
54
|
+
:number_results => PAGE_SIZE
|
55
|
+
}
|
56
|
+
}
|
57
|
+
|
58
|
+
# Set initial values.
|
59
|
+
offset, page = 0, {}
|
60
|
+
|
61
|
+
begin
|
62
|
+
page = campaign_criterion_srv.get(selector)
|
63
|
+
if page[:entries]
|
64
|
+
page[:entries].each do |typed_criterion|
|
65
|
+
negative = typed_criterion[:xsi_type] == 'NegativeCampaignCriterion' ?
|
66
|
+
' (negative)' : ''
|
67
|
+
criterion = typed_criterion[:criterion]
|
68
|
+
puts ("Campaign criterion%s with ID %d, type '%s' and text '%s'" +
|
69
|
+
" was found.") %
|
70
|
+
[negative, criterion[:id], criterion[:type], criterion[:text]]
|
71
|
+
end
|
72
|
+
# Increment values to request the next page.
|
73
|
+
offset += PAGE_SIZE
|
74
|
+
selector[:paging][:start_index] = offset
|
75
|
+
end
|
76
|
+
end while page[:total_num_entries] > offset
|
77
|
+
|
78
|
+
if page.include?(:total_num_entries)
|
79
|
+
puts "\tCampaign ID %d has %d criteria." %
|
80
|
+
[campaign_id, page[:total_num_entries]]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
if __FILE__ == $0
|
85
|
+
begin
|
86
|
+
get_campaign_targeting_criteria()
|
87
|
+
|
88
|
+
# HTTP errors.
|
89
|
+
rescue AdsCommon::Errors::HttpError => e
|
90
|
+
puts "HTTP Error: %s" % e
|
91
|
+
|
92
|
+
# API errors.
|
93
|
+
rescue AdwordsApi::Errors::ApiException => e
|
94
|
+
puts "Message: %s" % e.message
|
95
|
+
puts 'Errors:'
|
96
|
+
e.errors.each_with_index do |error, index|
|
97
|
+
puts "\tError [%d]:" % (index + 1)
|
98
|
+
error.each do |field, value|
|
99
|
+
puts "\t\t%s: %s" % [field, value]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'adapi'
|
4
|
+
|
5
|
+
# find location by LocationService
|
6
|
+
|
7
|
+
$search_params = [
|
8
|
+
{ :country => 'CZ' },
|
9
|
+
{ :country => 'CZ', :province => 'Prague' },
|
10
|
+
{ :country => 'CZ', :province => 'Prague', :city => 'Prague' },
|
11
|
+
{ :province => 'Prague' },
|
12
|
+
{ :city => 'Prague' }
|
13
|
+
]
|
14
|
+
|
15
|
+
$search_params.each do |params|
|
16
|
+
$location = Adapi::Location.find(params)
|
17
|
+
|
18
|
+
puts "\nSearched for: " + params.inspect
|
19
|
+
# puts "Found Location ID: #{Adapi::Location.location_tree($location)}"
|
20
|
+
puts "Found Location ID: #{$location[:id]}"
|
21
|
+
end
|
@@ -2,8 +2,11 @@
|
|
2
2
|
|
3
3
|
require 'adapi'
|
4
4
|
|
5
|
-
# create
|
6
|
-
#
|
5
|
+
# This test tries to create a complete campaign and fails because ad is
|
6
|
+
# intentionally left without url. The point is to test how create_campaign
|
7
|
+
# fails: campaign status should be set to DELETED and name changed (so the
|
8
|
+
# name isn't blocked and another campaign can be created with the same name
|
9
|
+
# eventually)
|
7
10
|
|
8
11
|
campaign_data = {
|
9
12
|
:name => "Campaign #%d" % (Time.new.to_f * 1000).to_i,
|
@@ -19,9 +22,8 @@ campaign_data = {
|
|
19
22
|
:target_content_contextual => false
|
20
23
|
},
|
21
24
|
|
22
|
-
:
|
25
|
+
:criteria => {
|
23
26
|
:language => [ 'en', 'cs' ],
|
24
|
-
# TODO test together with city target
|
25
27
|
:geo => { :proximity => { :geo_point => '38.89859,-77.035971', :radius => '10 km' } }
|
26
28
|
},
|
27
29
|
|
@@ -37,7 +39,7 @@ campaign_data = {
|
|
37
39
|
:headline => "Code like Neo",
|
38
40
|
:description1 => 'Need mad coding skills?',
|
39
41
|
:description2 => 'Check out my new blog!',
|
40
|
-
:url => '', # THIS
|
42
|
+
:url => '', # THIS SHOULD FAIL
|
41
43
|
:display_url => 'http://www.demcodez.com'
|
42
44
|
}
|
43
45
|
]
|
@@ -54,4 +56,3 @@ pp $campaign.attributes
|
|
54
56
|
|
55
57
|
p "with errors:"
|
56
58
|
pp $campaign.errors.to_a
|
57
|
-
|
data/examples/update_campaign.rb
CHANGED
data/lib/adapi.rb
CHANGED
@@ -2,38 +2,40 @@
|
|
2
2
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'adwords_api'
|
5
|
+
# require 'logger'
|
5
6
|
require 'yaml'
|
6
7
|
require 'pp'
|
7
8
|
|
8
9
|
require 'active_model'
|
9
|
-
# require only ActiveSupport parts that we actually use
|
10
|
+
# TODO require only ActiveSupport parts that we actually use
|
10
11
|
require 'active_support/all'
|
11
12
|
|
12
13
|
require 'adapi/version'
|
13
14
|
require 'adapi/config'
|
14
15
|
require 'adapi/api'
|
15
16
|
require 'adapi/campaign'
|
17
|
+
require 'adapi/campaign_criterion'
|
16
18
|
require 'adapi/campaign_target'
|
17
19
|
require 'adapi/ad_group'
|
18
20
|
require 'adapi/ad_group_criterion'
|
19
21
|
require 'adapi/keyword'
|
20
22
|
require 'adapi/ad'
|
21
23
|
require 'adapi/ad/text_ad'
|
24
|
+
require 'adapi/ad_param'
|
25
|
+
require 'adapi/constant_data'
|
26
|
+
require 'adapi/constant_data/language'
|
27
|
+
require 'adapi/location'
|
22
28
|
|
23
|
-
# monkeypatch HTTPI
|
29
|
+
# monkeypatch HTTPI - important, check the file!
|
24
30
|
require 'httpi_request_monkeypatch'
|
25
31
|
|
26
32
|
HTTPI.adapter = :curb
|
27
|
-
# supress HTTPI output
|
28
|
-
# HTTPI.log = false
|
33
|
+
HTTPI.log = false # supress HTTPI output
|
29
34
|
|
30
35
|
module Adapi
|
31
|
-
API_VERSION = :
|
36
|
+
API_VERSION = :v201109
|
32
37
|
end
|
33
38
|
|
34
|
-
|
35
|
-
# FIXME there's gotta be more elegant way to do this
|
36
|
-
`ruby -v`.to_s =~ /^ruby (\d\.\d)\./
|
37
|
-
if $1.to_f < 1.9
|
39
|
+
if RUBY_VERSION < '1.9'
|
38
40
|
puts "WARNING: please use ruby 1.9, adapi gem won't work properly in 1.8 and earlier versions"
|
39
41
|
end
|
data/lib/adapi/ad/text_ad.rb
CHANGED
@@ -101,7 +101,8 @@ module Adapi
|
|
101
101
|
# supported condition parameters: ad_group_id and id
|
102
102
|
predicates = [ :ad_group_id, :id ].map do |param_name|
|
103
103
|
if params[param_name]
|
104
|
-
|
104
|
+
value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]]
|
105
|
+
{:field => param_name.to_s.camelcase, :operator => 'IN', :values => value }
|
105
106
|
end
|
106
107
|
end.compact
|
107
108
|
|
data/lib/adapi/ad_group.rb
CHANGED
@@ -24,19 +24,22 @@ module Adapi
|
|
24
24
|
# convert bids to GoogleApi format
|
25
25
|
#
|
26
26
|
# can be either string (just xsi_type) or hash (xsi_type with params)
|
27
|
-
#
|
27
|
+
# although I'm not sure if just string makes sense in this case
|
28
28
|
#
|
29
29
|
if @bids
|
30
30
|
unless @bids.is_a?(Hash)
|
31
31
|
@bids = { :xsi_type => @bids }
|
32
32
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
33
|
+
|
34
|
+
# convert bid amounts to micro_amounts
|
35
|
+
[ :proxy_keyword_max_cpc, :proxy_site_max_cpc ].each do |k|
|
36
|
+
if @bids[k] and not @bids[k].is_a?(Hash)
|
37
|
+
@bids[k] = {
|
38
|
+
:amount => {
|
39
|
+
:micro_amount => Api.to_micro_units(@bids[k])
|
40
|
+
}
|
38
41
|
}
|
39
|
-
|
42
|
+
end
|
40
43
|
end
|
41
44
|
end
|
42
45
|
|
@@ -93,10 +96,11 @@ module Adapi
|
|
93
96
|
first_only = (amount.to_sym == :first)
|
94
97
|
|
95
98
|
raise "Campaign ID is required" unless params[:campaign_id]
|
96
|
-
|
99
|
+
|
97
100
|
predicates = [ :campaign_id, :id ].map do |param_name|
|
98
101
|
if params[param_name]
|
99
|
-
|
102
|
+
value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]]
|
103
|
+
{:field => param_name.to_s.camelcase, :operator => 'IN', :values => value }
|
100
104
|
end
|
101
105
|
end.compact
|
102
106
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Adapi
|
4
|
+
class AdParam < Api
|
5
|
+
attr_accessor :ad_group_id, :criterion_id, :insertion_text, :param_index
|
6
|
+
|
7
|
+
validates_presence_of :ad_group_id, :criterion_id
|
8
|
+
|
9
|
+
def attributes
|
10
|
+
super.merge(
|
11
|
+
'ad_group_id' => ad_group_id, 'criterion_id' => criterion_id,
|
12
|
+
'insertion_text' => insertion_text, 'param_index' => param_index
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(params = {})
|
17
|
+
params[:service_name] = :AdParamService
|
18
|
+
|
19
|
+
%w{ ad_group_id criterion_id insertion_text param_index }.each do |param_name|
|
20
|
+
self.send "#{param_name}=", params[param_name.to_sym]
|
21
|
+
end
|
22
|
+
|
23
|
+
super(params)
|
24
|
+
end
|
25
|
+
|
26
|
+
def create
|
27
|
+
operation = {
|
28
|
+
:operator => 'SET',
|
29
|
+
:operand => serializable_hash
|
30
|
+
}
|
31
|
+
|
32
|
+
begin
|
33
|
+
response = @service.mutate([operation])
|
34
|
+
|
35
|
+
#rescue AdsCommon::Errors::HttpError => e
|
36
|
+
#self.errors.add(:base, e.message)
|
37
|
+
|
38
|
+
## traps any exceptions raised by AdWords API
|
39
|
+
#rescue AdwordsApi::Errors::ApiException => e
|
40
|
+
# # return PolicyViolations so they can be sent again
|
41
|
+
# e.errors.each do |error|
|
42
|
+
# if (error[:api_error_type] == 'PolicyViolationError') && error[:is_exemptable]
|
43
|
+
# self.errors.add(error[:api_error_type], error[:key])
|
44
|
+
# else
|
45
|
+
# # otherwise, just report the errors
|
46
|
+
# self.errors.add( "[#{self.xsi_type.underscore}]", "#{error[:error_string]} @ #{error[:field_path]}")
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
end
|
50
|
+
|
51
|
+
response
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.find(params = {})
|
55
|
+
params.symbolize_keys!
|
56
|
+
|
57
|
+
predicates = [ :ad_group_id, :criterion_id ].map do |param_name|
|
58
|
+
if params[param_name]
|
59
|
+
value = Array.try_convert(params[param_name]) ? params_param_name : [params[param_name]]
|
60
|
+
{:field => param_name.to_s.camelcase, :operator => 'IN', :values => value }
|
61
|
+
end
|
62
|
+
end.compact
|
63
|
+
|
64
|
+
selector = {
|
65
|
+
:fields => ['AdGroupId', 'CriterionId', 'InsertionText', 'ParamIndex'],
|
66
|
+
:predicates => predicates
|
67
|
+
}
|
68
|
+
|
69
|
+
response = AdParam.new.service.get(selector)
|
70
|
+
|
71
|
+
response = (response and response[:entries]) ? response[:entries] : []
|
72
|
+
|
73
|
+
response.map! do |ad_params_data|
|
74
|
+
AdParam.new(ad_params_data)
|
75
|
+
end
|
76
|
+
|
77
|
+
response
|
78
|
+
end
|
79
|
+
|
80
|
+
def serializable_hash
|
81
|
+
{
|
82
|
+
:ad_group_id => ad_group_id,
|
83
|
+
:criterion_id => criterion_id,
|
84
|
+
:param_index => param_index,
|
85
|
+
:insertion_text => insertion_text
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|