sem4r 0.1.1
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.
- data/LICENSE +22 -0
- data/README.rdoc +74 -0
- data/Rakefile +126 -0
- data/VERSION.yml +5 -0
- data/bin/sem +34 -0
- data/config/sem4r.example.yml +51 -0
- data/examples_blog/2009-11-29-hello-world.rb +12 -0
- data/examples_blog/2009-12-12-create-campaign.rb +29 -0
- data/examples_blog/2010-02-06-constants-scope.rb +8 -0
- data/examples_blog/2010-02-07-ad-parameters.rb +47 -0
- data/examples_sem4r/01_get_account.rb +37 -0
- data/examples_sem4r/02_get_info.rb +32 -0
- data/examples_sem4r/03_list_ad.rb +47 -0
- data/examples_sem4r/04_list_keywords.rb +54 -0
- data/examples_sem4r/05_request_report.rb +51 -0
- data/examples_sem4r/05_request_report_2010.rb +40 -0
- data/examples_sem4r/06_create_campaigns.rb +86 -0
- data/examples_sem4r/07_create_campaigns_block.rb +99 -0
- data/examples_sem4r/07_create_campaigns_simple.rb +65 -0
- data/examples_sem4r/08_ad_params.rb +70 -0
- data/examples_sem4r/09_targeting_idea.rb +59 -0
- data/examples_sem4r/10_get_location.rb +28 -0
- data/examples_sem4r/11_submit_bulk_job.rb +82 -0
- data/examples_sem4r/12_list_bulk_job.rb +29 -0
- data/examples_sem4r/30_prune_empty_adgroup.rb +50 -0
- data/examples_sem4r/31_empty_accounts.rb +40 -0
- data/examples_sem4r/example_helper.rb +115 -0
- data/lib/sem4r/account.rb +349 -0
- data/lib/sem4r/ad_extension_override/ad_extension_override_service.rb +30 -0
- data/lib/sem4r/ad_group/ad_group.rb +340 -0
- data/lib/sem4r/ad_group/ad_group_bids.rb +170 -0
- data/lib/sem4r/ad_group/ad_group_service.rb +75 -0
- data/lib/sem4r/ad_group/mobile_ad_image.rb +33 -0
- data/lib/sem4r/ad_group_ad/ad_group_ad.rb +98 -0
- data/lib/sem4r/ad_group_ad/ad_group_ad_operations.rb +38 -0
- data/lib/sem4r/ad_group_ad/ad_group_ad_service.rb +56 -0
- data/lib/sem4r/ad_group_ad/ad_group_mobile_ad.rb +137 -0
- data/lib/sem4r/ad_group_ad/ad_group_text_ad.rb +98 -0
- data/lib/sem4r/ad_group_criterion/ad_group_criterion.rb +144 -0
- data/lib/sem4r/ad_group_criterion/ad_group_criterion_bids.rb +115 -0
- data/lib/sem4r/ad_group_criterion/ad_group_criterion_operations.rb +35 -0
- data/lib/sem4r/ad_group_criterion/ad_group_criterion_service.rb +58 -0
- data/lib/sem4r/ad_group_criterion/criterion.rb +67 -0
- data/lib/sem4r/ad_group_criterion/criterion_keyword.rb +80 -0
- data/lib/sem4r/ad_group_criterion/criterion_placement.rb +66 -0
- data/lib/sem4r/ad_param/ad_param.rb +96 -0
- data/lib/sem4r/ad_param/ad_param_operation.rb +34 -0
- data/lib/sem4r/ad_param/ad_param_service.rb +59 -0
- data/lib/sem4r/adwords.rb +242 -0
- data/lib/sem4r/api_counters.rb +7 -0
- data/lib/sem4r/base.rb +43 -0
- data/lib/sem4r/bulk_mutate_job/bulk_mutate_job.rb +108 -0
- data/lib/sem4r/bulk_mutate_job/bulk_mutate_job_selector.rb +56 -0
- data/lib/sem4r/bulk_mutate_job/bulk_mutate_job_service.rb +57 -0
- data/lib/sem4r/bulk_mutate_job/job_operations.rb +35 -0
- data/lib/sem4r/campaign/campaign.rb +193 -0
- data/lib/sem4r/campaign/campaign_service.rb +91 -0
- data/lib/sem4r/campaign_criterion/campaign_criterion_service.rb +30 -0
- data/lib/sem4r/campaign_target/campaign_target_service.rb +30 -0
- data/lib/sem4r/cli/cli_command.rb +101 -0
- data/lib/sem4r/cli/cli_commands.rb +60 -0
- data/lib/sem4r/cli/cli_common_args.rb +293 -0
- data/lib/sem4r/cli/cli_download_report.rb +81 -0
- data/lib/sem4r/cli/cli_helpers.rb +57 -0
- data/lib/sem4r/cli/cli_ideas.rb +100 -0
- data/lib/sem4r/cli/cli_list_ads.rb +74 -0
- data/lib/sem4r/cli/cli_list_keywords.rb +76 -0
- data/lib/sem4r/cli/cli_request_report.rb +104 -0
- data/lib/sem4r/cli/cli_sem.rb +64 -0
- data/lib/sem4r/common/operation.rb +78 -0
- data/lib/sem4r/credentials.rb +86 -0
- data/lib/sem4r/extensions.rb +62 -0
- data/lib/sem4r/geo_location/geo_location_service.rb +59 -0
- data/lib/sem4r/info/info_service.rb +115 -0
- data/lib/sem4r/report_definition/report_definition.rb +104 -0
- data/lib/sem4r/report_definition/report_definition_operation.rb +34 -0
- data/lib/sem4r/report_definition/report_definition_selector.rb +31 -0
- data/lib/sem4r/report_definition/report_definition_service.rb +55 -0
- data/lib/sem4r/sem4r_error.rb +28 -0
- data/lib/sem4r/services/service.rb +74 -0
- data/lib/sem4r/services/soap_call.rb +100 -0
- data/lib/sem4r/services/soap_connector.rb +284 -0
- data/lib/sem4r/services/soap_error.rb +38 -0
- data/lib/sem4r/services/soap_message_v13.rb +129 -0
- data/lib/sem4r/services/soap_message_v2009.rb +170 -0
- data/lib/sem4r/soap_attributes.rb +141 -0
- data/lib/sem4r/targeting_idea/targeting_idea.rb +158 -0
- data/lib/sem4r/targeting_idea/targeting_idea_selector.rb +200 -0
- data/lib/sem4r/targeting_idea/targeting_idea_service.rb +51 -0
- data/lib/sem4r/v13_account/account_service.rb +54 -0
- data/lib/sem4r/v13_account/billing_address.rb +67 -0
- data/lib/sem4r/v13_report/report.rb +185 -0
- data/lib/sem4r/v13_report/report_job.rb +51 -0
- data/lib/sem4r/v13_report/report_service.rb +89 -0
- data/lib/sem4r/v13_traffic_estimator/traffic_estimator_service.rb +30 -0
- data/lib/sem4r.rb +142 -0
- data/lib/sem4r_cli.rb +40 -0
- data/sem4r.gemspec +247 -0
- data/spec/aggregates_spec_helper.rb +59 -0
- data/spec/fixtures/sem4r.example.yml +26 -0
- data/spec/fixtures/services/ad_group/get-first-req.xml +28 -0
- data/spec/fixtures/services/ad_group/get-first-res.xml +91 -0
- data/spec/fixtures/services/ad_group/get-manual-cpm-bids-req.xml +106 -0
- data/spec/fixtures/services/ad_group/get-manual-cpm-bids-res.xml +75 -0
- data/spec/fixtures/services/ad_group/mutate_add-req.xml +52 -0
- data/spec/fixtures/services/ad_group/mutate_add-res.xml +33 -0
- data/spec/fixtures/services/ad_group_ad/get_mobile_ad-req.xml +28 -0
- data/spec/fixtures/services/ad_group_ad/get_mobile_ad-res.xml +194 -0
- data/spec/fixtures/services/ad_group_ad/get_text_ad-req.xml +29 -0
- data/spec/fixtures/services/ad_group_ad/get_text_ad-res.xml +55 -0
- data/spec/fixtures/services/ad_group_ad/mutate_add_mobile_ad-req.xml +50 -0
- data/spec/fixtures/services/ad_group_ad/mutate_add_mobile_ad-res.xml +42 -0
- data/spec/fixtures/services/ad_group_ad/mutate_add_text_ad-req.xml +37 -0
- data/spec/fixtures/services/ad_group_ad/mutate_add_text_ad-res.xml +32 -0
- data/spec/fixtures/services/ad_group_ad/mutate_add_two_criterions-req.xml +83 -0
- data/spec/fixtures/services/ad_group_ad/mutate_add_two_criterions-res.xml +95 -0
- data/spec/fixtures/services/ad_group_criterion/get-req.xml +28 -0
- data/spec/fixtures/services/ad_group_criterion/get-res.xml +242 -0
- data/spec/fixtures/services/ad_group_criterion/mutate_add_criterion_keyword-req.xml +48 -0
- data/spec/fixtures/services/ad_group_criterion/mutate_add_criterion_keyword-res.xml +47 -0
- data/spec/fixtures/services/ad_group_criterion/mutate_add_criterion_placement-req.xml +35 -0
- data/spec/fixtures/services/ad_group_criterion/mutate_add_criterion_placement-res.xml +32 -0
- data/spec/fixtures/services/ad_group_criterion/mutate_add_negative_keyword-req.xml +37 -0
- data/spec/fixtures/services/ad_group_criterion/mutate_add_negative_keyword-res.xml +51 -0
- data/spec/fixtures/services/ad_param/mutate_set-req.xml +43 -0
- data/spec/fixtures/services/ad_param/mutate_set-res.xml +29 -0
- data/spec/fixtures/services/bulk_mutate_job/get-req.xml +36 -0
- data/spec/fixtures/services/bulk_mutate_job/get-res.xml +54 -0
- data/spec/fixtures/services/bulk_mutate_job/mutate-req.xml +69 -0
- data/spec/fixtures/services/bulk_mutate_job/mutate-res.xml +48 -0
- data/spec/fixtures/services/campaign/get-req.xml +37 -0
- data/spec/fixtures/services/campaign/get-res.xml +1986 -0
- data/spec/fixtures/services/campaign/mutate_add-req.xml +37 -0
- data/spec/fixtures/services/campaign/mutate_add-res.xml +42 -0
- data/spec/fixtures/services/error.xml +28 -0
- data/spec/fixtures/services/info/get_unit_count-req.xml +30 -0
- data/spec/fixtures/services/info/get_unit_count-res.xml +29 -0
- data/spec/fixtures/services/report_definition/mutate_add-req.xml +24 -0
- data/spec/fixtures/services/report_definition/mutate_add-req_orig.xml +45 -0
- data/spec/fixtures/services/targeting_idea/get-req-all-options.xml +57 -0
- data/spec/fixtures/services/targeting_idea/get-req.xml +60 -0
- data/spec/fixtures/services/targeting_idea/get-res.xml +3601 -0
- data/spec/fixtures/services/v13_account/get_account_info-req.xml +23 -0
- data/spec/fixtures/services/v13_account/get_account_info-res.xml +54 -0
- data/spec/fixtures/services/v13_account/get_client_accounts-req.xml +22 -0
- data/spec/fixtures/services/v13_account/get_client_accounts-res.xml +37 -0
- data/spec/fixtures/services/v13_report/get_all_jobs-req.xml +21 -0
- data/spec/fixtures/services/v13_report/get_all_jobs-res.xml +109 -0
- data/spec/fixtures/services/v13_report/schedule_report_job-req.xml +56 -0
- data/spec/fixtures/services/v13_report/schedule_report_job-res.xml +24 -0
- data/spec/sem4r/account_spec.rb +86 -0
- data/spec/sem4r/ad_group/ad_group_bids_spec.rb +67 -0
- data/spec/sem4r/ad_group/ad_group_service_spec.rb +66 -0
- data/spec/sem4r/ad_group/ad_group_spec.rb +212 -0
- data/spec/sem4r/ad_group_ad/ad_group_ad_operation_spec.rb +88 -0
- data/spec/sem4r/ad_group_ad/ad_group_ad_service_spec.rb +55 -0
- data/spec/sem4r/ad_group_ad/ad_group_ad_spec.rb +173 -0
- data/spec/sem4r/ad_group_criterion/ad_group_criterion_bids_spec.rb +60 -0
- data/spec/sem4r/ad_group_criterion/ad_group_criterion_service_spec.rb +55 -0
- data/spec/sem4r/ad_group_criterion/ad_group_criterion_spec.rb +103 -0
- data/spec/sem4r/ad_group_criterion/criterion_spec.rb +85 -0
- data/spec/sem4r/ad_param/ad_param_service_spec.rb +55 -0
- data/spec/sem4r/ad_param/ad_param_spec.rb +59 -0
- data/spec/sem4r/adwords_spec.rb +110 -0
- data/spec/sem4r/bulk_mutate_job/bulk_mutate_job_service_spec.rb +63 -0
- data/spec/sem4r/bulk_mutate_job/bulk_mutate_job_spec.rb +69 -0
- data/spec/sem4r/bulk_mutate_job/job_operation_spec.rb +48 -0
- data/spec/sem4r/campaign/campaign_service_spec.rb +66 -0
- data/spec/sem4r/campaign/campaign_spec.rb +105 -0
- data/spec/sem4r/cli/cli_spec.rb +71 -0
- data/spec/sem4r/credentials_spec.rb +65 -0
- data/spec/sem4r/report_definition/report_definition_service_spec.rb +44 -0
- data/spec/sem4r/report_definition/report_definition_spec.rb +105 -0
- data/spec/sem4r/rexml_parsing_spec.rb +103 -0
- data/spec/sem4r/services/service_spec.rb +36 -0
- data/spec/sem4r/services/soap_call_spec.rb +115 -0
- data/spec/sem4r/services/soap_message_v13_spec.rb +54 -0
- data/spec/sem4r/soap_attributes_spec.rb +116 -0
- data/spec/sem4r/targeting_idea/targeting_idea_selector_spec.rb +120 -0
- data/spec/sem4r/targeting_idea/targeting_idea_service_spec.rb +44 -0
- data/spec/sem4r/targeting_idea/targeting_idea_spec.rb +53 -0
- data/spec/sem4r/v13_account/account_service_spec.rb +60 -0
- data/spec/sem4r/v13_report/report_service_spec.rb +104 -0
- data/spec/sem4r/v13_report/report_spec.rb +79 -0
- data/spec/sem4r_spec_helper.rb +353 -0
- data/spec/spec_helper.rb +12 -0
- metadata +375 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# -------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2009-2010 Sem4r sem4ruby@gmail.com
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
# a copy of this software and associated documentation files (the
|
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
# the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be
|
|
13
|
+
# included in all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
# -------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
module Sem4r
|
|
25
|
+
class AdGroup < Base
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# enum AdGroup.Status
|
|
29
|
+
#
|
|
30
|
+
enum :Statuses, [
|
|
31
|
+
:ENABLED,
|
|
32
|
+
:PAUSED,
|
|
33
|
+
:DELETED
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
attr_reader :campaign
|
|
37
|
+
|
|
38
|
+
g_accessor :name
|
|
39
|
+
g_accessor :status
|
|
40
|
+
|
|
41
|
+
def initialize(campaign, name = nil, &block)
|
|
42
|
+
super( campaign.adwords, campaign.credentials )
|
|
43
|
+
@campaign = campaign
|
|
44
|
+
@id = nil
|
|
45
|
+
@criterions = nil
|
|
46
|
+
self.name = name unless name.nil?
|
|
47
|
+
if block_given?
|
|
48
|
+
@inside_initialize = true
|
|
49
|
+
block.arity < 1 ? instance_eval(&block) : block.call(self)
|
|
50
|
+
save unless campaign.inside_initialize?
|
|
51
|
+
end
|
|
52
|
+
@inside_initialize = false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def inside_initialize?
|
|
56
|
+
@inside_initialize
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def id
|
|
60
|
+
_save unless @id
|
|
61
|
+
@id
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def to_s
|
|
65
|
+
"#{@id ? @id : 'unsaved'} '#{@name}' (#{@status}) - #{@bid}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def xml(t)
|
|
69
|
+
t.campaignId campaign.id
|
|
70
|
+
t.name name
|
|
71
|
+
t.status "ENABLED"
|
|
72
|
+
@bids.to_xml(t) if @bids
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def to_xml(tag)
|
|
76
|
+
builder = Builder::XmlMarkup.new
|
|
77
|
+
builder.tag!(tag) { |t|
|
|
78
|
+
xml(t)
|
|
79
|
+
# xml.campaignId campaign.id
|
|
80
|
+
# xml.name name
|
|
81
|
+
# xml.status "ENABLED"
|
|
82
|
+
# @bids.to_xml(xml) if @bids
|
|
83
|
+
}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def empty?
|
|
87
|
+
criterions.empty?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
###########################################################################
|
|
91
|
+
# management
|
|
92
|
+
|
|
93
|
+
def self.create(campaign, &block)
|
|
94
|
+
new(campaign, &block).save
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.from_element(campaign, el)
|
|
98
|
+
new(campaign) do
|
|
99
|
+
@id = el.elements["id"].text.strip.to_i
|
|
100
|
+
name el.elements["name"].text.strip
|
|
101
|
+
status el.elements["status"].text.strip
|
|
102
|
+
bids el.elements["bids"]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def save
|
|
107
|
+
_save
|
|
108
|
+
_save_criterions
|
|
109
|
+
_save_ads
|
|
110
|
+
_save_ad_params
|
|
111
|
+
self
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def _save
|
|
117
|
+
unless @id
|
|
118
|
+
soap_message = service.ad_group.create(credentials, to_xml("operand"))
|
|
119
|
+
add_counters( soap_message.counters )
|
|
120
|
+
rval = REXML::XPath.first( soap_message.response, "//mutateResponse/rval")
|
|
121
|
+
id = REXML::XPath.match( rval, "value/id" ).first
|
|
122
|
+
@id = id.text.strip.to_i
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
public
|
|
127
|
+
|
|
128
|
+
def delete
|
|
129
|
+
soap_message = service.ad_group.delete(credentials, @id)
|
|
130
|
+
add_counters( soap_message.counters )
|
|
131
|
+
@id = -1 # logical delete
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
###########################################################################
|
|
135
|
+
# bids management
|
|
136
|
+
|
|
137
|
+
def bids(el = nil)
|
|
138
|
+
@bids = AdGroupBids.from_element(el) if el
|
|
139
|
+
@bids
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def manual_cpc_bids(&block)
|
|
143
|
+
@bids = ManualCPCAdGroupBids.new(&block)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def manual_cpm_bids(&block)
|
|
147
|
+
@bids = ManualCPMAdGroupBids.new(&block)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
###########################################################################
|
|
151
|
+
# ad management
|
|
152
|
+
|
|
153
|
+
def text_ad(&block)
|
|
154
|
+
ad = AdGroupTextAd.new(self, &block)
|
|
155
|
+
@ads ||= []
|
|
156
|
+
@ads.push(ad)
|
|
157
|
+
ad
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def mobile_ad(&block)
|
|
161
|
+
ad = AdGroupMobileAd.new(self, &block)
|
|
162
|
+
@ads ||= []
|
|
163
|
+
@ads.push(ad)
|
|
164
|
+
ad
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def ads(refresh = false)
|
|
168
|
+
_ads unless @ads and !refresh
|
|
169
|
+
@ads
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def p_ads(refresh = false)
|
|
173
|
+
ads(refresh).each do |ad|
|
|
174
|
+
puts ad.to_s
|
|
175
|
+
end
|
|
176
|
+
self
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
private
|
|
180
|
+
|
|
181
|
+
def _ads
|
|
182
|
+
soap_message = service.ad_group_ad.all(credentials, id)
|
|
183
|
+
add_counters( soap_message.counters )
|
|
184
|
+
rval = REXML::XPath.first( soap_message.response, "//getResponse/rval")
|
|
185
|
+
els = REXML::XPath.match( rval, "entries/ad")
|
|
186
|
+
@ads = els.map do |el|
|
|
187
|
+
AdGroupAd.from_element( self, el )
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def _save_ads
|
|
192
|
+
return unless @ads
|
|
193
|
+
|
|
194
|
+
unsaved_ads = @ads.select { |a| !a.saved? }
|
|
195
|
+
return if unsaved_ads.empty?
|
|
196
|
+
|
|
197
|
+
xml = unsaved_ads.inject('') do |xml,ad|
|
|
198
|
+
o = AdGroupAdOperation.new.add(ad)
|
|
199
|
+
xml + o.to_xml("operations")
|
|
200
|
+
end
|
|
201
|
+
soap_message = service.ad_group_ad.mutate(credentials, xml)
|
|
202
|
+
add_counters( soap_message.counters )
|
|
203
|
+
els = REXML::XPath.match( soap_message.response, "//mutateResponse/rval/value/ad/id")
|
|
204
|
+
els.each_with_index do |e,index|
|
|
205
|
+
id = e.text.strip.to_i
|
|
206
|
+
unsaved_ads[index].instance_eval{ @id = id }
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
public
|
|
211
|
+
|
|
212
|
+
###########################################################################
|
|
213
|
+
# criterion management
|
|
214
|
+
|
|
215
|
+
def negative_keyword(text = nil, match = CriterionKeyword::BROAD, &block)
|
|
216
|
+
negative_criterion = NegativeAdGroupCriterion.new(self)
|
|
217
|
+
criterion = CriterionKeyword.new(self, text, match, &block)
|
|
218
|
+
negative_criterion.criterion = criterion
|
|
219
|
+
@criterions ||= []
|
|
220
|
+
@criterions.push( negative_criterion )
|
|
221
|
+
negative_criterion
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
#
|
|
225
|
+
# instanziate an BiddableAdGroupCriterion but it is called 'keyword' for convenience
|
|
226
|
+
#
|
|
227
|
+
# http://code.google.com/apis/adwords/v2009/docs/reference/AdGroupCriterionService.BiddableAdGroupCriterion.html
|
|
228
|
+
#
|
|
229
|
+
def keyword(text = nil, match = nil, &block)
|
|
230
|
+
biddable_criterion = BiddableAdGroupCriterion.new(self)
|
|
231
|
+
criterion = CriterionKeyword.new(self, text, match, &block)
|
|
232
|
+
biddable_criterion.criterion = criterion
|
|
233
|
+
@criterions ||= []
|
|
234
|
+
@criterions.push( biddable_criterion )
|
|
235
|
+
biddable_criterion
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def placement(url = nil, &block)
|
|
239
|
+
biddable_criterion = BiddableAdGroupCriterion.new(self)
|
|
240
|
+
criterion = CriterionPlacement.new(self, url, &block)
|
|
241
|
+
biddable_criterion.criterion = criterion
|
|
242
|
+
@criterions ||= []
|
|
243
|
+
@criterions.push( biddable_criterion )
|
|
244
|
+
biddable_criterion
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def criterions(refresh = false)
|
|
248
|
+
_criterions unless @criterions and !refresh
|
|
249
|
+
@criterions
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def p_criterions(refresh = false)
|
|
253
|
+
criterions(refresh).each do |criterion|
|
|
254
|
+
puts criterion.to_s
|
|
255
|
+
end
|
|
256
|
+
self
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def find_criterion(criterion_id, refresh = false)
|
|
260
|
+
_criterions unless @criterions and !refresh
|
|
261
|
+
@criterions.find {|c| c.id == criterion_id}
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
private
|
|
265
|
+
|
|
266
|
+
def _criterions
|
|
267
|
+
soap_message = service.ad_group_criterion.all(credentials, id)
|
|
268
|
+
add_counters( soap_message.counters )
|
|
269
|
+
rval = REXML::XPath.first( soap_message.response, "//getResponse/rval")
|
|
270
|
+
els = REXML::XPath.match( rval, "entries/criterion")
|
|
271
|
+
@criterions = els.map do |el|
|
|
272
|
+
Criterion.from_element( self, el )
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def _save_criterions
|
|
277
|
+
# @criterions.each { |criterion| criterion.save } if @criterions
|
|
278
|
+
|
|
279
|
+
return unless @criterions
|
|
280
|
+
|
|
281
|
+
unsaved_criterions = @criterions.select { |a| !a.saved? }
|
|
282
|
+
return if unsaved_criterions.empty?
|
|
283
|
+
|
|
284
|
+
xml = unsaved_criterions.inject('') do |xml,ad|
|
|
285
|
+
o = AdGroupCriterionOperation.new.add(ad)
|
|
286
|
+
xml + o.to_xml("operations")
|
|
287
|
+
end
|
|
288
|
+
soap_message = service.ad_group_criterion.mutate(credentials, xml)
|
|
289
|
+
add_counters( soap_message.counters )
|
|
290
|
+
els = REXML::XPath.match( soap_message.response, "//mutateResponse/rval/value/criterion/id")
|
|
291
|
+
els.each_with_index do |e,index|
|
|
292
|
+
id = e.text.strip.to_i
|
|
293
|
+
unsaved_criterions[index].criterion.instance_eval{ @id = id }
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
public
|
|
298
|
+
|
|
299
|
+
###########################################################################
|
|
300
|
+
# ad param management
|
|
301
|
+
|
|
302
|
+
def ad_param(criterion, index = nil, text = nil, &block)
|
|
303
|
+
ad_param = AdParam.new(self, criterion, index, text, &block)
|
|
304
|
+
ad_param.save unless inside_initialize? or criterion.inside_initialize?
|
|
305
|
+
@ad_params ||= []
|
|
306
|
+
@ad_params.push( ad_param )
|
|
307
|
+
ad_param
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def ad_params(refresh = false)
|
|
311
|
+
_ad_params unless @ad_params and !refresh
|
|
312
|
+
@ad_params
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def p_ad_params(refresh = false)
|
|
316
|
+
ad_params(refresh).each do |ad_param|
|
|
317
|
+
puts ad_param.to_s
|
|
318
|
+
end
|
|
319
|
+
self
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
private
|
|
323
|
+
|
|
324
|
+
def _ad_params
|
|
325
|
+
soap_message = service.ad_param.all(credentials, id)
|
|
326
|
+
add_counters( soap_message.counters )
|
|
327
|
+
rval = REXML::XPath.first( soap_message.response, "//getResponse/rval")
|
|
328
|
+
els = REXML::XPath.match( rval, "entries")
|
|
329
|
+
@ad_params = els.map do |el|
|
|
330
|
+
AdParam.from_element( self, el )
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def _save_ad_params
|
|
335
|
+
return unless @ad_params
|
|
336
|
+
@ad_params.each { |p| p.save }
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
end
|
|
340
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# -------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2009-2010 Sem4r sem4ruby@gmail.com
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
# a copy of this software and associated documentation files (the
|
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
# the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be
|
|
13
|
+
# included in all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
# -------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
module Sem4r
|
|
25
|
+
class AdGroupBids
|
|
26
|
+
include SoapAttributes
|
|
27
|
+
|
|
28
|
+
enum :Types, [
|
|
29
|
+
:BudgetOptimizerAdGroupBids,
|
|
30
|
+
:ConversionOptimizerAdGroupBids,
|
|
31
|
+
:ManualCPCAdGroupBids,
|
|
32
|
+
:ManualCPMAdGroupBids]
|
|
33
|
+
|
|
34
|
+
# g_accessor :type
|
|
35
|
+
|
|
36
|
+
def initialize(&block)
|
|
37
|
+
if block_given?
|
|
38
|
+
block.arity < 1 ? instance_eval(&block) : block.call(self)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.from_element(el)
|
|
43
|
+
type = el.elements["AdGroupBids.Type"].text.strip
|
|
44
|
+
klass = Module::const_get(type)
|
|
45
|
+
klass.from_element(el)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_s
|
|
49
|
+
"#{@maxcpc / 10000} cents"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class BudgetOptimizerAdGroupBids < AdGroupBids
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class ConversionOptimizerAdGroupBids < AdGroupBids
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class ManualCPCAdGroupBids < AdGroupBids
|
|
60
|
+
|
|
61
|
+
g_accessor :keyword_max_cpc
|
|
62
|
+
g_accessor :site_max_cpc
|
|
63
|
+
|
|
64
|
+
def xml(t)
|
|
65
|
+
return "" unless @keyword_max_cpc or @site_max_cpc
|
|
66
|
+
t.tag!('bids', 'xsi:type' => 'ManualCPCAdGroupBids') { |xml|
|
|
67
|
+
xml.tag!('AdGroupBids.Type') { xml.text! 'ManualCPCAdGroupBids' }
|
|
68
|
+
|
|
69
|
+
if @keyword_max_cpc
|
|
70
|
+
xml.keywordMaxCpc {
|
|
71
|
+
xml.amount {
|
|
72
|
+
xml.tag!('ComparableValue.Type') { xml.text! 'Money' }
|
|
73
|
+
xml.microAmount keyword_max_cpc
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
if @site_max_cpc
|
|
79
|
+
xml.siteMaxCpc {
|
|
80
|
+
xml.amount {
|
|
81
|
+
xml.tag!('ComparableValue.Type') { xml.text! 'Money' }
|
|
82
|
+
xml.microAmount site_max_cpc
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
end
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_xml(builder = nil)
|
|
90
|
+
builder = Builder::XmlMarkup.new unless builder
|
|
91
|
+
xml(builder)
|
|
92
|
+
# xml = builder.tag!('bids', 'xsi:type' => 'ManualCPCAdGroupBids') { |xml|
|
|
93
|
+
# xml.tag!('AdGroupBids.Type') { xml.text! 'ManualCPCAdGroupBids' }
|
|
94
|
+
#
|
|
95
|
+
# if @keyword_max_cpc
|
|
96
|
+
# xml.keywordMaxCpc {
|
|
97
|
+
# xml.amount {
|
|
98
|
+
# xml.tag!('ComparableValue.Type') { xml.text! 'Money' }
|
|
99
|
+
# xml.microAmount keyword_max_cpc
|
|
100
|
+
# }
|
|
101
|
+
# }
|
|
102
|
+
# end
|
|
103
|
+
#
|
|
104
|
+
# if @site_max_cpc
|
|
105
|
+
# xml.siteMaxCpc {
|
|
106
|
+
# xml.amount {
|
|
107
|
+
# xml.tag!('ComparableValue.Type') { xml.text! 'Money' }
|
|
108
|
+
# xml.microAmount site_max_cpc
|
|
109
|
+
# }
|
|
110
|
+
# }
|
|
111
|
+
# end
|
|
112
|
+
# }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.from_element(el)
|
|
116
|
+
new do
|
|
117
|
+
kel = el.elements["keywordMaxCpc"]
|
|
118
|
+
if kel
|
|
119
|
+
el_amount = kel.elements["amount"]
|
|
120
|
+
keyword_max_cpc el_amount.elements["microAmount"].text.strip.to_i
|
|
121
|
+
end
|
|
122
|
+
sel = el.elements["siteMaxCpc"]
|
|
123
|
+
if sel
|
|
124
|
+
el_amount = sel.elements["amount"]
|
|
125
|
+
site_max_cpc el_amount.elements["microAmount"].text.strip.to_i
|
|
126
|
+
end
|
|
127
|
+
# TODO: it is possible something like:
|
|
128
|
+
# el.elements["maxCpc"] do |el|
|
|
129
|
+
# el.elements["amount"] do el
|
|
130
|
+
# max_cpc el["microAmount"]
|
|
131
|
+
# end
|
|
132
|
+
# end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
class ManualCPMAdGroupBids < AdGroupBids
|
|
138
|
+
g_accessor :max_cpm
|
|
139
|
+
|
|
140
|
+
def xml(t)
|
|
141
|
+
return "" unless @max_cpm
|
|
142
|
+
t.tag!('bids', 'xsi:type' => 'ManualCPMAdGroupBids') { |xml|
|
|
143
|
+
xml.tag!('AdGroupBids.Type') { xml.text! 'ManualCPMAdGroupBids' }
|
|
144
|
+
xml.maxCpm {
|
|
145
|
+
xml.amount {
|
|
146
|
+
xml.tag!('ComparableValue.Type') { xml.text! 'Money' }
|
|
147
|
+
xml.microAmount max_cpm
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def to_xml(builder = nil)
|
|
154
|
+
builder = Builder::XmlMarkup.new unless builder
|
|
155
|
+
xml(builder)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def self.from_element(el)
|
|
159
|
+
new do
|
|
160
|
+
kel = el.elements["maxCpm"]
|
|
161
|
+
if kel
|
|
162
|
+
el_amount = kel.elements["amount"]
|
|
163
|
+
max_cpm el_amount.elements["microAmount"].text.strip.to_i
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# -------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2009-2010 Sem4r sem4ruby@gmail.com
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
# a copy of this software and associated documentation files (the
|
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
# the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be
|
|
13
|
+
# included in all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
# -------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
module Sem4r
|
|
25
|
+
class AdGroupService
|
|
26
|
+
include SoapCall
|
|
27
|
+
|
|
28
|
+
def initialize(connector)
|
|
29
|
+
@connector = connector
|
|
30
|
+
@service_namespace = "https://adwords.google.com/api/adwords/cm/v201003"
|
|
31
|
+
@header_namespace = @service_namespace
|
|
32
|
+
@sandbox_service_url = "https://adwords-sandbox.google.com/api/adwords/cm/v201003/AdGroupService"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
soap_call_v2009 :all, :mutate => false
|
|
36
|
+
soap_call_v2009 :create, :mutate => true
|
|
37
|
+
soap_call_v2009 :delete, :mutate => true
|
|
38
|
+
|
|
39
|
+
def _all(campaign_id)
|
|
40
|
+
<<-EOFS
|
|
41
|
+
<get xmlns="#{@service_namespace}">
|
|
42
|
+
<selector>
|
|
43
|
+
<campaignIds>#{campaign_id}</campaignIds>
|
|
44
|
+
</selector>
|
|
45
|
+
</get>
|
|
46
|
+
EOFS
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def _create( xml )
|
|
50
|
+
<<-EOFS
|
|
51
|
+
<mutate xmlns="#{@service_namespace}">
|
|
52
|
+
<operations xsi:type="AdGroupOperation">
|
|
53
|
+
<operator>ADD</operator>
|
|
54
|
+
#{xml}
|
|
55
|
+
</operations>
|
|
56
|
+
</mutate>
|
|
57
|
+
EOFS
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def _delete( id )
|
|
61
|
+
<<-EOFS
|
|
62
|
+
<mutate xmlns="#{@service_namespace}">
|
|
63
|
+
<operations xsi:type="AdGroupOperation">
|
|
64
|
+
<operator>SET</operator>
|
|
65
|
+
<operand>
|
|
66
|
+
<id>#{id}</id>
|
|
67
|
+
<status>DELETED</status>
|
|
68
|
+
</operand>
|
|
69
|
+
</operations>
|
|
70
|
+
</mutate>
|
|
71
|
+
EOFS
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2009-2010 Sem4r sem4ruby@gmail.com
|
|
3
|
+
#
|
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
|
5
|
+
# a copy of this software and associated documentation files (the
|
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
|
10
|
+
# the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be
|
|
13
|
+
# included in all copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
#
|
|
23
|
+
# -------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
module Sem4r
|
|
26
|
+
|
|
27
|
+
class MobileAdImage
|
|
28
|
+
def initialize(mobile_ad, &block)
|
|
29
|
+
@mobile_ad = mobile_ad
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|