zuck 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/CHANGELOG.markdown +35 -5
- data/Gemfile +3 -2
- data/Gemfile.lock +13 -10
- data/README.markdown +26 -38
- data/VERSION +1 -1
- data/console +8 -2
- data/lib/zuck/facebook/ad_account.rb +22 -5
- data/lib/zuck/facebook/ad_campaign.rb +12 -15
- data/lib/zuck/facebook/ad_creative.rb +14 -14
- data/lib/zuck/facebook/ad_group.rb +12 -12
- data/lib/zuck/facebook/ad_interest.rb +12 -15
- data/lib/zuck/facebook/ad_set.rb +30 -0
- data/lib/zuck/facebook/targeting_spec.rb +4 -4
- data/lib/zuck/fb_object/dsl.rb +15 -7
- data/lib/zuck/fb_object/hash_delegator.rb +20 -3
- data/lib/zuck/fb_object/write.rb +18 -6
- data/lib/zuck/fb_object.rb +1 -1
- data/lib/zuck/helpers.rb +20 -1
- data/spec/fixtures/a_single_account.yml +146 -29
- data/spec/fixtures/a_single_campaign.yml +61 -12
- data/spec/fixtures/a_single_group.yml +65 -12
- data/spec/fixtures/ad_interest_search_disney.yml +16 -14
- data/spec/fixtures/ad_interest_search_moviepilot.yml +15 -98
- data/spec/fixtures/ad_interest_search_nonexistant.yml +7 -5
- data/spec/fixtures/create_ad_campaign.yml +334 -9
- data/spec/fixtures/create_ad_group.yml +154 -9
- data/spec/fixtures/create_ad_set.yml +95 -0
- data/spec/fixtures/delete_ad_group.yml +55 -8
- data/spec/fixtures/find_a_single_group_and_update_it.yml +122 -95
- data/spec/fixtures/list_of_ad_accounts.yml +77 -58
- data/spec/fixtures/list_of_ad_campaigns.yml +77 -55
- data/spec/fixtures/list_of_ad_creatives.yml +61 -13
- data/spec/fixtures/list_of_ad_groups.yml +77 -60
- data/spec/fixtures/list_of_all_ad_creatives_of_account.yml +77 -73
- data/spec/fixtures/reach_for_invalid_interest.yml +29 -111
- data/spec/fixtures/reach_for_valid_keywords.yml +11 -54
- data/spec/fixtures/reach_for_valid_keywords_male_young.yml +11 -60
- data/spec/lib/zuck/facebook/ad_account_spec.rb +1 -1
- data/spec/lib/zuck/facebook/ad_interest_spec.rb +2 -2
- data/spec/lib/zuck/facebook/targeting_spec_spec.rb +13 -21
- data/spec/lib/zuck_spec.rb +34 -23
- data/spec/spec_helper.rb +2 -5
- data/test_access_token +1 -0
- data/zuck.gemspec +15 -9
- metadata +63 -46
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ZjllZWZjNTgxYzdhYmRiNjQ1YmE2OTY5NTI5MmMyNmFjY2M2NDgyYQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 40becd6a7fffa32053f38e6e550c76ba84480044
|
4
|
+
data.tar.gz: 9ad567305d626e899c9b3bc1563297df9f49164b
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
MWM5OWYwYTAyNmM1ODA4MTM1MmExYzI0MWM4YjM1N2MxNzc4NGFmMGYzMDc5
|
11
|
-
ODMwNzkwNTEzMThhYzgzYTE4ZWEyMWQ3NDM4NTdmMmZlMmY5Nzk=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NTZkMDQ0NzZlYzQyMGVmMmJiNjcwZmU2NmYzOTgwZTM1OWY2MGEwNjJhODQ3
|
14
|
-
YjcxMThmNzkxNDllOWYxMDhkMzhmMTBkNDk2ODg1ODFjY2RmYzBiODRjZDY2
|
15
|
-
MWMzZjkxNjhkNThmYjJkN2UxYTM3ZjM3ODE4Njg2ZGU1NDBlM2I=
|
6
|
+
metadata.gz: 5fc4c51ef900b5b8d4084500aa339ee2d0e3320d2c89baa9ccf1ce044ee965eb379e91c22010d79e68c47281c7f1988d0622f42ca1d296d6d0b3904def9fe5e0
|
7
|
+
data.tar.gz: 42e866685f4fefc664e59871b98db3428a0407144a7b4ce015fc68fc358fbc4e313318b8f27a73fd8ec187292555be917ce9152ac849591e2ecbd05a42b7f592
|
data/CHANGELOG.markdown
CHANGED
@@ -1,5 +1,35 @@
|
|
1
|
+
2.0.0
|
2
|
+
=====
|
3
|
+
|
4
|
+
- update Koala to 1.10.1
|
5
|
+
- use the `/search?type=adinterestq=interest` endpoint to validate ad interests
|
6
|
+
instead of `/search?type=adinterestvalid&interest_list=['interest']`
|
7
|
+
- use json objects instead of string values for ad interests
|
8
|
+
- don't escape commas with %2C when passing searches on to koala
|
9
|
+
- update ad account properties
|
10
|
+
- fix consecutive `#save`s without a `#reload` in between
|
11
|
+
|
12
|
+
Breaking changes:
|
13
|
+
----------------
|
14
|
+
|
15
|
+
If you upgrade from 1.x, make sure you read the following:
|
16
|
+
|
17
|
+
- `Zuck::AdInterest.search` results use the `audience_size` key in order to use
|
18
|
+
the same names as the facebook api. In zuck 1.x it was `audience`
|
19
|
+
- `Zuck::TargetingSpec`
|
20
|
+
- `initialize` does not take string interests anymore,
|
21
|
+
but expects `{id: '123', name: 'foo'} objects, see
|
22
|
+
"[Specifying values for interests as a string will be sunset in favor of a JSON object of id and name](https://developers.facebook.com/docs/apps/migrations/ads-api-changes-2014-04-09)"
|
23
|
+
- in your targeting spec, don't specify `{countries: ['US']}`, but
|
24
|
+
`{geo_locations: { countries: ['US']}}`
|
25
|
+
- `Zuck::AdInterest.best_guess` returns an ad interest object, what was
|
26
|
+
formerly the `:interest` key is now called `:name`
|
27
|
+
- Ad Campaigns are now called Ad Sets, and Ad Sets can be grouped
|
28
|
+
together in Ad Campaigns [see docs](https://developers.facebook.com/docs/reference/ads-api/adcampaign/v2.2)
|
29
|
+
|
30
|
+
|
1
31
|
1.0.0
|
2
|
-
|
32
|
+
=====
|
3
33
|
- Implement Facebook's April 9 breaking changes, which also change this
|
4
34
|
gem
|
5
35
|
- AdKeyword/keywords are gone, welcome AdInterest/interests
|
@@ -10,16 +40,16 @@ docs](https://developers.facebook.com/docs/reference/ads-api/interest-targeting)
|
|
10
40
|
docs](https://developers.facebook.com/docs/reference/ads-api/targeting-specs))
|
11
41
|
|
12
42
|
0.2.0
|
13
|
-
|
43
|
+
=====
|
14
44
|
- implement Facebook's [Oct 2013 breaking changes](https://developers.facebook.com/roadmap/#q4_2013)
|
15
45
|
|
16
46
|
0.0.9
|
17
|
-
|
47
|
+
=====
|
18
48
|
- add Zuck::TargetingSpec.valid_countries? method to allow for country
|
19
49
|
validation from the outside
|
20
50
|
|
21
51
|
0.0.8
|
22
|
-
|
52
|
+
=====
|
23
53
|
- validate the countries before fetching a reach estimate
|
24
54
|
|
25
55
|
0.0.7
|
@@ -28,5 +58,5 @@ docs](https://developers.facebook.com/docs/reference/ads-api/targeting-specs))
|
|
28
58
|
with a # as a prefix
|
29
59
|
|
30
60
|
0.0.4
|
31
|
-
|
61
|
+
=====
|
32
62
|
- integrated targeting spec to fetch reach estimates
|
data/Gemfile
CHANGED
@@ -2,7 +2,7 @@ source 'http://rubygems.org'
|
|
2
2
|
|
3
3
|
gem 'rvm'
|
4
4
|
gem 'bundler'
|
5
|
-
gem 'koala'
|
5
|
+
gem 'koala'
|
6
6
|
gem 'activesupport'
|
7
7
|
|
8
8
|
group :development do
|
@@ -16,11 +16,12 @@ group :development, :test do
|
|
16
16
|
gem 'webmock', '~>1.8.0'
|
17
17
|
gem 'rspec'
|
18
18
|
gem 'vcr'
|
19
|
+
gem 'pry'
|
19
20
|
end
|
20
21
|
|
21
22
|
platform :ruby do
|
22
23
|
group :development do
|
23
|
-
gem 'guard'
|
24
|
+
gem 'guard-ctags-bundler'
|
24
25
|
gem 'guard-rspec'
|
25
26
|
gem 'guard-bundler'
|
26
27
|
gem 'guard-yard'
|
data/Gemfile.lock
CHANGED
@@ -4,15 +4,15 @@ GEM
|
|
4
4
|
activesupport (3.2.9)
|
5
5
|
i18n (~> 0.6)
|
6
6
|
multi_json (~> 1.0)
|
7
|
-
addressable (2.3.
|
7
|
+
addressable (2.3.6)
|
8
8
|
bourne (1.1.2)
|
9
9
|
mocha (= 0.10.5)
|
10
10
|
builder (3.2.2)
|
11
11
|
coderay (1.0.8)
|
12
12
|
crack (0.3.1)
|
13
13
|
diff-lcs (1.1.3)
|
14
|
-
faraday (0.8.
|
15
|
-
multipart-post (~> 1.
|
14
|
+
faraday (0.8.9)
|
15
|
+
multipart-post (~> 1.2.0)
|
16
16
|
git (1.2.6)
|
17
17
|
github_api (0.10.2)
|
18
18
|
addressable
|
@@ -30,6 +30,8 @@ GEM
|
|
30
30
|
guard-bundler (1.0.0)
|
31
31
|
bundler (~> 1.0)
|
32
32
|
guard (~> 1.1)
|
33
|
+
guard-ctags-bundler (0.2.0)
|
34
|
+
guard (>= 1.1)
|
33
35
|
guard-rspec (2.3.3)
|
34
36
|
guard (>= 1.1)
|
35
37
|
rspec (~> 2.11)
|
@@ -52,10 +54,10 @@ GEM
|
|
52
54
|
json (1.8.1-java)
|
53
55
|
jwt (0.1.11)
|
54
56
|
multi_json (>= 1.5)
|
55
|
-
koala (1.
|
56
|
-
addressable
|
57
|
-
faraday
|
58
|
-
multi_json
|
57
|
+
koala (1.10.1)
|
58
|
+
addressable
|
59
|
+
faraday
|
60
|
+
multi_json
|
59
61
|
listen (0.6.0)
|
60
62
|
lumberjack (1.0.2)
|
61
63
|
metaclass (0.0.1)
|
@@ -63,7 +65,7 @@ GEM
|
|
63
65
|
mini_portile (0.5.3)
|
64
66
|
mocha (0.10.5)
|
65
67
|
metaclass (~> 0.0.1)
|
66
|
-
multi_json (1.
|
68
|
+
multi_json (1.10.1)
|
67
69
|
multi_xml (0.5.5)
|
68
70
|
multipart-post (1.2.0)
|
69
71
|
nokogiri (1.6.1)
|
@@ -122,12 +124,13 @@ DEPENDENCIES
|
|
122
124
|
activesupport
|
123
125
|
bundler
|
124
126
|
growl
|
125
|
-
guard
|
126
127
|
guard-bundler
|
128
|
+
guard-ctags-bundler
|
127
129
|
guard-rspec
|
128
130
|
guard-yard
|
129
131
|
jeweler (~> 2.0.1)
|
130
|
-
koala
|
132
|
+
koala
|
133
|
+
pry
|
131
134
|
rb-fsevent
|
132
135
|
rdoc (~> 3.12)
|
133
136
|
redcarpet
|
data/README.markdown
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
Zuck; use facebook's advertisement API with ruby
|
4
4
|
================
|
5
5
|
|
6
|
-
This is a little gem that makes access to facebook's
|
6
|
+
This is a little gem that makes access to facebook's
|
7
7
|
ads API a little easier. Check out facebook's
|
8
8
|
[documentation](https://developers.facebook.com/docs/reference/ads-api/)
|
9
9
|
for a nice diagram that explains how things work.
|
@@ -28,16 +28,12 @@ accounts = Zuck::AdAccount.all
|
|
28
28
|
|
29
29
|
# Let's look at an account
|
30
30
|
my_account = accounts.first
|
31
|
-
=> #<Zuck::AdAccount id: "act_10150585630710217", account_id: "10150585630710217", name: "", account_status: 1, currency: "USD", timezone_id: 47, timezone_name: "Europe/Berlin", timezone_offset_hours_utc: 2, is_personal: 0, business_name: "Big Mike Alright UG (haftungsbeschr\u00e4nkt)", business_street: "Big Mike Alright UG (haftungsbeschr\u00e4nkt)", business_street2: "J\u00e4gerndorfer Zeile 61", business_city: "Berlin", business_state: "Berlin", business_zip: "12209", business_country_code: "DE", vat_status: 3, daily_spend_limit: 25000, users: [{"uid":501730216,"permissions":[1,2,3,4,5,7],"role":1001}], notification_settings: {"501730216":{"1000":{"1":1},"1001":{"1":1},"1002":{"1":1,"2":60},"1003":{"1":1,"2":60},"1004":{"1":1},"1005":{"1":1},"1006":{"1":1},"1009":{"1":1},"1010":{"1":1},"1011":{"1":1},"2000":{"1":1,"2":60},"2001":{"1":1,"2":60},"2002":{"2":60},"2003":{"1":1,"2":60},"2004":{"1":1,"2":60},"2005":{"1":1,"2":60},"3000":{"1":1,"2":60},"3001":{"1":1,"2":60},"3002":{"2":60},"3003":{"1":1,"2":60},"5000":{"1":1},"6000":{"1":1},"6001":{"1":1},"9000":{"1":1,"2":60},"8000":{"1":1,"2":60}}}, capabilities: [], balance: 0, moo_default_conversion_bid: 1000, moo_default_bid: 1000>
|
31
|
+
=> #<Zuck::AdAccount id: "act_10150585630710217", account_id: "10150585630710217", name: "", account_status: 1, currency: "USD", timezone_id: 47, timezone_name: "Europe/Berlin", timezone_offset_hours_utc: 2, is_personal: 0, business_name: "Big Mike Alright UG (haftungsbeschr\u00e4nkt)", business_street: "Big Mike Alright UG (haftungsbeschr\u00e4nkt)", business_street2: "J\u00e4gerndorfer Zeile 61", business_city: "Berlin", business_state: "Berlin", business_zip: "12209", business_country_code: "DE", vat_status: 3, daily_spend_limit: 25000, users: [{"uid":501730216,"permissions":[1,2,3,4,5,7],"role":1001}], notification_settings: {"501730216":{"1000":{"1":1},"1001":{"1":1},"1002":{"1":1,"2":60},"1003":{"1":1,"2":60},"1004":{"1":1},"1005":{"1":1},"1006":{"1":1},"1009":{"1":1},"1010":{"1":1},"1011":{"1":1},"2000":{"1":1,"2":60},"2001":{"1":1,"2":60},"2002":{"2":60},"2003":{"1":1,"2":60},"2004":{"1":1,"2":60},"2005":{"1":1,"2":60},"3000":{"1":1,"2":60},"3001":{"1":1,"2":60},"3002":{"2":60},"3003":{"1":1,"2":60},"5000":{"1":1},"6000":{"1":1},"6001":{"1":1},"9000":{"1":1,"2":60},"8000":{"1":1,"2":60}}}, capabilities: [], balance: 0, moo_default_conversion_bid: 1000, moo_default_bid: 1000>
|
32
32
|
|
33
33
|
# Aha. How do I access properties? The documented properties
|
34
34
|
# have getters:
|
35
35
|
my_account.currency
|
36
|
-
=> "USD"
|
37
|
-
|
38
|
-
# But facebook also returns some non documented stuff
|
39
|
-
my_account[:moo_default_bid]
|
40
|
-
=> 1000
|
36
|
+
=> "USD"
|
41
37
|
|
42
38
|
# Let's fetch the campaigns for this account
|
43
39
|
my_campaign = my_account.ad_campaigns.first
|
@@ -67,12 +63,12 @@ creative = '{"type":25,"action_spec":{"action.type":"like", "post":1015042041088
|
|
67
63
|
o = { bid_type: 1,
|
68
64
|
max_bid: 1,
|
69
65
|
name: "My first ad group",
|
70
|
-
targeting: '{"countries":["US"]}',
|
66
|
+
targeting: '{"geo_locations": {"countries":["US"]}}',
|
71
67
|
creative: creative}
|
72
|
-
|
68
|
+
|
73
69
|
# Create it in the context of my_campaign
|
74
70
|
group = my_campaign.create_ad_group(o)
|
75
|
-
=> #<Zuck::AdGroup adgroup_id: 6005851390151, ad_id: 6005851390151, campaign_id: 6005851032951, name: "My first ad group", adgroup_status: 4, bid_type: 1, max_bid: "1", bid_info: {"1":"1"}, ad_status: 4, account_id: "10150585630710217", id: "6005851390151", creative_ids: [6005851371551], targeting: {"countries":["US"],"friends_of_connections":[{"id":"6005851366351","name":null}]}, conversion_specs: [{"action.type":"like","post":"10150420410887685"}], start_time: null, end_time: null, updated_time: 1343916568, created_time: 1343916568>
|
71
|
+
=> #<Zuck::AdGroup adgroup_id: 6005851390151, ad_id: 6005851390151, campaign_id: 6005851032951, name: "My first ad group", adgroup_status: 4, bid_type: 1, max_bid: "1", bid_info: {"1":"1"}, ad_status: 4, account_id: "10150585630710217", id: "6005851390151", creative_ids: [6005851371551], targeting: {"geo_locations": {"countries":["US"]},"friends_of_connections":[{"id":"6005851366351","name":null}]}, conversion_specs: [{"action.type":"like","post":"10150420410887685"}], start_time: null, end_time: null, updated_time: 1343916568, created_time: 1343916568>
|
76
72
|
|
77
73
|
# Shoot, that was the wrong name
|
78
74
|
group.name = "My serious ad group"
|
@@ -94,31 +90,31 @@ AdInterest convenience methods
|
|
94
90
|
```ruby
|
95
91
|
graph = Zuck.graph
|
96
92
|
|
97
|
-
# Search for
|
93
|
+
# Search for interests (to auto complete, for example) (yes, facebook sometimes returns ids as string and sometimes as numbers)
|
98
94
|
Zuck::AdInterest.search(graph, "Auto")
|
99
95
|
=> [
|
100
|
-
{:
|
101
|
-
{:
|
102
|
-
{:
|
103
|
-
{:
|
96
|
+
{:interest=>"Auto", :id=>"6003156165433", :audience=>nil},
|
97
|
+
{:interest=>"#Automobile", :id=>6003176678152, :audience=>97900000},
|
98
|
+
{:interest=>"#Auto racing", :id=>6003146718552, :audience=>21800000},
|
99
|
+
{:interest=>"#Auto mechanic", :id=>6003109384433, :audience=>14600000}
|
104
100
|
]
|
105
101
|
|
106
|
-
# Quickly check if a
|
102
|
+
# Quickly check if a interest is valid
|
107
103
|
Zuck::AdInterest.validate(graph, '#Eminem')
|
108
104
|
=> {"#Eminem" => true}
|
109
105
|
|
110
|
-
# Quickly check a couple of
|
106
|
+
# Quickly check a couple of interests
|
111
107
|
Zuck::AdInterest.validate(graph, ['#Eminem', 'Wil Ferel', 'Bronson'])
|
112
108
|
=> {"#Eminem"=>true, "Bronson"=>true, "Wil Ferel"=>false}
|
113
109
|
|
114
|
-
# Make a best guess on how a
|
110
|
+
# Make a best guess on how a interest is called on Facebook
|
115
111
|
Zuck::AdInterest.best_guess(graph, 'Disney')
|
116
|
-
=> {:
|
112
|
+
=> {:interest=>"#The Walt Disney Company", :id=>6003270522085, :audience=>72500000}
|
117
113
|
|
118
|
-
# Sometimes a best guess does not return a
|
119
|
-
# means that we don't know the audience of that
|
114
|
+
# Sometimes a best guess does not return a interest with a # prefix, and that
|
115
|
+
# means that we don't know the audience of that interest:
|
120
116
|
Zuck::AdInterest.best_guess(graph, 'Moviepilot')
|
121
|
-
=> {:
|
117
|
+
=> {:interest=>"Moviepilot", :id=>6003327847780, :audience=>nil}
|
122
118
|
|
123
119
|
```
|
124
120
|
|
@@ -137,27 +133,19 @@ Here's a support chart:
|
|
137
133
|
<th style="text-align:center">.save</th>
|
138
134
|
<th style="text-align:center">.destroy</th>
|
139
135
|
<th style="text-align:center">parent.create_obj*</th>
|
140
|
-
<th style="text-align:center">Convenience methods**</th>
|
141
136
|
</tr>
|
142
|
-
<tr><td style="text-align: right">Ad account</td> <td>✔</td><td>-</td><td>✔</td><td>✔</td><td>-</td
|
143
|
-
<tr><td style="text-align: right">Ad account group</td> <td>-</td><td>-</td><td>-</td><td>-</td><td>-</td
|
144
|
-
<tr><td style="text-align: right">Ad campaign</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td
|
145
|
-
<tr><td style="text-align: right">Ad creative</td> <td>✔</td><td>-</td><td>-</td><td>-</td><td>-</td
|
146
|
-
<tr><td style="text-align: right">Ad group</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td
|
147
|
-
<tr><td style="text-align: right">Ad
|
148
|
-
<tr><td style="text-align: right">Ad user</td> <td>-</td><td>-</td><td>-</td><td>-</td><td>-</td
|
137
|
+
<tr><td style="text-align: right">Ad account</td> <td>✔</td><td>-</td><td>✔</td><td>✔</td><td>-</td></tr>
|
138
|
+
<tr><td style="text-align: right">Ad account group</td> <td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
|
139
|
+
<tr><td style="text-align: right">Ad campaign</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td></tr>
|
140
|
+
<tr><td style="text-align: right">Ad creative</td> <td>✔</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
|
141
|
+
<tr><td style="text-align: right">Ad group</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td></tr>
|
142
|
+
<tr><td style="text-align: right">Ad set</td> <td>✔</td><td>✔</td><td>✔</td><td>✔</td><td>✔</td></tr>
|
143
|
+
<tr><td style="text-align: right">Ad user</td> <td>-</td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
|
149
144
|
</table>
|
150
145
|
|
151
|
-
(*) This means
|
146
|
+
(*) This means if you can, for example, create a new ad group by calling
|
152
147
|
`my_campaign.create_ad_group(data)` or not.
|
153
148
|
|
154
|
-
(**) Right now, everything goes right to facebook, but we'll want some
|
155
|
-
convenience methods that tell you, for example, what
|
156
|
-
`ad_group.ad_status == 3` actually means
|
157
|
-
|
158
|
-
( ) These don't exist as their own objects in this gem but live in their
|
159
|
-
parents. This means you can, for now, only read them:
|
160
|
-
|
161
149
|
Users don't exist as objects yet, but you can list all ad users of
|
162
150
|
an account via `my_ad_account.users` and you will get an array of hashes.
|
163
151
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
data/console
CHANGED
@@ -4,10 +4,11 @@ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
|
|
4
4
|
require 'bundler'
|
5
5
|
Bundler.require
|
6
6
|
|
7
|
-
require '
|
7
|
+
require 'pry'
|
8
8
|
require 'zuck'
|
9
9
|
|
10
10
|
def reload!
|
11
|
+
Zuck.graph = Koala::Facebook::API.new(File.read("test_access_token"))
|
11
12
|
@loaded_files ||= {}
|
12
13
|
count = 0
|
13
14
|
|
@@ -23,4 +24,9 @@ def reload!
|
|
23
24
|
"reloaded #{count} files"
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
+
def graph
|
28
|
+
Zuck.graph
|
29
|
+
end
|
30
|
+
|
31
|
+
Zuck.graph = Koala::Facebook::API.new(File.read("test_access_token"))
|
32
|
+
binding.pry
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module Zuck
|
2
2
|
class AdAccount < RawFbObject
|
3
|
+
include Zuck::Helpers
|
3
4
|
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# actually returned.
|
5
|
+
# Known keys as per
|
6
|
+
# [fb docs](https://developers.facebook.com/docs/reference/ads-api/adaccount/)
|
7
7
|
known_keys :account_groups,
|
8
8
|
:account_id,
|
9
9
|
:account_status,
|
@@ -19,25 +19,42 @@ module Zuck
|
|
19
19
|
:business_street,
|
20
20
|
:business_zip,
|
21
21
|
:capabilities,
|
22
|
+
:created_time,
|
22
23
|
:currency,
|
23
24
|
:daily_spend_limit,
|
25
|
+
:end_advertiser,
|
26
|
+
:funding_source,
|
27
|
+
:funding_source_details,
|
24
28
|
:id,
|
25
29
|
:is_personal,
|
30
|
+
:media_agency,
|
26
31
|
:name,
|
32
|
+
:offsite_pixels_tos_accepted,
|
33
|
+
:partner,
|
27
34
|
:spend_cap,
|
28
35
|
:timezone_id,
|
29
36
|
:timezone_name,
|
30
37
|
:timezone_offset_hours_utc,
|
31
38
|
:tos_accepted,
|
32
39
|
:users,
|
33
|
-
:
|
40
|
+
:tax_id_status
|
34
41
|
|
35
42
|
|
36
43
|
list_path 'me/adaccounts'
|
37
|
-
connections :ad_campaigns
|
44
|
+
connections :ad_campaigns, :ad_sets, :ad_groups
|
38
45
|
|
39
46
|
def self.all(graph = Zuck.graph)
|
40
47
|
super(graph)
|
41
48
|
end
|
49
|
+
|
50
|
+
def path
|
51
|
+
normalize_account_id(id)
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_data(data)
|
55
|
+
super
|
56
|
+
self.id = normalize_account_id(id)
|
57
|
+
end
|
58
|
+
|
42
59
|
end
|
43
60
|
end
|
@@ -1,23 +1,20 @@
|
|
1
1
|
module Zuck
|
2
2
|
class AdCampaign < RawFbObject
|
3
3
|
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
known_keys :
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:daily_imps,
|
11
|
-
:end_time,
|
12
|
-
:id,
|
13
|
-
:lifetime_budget,
|
4
|
+
# Known keys as per
|
5
|
+
# the [fb docs](https://developers.facebook.com/docs/reference/ads-api/adcampaign/v2.2)
|
6
|
+
# as well as undocumented keys returned by the Graph API
|
7
|
+
known_keys :id,
|
8
|
+
:account_id,
|
9
|
+
:objective,
|
14
10
|
:name,
|
15
|
-
:
|
16
|
-
:
|
11
|
+
:adgroups,
|
12
|
+
:campaign_group_status,
|
13
|
+
:buying_type
|
17
14
|
|
18
|
-
parent_object :ad_account
|
19
|
-
list_path :
|
20
|
-
connections :ad_groups
|
15
|
+
parent_object :ad_account, as: :account_id
|
16
|
+
list_path :adcampaign_groups
|
17
|
+
connections :ad_groups, :ad_campaigns
|
21
18
|
|
22
19
|
end
|
23
20
|
end
|
@@ -4,25 +4,25 @@ module Zuck
|
|
4
4
|
# Can't create this directly (yet)
|
5
5
|
read_only
|
6
6
|
|
7
|
-
#
|
8
|
-
#
|
9
|
-
|
10
|
-
known_keys :name,
|
11
|
-
:type,
|
12
|
-
:object_id,
|
7
|
+
# Known keys as per
|
8
|
+
# [fb docs](https://developers.facebook.com/docs/reference/ads-api/adaccount/)
|
9
|
+
known_keys :actor_id,
|
13
10
|
:body,
|
11
|
+
:call_to_action_type,
|
12
|
+
:follow_redirect,
|
13
|
+
:image_crops,
|
14
|
+
:image_file,
|
14
15
|
:image_hash,
|
15
16
|
:image_url,
|
16
|
-
:id,
|
17
|
-
:title,
|
18
17
|
:link_url,
|
18
|
+
:name,
|
19
|
+
:object_id,
|
20
|
+
:object_story_id,
|
21
|
+
:object_story_spec,
|
22
|
+
:object_url,
|
23
|
+
:title,
|
19
24
|
:url_tags,
|
20
|
-
:
|
21
|
-
:related_fan_page,
|
22
|
-
:follow_redirect,
|
23
|
-
:auto_update,
|
24
|
-
:story_id,
|
25
|
-
:action_spec
|
25
|
+
:id
|
26
26
|
|
27
27
|
parent_object :ad_group
|
28
28
|
list_path :adcreatives
|
@@ -3,20 +3,20 @@ require 'zuck/facebook/ad_creative'
|
|
3
3
|
module Zuck
|
4
4
|
class AdGroup < RawFbObject
|
5
5
|
|
6
|
-
#
|
7
|
-
#
|
8
|
-
|
9
|
-
|
6
|
+
# Known keys as per
|
7
|
+
# [fb docs](https://developers.facebook.com/docs/reference/ads-api/adgroup/v2.2)
|
8
|
+
known_keys :id,
|
9
|
+
:account_id,
|
10
10
|
:adgroup_status,
|
11
|
-
:bid_info,
|
12
11
|
:bid_type,
|
12
|
+
:bid_info,
|
13
|
+
:conversion_specs,
|
13
14
|
:campaign_id,
|
15
|
+
:campaign_group_id,
|
14
16
|
:conversion_specs,
|
15
17
|
:created_time,
|
16
18
|
:creative_ids,
|
17
|
-
:
|
18
|
-
:disapprove_reason_descriptions,
|
19
|
-
:last_updated_by_app_id,
|
19
|
+
:failed_delivery_checks,
|
20
20
|
:name,
|
21
21
|
:targeting,
|
22
22
|
:tracking_specs,
|
@@ -27,10 +27,10 @@ module Zuck
|
|
27
27
|
list_path :adgroups
|
28
28
|
connections :ad_creatives
|
29
29
|
|
30
|
-
def self.create(graph, data,
|
31
|
-
path =
|
32
|
-
data['campaign_id'] =
|
33
|
-
super(graph, data,
|
30
|
+
def self.create(graph, data, ad_set)
|
31
|
+
path = ad_set.ad_account.path
|
32
|
+
data['campaign_id'] = ad_set.id
|
33
|
+
super(graph, data, ad_set, path)
|
34
34
|
end
|
35
35
|
|
36
36
|
end
|
@@ -9,35 +9,32 @@ module Zuck
|
|
9
9
|
# popular one
|
10
10
|
def best_guess(graph, interest)
|
11
11
|
search(graph, interest).sort do |a,b|
|
12
|
-
if a[:
|
13
|
-
a[:
|
12
|
+
if a[:audience_size].to_i > 0 || b[:audience_size].to_i > 0
|
13
|
+
a[:audience_size].to_i <=> b[:audience_size].to_i
|
14
14
|
else
|
15
|
-
b[:
|
15
|
+
b[:name].length <=> a[:name].length
|
16
16
|
end
|
17
17
|
end.last
|
18
18
|
end
|
19
19
|
|
20
|
-
# Checks the ad api to see if the given interests are valid
|
20
|
+
# Checks the ad api to see if the given interests are valid. You can either
|
21
|
+
# pass it an array of strings, or an array of hashes with `:name` and `:id` keys.
|
21
22
|
# @return [Hash] The keys are the (lowercased) interests and the values their validity
|
22
23
|
def validate(graph, interests)
|
23
|
-
interests = normalize_array(interests).map{|k| k.gsub(',', '%2C')}
|
24
|
-
search = graph.search(nil, type: 'adinterestvalid', interest_list: [interests].flatten)
|
25
24
|
results = {}
|
26
|
-
|
27
|
-
|
25
|
+
normalized = normalize_array(values_from_string_or_object_interests(interests))
|
26
|
+
normalized.each do |interest|
|
27
|
+
# The interest is valid if we found at least one match that
|
28
|
+
# has the exactly the same name (ignoring case)
|
29
|
+
hits = search(graph, interest).select{|d| d['name'].downcase == interest.downcase}
|
30
|
+
results[interest] = hits.count > 0
|
28
31
|
end
|
29
32
|
results
|
30
33
|
end
|
31
34
|
|
32
35
|
# Ad interest search
|
33
36
|
def search(graph, interest)
|
34
|
-
|
35
|
-
{
|
36
|
-
interest: r['name'],
|
37
|
-
id: r['id'],
|
38
|
-
audience: r['audience_size']
|
39
|
-
}
|
40
|
-
end
|
37
|
+
graph.search(interest, type: :adinterest).map(&:with_indifferent_access)
|
41
38
|
end
|
42
39
|
end
|
43
40
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'zuck/facebook/ad_creative'
|
2
|
+
|
3
|
+
module Zuck
|
4
|
+
class AdSet < RawFbObject
|
5
|
+
|
6
|
+
# Known keys as per
|
7
|
+
# [fb docs](https://developers.facebook.com/docs/reference/ads-api/adset/v2.2)
|
8
|
+
known_keys :id,
|
9
|
+
:name,
|
10
|
+
:account_id,
|
11
|
+
:bid_type,
|
12
|
+
:bid_info,
|
13
|
+
:campaign_group_id,
|
14
|
+
:campaign_status,
|
15
|
+
:start_time,
|
16
|
+
:end_time,
|
17
|
+
:updated_time,
|
18
|
+
:created_time,
|
19
|
+
:daily_budget,
|
20
|
+
:lifetime_budget,
|
21
|
+
:budget_remaining,
|
22
|
+
:targeting,
|
23
|
+
:promoted_object
|
24
|
+
|
25
|
+
parent_object :ad_account, as: :account_id
|
26
|
+
list_path :adcampaigns # Yes, this is correct, "for legacy reasons"
|
27
|
+
connections :ad_groups, :ad_creatives
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -70,7 +70,7 @@ module Zuck
|
|
70
70
|
def initialize(graph, ad_account, spec = nil)
|
71
71
|
@validated_interests = {}
|
72
72
|
@graph = graph
|
73
|
-
@ad_account =
|
73
|
+
@ad_account = normalize_account_id(ad_account)
|
74
74
|
self.spec = spec
|
75
75
|
end
|
76
76
|
|
@@ -104,7 +104,7 @@ module Zuck
|
|
104
104
|
# @return boolean
|
105
105
|
def validate_interest(interest)
|
106
106
|
if @validated_interests[interest] == nil
|
107
|
-
interests =
|
107
|
+
interests = [@spec[:interests]] + [interest]
|
108
108
|
@validated_interests = Zuck::AdInterest.validate(@graph, interests)
|
109
109
|
end
|
110
110
|
@validated_interests[interest] == true
|
@@ -153,7 +153,7 @@ module Zuck
|
|
153
153
|
private
|
154
154
|
|
155
155
|
def validate_spec
|
156
|
-
@spec[:interests]
|
156
|
+
@spec[:interests] = @spec[:interests]
|
157
157
|
@spec[:broad_age] ||= false
|
158
158
|
validate_countries
|
159
159
|
unless @spec[:interests].present? or @spec[:connections].present?
|
@@ -195,7 +195,7 @@ module Zuck
|
|
195
195
|
@spec[:genders] = [2] if gender.to_s == 'female'
|
196
196
|
|
197
197
|
interest = spec.delete(:interest)
|
198
|
-
@spec[:interests] =
|
198
|
+
@spec[:interests] = [interest, @spec[:interests]].compact.flatten
|
199
199
|
|
200
200
|
country = spec.delete(:country)
|
201
201
|
@spec[:countries] = normalize_countries([country, @spec[:countries]])
|