facebook_ads 0.1.5 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +23 -0
- data/.travis.yml +1 -1
- data/Gemfile +4 -5
- data/Gemfile.lock +34 -82
- data/{README.markdown → README.md} +43 -10
- data/Rakefile +6 -0
- data/bin/console +15 -1
- data/facebook_ads.gemspec +11 -13
- data/lib/facebook_ads.rb +11 -7
- data/lib/facebook_ads/ad.rb +6 -8
- data/lib/facebook_ads/ad_account.rb +65 -54
- data/lib/facebook_ads/ad_audience.rb +22 -0
- data/lib/facebook_ads/ad_campaign.rb +21 -17
- data/lib/facebook_ads/ad_creative.rb +7 -9
- data/lib/facebook_ads/ad_image.rb +3 -5
- data/lib/facebook_ads/ad_insight.rb +4 -5
- data/lib/facebook_ads/ad_product.rb +6 -0
- data/lib/facebook_ads/ad_product_catalog.rb +60 -0
- data/lib/facebook_ads/ad_product_feed.rb +6 -0
- data/lib/facebook_ads/ad_product_set.rb +6 -0
- data/lib/facebook_ads/ad_set.rb +9 -11
- data/lib/facebook_ads/ad_targeting.rb +19 -21
- data/lib/facebook_ads/base.rb +107 -62
- data/test/ad_account_test.rb +27 -0
- data/test/ad_campaign_test.rb +32 -0
- data/test/ad_creative_test.rb +7 -0
- data/test/ad_image_test.rb +27 -0
- data/test/ad_insight_test.rb +7 -0
- data/test/ad_product_catalog_test.rb +28 -0
- data/test/ad_product_feed_test.rb +7 -0
- data/test/ad_product_set_test.rb +7 -0
- data/test/ad_product_test.rb +22 -0
- data/test/ad_set_test.rb +7 -0
- data/test/ad_targeting_test.rb +7 -0
- data/test/ad_test.rb +7 -0
- data/test/facebook_ads_test.rb +8 -0
- data/test/test_helper.rb +64 -0
- data/test/vcr_cassettes/AdAccountTest-test_all.yml +70 -0
- data/test/vcr_cassettes/AdAccountTest-test_applications.yml +130 -0
- data/test/vcr_cassettes/AdAccountTest-test_find_by.yml +71 -0
- data/test/vcr_cassettes/AdCampaignTest-test_create.yml +295 -0
- data/test/vcr_cassettes/AdCampaignTest-test_list.yml +133 -0
- data/test/vcr_cassettes/AdImageTest-test_create.yml +2963 -0
- data/test/vcr_cassettes/AdImageTest-test_list.yml +137 -0
- data/test/vcr_cassettes/AdProductCatalogTest-test_all.yml +60 -0
- data/test/vcr_cassettes/AdProductCatalogTest-test_create.yml +256 -0
- data/test/vcr_cassettes/AdProductTest-test_list.yml +130 -0
- metadata +44 -49
- data/spec/ad_account_spec.rb +0 -78
- data/spec/ad_campaign_spec.rb +0 -13
- data/spec/ad_creative_spec.rb +0 -14
- data/spec/ad_image_spec.rb +0 -11
- data/spec/ad_insight_spec.rb +0 -11
- data/spec/ad_set_spec.rb +0 -13
- data/spec/ad_spec.rb +0 -13
- data/spec/ad_targeting_spec.rb +0 -4
- data/spec/spec_helper.rb +0 -15
- data/spec/support/fixtures.sh +0 -17
- data/spec/support/fixtures/6057330925170.json +0 -1
- data/spec/support/fixtures/6057810634370.json +0 -1
- data/spec/support/fixtures/6057810946970.json +0 -1
- data/spec/support/fixtures/6057824295570.json +0 -1
- data/spec/support/fixtures/act_861827983860489.json +0 -1
- data/spec/support/fixtures/act_861827983860489/adcreatives.json +0 -1
- data/spec/support/fixtures/act_861827983860489/adimages.json +0 -1
- data/spec/support/fixtures/act_861827983860489/ads.json +0 -1
- data/spec/support/fixtures/act_861827983860489/adsets.json +0 -1
- data/spec/support/fixtures/act_861827983860489/campaigns.json +0 -1
- data/spec/support/fixtures/me/adaccounts.json +0 -1
- data/spec/support/rack_facebook.rb +0 -22
data/lib/facebook_ads.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
# External requires.
|
2
|
-
require '
|
3
|
-
require '
|
2
|
+
require 'json'
|
3
|
+
require 'rest-client'
|
4
4
|
require 'hashie'
|
5
|
+
require 'logger'
|
5
6
|
|
6
7
|
# Internal requires.
|
7
8
|
require 'facebook_ads/base'
|
@@ -9,7 +10,6 @@ Dir[File.expand_path('../facebook_ads/*.rb', __FILE__)].each { |f| require f }
|
|
9
10
|
|
10
11
|
# The primary namespace for this gem.
|
11
12
|
module FacebookAds
|
12
|
-
|
13
13
|
def self.logger=(logger)
|
14
14
|
@logger = logger
|
15
15
|
end
|
@@ -28,10 +28,7 @@ module FacebookAds
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def self.base_uri
|
31
|
-
unless defined?(@base_uri)
|
32
|
-
@base_uri = 'https://graph.facebook.com/v2.6'
|
33
|
-
end
|
34
|
-
|
31
|
+
@base_uri = 'https://graph.facebook.com/v2.8' unless defined?(@base_uri)
|
35
32
|
@base_uri
|
36
33
|
end
|
37
34
|
|
@@ -43,4 +40,11 @@ module FacebookAds
|
|
43
40
|
@access_token
|
44
41
|
end
|
45
42
|
|
43
|
+
def self.business_id=(business_id)
|
44
|
+
@business_id = business_id
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.business_id
|
48
|
+
@business_id
|
49
|
+
end
|
46
50
|
end
|
data/lib/facebook_ads/ad.rb
CHANGED
@@ -2,33 +2,31 @@ module FacebookAds
|
|
2
2
|
# An ad belongs to an ad set. It is created using an ad creative.
|
3
3
|
# https://developers.facebook.com/docs/marketing-api/reference/adgroup
|
4
4
|
class Ad < Base
|
5
|
-
|
6
|
-
|
7
|
-
STATUSES = %w(ACTIVE PAUSED DELETED PENDING_REVIEW DISAPPROVED PREAPPROVED PENDING_BILLING_INFO CAMPAIGN_PAUSED ARCHIVED ADSET_PAUSED)
|
5
|
+
FIELDS = %w(id account_id campaign_id adset_id adlabels bid_amount bid_info bid_type configured_status conversion_specs created_time creative effective_status last_updated_by_app_id name tracking_specs updated_time ad_review_feedback).freeze
|
6
|
+
STATUSES = %w(ACTIVE PAUSED DELETED PENDING_REVIEW DISAPPROVED PREAPPROVED PENDING_BILLING_INFO CAMPAIGN_PAUSED ARCHIVED ADSET_PAUSED).freeze
|
8
7
|
|
9
8
|
# belongs_to ad_account
|
10
9
|
|
11
10
|
def ad_account
|
12
|
-
@ad_set ||=
|
11
|
+
@ad_set ||= AdAccount.find(account_id)
|
13
12
|
end
|
14
13
|
|
15
14
|
# belongs_to ad_campaign
|
16
15
|
|
17
16
|
def ad_campaign
|
18
|
-
@ad_set ||=
|
17
|
+
@ad_set ||= AdCampaign.find(campaign_id)
|
19
18
|
end
|
20
19
|
|
21
20
|
# belongs_to ad_set
|
22
21
|
|
23
22
|
def ad_set
|
24
|
-
@ad_set ||=
|
23
|
+
@ad_set ||= AdSet.find(adset_id)
|
25
24
|
end
|
26
25
|
|
27
26
|
# belongs_to ad_creative
|
28
27
|
|
29
28
|
def ad_creative
|
30
|
-
@ad_creative ||=
|
29
|
+
@ad_creative ||= AdCreative.find(creative['id'])
|
31
30
|
end
|
32
|
-
|
33
31
|
end
|
34
32
|
end
|
@@ -2,8 +2,7 @@ module FacebookAds
|
|
2
2
|
# An ad account has many ad campaigns, ad images, and ad creatives.
|
3
3
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-account
|
4
4
|
class AdAccount < Base
|
5
|
-
|
6
|
-
FIELDS = %w(id account_id account_status age created_time currency name last_used_time)
|
5
|
+
FIELDS = %w(id account_id account_status age created_time currency name).freeze
|
7
6
|
|
8
7
|
class << self
|
9
8
|
def all
|
@@ -11,9 +10,9 @@ module FacebookAds
|
|
11
10
|
end
|
12
11
|
|
13
12
|
def find_by(conditions)
|
14
|
-
all.detect do |
|
13
|
+
all.detect do |object|
|
15
14
|
conditions.all? do |key, value|
|
16
|
-
|
15
|
+
object.send(key) == value
|
17
16
|
end
|
18
17
|
end
|
19
18
|
end
|
@@ -22,90 +21,63 @@ module FacebookAds
|
|
22
21
|
# has_many campaigns
|
23
22
|
|
24
23
|
def ad_campaigns(effective_status: ['ACTIVE'], limit: 100)
|
25
|
-
|
24
|
+
AdCampaign.paginate("/#{id}/campaigns", query: { effective_status: effective_status, limit: limit })
|
26
25
|
end
|
27
26
|
|
28
27
|
def create_ad_campaign(name:, objective:, status: 'ACTIVE')
|
29
|
-
raise Exception, "Objective must be one of: #{
|
30
|
-
raise Exception, "Status must be one of: #{
|
31
|
-
campaign =
|
32
|
-
|
28
|
+
raise Exception, "Objective must be one of: #{AdCampaign::OBJECTIVES.to_sentence}" unless AdCampaign::OBJECTIVES.include?(objective)
|
29
|
+
raise Exception, "Status must be one of: #{AdCampaign::STATUSES.to_sentence}" unless AdCampaign::STATUSES.include?(status)
|
30
|
+
campaign = AdCampaign.post("/#{id}/campaigns", query: { name: name, objective: objective, status: status }, objectify: true)
|
31
|
+
AdCampaign.find(campaign.id)
|
33
32
|
end
|
34
33
|
|
35
34
|
# has_many ad_images
|
36
35
|
|
37
36
|
def ad_images(hashes: nil, limit: 100)
|
38
|
-
if hashes.
|
39
|
-
|
37
|
+
if !hashes.nil?
|
38
|
+
AdImage.get("/#{id}/adimages", query: { hashes: hashes }, objectify: true)
|
40
39
|
else
|
41
|
-
|
40
|
+
AdImage.paginate("/#{id}/adimages", query: { limit: limit })
|
42
41
|
end
|
43
42
|
end
|
44
43
|
|
45
44
|
def create_ad_images(urls)
|
46
|
-
files = urls.
|
47
|
-
|
48
|
-
name
|
49
|
-
data = HTTParty.get(url).body
|
50
|
-
file = File.open("/tmp/#{name}", 'w') # Assume *nix-based system.
|
51
|
-
file.binmode
|
52
|
-
file.write(data)
|
53
|
-
file.close
|
54
|
-
[name, File.open(file.path)]
|
45
|
+
files = urls.map do |url|
|
46
|
+
name, path = download(url)
|
47
|
+
[name, File.open(path)]
|
55
48
|
end.to_h
|
56
49
|
|
57
|
-
response =
|
50
|
+
response = AdImage.post("/#{id}/adimages", query: files, objectify: false)
|
58
51
|
files.values.each { |file| File.delete(file.path) }
|
59
|
-
|
60
|
-
if response['images'].present?
|
61
|
-
hashes = response['images'].map { |key, hash| hash['hash'] }
|
62
|
-
ad_images(hashes: hashes)
|
63
|
-
else
|
64
|
-
[]
|
65
|
-
end
|
52
|
+
!response['images'].nil? ? ad_images(hashes: response['images'].map { |_key, hash| hash['hash'] }) : []
|
66
53
|
end
|
67
54
|
|
68
55
|
# has_many ad_creatives
|
69
56
|
|
70
57
|
def ad_creatives(limit: 100)
|
71
|
-
|
58
|
+
AdCreative.paginate("/#{id}/adcreatives", query: { limit: limit })
|
72
59
|
end
|
73
60
|
|
74
61
|
def create_ad_creative(creative, carousel: true)
|
75
|
-
|
76
|
-
|
77
|
-
required = if carousel
|
78
|
-
%i(name page_id link message assets call_to_action_type multi_share_optimized multi_share_end_card)
|
79
|
-
else
|
80
|
-
%i(name page_id message link link_title image_hash call_to_action_type)
|
81
|
-
end
|
82
|
-
|
83
|
-
if (keys = required - creative.keys).present?
|
84
|
-
raise Exception, "Creative is missing the following: #{keys.to_sentence}"
|
85
|
-
end
|
86
|
-
|
87
|
-
raise Exception, "Creative call_to_action_type must be one of: #{FacebookAds::AdCreative::CALL_TO_ACTION_TYPES.to_sentence}" unless FacebookAds::AdCreative::CALL_TO_ACTION_TYPES.include?(creative[:call_to_action_type])
|
88
|
-
|
89
|
-
query = if carousel
|
90
|
-
FacebookAds::AdCreative.carousel(creative)
|
91
|
-
else
|
92
|
-
FacebookAds::AdCreative.photo(creative)
|
93
|
-
end
|
94
|
-
|
95
|
-
creative = FacebookAds::AdCreative.post("/#{id}/adcreatives", query: query, objectify: true) # Returns a FacebookAds::AdCreative instance.
|
96
|
-
FacebookAds::AdCreative.find(creative.id)
|
62
|
+
carousel ? create_carousel_ad_creative(creative) : create_image_ad_creative(creative)
|
97
63
|
end
|
98
64
|
|
99
65
|
# has_many ad_sets
|
100
66
|
|
101
67
|
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
102
|
-
|
68
|
+
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
103
69
|
end
|
104
70
|
|
105
71
|
# has_many ads
|
106
72
|
|
107
73
|
def ads(effective_status: ['ACTIVE'], limit: 100)
|
108
|
-
|
74
|
+
Ad.paginate("/#{id}/ads", query: { effective_status: effective_status, limit: limit })
|
75
|
+
end
|
76
|
+
|
77
|
+
# has_many ad_audiences
|
78
|
+
|
79
|
+
def ad_audiences
|
80
|
+
AdAudience.paginate("/#{id}/customaudiences")
|
109
81
|
end
|
110
82
|
|
111
83
|
# has_many ad_insights
|
@@ -122,5 +94,44 @@ module FacebookAds
|
|
122
94
|
self.class.get("/#{id}/advertisable_applications", objectify: false)
|
123
95
|
end
|
124
96
|
|
97
|
+
private
|
98
|
+
|
99
|
+
def create_carousel_ad_creative(creative)
|
100
|
+
required = %i(name page_id link message assets call_to_action_type multi_share_optimized multi_share_end_card)
|
101
|
+
|
102
|
+
unless (keys = required - creative.keys).length.zero?
|
103
|
+
raise Exception, "Creative is missing the following: #{keys.to_sentence}"
|
104
|
+
end
|
105
|
+
|
106
|
+
raise Exception, "Creative call_to_action_type must be one of: #{AdCreative::CALL_TO_ACTION_TYPES.to_sentence}" unless AdCreative::CALL_TO_ACTION_TYPES.include?(creative[:call_to_action_type])
|
107
|
+
query = AdCreative.carousel(creative)
|
108
|
+
creative = AdCreative.post("/#{id}/adcreatives", query: query, objectify: true) # Returns an AdCreative instance.
|
109
|
+
AdCreative.find(creative.id)
|
110
|
+
end
|
111
|
+
|
112
|
+
def create_image_ad_creative(creative)
|
113
|
+
required = %i(name page_id message link link_title image_hash call_to_action_type)
|
114
|
+
|
115
|
+
unless (keys = required - creative.keys).length.zero?
|
116
|
+
raise Exception, "Creative is missing the following: #{keys.to_sentence}"
|
117
|
+
end
|
118
|
+
|
119
|
+
raise Exception, "Creative call_to_action_type must be one of: #{AdCreative::CALL_TO_ACTION_TYPES.to_sentence}" unless AdCreative::CALL_TO_ACTION_TYPES.include?(creative[:call_to_action_type])
|
120
|
+
query = AdCreative.photo(creative)
|
121
|
+
creative = AdCreative.post("/#{id}/adcreatives", query: query, objectify: true) # Returns an AdCreative instance.
|
122
|
+
AdCreative.find(creative.id)
|
123
|
+
end
|
124
|
+
|
125
|
+
def download(url)
|
126
|
+
pathname = Pathname.new(url)
|
127
|
+
name = "#{pathname.dirname.basename}.jpg"
|
128
|
+
# @FIXME: Need to handle exception here.
|
129
|
+
data = RestClient.get(url).body
|
130
|
+
file = File.open("/tmp/#{name}", 'w') # Assume *nix-based system.
|
131
|
+
file.binmode
|
132
|
+
file.write(data)
|
133
|
+
file.close
|
134
|
+
[name, file.path]
|
135
|
+
end
|
125
136
|
end
|
126
137
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module FacebookAds
|
2
|
+
# https://developers.facebook.com/docs/marketing-api/reference/custom-audience
|
3
|
+
class AdAudience < Base
|
4
|
+
FIELDS = %w(id account_id subtype name description approximate_count data_source delivery_status external_event_source lookalike_audience_ids lookalike_spec operation_status opt_out_link permission_for_actions pixel_id retention_days rule time_content_updated time_created time_updated).freeze
|
5
|
+
|
6
|
+
# belongs_to ad_account
|
7
|
+
|
8
|
+
def ad_account
|
9
|
+
@ad_account ||= AdAccount.find("act_#{account_id}")
|
10
|
+
end
|
11
|
+
|
12
|
+
# actions
|
13
|
+
|
14
|
+
def share(account_id)
|
15
|
+
query = {
|
16
|
+
share_with_object_id: account_id,
|
17
|
+
share_with_object_type: 'Account'
|
18
|
+
}
|
19
|
+
AdAccount.post("/#{id}/share_with_objects", query: query, objectify: false)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -2,53 +2,57 @@ module FacebookAds
|
|
2
2
|
# An ad campaign has many ad sets and belongs to an ad account.
|
3
3
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group
|
4
4
|
class AdCampaign < Base
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
OBJECTIVES = %w(CONVERSIONS MOBILE_APP_INSTALLS) # %w(BRAND_AWARENESS CANVAS_APP_ENGAGEMENT CANVAS_APP_INSTALLS CONVERSIONS EVENT_RESPONSES EXTERNAL LEAD_GENERATION LINK_CLICKS LOCAL_AWARENESS MOBILE_APP_ENGAGEMENT MOBILE_APP_INSTALLS OFFER_CLAIMS PAGE_LIKES POST_ENGAGEMENT PRODUCT_CATALOG_SALES REACH VIDEO_VIEWS)
|
5
|
+
FIELDS = %w(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).freeze
|
6
|
+
STATUSES = %w(ACTIVE PAUSED DELETED PENDING_REVIEW DISAPPROVED PREAPPROVED PENDING_BILLING_INFO CAMPAIGN_PAUSED ARCHIVED ADSET_PAUSED).freeze
|
7
|
+
OBJECTIVES = %w(CONVERSIONS MOBILE_APP_INSTALLS).freeze
|
9
8
|
|
10
9
|
# belongs_to ad_account
|
11
10
|
|
12
11
|
def ad_account
|
13
|
-
@ad_account ||=
|
12
|
+
@ad_account ||= AdAccount.find("act_#{account_id}")
|
14
13
|
end
|
15
14
|
|
16
15
|
# has_many ad_sets
|
17
16
|
|
18
17
|
def ad_sets(effective_status: ['ACTIVE'], limit: 100)
|
19
|
-
|
18
|
+
AdSet.paginate("/#{id}/adsets", query: { effective_status: effective_status, limit: limit })
|
20
19
|
end
|
21
20
|
|
22
21
|
def create_ad_set(name:, promoted_object:, targeting:, daily_budget:, optimization_goal:, billing_event: 'IMPRESSIONS', status: 'ACTIVE', is_autobid: true)
|
23
|
-
raise Exception, "Optimization goal must be one of: #{
|
24
|
-
raise Exception, "Billing event must be one of: #{
|
22
|
+
raise Exception, "Optimization goal must be one of: #{AdSet::OPTIMIZATION_GOALS.to_sentence}" unless AdSet::OPTIMIZATION_GOALS.include?(optimization_goal)
|
23
|
+
raise Exception, "Billing event must be one of: #{AdSet::BILLING_EVENTS.to_sentence}" unless AdSet::BILLING_EVENTS.include?(billing_event)
|
25
24
|
|
26
|
-
|
25
|
+
if targeting.is_a?(Hash)
|
26
|
+
# NOP
|
27
|
+
else
|
28
|
+
targeting.validate! # Will raise if invalid.
|
29
|
+
targeting = targeting.to_hash
|
30
|
+
end
|
27
31
|
|
28
|
-
|
32
|
+
query = {
|
29
33
|
campaign_id: id,
|
30
34
|
name: name,
|
31
|
-
targeting: targeting.
|
35
|
+
targeting: targeting.to_json,
|
32
36
|
promoted_object: promoted_object.to_json,
|
33
37
|
optimization_goal: optimization_goal,
|
34
38
|
daily_budget: daily_budget,
|
35
39
|
billing_event: billing_event,
|
36
40
|
status: status,
|
37
41
|
is_autobid: is_autobid
|
38
|
-
}
|
39
|
-
|
40
|
-
|
42
|
+
}
|
43
|
+
ad_set = AdSet.post("/act_#{account_id}/adsets", query: query, objectify: true) # Returns an AdSet instance.
|
44
|
+
AdSet.find(ad_set.id)
|
41
45
|
end
|
42
46
|
|
43
47
|
# has_many ad_insights
|
44
48
|
|
45
49
|
def ad_insights(range: Date.today..Date.today, level: 'ad', time_increment: 1)
|
46
|
-
|
50
|
+
query = {
|
47
51
|
level: level,
|
48
52
|
time_increment: time_increment,
|
49
53
|
time_range: { 'since': range.first.to_s, 'until': range.last.to_s }
|
50
|
-
}
|
54
|
+
}
|
55
|
+
AdInsight.paginate("/#{id}/insights", query: query)
|
51
56
|
end
|
52
|
-
|
53
57
|
end
|
54
58
|
end
|
@@ -2,12 +2,10 @@ module FacebookAds
|
|
2
2
|
# Ad ad creative has many ad images and belongs to an ad account.
|
3
3
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-creative
|
4
4
|
class AdCreative < Base
|
5
|
-
|
6
|
-
|
7
|
-
CALL_TO_ACTION_TYPES = %w(SHOP_NOW INSTALL_MOBILE_APP USE_MOBILE_APP SIGN_UP DOWNLOAD BUY_NOW) # %w(OPEN_LINK LIKE_PAGE SHOP_NOW PLAY_GAME INSTALL_APP USE_APP INSTALL_MOBILE_APP USE_MOBILE_APP BOOK_TRAVEL LISTEN_MUSIC WATCH_VIDEO LEARN_MORE SIGN_UP DOWNLOAD WATCH_MORE NO_BUTTON CALL_NOW BUY_NOW GET_OFFER GET_OFFER_VIEW GET_DIRECTIONS MESSAGE_PAGE SUBSCRIBE SELL_NOW DONATE_NOW GET_QUOTE CONTACT_US RECORD_NOW VOTE_NOW OPEN_MOVIES)
|
5
|
+
FIELDS = %w(id name object_story_id object_story_spec object_type thumbnail_url run_status).freeze
|
6
|
+
CALL_TO_ACTION_TYPES = %w(SHOP_NOW INSTALL_MOBILE_APP USE_MOBILE_APP SIGN_UP DOWNLOAD BUY_NOW).freeze
|
8
7
|
|
9
8
|
class << self
|
10
|
-
|
11
9
|
def photo(name:, page_id:, instagram_actor_id: nil, message:, link:, link_title:, image_hash:, call_to_action_type:)
|
12
10
|
object_story_spec = {
|
13
11
|
'page_id' => page_id, # 300664329976860
|
@@ -26,13 +24,14 @@ module FacebookAds
|
|
26
24
|
}
|
27
25
|
}
|
28
26
|
}
|
27
|
+
|
29
28
|
{
|
30
29
|
name: name,
|
31
30
|
object_story_spec: object_story_spec.to_json
|
32
31
|
}
|
33
32
|
end
|
34
33
|
|
35
|
-
# https://developers.facebook.com/docs/marketing-api/guides/
|
34
|
+
# https://developers.facebook.com/docs/marketing-api/guides/videoads
|
36
35
|
def carousel(name:, page_id:, instagram_actor_id: nil, link:, message:, assets:, call_to_action_type:, multi_share_optimized:, multi_share_end_card:)
|
37
36
|
object_story_spec = {
|
38
37
|
'page_id' => page_id, # 300664329976860
|
@@ -41,7 +40,7 @@ module FacebookAds
|
|
41
40
|
'link' => link, # https://tophatter.com/, https://itunes.apple.com/app/id619460348, http://play.google.com/store/apps/details?id=com.tophatter
|
42
41
|
'message' => message,
|
43
42
|
'call_to_action' => { 'type' => call_to_action_type },
|
44
|
-
'child_attachments' => assets.collect
|
43
|
+
'child_attachments' => assets.collect do |asset|
|
45
44
|
{
|
46
45
|
'link' => link,
|
47
46
|
'image_hash' => asset[:hash],
|
@@ -49,18 +48,17 @@ module FacebookAds
|
|
49
48
|
# 'description' => asset[:title],
|
50
49
|
'call_to_action' => { 'type' => call_to_action_type } # Redundant?
|
51
50
|
}
|
52
|
-
|
51
|
+
end,
|
53
52
|
'multi_share_optimized' => multi_share_optimized,
|
54
53
|
'multi_share_end_card' => multi_share_end_card
|
55
54
|
}
|
56
55
|
}
|
56
|
+
|
57
57
|
{
|
58
58
|
name: name,
|
59
59
|
object_story_spec: object_story_spec.to_json
|
60
60
|
}
|
61
61
|
end
|
62
|
-
|
63
62
|
end
|
64
|
-
|
65
63
|
end
|
66
64
|
end
|
@@ -3,11 +3,10 @@ module FacebookAds
|
|
3
3
|
# An image will always produce the same hash.
|
4
4
|
# https://developers.facebook.com/docs/marketing-api/reference/ad-image
|
5
5
|
class AdImage < Base
|
6
|
-
|
7
|
-
FIELDS = %w(id hash account_id name permalink_url original_width original_height)
|
6
|
+
FIELDS = %w(id hash account_id name permalink_url original_width original_height).freeze
|
8
7
|
|
9
8
|
class << self
|
10
|
-
def find(
|
9
|
+
def find(_id)
|
11
10
|
raise Exception, 'NOT IMPLEMENTED'
|
12
11
|
end
|
13
12
|
end
|
@@ -16,13 +15,12 @@ module FacebookAds
|
|
16
15
|
self[:hash]
|
17
16
|
end
|
18
17
|
|
19
|
-
def update(
|
18
|
+
def update(_data)
|
20
19
|
raise Exception, 'NOT IMPLEMENTED'
|
21
20
|
end
|
22
21
|
|
23
22
|
def destroy
|
24
23
|
super(path: "/act_#{account_id}/adimages", query: { hash: hash })
|
25
24
|
end
|
26
|
-
|
27
25
|
end
|
28
26
|
end
|