facebook_ads 0.1.5 → 0.1.7
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/.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
|