facebook_ads 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -10
- data/.travis.yml +1 -4
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -1
- data/README.md +4 -4
- data/Rakefile +2 -0
- data/bin/{console → facebook_ads_console} +1 -0
- data/facebook_ads.gemspec +3 -2
- data/lib/facebook_ads.rb +4 -6
- data/lib/facebook_ads/account.rb +13 -0
- data/lib/facebook_ads/ad.rb +2 -0
- data/lib/facebook_ads/ad_account.rb +45 -46
- data/lib/facebook_ads/ad_audience.rb +2 -0
- data/lib/facebook_ads/ad_campaign.rb +7 -4
- data/lib/facebook_ads/ad_creative.rb +2 -0
- data/lib/facebook_ads/ad_exception.rb +2 -0
- data/lib/facebook_ads/ad_image.rb +6 -5
- data/lib/facebook_ads/ad_insight.rb +2 -0
- data/lib/facebook_ads/ad_product.rb +2 -0
- data/lib/facebook_ads/ad_product_catalog.rb +2 -8
- data/lib/facebook_ads/ad_product_feed.rb +2 -0
- data/lib/facebook_ads/ad_product_set.rb +2 -0
- data/lib/facebook_ads/ad_set.rb +54 -30
- data/lib/facebook_ads/ad_targeting.rb +6 -4
- data/lib/facebook_ads/ad_user.rb +15 -0
- data/lib/facebook_ads/advertisable_application.rb +14 -0
- data/lib/facebook_ads/base.rb +37 -11
- data/spec/facebook_ads/ad_account_spec.rb +2 -0
- data/spec/facebook_ads/ad_campaign_spec.rb +2 -0
- data/spec/facebook_ads/ad_creative_spec.rb +2 -0
- data/spec/facebook_ads/ad_image_spec.rb +2 -0
- data/spec/facebook_ads/ad_insight_spec.rb +2 -0
- data/spec/facebook_ads/ad_product_catalog_spec.rb +2 -0
- data/spec/facebook_ads/ad_product_feed_spec.rb +2 -0
- data/spec/facebook_ads/ad_product_set_spec.rb +2 -0
- data/spec/facebook_ads/ad_product_spec.rb +2 -0
- data/spec/facebook_ads/ad_set_spec.rb +2 -0
- data/spec/facebook_ads/ad_spec.rb +2 -0
- data/spec/facebook_ads/ad_targeting_spec.rb +2 -0
- data/spec/facebook_ads/facebook_ads_spec.rb +2 -0
- data/spec/fixtures/cassettes/FacebookAds_AdAccount/_ad_campaigns/lists_campaigns.yml +1 -1
- data/spec/fixtures/cassettes/FacebookAds_AdAccount/_create_ad_campaign/creates_a_new_ad_campaign.yml +1 -1
- data/spec/fixtures/cassettes/FacebookAds_AdCampaign/_destroy/sets_effective_status_to_deleted.yml +2 -2
- data/spec/spec_helper.rb +2 -0
- data/spec/support/approvals.rb +2 -0
- data/spec/support/facebook_ads.rb +2 -0
- data/spec/support/rest_client.rb +3 -5
- data/spec/support/vcr.rb +2 -0
- metadata +8 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 35a61cb9e965397a7e50da136ce697ed6ea3437e
|
|
4
|
+
data.tar.gz: ce5c09cf7acd43f39b0aa8f82d87d3a21962d50e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ad4ac70297f77209589c34323fd9af60d5721e8824cd2b3380e9a320c89de7124b45dd7a1133be489a362a631d35a873e00f059e10c2b9835231eae7f41f9c1
|
|
7
|
+
data.tar.gz: 365dda2ba4b84cdc4d7d86ca95ba1ccfdfd399dbc34664c2aa800cbba44b2cd41d3e181989653b03dc7821a9071dda4ef96f1b25f947aa71b7fd3861e1d8c236
|
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion: 2.
|
|
2
|
+
TargetRubyVersion: 2.3
|
|
3
3
|
|
|
4
4
|
Metrics/AbcSize:
|
|
5
5
|
Enabled: false
|
|
@@ -22,17 +22,12 @@ Metrics/ParameterLists:
|
|
|
22
22
|
Lint/EndAlignment:
|
|
23
23
|
Enabled: false
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Layout/IndentationWidth:
|
|
26
26
|
Enabled: false
|
|
27
|
-
|
|
27
|
+
Layout/ElseAlignment:
|
|
28
28
|
Enabled: false
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
Style/Documentation:
|
|
30
31
|
Enabled: false
|
|
31
32
|
Style/GuardClause:
|
|
32
33
|
Enabled: false
|
|
33
|
-
Style/NumericLiterals:
|
|
34
|
-
Enabled: false
|
|
35
|
-
|
|
36
|
-
# This is temporary - it can be remove after updating rubocop to the latest.
|
|
37
|
-
Style/PercentLiteralDelimiters:
|
|
38
|
-
Enabled: false
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
## 0.6.4 (2018-10-03)
|
|
2
|
+
- Expose `budget_remaining`, `daily_budget`, `lifetime_budget` for Ad Campaigns. These are needed when we have budgets at the Ad Campaign level and not at the Ad Set level.
|
|
3
|
+
|
|
1
4
|
## 0.6.3 (2018-05-17)
|
|
2
5
|
- Replaced deprecated `is_autobid` with new `bid_strategy` field
|
|
3
6
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -49,7 +49,7 @@ It reads the Access Token from a file called test_access_token.
|
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
51
|
echo [YOUR_ACCESS_TOKEN] > test_access_token
|
|
52
|
-
bin/
|
|
52
|
+
bin/facebook_ads_console
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
## Usage Examples
|
|
@@ -230,9 +230,9 @@ image_ad_creative = account.create_ad_creative({
|
|
|
230
230
|
Create a single creative for a web link:
|
|
231
231
|
```ruby
|
|
232
232
|
image_ad_creative = account.create_ad_creative({
|
|
233
|
-
title: 'Test Link Title',
|
|
234
|
-
body: 'Link Description Text',
|
|
235
|
-
object_url: 'www.example.com/my-ad-link',
|
|
233
|
+
title: 'Test Link Title',
|
|
234
|
+
body: 'Link Description Text',
|
|
235
|
+
object_url: 'www.example.com/my-ad-link',
|
|
236
236
|
link_url: 'www.example.com/my-ad-link',
|
|
237
237
|
image_hash: ad_images.first.hash,
|
|
238
238
|
}, creative_type: 'link')
|
data/Rakefile
CHANGED
data/facebook_ads.gemspec
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
+
# frozen_string_literal: true
|
|
2
3
|
|
|
3
4
|
# To publish the next version:
|
|
4
5
|
# gem build facebook_ads.gemspec
|
|
5
|
-
# gem push facebook_ads-0.6.
|
|
6
|
+
# gem push facebook_ads-0.6.4.gem
|
|
6
7
|
Gem::Specification.new do |s|
|
|
7
8
|
s.name = 'facebook_ads'
|
|
8
|
-
s.version = '0.6.
|
|
9
|
+
s.version = '0.6.4'
|
|
9
10
|
s.platform = Gem::Platform::RUBY
|
|
10
11
|
s.licenses = ['MIT']
|
|
11
12
|
s.authors = ['Chris Estreich']
|
data/lib/facebook_ads.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# External requires.
|
|
2
4
|
require 'json'
|
|
3
5
|
require 'rest-client'
|
|
@@ -38,7 +40,7 @@ module FacebookAds
|
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def self.api_version
|
|
41
|
-
@api_version = '
|
|
43
|
+
@api_version = '3.1' unless defined?(@api_version)
|
|
42
44
|
@api_version
|
|
43
45
|
end
|
|
44
46
|
|
|
@@ -59,11 +61,7 @@ module FacebookAds
|
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def self.appsecret_proof
|
|
62
|
-
OpenSSL::HMAC.hexdigest(
|
|
63
|
-
OpenSSL::Digest.new('sha256'),
|
|
64
|
-
@app_secret,
|
|
65
|
-
@access_token
|
|
66
|
-
)
|
|
64
|
+
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), @app_secret, @access_token)
|
|
67
65
|
end
|
|
68
66
|
|
|
69
67
|
def self.business_id=(business_id)
|
data/lib/facebook_ads/ad.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module FacebookAds
|
|
2
|
-
# An ad account has many ad campaigns, ad images, and ad creatives.
|
|
3
4
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-account
|
|
4
5
|
class AdAccount < Base
|
|
5
6
|
FIELDS = %w[id account_id account_status age created_time currency name].freeze
|
|
@@ -8,17 +9,21 @@ module FacebookAds
|
|
|
8
9
|
def all(query = {})
|
|
9
10
|
get('/me/adaccounts', query: query, objectify: true)
|
|
10
11
|
end
|
|
12
|
+
end
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
# AdvertisableApplication
|
|
15
|
+
|
|
16
|
+
def advertisable_applications(limit: 100)
|
|
17
|
+
AdvertisableApplication.paginate("/#{id}/advertisable_applications", query: { limit: limit })
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# AdUser
|
|
21
|
+
|
|
22
|
+
def ad_users(limit: 100)
|
|
23
|
+
AdCampaign.paginate("/#{id}/users", query: { limit: limit })
|
|
19
24
|
end
|
|
20
25
|
|
|
21
|
-
#
|
|
26
|
+
# AdCampaign
|
|
22
27
|
|
|
23
28
|
def ad_campaigns(effective_status: ['ACTIVE'], limit: 100)
|
|
24
29
|
AdCampaign.paginate("/#{id}/campaigns", query: { effective_status: effective_status, limit: limit })
|
|
@@ -39,7 +44,19 @@ module FacebookAds
|
|
|
39
44
|
AdCampaign.find(result['id'])
|
|
40
45
|
end
|
|
41
46
|
|
|
42
|
-
#
|
|
47
|
+
# AdSet
|
|
48
|
+
|
|
49
|
+
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
|
50
|
+
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Ad
|
|
54
|
+
|
|
55
|
+
def ads(effective_status: ['ACTIVE'], limit: 100)
|
|
56
|
+
Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# AdImage
|
|
43
60
|
|
|
44
61
|
def ad_images(hashes: nil, limit: 100)
|
|
45
62
|
if !hashes.nil?
|
|
@@ -60,7 +77,7 @@ module FacebookAds
|
|
|
60
77
|
!response['images'].nil? ? ad_images(hashes: response['images'].map { |_key, hash| hash['hash'] }) : []
|
|
61
78
|
end
|
|
62
79
|
|
|
63
|
-
#
|
|
80
|
+
# AdCreative
|
|
64
81
|
|
|
65
82
|
def ad_creatives(limit: 100)
|
|
66
83
|
AdCreative.paginate("/#{id}/adcreatives", query: { limit: limit })
|
|
@@ -81,25 +98,27 @@ module FacebookAds
|
|
|
81
98
|
end
|
|
82
99
|
end
|
|
83
100
|
|
|
84
|
-
#
|
|
85
|
-
|
|
86
|
-
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
|
87
|
-
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
# has_many ads
|
|
101
|
+
# AdAudience
|
|
91
102
|
|
|
92
|
-
def
|
|
93
|
-
|
|
103
|
+
def ad_audiences(limit: 100)
|
|
104
|
+
AdAudience.paginate("/#{id}/customaudiences", query: { limit: limit })
|
|
94
105
|
end
|
|
95
106
|
|
|
96
|
-
|
|
107
|
+
def create_ad_audience_with_pixel(name:, pixel_id:, event_name:, subtype: 'WEBSITE', retention_days: 15)
|
|
108
|
+
query = {
|
|
109
|
+
name: name,
|
|
110
|
+
pixel_id: pixel_id,
|
|
111
|
+
subtype: subtype,
|
|
112
|
+
retention_days: retention_days,
|
|
113
|
+
rule: { event: { i_contains: event_name } }.to_json,
|
|
114
|
+
prefill: 1
|
|
115
|
+
}
|
|
97
116
|
|
|
98
|
-
|
|
99
|
-
AdAudience.
|
|
117
|
+
result = AdAudience.post("/#{id}/customaudiences", query: query)
|
|
118
|
+
AdAudience.find(result['id'])
|
|
100
119
|
end
|
|
101
120
|
|
|
102
|
-
#
|
|
121
|
+
# AdInsight
|
|
103
122
|
|
|
104
123
|
def ad_insights(range: Date.today..Date.today, level: 'ad', time_increment: 1)
|
|
105
124
|
ad_campaigns.map do |ad_campaign|
|
|
@@ -123,6 +142,7 @@ module FacebookAds
|
|
|
123
142
|
optimize_for: optimization_goal,
|
|
124
143
|
currency: currency
|
|
125
144
|
}
|
|
145
|
+
|
|
126
146
|
self.class.get("/#{id}/reachestimate", query: query, objectify: false)
|
|
127
147
|
end
|
|
128
148
|
|
|
@@ -142,29 +162,8 @@ module FacebookAds
|
|
|
142
162
|
optimization_goal: optimization_goal,
|
|
143
163
|
currency: currency
|
|
144
164
|
}
|
|
145
|
-
self.class.get("/#{id}/delivery_estimate", query: query, objectify: false)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# has_many applications
|
|
149
|
-
|
|
150
|
-
def applications
|
|
151
|
-
self.class.get("/#{id}/advertisable_applications", objectify: false)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# hash_many ad_audiences
|
|
155
|
-
|
|
156
|
-
def create_ad_audience_with_pixel(name:, pixel_id:, event_name:, subtype: 'WEBSITE', retention_days: 15)
|
|
157
|
-
query = {
|
|
158
|
-
name: name,
|
|
159
|
-
pixel_id: pixel_id,
|
|
160
|
-
subtype: subtype,
|
|
161
|
-
retention_days: retention_days,
|
|
162
|
-
rule: { event: { i_contains: event_name } }.to_json,
|
|
163
|
-
prefill: 1
|
|
164
|
-
}
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
AdAudience.find(result['id'])
|
|
166
|
+
self.class.get("/#{id}/delivery_estimate", query: query, objectify: false)
|
|
168
167
|
end
|
|
169
168
|
|
|
170
169
|
private
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module FacebookAds
|
|
2
|
-
# An ad campaign has many ad sets and belongs to an ad account.
|
|
3
4
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group
|
|
4
5
|
class AdCampaign < Base
|
|
5
6
|
FIELDS = %w[
|
|
@@ -15,6 +16,7 @@ module FacebookAds
|
|
|
15
16
|
start_time
|
|
16
17
|
stop_time
|
|
17
18
|
updated_time spend_cap
|
|
19
|
+
budget_remaining daily_budget lifetime_budget
|
|
18
20
|
].freeze
|
|
19
21
|
|
|
20
22
|
STATUSES = %w[
|
|
@@ -45,13 +47,13 @@ module FacebookAds
|
|
|
45
47
|
VIDEO_VIEWS
|
|
46
48
|
].freeze
|
|
47
49
|
|
|
48
|
-
#
|
|
50
|
+
# AdAccount
|
|
49
51
|
|
|
50
52
|
def ad_account
|
|
51
53
|
@ad_account ||= AdAccount.find("act_#{account_id}")
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
#
|
|
56
|
+
# AdSet
|
|
55
57
|
|
|
56
58
|
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
|
57
59
|
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
|
@@ -94,7 +96,7 @@ module FacebookAds
|
|
|
94
96
|
AdSet.find(result['id'])
|
|
95
97
|
end
|
|
96
98
|
|
|
97
|
-
#
|
|
99
|
+
# AdInsight
|
|
98
100
|
|
|
99
101
|
def ad_insights(range: Date.today..Date.today, level: 'ad', time_increment: 1)
|
|
100
102
|
query = {
|
|
@@ -102,6 +104,7 @@ module FacebookAds
|
|
|
102
104
|
time_increment: time_increment,
|
|
103
105
|
time_range: { since: range.first.to_s, until: range.last.to_s }
|
|
104
106
|
}
|
|
107
|
+
|
|
105
108
|
AdInsight.paginate("/#{id}/insights", query: query)
|
|
106
109
|
end
|
|
107
110
|
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module FacebookAds
|
|
2
|
-
# An ad image belongs to an ad account.
|
|
3
4
|
# An image will always produce the same hash.
|
|
4
5
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-image
|
|
5
6
|
class AdImage < Base
|
|
@@ -11,10 +12,10 @@ module FacebookAds
|
|
|
11
12
|
end
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
# @
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
# @FIXME: You are setting a key that conflicts with a built-in method
|
|
16
|
+
# FacebookAds::AdImage#hash defined in Hash.
|
|
17
|
+
disable_warnings
|
|
18
|
+
|
|
18
19
|
def hash
|
|
19
20
|
self[:hash]
|
|
20
21
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module FacebookAds
|
|
2
4
|
# https://developers.facebook.com/docs/marketing-api/reference/product-catalog
|
|
3
5
|
class AdProductCatalog < Base
|
|
@@ -8,14 +10,6 @@ module FacebookAds
|
|
|
8
10
|
get("/#{FacebookAds.business_id}/owned_product_catalogs", query: query, objectify: true)
|
|
9
11
|
end
|
|
10
12
|
|
|
11
|
-
def find_by(conditions)
|
|
12
|
-
all.detect do |object|
|
|
13
|
-
conditions.all? do |key, value|
|
|
14
|
-
object.send(key) == value
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
13
|
def create(name:)
|
|
20
14
|
query = { name: name }
|
|
21
15
|
result = post("/#{FacebookAds.business_id}/owned_product_catalogs", query: query)
|
data/lib/facebook_ads/ad_set.rb
CHANGED
|
@@ -1,38 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module FacebookAds
|
|
2
|
-
#
|
|
3
|
-
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign
|
|
4
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-account/adsets
|
|
4
5
|
class AdSet < Base
|
|
5
|
-
# Full set of fields:
|
|
6
|
-
# FIELDS = %w(
|
|
7
|
-
# id account_id adlabels adset_schedule attribution_window_days
|
|
8
|
-
# bid_amount bid_info billing_event budget_remaining
|
|
9
|
-
# campaign campaign_id configured_status created_time
|
|
10
|
-
# creative_sequence daily_budget effective_status end_time
|
|
11
|
-
# frequency_cap frequency_cap_reset_period
|
|
12
|
-
# frequency_control_specs instagram_actor_id
|
|
13
|
-
# bid_strategy lifetime_budget
|
|
14
|
-
# lifetime_frequency_cap lifetime_imps name optimization_goal
|
|
15
|
-
# pacing_type promoted_object recommendations
|
|
16
|
-
# recurring_budget_semantics rf_prediction_id rtb_flag
|
|
17
|
-
# start_time status targeting time_based_ad_rotation_id_blocks
|
|
18
|
-
# time_based_ad_rotation_intervals updated_time
|
|
19
|
-
# use_new_app_click
|
|
20
|
-
# ).freeze
|
|
21
|
-
|
|
22
|
-
# Fields we might actually care about:
|
|
23
6
|
FIELDS = %w[
|
|
24
|
-
id
|
|
7
|
+
id
|
|
8
|
+
account_id
|
|
9
|
+
campaign_id
|
|
25
10
|
name
|
|
26
|
-
status
|
|
27
|
-
|
|
28
|
-
|
|
11
|
+
status
|
|
12
|
+
configured_status
|
|
13
|
+
effective_status
|
|
14
|
+
bid_strategy
|
|
15
|
+
bid_amount
|
|
16
|
+
billing_event
|
|
17
|
+
optimization_goal
|
|
18
|
+
pacing_type
|
|
19
|
+
daily_budget
|
|
20
|
+
budget_remaining
|
|
21
|
+
lifetime_budget
|
|
29
22
|
promoted_object
|
|
30
23
|
targeting
|
|
31
|
-
created_time
|
|
24
|
+
created_time
|
|
25
|
+
updated_time
|
|
32
26
|
].freeze
|
|
33
27
|
|
|
34
|
-
STATUSES
|
|
35
|
-
|
|
28
|
+
STATUSES = %w[
|
|
29
|
+
ACTIVE
|
|
30
|
+
PAUSED
|
|
31
|
+
DELETED
|
|
32
|
+
PENDING_REVIEW
|
|
33
|
+
DISAPPROVED
|
|
34
|
+
PREAPPROVED
|
|
35
|
+
PENDING_BILLING_INFO
|
|
36
|
+
CAMPAIGN_PAUSED
|
|
37
|
+
ARCHIVED
|
|
38
|
+
ADSET_PAUSED
|
|
39
|
+
].freeze
|
|
40
|
+
|
|
41
|
+
BILLING_EVENTS = %w[APP_INSTALLS IMPRESSIONS].freeze
|
|
42
|
+
|
|
36
43
|
OPTIMIZATION_GOALS = %w[
|
|
37
44
|
NONE
|
|
38
45
|
APP_INSTALLS
|
|
@@ -55,25 +62,26 @@ module FacebookAds
|
|
|
55
62
|
APP_DOWNLOADS
|
|
56
63
|
LANDING_PAGE_VIEWS
|
|
57
64
|
].freeze
|
|
65
|
+
|
|
58
66
|
BID_STRATEGIES = %w[
|
|
59
67
|
LOWEST_COST_WITHOUT_CAP
|
|
60
68
|
LOWEST_COST_WITH_BID_CAP
|
|
61
69
|
TARGET_COST
|
|
62
70
|
].freeze
|
|
63
71
|
|
|
64
|
-
#
|
|
72
|
+
# AdAccount
|
|
65
73
|
|
|
66
74
|
def ad_account
|
|
67
75
|
@ad_account ||= AdAccount.find("act_#{account_id}")
|
|
68
76
|
end
|
|
69
77
|
|
|
70
|
-
#
|
|
78
|
+
# AdCampaign
|
|
71
79
|
|
|
72
80
|
def ad_campaign
|
|
73
81
|
@campaign ||= AdCampaign.find(campaign_id)
|
|
74
82
|
end
|
|
75
83
|
|
|
76
|
-
#
|
|
84
|
+
# Ad
|
|
77
85
|
|
|
78
86
|
def ads(effective_status: ['ACTIVE'], limit: 100)
|
|
79
87
|
Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
|
|
@@ -84,5 +92,21 @@ module FacebookAds
|
|
|
84
92
|
result = Ad.post("/act_#{account_id}/ads", query: query)
|
|
85
93
|
Ad.find(result['id'])
|
|
86
94
|
end
|
|
95
|
+
|
|
96
|
+
# AdInsight
|
|
97
|
+
|
|
98
|
+
# levels = enum {ad, adset, campaign, account}
|
|
99
|
+
# breakdowns = list<enum {..., product_id, ...}>
|
|
100
|
+
def ad_insights(range: Date.today..Date.today, level: nil, breakdowns: [], fields: [], limit: 1_000)
|
|
101
|
+
query = {
|
|
102
|
+
time_range: { since: range.first.to_s, until: range.last.to_s },
|
|
103
|
+
level: level,
|
|
104
|
+
breakdowns: breakdowns&.join(','),
|
|
105
|
+
fields: fields&.join(','),
|
|
106
|
+
limit: limit
|
|
107
|
+
}.reject { |_key, value| value.nil? || (value.respond_to?(:empty?) && value.empty?) }
|
|
108
|
+
|
|
109
|
+
AdInsight.paginate("/#{id}/insights", query: query)
|
|
110
|
+
end
|
|
87
111
|
end
|
|
88
112
|
end
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module FacebookAds
|
|
2
4
|
# https://developers.facebook.com/docs/marketing-api/targeting-specs
|
|
3
5
|
class AdTargeting
|
|
4
6
|
MEN = 1
|
|
5
7
|
WOMEN = 2
|
|
6
8
|
GENDERS = [MEN, WOMEN].freeze
|
|
7
|
-
ANDROID_OS = 'Android'
|
|
8
|
-
APPLE_OS = 'iOS'
|
|
9
|
+
ANDROID_OS = 'Android'
|
|
10
|
+
APPLE_OS = 'iOS'
|
|
9
11
|
OSES = [ANDROID_OS, APPLE_OS].freeze
|
|
10
12
|
ANDROID_DEVICES = %w[Android_Smartphone Android_Tablet].freeze
|
|
11
13
|
APPLE_DEVICES = %w[iPhone iPad iPod].freeze
|
|
12
14
|
DEVICES = ANDROID_DEVICES + APPLE_DEVICES
|
|
13
|
-
INSTALLED = 'installed'
|
|
14
|
-
NOT_INSTALLED = 'not_installed'
|
|
15
|
+
INSTALLED = 'installed'
|
|
16
|
+
NOT_INSTALLED = 'not_installed'
|
|
15
17
|
APP_INSTALL_STATES = [INSTALLED, NOT_INSTALLED].freeze
|
|
16
18
|
|
|
17
19
|
attr_accessor :genders, :age_min, :age_max, :countries, :user_os, :user_device, :app_install_state, :custom_locations, :income
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FacebookAds
|
|
4
|
+
# An ad user belongs to an ad account.
|
|
5
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group
|
|
6
|
+
class AdUser < Base
|
|
7
|
+
FIELDS = %w[].freeze
|
|
8
|
+
|
|
9
|
+
# belongs_to ad_account
|
|
10
|
+
|
|
11
|
+
def ad_account
|
|
12
|
+
@ad_account ||= AdAccount.find("act_#{account_id}")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module FacebookAds
|
|
4
|
+
# https://developers.facebook.com/docs/marketing-api/reference/ad-account/advertisable_applications
|
|
5
|
+
class AdvertisableApplication < Base
|
|
6
|
+
FIELDS = %w[].freeze
|
|
7
|
+
|
|
8
|
+
# belongs_to ad_account
|
|
9
|
+
|
|
10
|
+
def ad_account
|
|
11
|
+
@ad_account ||= AdAccount.find("act_#{account_id}")
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/facebook_ads/base.rb
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module FacebookAds
|
|
2
4
|
# The base class for all ads objects.
|
|
3
5
|
class Base < Hashie::Mash
|
|
4
6
|
class << self
|
|
5
|
-
|
|
6
|
-
get("/#{id}", objectify: true)
|
|
7
|
-
end
|
|
7
|
+
# HTTP verbs.
|
|
8
8
|
|
|
9
9
|
def get(path, query: {}, objectify:)
|
|
10
|
-
query = pack(query, objectify: objectify)
|
|
10
|
+
query = pack(query, objectify: objectify)
|
|
11
11
|
uri = "#{FacebookAds.base_uri}#{path}?" + build_nested_query(query)
|
|
12
12
|
FacebookAds.logger.debug "GET #{uri}"
|
|
13
|
+
|
|
13
14
|
response = begin
|
|
14
15
|
RestClient.get(uri, accept: :json, accept_encoding: :identity)
|
|
15
16
|
rescue RestClient::Exception => e
|
|
16
17
|
exception(:get, path, e)
|
|
17
18
|
end
|
|
19
|
+
|
|
18
20
|
unpack(response, objectify: objectify)
|
|
19
21
|
end
|
|
20
22
|
|
|
@@ -22,11 +24,13 @@ module FacebookAds
|
|
|
22
24
|
query = pack(query, objectify: false)
|
|
23
25
|
uri = "#{FacebookAds.base_uri}#{path}"
|
|
24
26
|
FacebookAds.logger.debug "POST #{uri} #{query}"
|
|
27
|
+
|
|
25
28
|
response = begin
|
|
26
29
|
RestClient.post(uri, query)
|
|
27
30
|
rescue RestClient::Exception => e
|
|
28
31
|
exception(:post, path, e)
|
|
29
32
|
end
|
|
33
|
+
|
|
30
34
|
unpack(response, objectify: false)
|
|
31
35
|
end
|
|
32
36
|
|
|
@@ -34,28 +38,50 @@ module FacebookAds
|
|
|
34
38
|
query = pack(query, objectify: false)
|
|
35
39
|
uri = "#{FacebookAds.base_uri}#{path}?" + build_nested_query(query)
|
|
36
40
|
FacebookAds.logger.debug "DELETE #{uri}"
|
|
41
|
+
|
|
37
42
|
response = begin
|
|
38
43
|
RestClient.delete(uri)
|
|
39
44
|
rescue RestClient::Exception => e
|
|
40
45
|
exception(:delete, path, e)
|
|
41
46
|
end
|
|
47
|
+
|
|
42
48
|
unpack(response, objectify: false)
|
|
43
49
|
end
|
|
44
50
|
|
|
51
|
+
# Common idioms.
|
|
52
|
+
|
|
53
|
+
def all(_query = {})
|
|
54
|
+
raise StandardError, 'Subclass must implement `all`.'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find(id)
|
|
58
|
+
get("/#{id}", objectify: true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def find_by(conditions)
|
|
62
|
+
all.detect do |object|
|
|
63
|
+
conditions.all? do |key, value|
|
|
64
|
+
object.send(key) == value
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
45
69
|
def paginate(path, query: {})
|
|
46
|
-
query[:limit]
|
|
47
|
-
|
|
48
|
-
response = get(path, query: query
|
|
49
|
-
data
|
|
70
|
+
query = query.merge(limit: 100) unless query[:limit]
|
|
71
|
+
query = query.merge(fields: self::FIELDS.join(',')) unless query[:fields] || self::FIELDS.empty?
|
|
72
|
+
response = get(path, query: query, objectify: false)
|
|
73
|
+
data = response['data'].nil? ? [] : response['data']
|
|
50
74
|
|
|
51
|
-
if data.length == limit
|
|
75
|
+
if data.length == query[:limit]
|
|
52
76
|
while !(paging = response['paging']).nil? && !(url = paging['next']).nil?
|
|
53
77
|
FacebookAds.logger.debug "GET #{url}"
|
|
78
|
+
|
|
54
79
|
response = begin
|
|
55
80
|
RestClient.get(url)
|
|
56
81
|
rescue RestClient::Exception => e
|
|
57
82
|
exception(:get, url, e)
|
|
58
83
|
end
|
|
84
|
+
|
|
59
85
|
response = unpack(response, objectify: false)
|
|
60
86
|
data += response['data'] unless response['data'].nil?
|
|
61
87
|
end
|
|
@@ -85,8 +111,8 @@ module FacebookAds
|
|
|
85
111
|
def pack(hash, objectify:)
|
|
86
112
|
hash = hash.merge(access_token: FacebookAds.access_token)
|
|
87
113
|
hash = hash.merge(appsecret_proof: FacebookAds.appsecret_proof) if FacebookAds.app_secret
|
|
88
|
-
hash = hash.merge(fields: self::FIELDS.join(',')) if objectify
|
|
89
|
-
hash.
|
|
114
|
+
hash = hash.merge(fields: self::FIELDS.join(',')) if objectify && !hash[:fields] && !self::FIELDS.empty?
|
|
115
|
+
hash.reject { |_key, value| value.nil? || (value.respond_to?(:empty?) && value.empty?) }
|
|
90
116
|
end
|
|
91
117
|
|
|
92
118
|
def unpack(response, objectify:)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
http_interactions:
|
|
3
3
|
- request:
|
|
4
4
|
method: get
|
|
5
|
-
uri: https://graph.facebook.com/v<api_version>/act_10152335766987003/campaigns?access_token=<access_token>&appsecret_proof=<appsecret_proof>&effective_status%5B%5D=ACTIVE&fields=id,account_id,buying_type,can_use_spend_cap,configured_status,created_time,effective_status,name,objective,start_time,stop_time,updated_time,spend_cap&limit=100
|
|
5
|
+
uri: https://graph.facebook.com/v<api_version>/act_10152335766987003/campaigns?access_token=<access_token>&appsecret_proof=<appsecret_proof>&effective_status%5B%5D=ACTIVE&fields=id,account_id,buying_type,can_use_spend_cap,configured_status,created_time,effective_status,name,objective,start_time,stop_time,updated_time,spend_cap,budget_remaining,daily_budget,lifetime_budget&limit=100
|
|
6
6
|
body:
|
|
7
7
|
encoding: US-ASCII
|
|
8
8
|
string: ''
|
data/spec/fixtures/cassettes/FacebookAds_AdAccount/_create_ad_campaign/creates_a_new_ad_campaign.yml
CHANGED
|
@@ -61,7 +61,7 @@ http_interactions:
|
|
|
61
61
|
recorded_at: Wed, 26 Apr 2017 00:48:41 GMT
|
|
62
62
|
- request:
|
|
63
63
|
method: get
|
|
64
|
-
uri: https://graph.facebook.com/v<api_version>/6076262393242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=id,account_id,buying_type,can_use_spend_cap,configured_status,created_time,effective_status,name,objective,start_time,stop_time,updated_time,spend_cap
|
|
64
|
+
uri: https://graph.facebook.com/v<api_version>/6076262393242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=id,account_id,buying_type,can_use_spend_cap,configured_status,created_time,effective_status,name,objective,start_time,stop_time,updated_time,spend_cap,budget_remaining,daily_budget,lifetime_budget
|
|
65
65
|
body:
|
|
66
66
|
encoding: US-ASCII
|
|
67
67
|
string: ''
|
data/spec/fixtures/cassettes/FacebookAds_AdCampaign/_destroy/sets_effective_status_to_deleted.yml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
http_interactions:
|
|
3
3
|
- request:
|
|
4
4
|
method: get
|
|
5
|
-
uri: https://graph.facebook.com/v<api_version>/6076262142242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=id,account_id,buying_type,can_use_spend_cap,configured_status,created_time,effective_status,name,objective,start_time,stop_time,updated_time,spend_cap
|
|
5
|
+
uri: https://graph.facebook.com/v<api_version>/6076262142242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=id,account_id,buying_type,can_use_spend_cap,configured_status,created_time,effective_status,name,objective,start_time,stop_time,updated_time,spend_cap,budget_remaining,daily_budget,lifetime_budget
|
|
6
6
|
body:
|
|
7
7
|
encoding: US-ASCII
|
|
8
8
|
string: ''
|
|
@@ -110,7 +110,7 @@ http_interactions:
|
|
|
110
110
|
recorded_at: Wed, 26 Apr 2017 00:52:20 GMT
|
|
111
111
|
- request:
|
|
112
112
|
method: get
|
|
113
|
-
uri: https://graph.facebook.com/v<api_version>/6076262142242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=id,account_id,buying_type,can_use_spend_cap,configured_status,created_time,effective_status,name,objective,start_time,stop_time,updated_time,spend_cap
|
|
113
|
+
uri: https://graph.facebook.com/v<api_version>/6076262142242?access_token=<access_token>&appsecret_proof=<appsecret_proof>&fields=id,account_id,buying_type,can_use_spend_cap,configured_status,created_time,effective_status,name,objective,start_time,stop_time,updated_time,spend_cap,budget_remaining,daily_budget,lifetime_budget
|
|
114
114
|
body:
|
|
115
115
|
encoding: US-ASCII
|
|
116
116
|
string: ''
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/approvals.rb
CHANGED
data/spec/support/rest_client.rb
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Force the request headers to only accept JSON, and disable gzip.
|
|
2
4
|
# This makes our VCR cassettes human readable.
|
|
3
5
|
module RestClient
|
|
4
6
|
class Request
|
|
5
7
|
def default_headers
|
|
6
|
-
{
|
|
7
|
-
accept: 'application/json',
|
|
8
|
-
accept_encoding: 'identity',
|
|
9
|
-
user_agent: RestClient::Platform.default_user_agent
|
|
10
|
-
}
|
|
8
|
+
{ accept: 'application/json', accept_encoding: 'identity' }
|
|
11
9
|
end
|
|
12
10
|
end
|
|
13
11
|
end
|
data/spec/support/vcr.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: facebook_ads
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.6.
|
|
4
|
+
version: 0.6.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Estreich
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-
|
|
11
|
+
date: 2018-10-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rest-client
|
|
@@ -42,7 +42,7 @@ description: This gem allows to easily manage your Facebook ads via Facebook's M
|
|
|
42
42
|
API in ruby.
|
|
43
43
|
email: chris@tophatter.com
|
|
44
44
|
executables:
|
|
45
|
-
-
|
|
45
|
+
- facebook_ads_console
|
|
46
46
|
extensions: []
|
|
47
47
|
extra_rdoc_files:
|
|
48
48
|
- README.md
|
|
@@ -57,9 +57,10 @@ files:
|
|
|
57
57
|
- LICENSE.md
|
|
58
58
|
- README.md
|
|
59
59
|
- Rakefile
|
|
60
|
-
- bin/
|
|
60
|
+
- bin/facebook_ads_console
|
|
61
61
|
- facebook_ads.gemspec
|
|
62
62
|
- lib/facebook_ads.rb
|
|
63
|
+
- lib/facebook_ads/account.rb
|
|
63
64
|
- lib/facebook_ads/ad.rb
|
|
64
65
|
- lib/facebook_ads/ad_account.rb
|
|
65
66
|
- lib/facebook_ads/ad_audience.rb
|
|
@@ -74,6 +75,8 @@ files:
|
|
|
74
75
|
- lib/facebook_ads/ad_product_set.rb
|
|
75
76
|
- lib/facebook_ads/ad_set.rb
|
|
76
77
|
- lib/facebook_ads/ad_targeting.rb
|
|
78
|
+
- lib/facebook_ads/ad_user.rb
|
|
79
|
+
- lib/facebook_ads/advertisable_application.rb
|
|
77
80
|
- lib/facebook_ads/base.rb
|
|
78
81
|
- spec/facebook_ads/ad_account_spec.rb
|
|
79
82
|
- spec/facebook_ads/ad_campaign_spec.rb
|
|
@@ -136,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
136
139
|
version: '0'
|
|
137
140
|
requirements: []
|
|
138
141
|
rubyforge_project:
|
|
139
|
-
rubygems_version: 2.6.
|
|
142
|
+
rubygems_version: 2.6.12
|
|
140
143
|
signing_key:
|
|
141
144
|
specification_version: 4
|
|
142
145
|
summary: Facebook Marketing API SDK for Ruby.
|