adparlor-facebook 0.5.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.gitlab-ci.yml +5 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +28 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +4 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.lock +136 -0
  10. data/Guardfile +13 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +164 -0
  13. data/Rakefile +3 -0
  14. data/adparlor-facebook.gemspec +38 -0
  15. data/bin/console +18 -0
  16. data/bin/gitlab_runner_specs.sh +22 -0
  17. data/bin/setup +7 -0
  18. data/lib/adparlor/facebook/api.rb +106 -0
  19. data/lib/adparlor/facebook/config.rb +24 -0
  20. data/lib/adparlor/facebook/error.rb +13 -0
  21. data/lib/adparlor/facebook/graph_api/activity.rb +11 -0
  22. data/lib/adparlor/facebook/graph_api/ad.rb +43 -0
  23. data/lib/adparlor/facebook/graph_api/ad_account.rb +168 -0
  24. data/lib/adparlor/facebook/graph_api/ad_creative.rb +22 -0
  25. data/lib/adparlor/facebook/graph_api/ad_image.rb +87 -0
  26. data/lib/adparlor/facebook/graph_api/ad_label.rb +12 -0
  27. data/lib/adparlor/facebook/graph_api/ad_preview.rb +15 -0
  28. data/lib/adparlor/facebook/graph_api/ad_set.rb +39 -0
  29. data/lib/adparlor/facebook/graph_api/ad_targeting_search.rb +64 -0
  30. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_education_major.rb +11 -0
  31. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_education_school.rb +11 -0
  32. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocation.rb +19 -0
  33. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocation_meta.rb +16 -0
  34. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_city.rb +13 -0
  35. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_country.rb +13 -0
  36. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_geo_market.rb +13 -0
  37. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_region.rb +13 -0
  38. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_geolocations/ad_zip_code.rb +13 -0
  39. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_interest.rb +11 -0
  40. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_interest_suggestion.rb +12 -0
  41. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_interest_validation.rb +18 -0
  42. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_locale.rb +11 -0
  43. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_radius_suggestion.rb +11 -0
  44. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_targeting_category.rb +18 -0
  45. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_work_employer.rb +11 -0
  46. data/lib/adparlor/facebook/graph_api/ad_targeting_types/ad_work_position.rb +11 -0
  47. data/lib/adparlor/facebook/graph_api/ad_video.rb +124 -0
  48. data/lib/adparlor/facebook/graph_api/ads_pixel.rb +22 -0
  49. data/lib/adparlor/facebook/graph_api/ads_pixel_stat.rb +25 -0
  50. data/lib/adparlor/facebook/graph_api/advertisable_application.rb +11 -0
  51. data/lib/adparlor/facebook/graph_api/app_event_type.rb +11 -0
  52. data/lib/adparlor/facebook/graph_api/application.rb +15 -0
  53. data/lib/adparlor/facebook/graph_api/batch_object.rb +147 -0
  54. data/lib/adparlor/facebook/graph_api/broad_targeting_category.rb +11 -0
  55. data/lib/adparlor/facebook/graph_api/business.rb +18 -0
  56. data/lib/adparlor/facebook/graph_api/campaign.rb +43 -0
  57. data/lib/adparlor/facebook/graph_api/collection_proxy.rb +36 -0
  58. data/lib/adparlor/facebook/graph_api/connection_object.rb +11 -0
  59. data/lib/adparlor/facebook/graph_api/custom_audience.rb +31 -0
  60. data/lib/adparlor/facebook/graph_api/custom_audience_tos.rb +11 -0
  61. data/lib/adparlor/facebook/graph_api/custom_audience_user.rb +85 -0
  62. data/lib/adparlor/facebook/graph_api/custom_conversion.rb +22 -0
  63. data/lib/adparlor/facebook/graph_api/delivery_estimate.rb +11 -0
  64. data/lib/adparlor/facebook/graph_api/fields/activity.rb +12 -0
  65. data/lib/adparlor/facebook/graph_api/fields/ad.rb +60 -0
  66. data/lib/adparlor/facebook/graph_api/fields/ad_account.rb +21 -0
  67. data/lib/adparlor/facebook/graph_api/fields/ad_creative.rb +23 -0
  68. data/lib/adparlor/facebook/graph_api/fields/ad_image.rb +14 -0
  69. data/lib/adparlor/facebook/graph_api/fields/ad_label.rb +11 -0
  70. data/lib/adparlor/facebook/graph_api/fields/ad_preview.rb +11 -0
  71. data/lib/adparlor/facebook/graph_api/fields/ad_set.rb +100 -0
  72. data/lib/adparlor/facebook/graph_api/fields/ad_video.rb +22 -0
  73. data/lib/adparlor/facebook/graph_api/fields/ads_pixel.rb +12 -0
  74. data/lib/adparlor/facebook/graph_api/fields/ads_pixel_stat.rb +11 -0
  75. data/lib/adparlor/facebook/graph_api/fields/advertisable_application.rb +17 -0
  76. data/lib/adparlor/facebook/graph_api/fields/app_event_type.rb +11 -0
  77. data/lib/adparlor/facebook/graph_api/fields/application.rb +31 -0
  78. data/lib/adparlor/facebook/graph_api/fields/broad_targeting_category.rb +12 -0
  79. data/lib/adparlor/facebook/graph_api/fields/business.rb +11 -0
  80. data/lib/adparlor/facebook/graph_api/fields/campaign.rb +17 -0
  81. data/lib/adparlor/facebook/graph_api/fields/connection_object.rb +16 -0
  82. data/lib/adparlor/facebook/graph_api/fields/custom_audience.rb +22 -0
  83. data/lib/adparlor/facebook/graph_api/fields/custom_audience_tos.rb +11 -0
  84. data/lib/adparlor/facebook/graph_api/fields/custom_audience_user.rb +14 -0
  85. data/lib/adparlor/facebook/graph_api/fields/custom_conversion.rb +14 -0
  86. data/lib/adparlor/facebook/graph_api/fields/delivery_estimate.rb +11 -0
  87. data/lib/adparlor/facebook/graph_api/fields/field_decorator.rb +51 -0
  88. data/lib/adparlor/facebook/graph_api/fields/insight.rb +16 -0
  89. data/lib/adparlor/facebook/graph_api/fields/instagram_account.rb +11 -0
  90. data/lib/adparlor/facebook/graph_api/fields/leadgen_form.rb +23 -0
  91. data/lib/adparlor/facebook/graph_api/fields/page.rb +121 -0
  92. data/lib/adparlor/facebook/graph_api/fields/page_and_place.rb +11 -0
  93. data/lib/adparlor/facebook/graph_api/fields/partner_category.rb +12 -0
  94. data/lib/adparlor/facebook/graph_api/fields/picture.rb +17 -0
  95. data/lib/adparlor/facebook/graph_api/fields/post.rb +17 -0
  96. data/lib/adparlor/facebook/graph_api/fields/product_audience.rb +12 -0
  97. data/lib/adparlor/facebook/graph_api/fields/product_catalog.rb +11 -0
  98. data/lib/adparlor/facebook/graph_api/fields/product_feed.rb +12 -0
  99. data/lib/adparlor/facebook/graph_api/fields/product_group.rb +11 -0
  100. data/lib/adparlor/facebook/graph_api/fields/product_item.rb +18 -0
  101. data/lib/adparlor/facebook/graph_api/fields/product_set.rb +13 -0
  102. data/lib/adparlor/facebook/graph_api/fields/reach_estimate.rb +11 -0
  103. data/lib/adparlor/facebook/graph_api/fields/reach_frequency_prediction.rb +24 -0
  104. data/lib/adparlor/facebook/graph_api/fields/targeting_sentence_line.rb +11 -0
  105. data/lib/adparlor/facebook/graph_api/fields/targeting_suggestion.rb +11 -0
  106. data/lib/adparlor/facebook/graph_api/fields/targeting_validation.rb +11 -0
  107. data/lib/adparlor/facebook/graph_api/fields/user_lead_gen_info.rb +18 -0
  108. data/lib/adparlor/facebook/graph_api/fields/video_thumbnail.rb +11 -0
  109. data/lib/adparlor/facebook/graph_api/graph_object.rb +140 -0
  110. data/lib/adparlor/facebook/graph_api/insight.rb +18 -0
  111. data/lib/adparlor/facebook/graph_api/instagram_account.rb +12 -0
  112. data/lib/adparlor/facebook/graph_api/leadgen_form.rb +10 -0
  113. data/lib/adparlor/facebook/graph_api/page.rb +27 -0
  114. data/lib/adparlor/facebook/graph_api/page_and_place.rb +11 -0
  115. data/lib/adparlor/facebook/graph_api/partner_category.rb +11 -0
  116. data/lib/adparlor/facebook/graph_api/picture.rb +15 -0
  117. data/lib/adparlor/facebook/graph_api/post.rb +17 -0
  118. data/lib/adparlor/facebook/graph_api/product_audience.rb +17 -0
  119. data/lib/adparlor/facebook/graph_api/product_catalog.rb +27 -0
  120. data/lib/adparlor/facebook/graph_api/product_feed.rb +11 -0
  121. data/lib/adparlor/facebook/graph_api/product_group.rb +11 -0
  122. data/lib/adparlor/facebook/graph_api/product_item.rb +11 -0
  123. data/lib/adparlor/facebook/graph_api/product_set.rb +26 -0
  124. data/lib/adparlor/facebook/graph_api/reach_estimate.rb +17 -0
  125. data/lib/adparlor/facebook/graph_api/reach_frequency_prediction.rb +18 -0
  126. data/lib/adparlor/facebook/graph_api/targeting_sentence_line.rb +11 -0
  127. data/lib/adparlor/facebook/graph_api/targeting_suggestion.rb +16 -0
  128. data/lib/adparlor/facebook/graph_api/targeting_validation.rb +16 -0
  129. data/lib/adparlor/facebook/graph_api/traits/methods.rb +64 -0
  130. data/lib/adparlor/facebook/graph_api/user_lead_gen_info.rb +11 -0
  131. data/lib/adparlor/facebook/graph_api/video_thumbnail.rb +11 -0
  132. data/lib/adparlor/facebook/graph_api.rb +9 -0
  133. data/lib/adparlor/facebook/version.rb +5 -0
  134. data/lib/adparlor/facebook.rb +9 -0
  135. data/solano.yml +15 -0
  136. metadata +391 -0
@@ -0,0 +1,11 @@
1
+ module Adparlor
2
+ module Facebook
3
+ module GraphApi
4
+ module AdTargetingTypes
5
+ class AdWorkEmployer
6
+ attr_accessor :id, :name, :coverage, :subtext
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Adparlor
2
+ module Facebook
3
+ module GraphApi
4
+ module AdTargetingTypes
5
+ class AdWorkPosition
6
+ attr_accessor :id, :name, :coverage
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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,11 @@
1
+ module Adparlor
2
+ module Facebook
3
+ module GraphApi
4
+ class AdvertisableApplication < GraphObject
5
+ include Fields::AdvertisableApplication
6
+ include Traits::Methods
7
+ field_attrs FIELDS
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Adparlor
2
+ module Facebook
3
+ module GraphApi
4
+ class AppEventType < GraphObject
5
+ include Fields::AppEventType
6
+ include Traits::Methods
7
+ field_attrs FIELDS
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ module Adparlor
2
+ module Facebook
3
+ module GraphApi
4
+ class BroadTargetingCategory < GraphObject
5
+ include Fields::BroadTargetingCategory
6
+ include Traits::Methods
7
+ field_attrs FIELDS
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ module Adparlor
2
+ module Facebook
3
+ module GraphApi
4
+ class ConnectionObject < GraphObject
5
+ include Fields::ConnectionObject
6
+ include Traits::Methods
7
+ field_attrs FIELDS
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ module Adparlor
2
+ module Facebook
3
+ module GraphApi
4
+ class CustomAudienceTos < GraphObject
5
+ include Fields::CustomAudienceTos
6
+ include Traits::Methods
7
+ field_attrs FIELDS
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -0,0 +1,11 @@
1
+ module Adparlor
2
+ module Facebook
3
+ module GraphApi
4
+ class DeliveryEstimate < GraphObject
5
+ include Fields::DeliveryEstimate
6
+ include Traits::Methods
7
+ field_attrs FIELDS
8
+ end
9
+ end
10
+ end
11
+ end