adparlor-facebook 0.5.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.gitlab-ci.yml +5 -0
- data/.rspec +2 -0
- data/.rubocop.yml +28 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +136 -0
- data/Guardfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +164 -0
- data/Rakefile +3 -0
- data/adparlor-facebook.gemspec +38 -0
- data/bin/console +18 -0
- data/bin/gitlab_runner_specs.sh +22 -0
- data/bin/setup +7 -0
- data/lib/adparlor/facebook/api.rb +106 -0
- data/lib/adparlor/facebook/config.rb +24 -0
- data/lib/adparlor/facebook/error.rb +13 -0
- data/lib/adparlor/facebook/graph_api/activity.rb +11 -0
- data/lib/adparlor/facebook/graph_api/ad.rb +43 -0
- data/lib/adparlor/facebook/graph_api/ad_account.rb +168 -0
- data/lib/adparlor/facebook/graph_api/ad_creative.rb +22 -0
- data/lib/adparlor/facebook/graph_api/ad_image.rb +87 -0
- data/lib/adparlor/facebook/graph_api/ad_label.rb +12 -0
- data/lib/adparlor/facebook/graph_api/ad_preview.rb +15 -0
- data/lib/adparlor/facebook/graph_api/ad_set.rb +39 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_search.rb +64 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_education_major.rb +11 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_education_school.rb +11 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocation.rb +19 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocation_meta.rb +16 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_city.rb +13 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_country.rb +13 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_geo_market.rb +13 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_region.rb +13 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_zip_code.rb +13 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_interest.rb +11 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_interest_suggestion.rb +12 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_interest_validation.rb +18 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_locale.rb +11 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_radius_suggestion.rb +11 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_targeting_category.rb +18 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_work_employer.rb +11 -0
- data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_work_position.rb +11 -0
- data/lib/adparlor/facebook/graph_api/ad_video.rb +124 -0
- data/lib/adparlor/facebook/graph_api/ads_pixel.rb +22 -0
- data/lib/adparlor/facebook/graph_api/ads_pixel_stat.rb +25 -0
- data/lib/adparlor/facebook/graph_api/advertisable_application.rb +11 -0
- data/lib/adparlor/facebook/graph_api/app_event_type.rb +11 -0
- data/lib/adparlor/facebook/graph_api/application.rb +15 -0
- data/lib/adparlor/facebook/graph_api/batch_object.rb +147 -0
- data/lib/adparlor/facebook/graph_api/broad_targeting_category.rb +11 -0
- data/lib/adparlor/facebook/graph_api/business.rb +18 -0
- data/lib/adparlor/facebook/graph_api/campaign.rb +43 -0
- data/lib/adparlor/facebook/graph_api/collection_proxy.rb +36 -0
- data/lib/adparlor/facebook/graph_api/connection_object.rb +11 -0
- data/lib/adparlor/facebook/graph_api/custom_audience.rb +31 -0
- data/lib/adparlor/facebook/graph_api/custom_audience_tos.rb +11 -0
- data/lib/adparlor/facebook/graph_api/custom_audience_user.rb +85 -0
- data/lib/adparlor/facebook/graph_api/custom_conversion.rb +22 -0
- data/lib/adparlor/facebook/graph_api/delivery_estimate.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/activity.rb +12 -0
- data/lib/adparlor/facebook/graph_api/fields/ad.rb +60 -0
- data/lib/adparlor/facebook/graph_api/fields/ad_account.rb +21 -0
- data/lib/adparlor/facebook/graph_api/fields/ad_creative.rb +23 -0
- data/lib/adparlor/facebook/graph_api/fields/ad_image.rb +14 -0
- data/lib/adparlor/facebook/graph_api/fields/ad_label.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/ad_preview.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/ad_set.rb +100 -0
- data/lib/adparlor/facebook/graph_api/fields/ad_video.rb +22 -0
- data/lib/adparlor/facebook/graph_api/fields/ads_pixel.rb +12 -0
- data/lib/adparlor/facebook/graph_api/fields/ads_pixel_stat.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/advertisable_application.rb +17 -0
- data/lib/adparlor/facebook/graph_api/fields/app_event_type.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/application.rb +31 -0
- data/lib/adparlor/facebook/graph_api/fields/broad_targeting_category.rb +12 -0
- data/lib/adparlor/facebook/graph_api/fields/business.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/campaign.rb +17 -0
- data/lib/adparlor/facebook/graph_api/fields/connection_object.rb +16 -0
- data/lib/adparlor/facebook/graph_api/fields/custom_audience.rb +22 -0
- data/lib/adparlor/facebook/graph_api/fields/custom_audience_tos.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/custom_audience_user.rb +14 -0
- data/lib/adparlor/facebook/graph_api/fields/custom_conversion.rb +14 -0
- data/lib/adparlor/facebook/graph_api/fields/delivery_estimate.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/field_decorator.rb +51 -0
- data/lib/adparlor/facebook/graph_api/fields/insight.rb +16 -0
- data/lib/adparlor/facebook/graph_api/fields/instagram_account.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/leadgen_form.rb +23 -0
- data/lib/adparlor/facebook/graph_api/fields/page.rb +121 -0
- data/lib/adparlor/facebook/graph_api/fields/page_and_place.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/partner_category.rb +12 -0
- data/lib/adparlor/facebook/graph_api/fields/picture.rb +17 -0
- data/lib/adparlor/facebook/graph_api/fields/post.rb +17 -0
- data/lib/adparlor/facebook/graph_api/fields/product_audience.rb +12 -0
- data/lib/adparlor/facebook/graph_api/fields/product_catalog.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/product_feed.rb +12 -0
- data/lib/adparlor/facebook/graph_api/fields/product_group.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/product_item.rb +18 -0
- data/lib/adparlor/facebook/graph_api/fields/product_set.rb +13 -0
- data/lib/adparlor/facebook/graph_api/fields/reach_estimate.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/reach_frequency_prediction.rb +24 -0
- data/lib/adparlor/facebook/graph_api/fields/targeting_sentence_line.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/targeting_suggestion.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/targeting_validation.rb +11 -0
- data/lib/adparlor/facebook/graph_api/fields/user_lead_gen_info.rb +18 -0
- data/lib/adparlor/facebook/graph_api/fields/video_thumbnail.rb +11 -0
- data/lib/adparlor/facebook/graph_api/graph_object.rb +140 -0
- data/lib/adparlor/facebook/graph_api/insight.rb +18 -0
- data/lib/adparlor/facebook/graph_api/instagram_account.rb +12 -0
- data/lib/adparlor/facebook/graph_api/leadgen_form.rb +10 -0
- data/lib/adparlor/facebook/graph_api/page.rb +27 -0
- data/lib/adparlor/facebook/graph_api/page_and_place.rb +11 -0
- data/lib/adparlor/facebook/graph_api/partner_category.rb +11 -0
- data/lib/adparlor/facebook/graph_api/picture.rb +15 -0
- data/lib/adparlor/facebook/graph_api/post.rb +17 -0
- data/lib/adparlor/facebook/graph_api/product_audience.rb +17 -0
- data/lib/adparlor/facebook/graph_api/product_catalog.rb +27 -0
- data/lib/adparlor/facebook/graph_api/product_feed.rb +11 -0
- data/lib/adparlor/facebook/graph_api/product_group.rb +11 -0
- data/lib/adparlor/facebook/graph_api/product_item.rb +11 -0
- data/lib/adparlor/facebook/graph_api/product_set.rb +26 -0
- data/lib/adparlor/facebook/graph_api/reach_estimate.rb +17 -0
- data/lib/adparlor/facebook/graph_api/reach_frequency_prediction.rb +18 -0
- data/lib/adparlor/facebook/graph_api/targeting_sentence_line.rb +11 -0
- data/lib/adparlor/facebook/graph_api/targeting_suggestion.rb +16 -0
- data/lib/adparlor/facebook/graph_api/targeting_validation.rb +16 -0
- data/lib/adparlor/facebook/graph_api/traits/methods.rb +64 -0
- data/lib/adparlor/facebook/graph_api/user_lead_gen_info.rb +11 -0
- data/lib/adparlor/facebook/graph_api/video_thumbnail.rb +11 -0
- data/lib/adparlor/facebook/graph_api.rb +9 -0
- data/lib/adparlor/facebook/version.rb +5 -0
- data/lib/adparlor/facebook.rb +9 -0
- data/solano.yml +15 -0
- metadata +391 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
module Adparlor
|
5
|
+
module Facebook
|
6
|
+
module GraphApi
|
7
|
+
class AdVideo < GraphObject
|
8
|
+
include Fields::AdVideo
|
9
|
+
include Traits::Methods
|
10
|
+
field_attrs FIELDS
|
11
|
+
allow_local_method :create, :destroy, :update
|
12
|
+
|
13
|
+
def base_uri
|
14
|
+
'https://graph-video.facebook.com'
|
15
|
+
end
|
16
|
+
|
17
|
+
def thumbnails
|
18
|
+
@thumbnails ||= CollectionProxy.new VideoThumbnail, "/#{id}/thumbnails", access_token
|
19
|
+
end
|
20
|
+
|
21
|
+
def path
|
22
|
+
raise FbError.new('missing one of account_id, user_id, event_id, page_id or group_id required for video creation', 500) unless @account_id || @user_id || @event_id || @page_id || @group_id
|
23
|
+
if @account_id
|
24
|
+
"/act_#{@account_id}/advideos"
|
25
|
+
else
|
26
|
+
"/#{@user_id || @event_id || @page_id || @group_id}/videos"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def post(path, options, _method = nil)
|
31
|
+
if @slideshow_spec
|
32
|
+
super(path, options)
|
33
|
+
else
|
34
|
+
response = start_video_request(options)
|
35
|
+
raise response['error']['message'] if response.key?('error')
|
36
|
+
upload_session_id = response['upload_session_id']
|
37
|
+
video_id = response['video_id']
|
38
|
+
loop do
|
39
|
+
response = upload_chunk(response['start_offset'], response['end_offset'], upload_session_id, options)
|
40
|
+
break if response['start_offset'] == response['end_offset']
|
41
|
+
sleep 5
|
42
|
+
end
|
43
|
+
response = finish_video_request(options, upload_session_id)
|
44
|
+
if response['success']
|
45
|
+
{ id: video_id }
|
46
|
+
else
|
47
|
+
response
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def create_file_from_offset(offset)
|
55
|
+
file = Tempfile.new('image_slice')
|
56
|
+
file.binmode
|
57
|
+
file.write(video.read(offset))
|
58
|
+
file.rewind
|
59
|
+
file
|
60
|
+
end
|
61
|
+
|
62
|
+
def finish_video_request(options, upload_session_id)
|
63
|
+
options[:upload_phase] = 'finish'
|
64
|
+
options[:upload_session_id] = upload_session_id
|
65
|
+
|
66
|
+
conn.post full_url(path, options) do |request|
|
67
|
+
request.headers['Content-Type'] = 'application/json'
|
68
|
+
request.params = options
|
69
|
+
end.body
|
70
|
+
end
|
71
|
+
|
72
|
+
# Remove any query string parameters before getting the mime type
|
73
|
+
# should probably be done in mime types gem
|
74
|
+
def normalized_file_name
|
75
|
+
@source.split('?').first
|
76
|
+
end
|
77
|
+
|
78
|
+
def start_video_request(options)
|
79
|
+
options[:upload_phase] = 'start'
|
80
|
+
options[:file_size] = video_size
|
81
|
+
|
82
|
+
conn.post full_url(path, options) do |request|
|
83
|
+
request.headers['Content-Type'] = 'application/json'
|
84
|
+
request.params = options
|
85
|
+
end.body
|
86
|
+
end
|
87
|
+
|
88
|
+
def upload_chunk(start_offset, end_offset, upload_session_id, options)
|
89
|
+
read_length = end_offset.to_i - start_offset.to_i unless end_offset == video_size
|
90
|
+
temp_file = create_file_from_offset(read_length)
|
91
|
+
response = conn.post full_url(path, options) do |request|
|
92
|
+
request.headers['Content-Type'] = 'multipart/form-data'
|
93
|
+
request.params = options
|
94
|
+
request.body = {
|
95
|
+
upload_phase: 'transfer', upload_session_id: upload_session_id,
|
96
|
+
start_offset: start_offset, video_file_chunk: Faraday::UploadIO.new(temp_file.path, MIME::Types.type_for(normalized_file_name))
|
97
|
+
}
|
98
|
+
end.body
|
99
|
+
if response.key?('error') && response['error']['error_subcode'] == 1487742
|
100
|
+
raise FbError.new('There have been too many calls from this ad-account. Wait a bit and try again.', 500)
|
101
|
+
else
|
102
|
+
response
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def video
|
107
|
+
@video ||= if %r{https?} =~ URI.parse(@source).scheme
|
108
|
+
tmp_file = Tempfile.new([File.basename(normalized_file_name), File.extname(normalized_file_name)])
|
109
|
+
tmp_file.binmode
|
110
|
+
open(@source) { |file| tmp_file.write(file.read) }
|
111
|
+
tmp_file.rewind
|
112
|
+
tmp_file
|
113
|
+
else
|
114
|
+
File.open(normalized_file_name, 'r')
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def video_size
|
119
|
+
File.size(video.path)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class AdsPixel < GraphObject
|
5
|
+
include Fields::AdsPixel
|
6
|
+
include Traits::Methods
|
7
|
+
field_attrs FIELDS
|
8
|
+
allow_method :create, :update
|
9
|
+
allow_local_method :update
|
10
|
+
|
11
|
+
def update_path
|
12
|
+
raise FbError.new('required parameter id missing', 500) unless id
|
13
|
+
"/#{id}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def stats
|
17
|
+
@stats ||= CollectionProxy.new AdsPixelStat, "#{id}/stats", access_token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class AdsPixelStat < GraphObject
|
5
|
+
include Fields::AdsPixelStat
|
6
|
+
include Traits::Methods
|
7
|
+
field_attrs FIELDS
|
8
|
+
|
9
|
+
def get(path, options)
|
10
|
+
validate_options(options)
|
11
|
+
super(path, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def validate_options(options)
|
17
|
+
options.delete(:fields) # just not required for this request so ignore them if they are passed
|
18
|
+
raise FbError.new('invalid parameters passed', 500) if
|
19
|
+
(options.keys.map(&:to_s) - %w(access_token aggregation end_time event start_time id)).any?
|
20
|
+
raise FbError.new('required parameter aggregation missing', 500) if options[:aggregation].nil?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class Application < GraphObject
|
5
|
+
include Fields::Application
|
6
|
+
include Traits::Methods
|
7
|
+
field_attrs FIELDS
|
8
|
+
|
9
|
+
def app_event_types
|
10
|
+
@app_event_types ||= CollectionProxy.new AppEventType, "/#{id}/app_event_types", access_token
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'mime/types'
|
2
|
+
|
3
|
+
module Adparlor
|
4
|
+
module Facebook
|
5
|
+
module GraphApi
|
6
|
+
class BatchObject < GraphObject
|
7
|
+
attr_accessor :batch_calls
|
8
|
+
|
9
|
+
def initialize(graph_object)
|
10
|
+
@access_token = graph_object.access_token
|
11
|
+
@batch_calls = { batch: [] }
|
12
|
+
@batch_types = []
|
13
|
+
@files = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(id, klass = nil, options = {})
|
17
|
+
top_level_access_token(options)
|
18
|
+
relative_url = relative_url(id, klass, options)
|
19
|
+
@batch_types << klass
|
20
|
+
@batch_calls[:batch] << { method: 'DELETE', relative_url: relative_url }
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def execute
|
25
|
+
content_type_header = @files.any? ? 'multipart/form-data' : 'application/json'
|
26
|
+
response = conn.post full_url('/', {}) do |request|
|
27
|
+
request.headers['Content-Type'] = content_type_header
|
28
|
+
request.params = { access_token: access_token }
|
29
|
+
request.body = @files.any? ? @files.merge(batch: @batch_calls[:batch].to_json) : @batch_calls.to_json
|
30
|
+
end
|
31
|
+
initialize_response(response)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(id, klass = nil, options = {})
|
35
|
+
top_level_access_token(options)
|
36
|
+
relative_url = relative_url(id, klass, options)
|
37
|
+
@batch_types << klass
|
38
|
+
@batch_calls[:batch] << { method: 'GET', relative_url: relative_url }
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def post(id, klass, attributes = {}, options = {})
|
43
|
+
top_level_access_token(options)
|
44
|
+
attached_file = attach_file(attributes)
|
45
|
+
name = options.delete(:name)
|
46
|
+
relative_url = relative_url(id, klass, options)
|
47
|
+
body = query_string attributes
|
48
|
+
@batch_types << klass
|
49
|
+
@batch_calls[:batch] << { method: 'POST', relative_url: relative_url, attached_files: attached_file, name: name, body: body }.delete_if { |_, v| v.nil? }
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def edge_endpoint(klass)
|
56
|
+
"#{klass.name.split('::').last.downcase}s"
|
57
|
+
end
|
58
|
+
|
59
|
+
def initialize_response(response)
|
60
|
+
data = []
|
61
|
+
response.body.each_with_index.map do |result, index|
|
62
|
+
if result.nil?
|
63
|
+
data[index] = nil
|
64
|
+
next
|
65
|
+
end
|
66
|
+
if result.include?('error')
|
67
|
+
data[index] = result.last
|
68
|
+
next
|
69
|
+
end
|
70
|
+
if result['code'] == 200
|
71
|
+
data[index] = []
|
72
|
+
body = JSON.parse(result['body'])
|
73
|
+
case
|
74
|
+
when body.key?('data')
|
75
|
+
if body['data'].is_a? Hash
|
76
|
+
data[index] << @batch_types[index].new(body['data'])
|
77
|
+
else
|
78
|
+
body['data'].each { |object| data[index] << @batch_types[index].new(object) }
|
79
|
+
if body['paging'] && body['paging']['next']
|
80
|
+
data[index] += @batch_types[index].get(body['paging']['next'].delete('\\'))
|
81
|
+
end
|
82
|
+
end
|
83
|
+
when body.key?('images')
|
84
|
+
body['images'].keys.each do |key|
|
85
|
+
data[index] << Adparlor::Facebook::GraphApi::AdImage.new(body['images'][key])
|
86
|
+
end
|
87
|
+
if body['paging'] && body['paging']['next']
|
88
|
+
data[index] += Adparlor::Facebook::GraphApi::AdImage.get(body['paging']['next'].delete('\\'))
|
89
|
+
end
|
90
|
+
when body.key?('id')
|
91
|
+
data[index] = @batch_types[index].new(body)
|
92
|
+
else
|
93
|
+
data[index] = body
|
94
|
+
end
|
95
|
+
else
|
96
|
+
data[index] = result
|
97
|
+
end
|
98
|
+
end
|
99
|
+
data
|
100
|
+
end
|
101
|
+
|
102
|
+
def path(id)
|
103
|
+
return nil unless id
|
104
|
+
id.to_s.include?('act_') ? id : "act_#{id}/"
|
105
|
+
end
|
106
|
+
|
107
|
+
def relative_url(id, klass, options)
|
108
|
+
api_version = api_version(options)
|
109
|
+
if %r{\A(act_)?\d+\z}.match(id.to_s)
|
110
|
+
path = path(id)
|
111
|
+
edge = edge_endpoint(klass)
|
112
|
+
elsif id.nil?
|
113
|
+
path = "#{options[:id]}"
|
114
|
+
options.delete(:id)
|
115
|
+
edge = ''
|
116
|
+
else
|
117
|
+
path = id
|
118
|
+
edge = ''
|
119
|
+
end
|
120
|
+
query = query_string(options)
|
121
|
+
"#{File.join(api_version, path, edge)}#{query.empty? ? nil : "?#{query}"}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def top_level_access_token(options)
|
125
|
+
@access_token = options[:access_token] unless @access_token || !options.key?(:access_token)
|
126
|
+
end
|
127
|
+
|
128
|
+
def query_string(options, depth = nil)
|
129
|
+
depth = 0 unless depth
|
130
|
+
options[:fields] = options[:fields].is_a?(Array) ? options[:fields].map(&:to_s).join(',') : options[:fields] if options.has_key?(:fields)
|
131
|
+
mapped_data = options.map do |k, v|
|
132
|
+
if depth == 0
|
133
|
+
v.respond_to?(:keys) ? "#{k}={#{query_string(v, depth + 1)}}" : "#{k}=#{v}"
|
134
|
+
else
|
135
|
+
"#{k}:#{v.to_json}"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
if depth == 0
|
139
|
+
mapped_data.join('&')
|
140
|
+
else
|
141
|
+
mapped_data.join(',')
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class Business < GraphObject
|
5
|
+
include Fields::Business
|
6
|
+
field_attrs FIELDS
|
7
|
+
|
8
|
+
def path
|
9
|
+
"/#{id}"
|
10
|
+
end
|
11
|
+
|
12
|
+
def product_catalogs
|
13
|
+
@product_catalogs ||= CollectionProxy.new ProductCatalog, "#{id}/product_catalogs", access_token
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class Campaign < GraphObject
|
5
|
+
include Fields::Campaign
|
6
|
+
include Traits::Methods
|
7
|
+
|
8
|
+
field_attrs FIELDS
|
9
|
+
allow_method :create
|
10
|
+
allow_local_method :create, :destroy, :update
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def destroy(attributes = {}, options = {})
|
14
|
+
obj = new(attributes.merge(status: 'DELETED'))
|
15
|
+
obj.post(obj.update_path, options, 'DELETE')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def path
|
20
|
+
raise FbError.new('required parameter account_id missing', 500) unless account_id
|
21
|
+
"/act_#{account_id}/campaigns"
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_path
|
25
|
+
raise FbError.new('required parameter id missing', 500) unless id
|
26
|
+
"/#{id}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete
|
30
|
+
raise FbError.new('delete not available, use soft delete update', 500)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ads
|
34
|
+
@ads ||= CollectionProxy.new(Ad, "/#{id}/ads", access_token)
|
35
|
+
end
|
36
|
+
|
37
|
+
def adsets
|
38
|
+
@adsets ||= CollectionProxy.new(AdSet, "/#{id}/adsets", access_token)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class CollectionProxy
|
5
|
+
def initialize(klass, path, access_token, data = nil, options = {})
|
6
|
+
@klass = klass
|
7
|
+
@path = path
|
8
|
+
@access_token = access_token
|
9
|
+
@options = options
|
10
|
+
@all = Array(data).collect { |attributes| @klass.new(attributes) } if data
|
11
|
+
end
|
12
|
+
|
13
|
+
def all(options = {})
|
14
|
+
options.merge!(access_token: @access_token) unless options[:access_token]
|
15
|
+
options.merge!(@options) if @options.any?
|
16
|
+
@all ||= @klass.get(@path, options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create(attributes = {}, options = {})
|
20
|
+
options.merge!(access_token: @access_token) unless options[:access_token]
|
21
|
+
@klass.new(attributes).create(@path, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy(attributes = {}, options = {})
|
25
|
+
options.merge!(access_token: @access_token) unless options[:access_token]
|
26
|
+
@klass.new(attributes).destroy(@path, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def update(attributes = {}, options = {})
|
30
|
+
options.merge!(access_token: @access_token) unless options[:access_token]
|
31
|
+
@klass.new(attributes).update(@path, options)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class CustomAudience < GraphObject
|
5
|
+
include Fields::CustomAudience
|
6
|
+
include Traits::Methods
|
7
|
+
field_attrs FIELDS
|
8
|
+
allow_method :create, :destroy
|
9
|
+
allow_local_method :create, :destroy, :update
|
10
|
+
|
11
|
+
def path
|
12
|
+
raise FbError.new('required parameter account_id missing', 500) unless account_id
|
13
|
+
"/act_#{account_id}/customaudiences"
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_path
|
17
|
+
raise FbError.new('required parameter id missing', 500) unless id
|
18
|
+
"/#{id}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def adaccounts
|
22
|
+
@adaccounts ||= CollectionProxy.new(AdAccount, "/#{id}/adaccounts", access_token)
|
23
|
+
end
|
24
|
+
|
25
|
+
def users
|
26
|
+
@users ||= CollectionProxy.new CustomAudienceUser, "#{id}/users", access_token
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class CustomAudienceUser < GraphObject
|
5
|
+
include Fields::CustomAudienceUser
|
6
|
+
include Traits::Methods
|
7
|
+
field_attrs FIELDS
|
8
|
+
allow_method :destroy, :update
|
9
|
+
|
10
|
+
FIELD_KEYS = {
|
11
|
+
emails: 'EMAIL_SHA256',
|
12
|
+
phone_numbers: 'PHONE_SHA256',
|
13
|
+
facebook_uids: 'UID',
|
14
|
+
mobile_adversiter_ids: 'MOBILE_ADVERTISER_ID',
|
15
|
+
genders: ['GEN'],
|
16
|
+
first_names: ['FN'],
|
17
|
+
last_name: ['LN'],
|
18
|
+
cities: ['CT'],
|
19
|
+
states: ['ST'],
|
20
|
+
zip_codes: ['ZIP'],
|
21
|
+
mobile_device_ids: ['MADID']
|
22
|
+
}
|
23
|
+
|
24
|
+
def destroy(path, options = {})
|
25
|
+
if @custom_audience_id
|
26
|
+
destroy_path = "/#{custom_audience_id}/users"
|
27
|
+
else
|
28
|
+
raise FbError.new('required parameter account_id missing', 500) unless @account_id
|
29
|
+
account_id = remove_instance_variable(:@account_id)
|
30
|
+
destroy_path = "act_#{account_id}/usersofanyaudience"
|
31
|
+
end
|
32
|
+
ready_payload
|
33
|
+
super(destroy_path, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def path
|
37
|
+
raise FbError.new('required parameter custom_audience_id missing', 500) unless custom_audience_id
|
38
|
+
"/#{custom_audience_id}/users"
|
39
|
+
end
|
40
|
+
|
41
|
+
def update(path, options = {})
|
42
|
+
ready_payload
|
43
|
+
super(path, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def ready_payload
|
49
|
+
schema, data = [], []
|
50
|
+
instance_variables.each do |iv|
|
51
|
+
schema_key = FIELD_KEYS[iv.to_s.delete('@').to_sym]
|
52
|
+
schema << schema_key
|
53
|
+
data << instance_variable_get(iv).map { |obj| normalize_object(obj, schema_key) }
|
54
|
+
remove_instance_variable iv
|
55
|
+
end
|
56
|
+
schema = schema.first if schema.length == 1
|
57
|
+
data = data.first if data.length == 1
|
58
|
+
@payload = { schema: schema, data: data }
|
59
|
+
end
|
60
|
+
|
61
|
+
def normalize_object(obj, type)
|
62
|
+
audience_type = type.is_a?(Array) ? type.first : type
|
63
|
+
case audience_type
|
64
|
+
when 'EMAIL_SHA256'
|
65
|
+
Digest::SHA256.hexdigest(obj.downcase.strip)
|
66
|
+
when 'PHONE_SHA256'
|
67
|
+
Digest::SHA256.hexdigest(obj.gsub(/\D/, ''))
|
68
|
+
when 'GEN'
|
69
|
+
if obj.downcase.include?('fe')
|
70
|
+
[Digest::SHA256.hexdigest('f')]
|
71
|
+
else
|
72
|
+
[Digest::SHA256.hexdigest('m')]
|
73
|
+
end
|
74
|
+
when 'LN', 'FN', 'CT'
|
75
|
+
[Digest::SHA256.hexdigest(obj.gsub(/(\W+|[0-9 ]+)/, ''))]
|
76
|
+
when 'ST', 'ZIP', 'MADID'
|
77
|
+
[Digest::SHA256.hexdigest(obj.downcase)]
|
78
|
+
else
|
79
|
+
Digest::SHA256.hexdigest(obj)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Adparlor
|
2
|
+
module Facebook
|
3
|
+
module GraphApi
|
4
|
+
class CustomConversion < GraphObject
|
5
|
+
include Fields::CustomConversion
|
6
|
+
include Traits::Methods
|
7
|
+
field_attrs FIELDS
|
8
|
+
allow_method :create, :update
|
9
|
+
allow_local_method :destroy, :update
|
10
|
+
|
11
|
+
def update_path
|
12
|
+
raise FbError.new('required parameter id missing', 500) unless id
|
13
|
+
"/#{id}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def stats
|
17
|
+
@stats ||= CollectionProxy.new AdsPixelStat, "#{id}/stats", access_token
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|