twitter-ads 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/CONTRIBUTING.md +77 -0
  5. data/LICENSE +22 -0
  6. data/README.md +111 -0
  7. data/Rakefile +86 -0
  8. data/bin/twitter-ads +42 -0
  9. data/lib/twitter-ads.rb +54 -0
  10. data/lib/twitter-ads/account.rb +229 -0
  11. data/lib/twitter-ads/audiences/tailored_audience.rb +177 -0
  12. data/lib/twitter-ads/campaign/app_list.rb +42 -0
  13. data/lib/twitter-ads/campaign/campaign.rb +40 -0
  14. data/lib/twitter-ads/campaign/funding_instrument.rb +33 -0
  15. data/lib/twitter-ads/campaign/line_item.rb +91 -0
  16. data/lib/twitter-ads/campaign/promotable_user.rb +28 -0
  17. data/lib/twitter-ads/campaign/targeting_criteria.rb +77 -0
  18. data/lib/twitter-ads/campaign/tweet.rb +83 -0
  19. data/lib/twitter-ads/client.rb +92 -0
  20. data/lib/twitter-ads/creative/app_download_card.rb +44 -0
  21. data/lib/twitter-ads/creative/image_app_download_card.rb +44 -0
  22. data/lib/twitter-ads/creative/image_conversation_card.rb +44 -0
  23. data/lib/twitter-ads/creative/lead_gen_card.rb +46 -0
  24. data/lib/twitter-ads/creative/promoted_account.rb +38 -0
  25. data/lib/twitter-ads/creative/promoted_tweet.rb +87 -0
  26. data/lib/twitter-ads/creative/video.rb +43 -0
  27. data/lib/twitter-ads/creative/video_app_download_card.rb +47 -0
  28. data/lib/twitter-ads/creative/video_conversation_card.rb +46 -0
  29. data/lib/twitter-ads/creative/website_card.rb +48 -0
  30. data/lib/twitter-ads/cursor.rb +127 -0
  31. data/lib/twitter-ads/enum.rb +135 -0
  32. data/lib/twitter-ads/error.rb +93 -0
  33. data/lib/twitter-ads/http/request.rb +127 -0
  34. data/lib/twitter-ads/http/response.rb +74 -0
  35. data/lib/twitter-ads/http/ton_upload.rb +140 -0
  36. data/lib/twitter-ads/legacy.rb +7 -0
  37. data/lib/twitter-ads/resources/analytics.rb +90 -0
  38. data/lib/twitter-ads/resources/dsl.rb +108 -0
  39. data/lib/twitter-ads/resources/persistence.rb +43 -0
  40. data/lib/twitter-ads/resources/resource.rb +92 -0
  41. data/lib/twitter-ads/targeting/reach_estimate.rb +69 -0
  42. data/lib/twitter-ads/utils.rb +76 -0
  43. data/lib/twitter-ads/version.rb +6 -0
  44. data/spec/fixtures/accounts_all.json +65 -0
  45. data/spec/fixtures/accounts_features.json +18 -0
  46. data/spec/fixtures/accounts_load.json +19 -0
  47. data/spec/fixtures/app_lists_all.json +22 -0
  48. data/spec/fixtures/app_lists_load.json +31 -0
  49. data/spec/fixtures/campaigns_all.json +208 -0
  50. data/spec/fixtures/campaigns_load.json +27 -0
  51. data/spec/fixtures/funding_instruments_all.json +74 -0
  52. data/spec/fixtures/funding_instruments_load.json +28 -0
  53. data/spec/fixtures/line_items_all.json +292 -0
  54. data/spec/fixtures/line_items_load.json +36 -0
  55. data/spec/fixtures/placements.json +35 -0
  56. data/spec/fixtures/promotable_users_all.json +57 -0
  57. data/spec/fixtures/promotable_users_load.json +18 -0
  58. data/spec/fixtures/promoted_tweets_all.json +212 -0
  59. data/spec/fixtures/promoted_tweets_load.json +19 -0
  60. data/spec/fixtures/reach_estimate.json +19 -0
  61. data/spec/fixtures/tailored_audiences_all.json +67 -0
  62. data/spec/fixtures/tailored_audiences_load.json +29 -0
  63. data/spec/fixtures/tweet_preview.json +24 -0
  64. data/spec/fixtures/videos_all.json +50 -0
  65. data/spec/fixtures/videos_load.json +22 -0
  66. data/spec/quality_spec.rb +15 -0
  67. data/spec/shared/properties.rb +20 -0
  68. data/spec/spec_helper.rb +61 -0
  69. data/spec/support/helpers.rb +42 -0
  70. data/spec/twitter-ads/account_spec.rb +315 -0
  71. data/spec/twitter-ads/audiences/tailored_audience_spec.rb +45 -0
  72. data/spec/twitter-ads/campaign/app_list_spec.rb +108 -0
  73. data/spec/twitter-ads/campaign/line_item_spec.rb +95 -0
  74. data/spec/twitter-ads/campaign/reach_estimate_spec.rb +98 -0
  75. data/spec/twitter-ads/campaign/targeting_criteria_spec.rb +39 -0
  76. data/spec/twitter-ads/campaign/tweet_spec.rb +83 -0
  77. data/spec/twitter-ads/client_spec.rb +115 -0
  78. data/spec/twitter-ads/creative/app_download_card_spec.rb +44 -0
  79. data/spec/twitter-ads/creative/image_app_download_card_spec.rb +43 -0
  80. data/spec/twitter-ads/creative/image_conversation_card_spec.rb +40 -0
  81. data/spec/twitter-ads/creative/lead_gen_card_spec.rb +46 -0
  82. data/spec/twitter-ads/creative/promoted_account_spec.rb +30 -0
  83. data/spec/twitter-ads/creative/promoted_tweet_spec.rb +46 -0
  84. data/spec/twitter-ads/creative/video_app_download_card_spec.rb +42 -0
  85. data/spec/twitter-ads/creative/video_conversation_card_spec.rb +52 -0
  86. data/spec/twitter-ads/creative/video_legacy_spec.rb +43 -0
  87. data/spec/twitter-ads/creative/video_spec.rb +43 -0
  88. data/spec/twitter-ads/creative/website_card_spec.rb +37 -0
  89. data/spec/twitter-ads/cursor_spec.rb +67 -0
  90. data/spec/twitter-ads/placements_spec.rb +36 -0
  91. data/spec/twitter-ads/utils_spec.rb +101 -0
  92. data/twitter-ads.gemspec +37 -0
  93. metadata +247 -0
  94. metadata.gz.sig +0 -0
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ class TailoredAudience
6
+
7
+ include TwitterAds::DSL
8
+ include TwitterAds::Resource
9
+
10
+ attr_reader :account
11
+
12
+ property :id, read_only: true
13
+ property :created_at, type: :time, read_only: true
14
+ property :updated_at, type: :time, read_only: true
15
+ property :deleted, type: :bool, read_only: true
16
+
17
+ property :name
18
+ property :list_type
19
+
20
+ property :audience_size, read_only: true
21
+ property :audience_type, read_only: true
22
+ property :metadata, read_only: true
23
+ property :partner_source, read_only: true
24
+ property :reasons_not_targetable, read_only: true
25
+ property :targetable, type: :bool, read_only: true
26
+ property :targetable_types, read_only: true
27
+
28
+ RESOURCE_COLLECTION = '/0/accounts/%{account_id}/tailored_audiences'.freeze # @api private
29
+ RESOURCE = '/0/accounts/%{account_id}/tailored_audiences/%{id}'.freeze # @api private
30
+ RESOURCE_UPDATE = '/0/accounts/%{account_id}/tailored_audience_changes'.freeze # @api private
31
+ GLOBAL_OPT_OUT =
32
+ '/0/accounts/%{account_id}/tailored_audiences/global_opt_out'.freeze # @api private
33
+
34
+ LIST_TYPES = %w(
35
+ EMAIL
36
+ DEVICE_ID
37
+ TWITTER_ID
38
+ HANDLE
39
+ PHONE_NUMBER
40
+ ).freeze
41
+
42
+ OPERATIONS = %w(
43
+ ADD
44
+ REMOVE
45
+ REPLACE
46
+ ).freeze
47
+
48
+ def initialize(account)
49
+ @account = account
50
+ self
51
+ end
52
+
53
+ class << self
54
+
55
+ # Creates a new tailored audience.
56
+ #
57
+ # @example
58
+ # audience = TailoredAudience.create(account, '/path/to/file', 'my list', 'EMAIL')
59
+ #
60
+ # @param account [Account] The account object instance.
61
+ # @param file_path [String] The path to the file to be uploaded.
62
+ # @param name [String] The tailored audience name.
63
+ # @param list_type [String] The tailored audience list type.
64
+ #
65
+ # @since 0.3.0
66
+ #
67
+ # @return [TailoredAudience] The newly created tailored audience instance.
68
+ def create(account, file_path, name, list_type)
69
+ upload = TwitterAds::TONUpload.new(account.client, file_path)
70
+
71
+ audience = new(account)
72
+ audience.send(:create_audience, name, list_type)
73
+
74
+ begin
75
+ audience.send(:update_audience, audience, upload.perform, list_type, 'ADD')
76
+ audience.reload!
77
+ rescue TwitterAds::ClientError => e
78
+ audience.delete!
79
+ raise e
80
+ end
81
+ end
82
+
83
+ # Updates the global opt-out list for the specified advertiser account.
84
+ #
85
+ # @example
86
+ # TailoredAudience.opt_out(account, '/path/to/file', 'EMAIL')
87
+ #
88
+ # @param account [Account] The account object instance.
89
+ # @param file_path [String] The path to the file to be uploaded.
90
+ # @param list_type [String] The tailored audience list type.
91
+ #
92
+ # @since 0.3.0
93
+ #
94
+ # @return [Boolean] The result of the opt-out update.
95
+ def opt_out(account, file_path, list_type)
96
+ upload = TwitterAds::TONUpload.new(account.client, file_path)
97
+ params = { input_file_path: upload.perform, list_type: list_type }
98
+ resource = GLOBAL_OPT_OUT % { account_id: account.id }
99
+ Request.new(account.client, :put, resource, params: params).perform
100
+ true
101
+ end
102
+
103
+ end
104
+
105
+ # Updates the current tailored audience instance.
106
+ #
107
+ # @example
108
+ # audience = account.tailored_audiences('xyz')
109
+ # audience.update('/path/to/file', 'EMAIL', 'REPLACE')
110
+ #
111
+ # @param file_path [String] The path to the file to be uploaded.
112
+ # @param list_type [String] The tailored audience list type.
113
+ # @param operation [String] The update operation type (Default: 'ADD').
114
+ #
115
+ # @since 0.3.0
116
+ #
117
+ # @return [TailoredAudience] [description]
118
+ def update(file_path, list_type, operation = 'ADD')
119
+ upload = TwitterAds::TONUpload.new(account.client, file_path)
120
+ update_audience(self, upload.perform, list_type, operation)
121
+ reload!
122
+ end
123
+
124
+ # Deletes the current tailored audience instance.
125
+ #
126
+ # @example
127
+ # audience.delete!
128
+ #
129
+ # Note: calls to this method are destructive and irreverisble.
130
+ #
131
+ # @since 0.3.0
132
+ #
133
+ # @return [self] Returns the tailored audience instance refreshed from the API.
134
+ def delete!
135
+ resource = RESOURCE % { account_id: account.id, id: id }
136
+ response = Request.new(account.client, :delete, resource).perform
137
+ from_response(response.body[:data])
138
+ end
139
+
140
+ # Returns the status of all changes for the current tailored audience instance.
141
+ #
142
+ # @example
143
+ # audience.status
144
+ #
145
+ # @since 0.3.0
146
+ #
147
+ # @return [Hash] Returns a hash object representing the tailored audience status.
148
+ def status
149
+ return nil unless id
150
+ resource = RESOURCE_UPDATE % { account_id: account.id }
151
+ request = Request.new(account.client, :get, resource, params: to_params)
152
+ Cursor.new(nil, request).to_a.select { |change| change[:tailored_audience_id] == id }
153
+ end
154
+
155
+ private
156
+
157
+ def create_audience(name, list_type)
158
+ params = { name: name, list_type: list_type }
159
+ resource = RESOURCE_COLLECTION % { account_id: account.id }
160
+ response = Request.new(account.client, :post, resource, params: params).perform
161
+ from_response(response.body[:data])
162
+ end
163
+
164
+ def update_audience(audience, location, list_type, operation)
165
+ params = {
166
+ tailored_audience_id: audience.id,
167
+ input_file_path: location,
168
+ list_type: list_type,
169
+ operation: operation
170
+ }
171
+
172
+ resource = RESOURCE_UPDATE % { account_id: audience.account.id }
173
+ Request.new(audience.account.client, :post, resource, params: params).perform
174
+ end
175
+
176
+ end
177
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ class AppList
6
+
7
+ include TwitterAds::DSL
8
+ include TwitterAds::Resource
9
+
10
+ attr_reader :account
11
+
12
+ property :id, read_only: true
13
+ property :apps, read_only: true
14
+ property :name, read_only: true
15
+
16
+ RESOURCE_COLLECTION = '/0/accounts/%{account_id}/app_lists'.freeze # @api private
17
+ RESOURCE = '/0/accounts/%{account_id}/app_lists/%{id}'.freeze # @api private
18
+
19
+ def initialize(account)
20
+ @account = account
21
+ self
22
+ end
23
+
24
+ # Creates a new App List
25
+ #
26
+ # @param name [String] The name for the app list to be created.
27
+ # @param ids [String] String or String Array of app IDs.
28
+ #
29
+ # @return [self] Returns the instance refreshed from the API
30
+ def create(name, *ids)
31
+ resource = self.class::RESOURCE_COLLECTION % { account_id: account.id }
32
+ params = to_params.merge!(app_store_identifiers: ids.join(','), name: name)
33
+ response = Request.new(account.client, :post, resource, params: params).perform
34
+ from_response(response.body[:data])
35
+ end
36
+
37
+ def apps
38
+ reload! if @id && !@apps
39
+ @apps
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ class Campaign
6
+
7
+ include TwitterAds::DSL
8
+ include TwitterAds::Resource
9
+ include TwitterAds::Persistence
10
+
11
+ attr_reader :account
12
+
13
+ property :id, read_only: true
14
+ property :reasons_not_servable, read_only: true
15
+ property :servable, read_only: true
16
+ property :deleted, type: :bool, read_only: true
17
+ property :created_at, type: :time, read_only: true
18
+ property :updated_at, type: :time, read_only: true
19
+
20
+ property :name
21
+ property :funding_instrument_id
22
+ property :end_time, type: :time
23
+ property :start_time, type: :time
24
+ property :paused, type: :bool
25
+ property :currency
26
+ property :standard_delivery
27
+ property :daily_budget_amount_local_micro
28
+ property :total_budget_amount_local_micro
29
+
30
+ RESOURCE_COLLECTION = '/0/accounts/%{account_id}/campaigns'.freeze # @api private
31
+ RESOURCE_STATS = '/0/stats/accounts/%{account_id}/campaigns'.freeze # @api private
32
+ RESOURCE = '/0/accounts/%{account_id}/campaigns/%{id}'.freeze # @api private
33
+
34
+ def initialize(account)
35
+ @account = account
36
+ self
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ class FundingInstrument
6
+
7
+ include TwitterAds::DSL
8
+ include TwitterAds::Resource
9
+
10
+ attr_reader :account
11
+
12
+ property :id, read_only: true
13
+ property :name, read_only: true
14
+ property :cancelled, read_only: true
15
+ property :credit_limit_local_micro, read_only: true
16
+ property :currency, read_only: true
17
+ property :description, read_only: true
18
+ property :funded_amount_local_micro, read_only: true
19
+ property :type, read_only: true
20
+ property :created_at, type: :time, read_only: true
21
+ property :updated_at, type: :time, read_only: true
22
+ property :deleted, type: :bool, read_only: true
23
+
24
+ RESOURCE_COLLECTION = '/0/accounts/%{account_id}/funding_instruments'.freeze # @api private
25
+ RESOURCE = '/0/accounts/%{account_id}/funding_instruments/%{id}'.freeze # @api private
26
+
27
+ def initialize(account)
28
+ @account = account
29
+ self
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ class LineItem
6
+
7
+ include TwitterAds::DSL
8
+ include TwitterAds::Resource
9
+ include TwitterAds::Persistence
10
+ include TwitterAds::Analytics
11
+
12
+ attr_reader :account
13
+
14
+ property :id, read_only: true
15
+ property :deleted, type: :bool, read_only: true
16
+ property :created_at, type: :time, read_only: true
17
+ property :updated_at, type: :time, read_only: true
18
+
19
+ property :name
20
+ property :campaign_id
21
+ property :advertiser_domain
22
+ property :categories
23
+ property :charge_by
24
+ property :include_sentiment
25
+ property :objective
26
+ property :optimization
27
+ property :paused, type: :bool
28
+ property :primary_web_event_tag
29
+ property :product_type
30
+ property :placements
31
+ property :bid_unit
32
+ property :automatically_select_bid
33
+ property :bid_amount_local_micro
34
+ property :total_budget_amount_local_micro
35
+
36
+ RESOURCE_COLLECTION = '/0/accounts/%{account_id}/line_items'.freeze # @api private
37
+ RESOURCE_STATS = '/0/stats/accounts/%{account_id}/line_items'.freeze # @api private
38
+ RESOURCE = '/0/accounts/%{account_id}/line_items/%{id}'.freeze # @api private
39
+
40
+ def initialize(account)
41
+ @account = account
42
+ self
43
+ end
44
+
45
+ # Overload for CUSTOM objective deprecation warning.
46
+ # @private
47
+ def objective=(value)
48
+ if value == TwitterAds::Objective::CUSTOM
49
+ TwitterAds::Utils.deprecated('TwitterAds::Objective::CUSTOM')
50
+ end
51
+ @objective = value
52
+ end
53
+
54
+ class << self
55
+
56
+ # Helper method to return a list a valid placement combinations by Product.
57
+ #
58
+ # @example
59
+ # LineItem.placements(Product::PROMOTED_TWEETS)
60
+ #
61
+ # @param product_type [Product] The enum value for the Product type being targeted.
62
+ #
63
+ # @return [Array] An array of valid placement combinations.
64
+ #
65
+ # @since 0.3.2
66
+ # @see https://dev.twitter.com/ads/reference/get/line_items/placements
67
+ def placements(client, product_type = nil)
68
+ resource = '/0/line_items/placements'
69
+ params = { product_type: product_type } if product_type
70
+ response = TwitterAds::Request.new(client, :get, resource, params: params).perform
71
+ response.body[:data][0][:placements]
72
+ end
73
+
74
+ end
75
+
76
+ # Returns a collection of targeting criteria available to the current line item.
77
+ #
78
+ # @param id [String] The TargetingCriteria ID value.
79
+ # @param opts [Hash] A Hash of extended options.
80
+ # @option opts [Boolean] :with_deleted Indicates if deleted items should be included.
81
+ # @option opts [String] :sort_by The object param to sort the API response by.
82
+ #
83
+ # @since 0.3.1
84
+ #
85
+ # @return A Cursor or object instance.
86
+ def targeting_criteria(id = nil, opts = {})
87
+ id ? TargetingCriteria.load(account, id, opts) : TargetingCriteria.all(account, @id, opts)
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ class PromotableUser
6
+
7
+ include TwitterAds::DSL
8
+ include TwitterAds::Resource
9
+
10
+ attr_reader :account
11
+
12
+ property :id, read_only: true
13
+ property :promotable_user_type, read_only: true
14
+ property :user_id, read_only: true
15
+ property :created_at, type: :time, read_only: true
16
+ property :updated_at, type: :time, read_only: true
17
+ property :deleted, type: :bool, read_only: true
18
+
19
+ RESOURCE_COLLECTION = '/0/accounts/%{account_id}/promotable_users'.freeze # @api private
20
+ RESOURCE = '/0/accounts/%{account_id}/promotable_users/%{id}'.freeze # @api private
21
+
22
+ def initialize(account)
23
+ @account = account
24
+ self
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ class TargetingCriteria
6
+
7
+ include TwitterAds::DSL
8
+ include TwitterAds::Persistence
9
+ include TwitterAds::Resource::InstanceMethods
10
+
11
+ attr_reader :account
12
+
13
+ property :id, read_only: true
14
+ property :name, read_only: true
15
+ property :localized_name, read_only: true
16
+ property :created_at, type: :time, read_only: true
17
+ property :updated_at, type: :time, read_only: true
18
+ property :deleted, type: :bool, read_only: true
19
+
20
+ property :line_item_id
21
+ property :targeting_type
22
+ property :targeting_value
23
+ property :tailored_audience_expansion, type: :bool
24
+ property :tailored_audience_type
25
+
26
+ RESOURCE_COLLECTION = '/0/accounts/%{account_id}/targeting_criteria'.freeze # @api private
27
+ RESOURCE = '/0/accounts/%{account_id}/targeting_criteria/%{id}'.freeze # @api private
28
+
29
+ def initialize(account)
30
+ @account = account
31
+ self
32
+ end
33
+
34
+ class << self
35
+
36
+ # Returns a Cursor instance for a given resource.
37
+ #
38
+ # @param account [Account] The Account object instance.
39
+ # @param line_item_id [String] The line item ID string.
40
+ # @param opts [Hash] An optional Hash of extended options.
41
+ # @option opts [Boolean] :with_deleted Indicates if deleted items should be included.
42
+ # @option opts [String] :sort_by The object param to sort the API response by.
43
+ #
44
+ # @return [Cursor] A Cusor object ready to iterate through the API response.
45
+ #
46
+ # @since 0.3.1
47
+ # @see Cursor
48
+ # @see https://dev.twitter.com/ads/basics/sorting Sorting
49
+ def all(account, line_item_id, opts = {})
50
+ params = { line_item_id: line_item_id }.merge!(opts)
51
+ resource = RESOURCE_COLLECTION % { account_id: account.id }
52
+ request = Request.new(account.client, :get, resource, params: params)
53
+ Cursor.new(self, request, init_with: [account])
54
+ end
55
+
56
+ # Returns an object instance for a given resource.
57
+ #
58
+ # @param account [Account] The Account object instance.
59
+ # @param id [String] The ID of the specific object to be loaded.
60
+ # @param opts [Hash] An optional Hash of extended options.
61
+ # @option opts [Boolean] :with_deleted Indicates if deleted items should be included.
62
+ # @option opts [String] :sort_by The object param to sort the API response by.
63
+ #
64
+ # @return [self] The object instance for the specified resource.
65
+ #
66
+ # @since 0.3.1
67
+ def load(account, id, opts = {})
68
+ params = { with_deleted: true }.merge!(opts)
69
+ resource = RESOURCE % { account_id: account.id, id: id }
70
+ response = Request.new(account.client, :get, resource, params: params).perform
71
+ new(account).from_response(response.body[:data])
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end