twitter-ads 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
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,108 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ # A light-wight DSL for building out Twitter Ads API resources.
6
+ module DSL
7
+
8
+ def self.included(klass)
9
+ klass.send :include, InstanceMethods
10
+ klass.extend ClassMethods
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ # Populates a given objects attributes from a parsed JSON API response. This helper
16
+ # handles all necessary type coercions as it assigns attribute values.
17
+ #
18
+ # @param object [Hash] The parsed JSON response object.
19
+ #
20
+ # @return [self] A fully hydrated instance of the current class.
21
+ #
22
+ # @since 0.1.0
23
+ def from_response(object)
24
+ self.class.properties.each do |name, type|
25
+ value = nil
26
+ if type == :time && object[name] && !object[name].empty?
27
+ value = Time.parse(object[name])
28
+ elsif type == :bool && object[name]
29
+ value = TwitterAds::Utils.to_bool(object[name])
30
+ end
31
+ instance_variable_set("@#{name}", value || object[name])
32
+ end
33
+ self
34
+ end
35
+
36
+ # Generates a Hash of property values for the current object. This helper
37
+ # handles all necessary type coercions as it generates its output.
38
+ #
39
+ # @return [Hash] A Hash of the object's properties and cooresponding values.
40
+ #
41
+ # @since 0.1.0
42
+ def to_params
43
+ params = {}
44
+ self.class.properties.each do |name, type|
45
+ value = instance_variable_get("@#{name}") || send(name)
46
+ next if value.nil?
47
+
48
+ # handles unset with empty string
49
+ if value.respond_to?(:strip) && value.strip == ''
50
+ params[name] = value.strip
51
+ next
52
+ end
53
+
54
+ if type == :time
55
+ params[name] = value.iso8601
56
+ elsif type == :bool
57
+ params[name] = TwitterAds::Utils.to_bool(value)
58
+ elsif value.is_a?(Array)
59
+ next if value.size < 1
60
+ params[name] = value.join(',')
61
+ else
62
+ params[name] = value
63
+ end
64
+ end
65
+ params
66
+ end
67
+
68
+ end
69
+
70
+ module ClassMethods
71
+
72
+ # Resource property declaration helper.
73
+ #
74
+ # @example
75
+ # class Foo
76
+ # include TwitterAds::DSL
77
+ #
78
+ # property :foo
79
+ # property :bar, type: :bool
80
+ # property :created_at, type: time, read_only: true
81
+ # end
82
+ #
83
+ # @param name [Symbol] The name of the property.
84
+ # @param opts [Hash] A Hash of extended options to be applied to the property (Optional).
85
+ #
86
+ # @return [Symbol] The property name.
87
+ #
88
+ # @since 0.1.0
89
+ def property(name, opts = {})
90
+ properties[name] = opts.fetch(:type, nil)
91
+ opts[:read_only] ? attr_reader(name) : attr_accessor(name)
92
+ name
93
+ end
94
+
95
+ # Helper for managing properties for the current class.
96
+ #
97
+ # @return [Hash] A Hash of properties declared in the current class.
98
+ #
99
+ # @api private
100
+ # @since 0.1.0
101
+ def properties
102
+ @properties ||= {}
103
+ end
104
+
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ module Persistence
6
+
7
+ # Saves or updates the current object instance depending on the presence of `object.id`.
8
+ #
9
+ # @example
10
+ # object.save
11
+ #
12
+ # @return [self] Returns the instance refreshed from the API.
13
+ #
14
+ # @since 0.1.0
15
+ def save
16
+ if @id
17
+ resource = self.class::RESOURCE % { account_id: account.id, id: id }
18
+ response = Request.new(account.client, :put, resource, params: to_params).perform
19
+ else
20
+ resource = self.class::RESOURCE_COLLECTION % { account_id: account.id }
21
+ response = Request.new(account.client, :post, resource, params: to_params).perform
22
+ end
23
+ from_response(response.body[:data])
24
+ end
25
+
26
+ # Deletes the current object instance depending on the presence of `object.id`.
27
+ #
28
+ # @example
29
+ # object.delete!
30
+ #
31
+ # Note: calls to this method are destructive and irreverisble for most API objects.
32
+ #
33
+ # @return [self] Returns the instance refreshed from the API.
34
+ #
35
+ # @since 0.1.0
36
+ def delete!
37
+ resource = self.class::RESOURCE % { account_id: account.id, id: id }
38
+ response = Request.new(account.client, :delete, resource).perform
39
+ from_response(response.body[:data])
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ module Resource
6
+
7
+ def self.included(klass)
8
+ klass.send :include, InstanceMethods
9
+ klass.extend ClassMethods
10
+ end
11
+
12
+ module InstanceMethods
13
+
14
+ # Reloads all attributes for the current object instance from the API.
15
+ #
16
+ # @example
17
+ # object.reload!
18
+ #
19
+ # Note: calls to this method dispose of any unsaved data on the object instance.
20
+ #
21
+ # @param opts [Hash] An optional Hash of extended request options.
22
+ #
23
+ # @return [self] The reloaded instance of the current object.
24
+ #
25
+ # @since 0.1.0
26
+ def reload!(opts = {})
27
+ return self unless id
28
+ params = { with_deleted: true }.merge!(opts)
29
+ resource = self.class::RESOURCE % { account_id: account.id, id: id }
30
+ response = Request.new(account.client, :get, resource, params: params).perform
31
+ from_response(response.body[:data])
32
+ end
33
+
34
+ # Returns an inspection string for the current object instance.
35
+ #
36
+ # @example
37
+ # object.inspect
38
+ #
39
+ # @return [String] The object instance details.
40
+ #
41
+ # @since 0.1.0
42
+ def inspect
43
+ str = String.new("#<#{self.class.name}:0x#{object_id}")
44
+ str << " id=\"#{@id}\"" if @id
45
+ str << ' deleted="true"' if @deleted
46
+ str << '>'
47
+ end
48
+
49
+ end
50
+
51
+ module ClassMethods
52
+
53
+ # Returns a Cursor instance for a given resource.
54
+ #
55
+ # @param account [Account] The Account object instance.
56
+ # @param opts [Hash] An optional Hash of extended options.
57
+ # @option opts [Boolean] :with_deleted Indicates if deleted items should be included.
58
+ # @option opts [String] :sort_by The object param to sort the API response by.
59
+ #
60
+ # @return [Cursor] A Cusor object ready to iterate through the API response.
61
+ #
62
+ # @since 0.1.0
63
+ # @see Cursor
64
+ # @see https://dev.twitter.com/ads/basics/sorting Sorting
65
+ def all(account, opts = {})
66
+ resource = self::RESOURCE_COLLECTION % { account_id: account.id }
67
+ request = Request.new(account.client, :get, resource, params: opts)
68
+ Cursor.new(self, request, init_with: [account])
69
+ end
70
+
71
+ # Returns an object instance for a given resource.
72
+ #
73
+ # @param account [Account] The Account object instance.
74
+ # @param id [String] The ID of the specific object to be loaded.
75
+ # @param opts [Hash] An optional Hash of extended options.
76
+ # @option opts [Boolean] :with_deleted Indicates if deleted items should be included.
77
+ # @option opts [String] :sort_by The object param to sort the API response by.
78
+ #
79
+ # @return [self] The object instance for the specified resource.
80
+ #
81
+ # @since 0.1.0
82
+ def load(account, id, opts = {})
83
+ params = { with_deleted: true }.merge!(opts)
84
+ resource = self::RESOURCE % { account_id: account.id, id: id }
85
+ response = Request.new(account.client, :get, resource, params: params).perform
86
+ new(account).from_response(response.body[:data])
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ module ReachEstimate
6
+
7
+ class << self
8
+
9
+ # Get a reach estimate for the specified line item details.
10
+ #
11
+ # @example
12
+ # TwitterAds::ReachEstimate.fetch(
13
+ # account,
14
+ # 'PROMOTED_TWEETS',
15
+ # 'WEBSITE_CLICKS',
16
+ # 2153688540,
17
+ # similar_to_followers_of_user: 2153688540,
18
+ # gender: 2
19
+ # )
20
+ #
21
+ # @param client [Client] The Client object instance.
22
+ # @param account [Account] The Ads Account instance for this request.
23
+ # @param product_type [String] The product type being targeted.
24
+ # @param objective [String] The objective being targeted.
25
+ # @param user_id [Long] The ID of the user whose content will be promoted.
26
+ # @param opts [Hash] A Hash of extended options.
27
+ #
28
+ # @option opts [String] :bid_type The bidding mechanism.
29
+ # @option opts [Long] :bid_amount_local_micro Bid amount in local currency micros.
30
+ # @option opts [String] :currency ISO-4217 Currency code for bid amount.
31
+ # @option opts [String] :followers_of_users Comma-separated user IDs.
32
+ # @option opts [String] :similar_to_followers_of_users Comma-separated user IDs.
33
+ # @option opts [String] :locations Comma-separated location IDs.
34
+ # @option opts [String] :interests Comma-seaprated interest IDs.
35
+ # @option opts [String] :gender Gender identifier.
36
+ # @option opts [String] :platforms Comma-separated platform IDs.
37
+ # @option opts [String] :tailored_audiences Comma-separated tailored audience IDs.
38
+ # @option opts [String] :tailored_audiences_expanded Comma-separated tailoerd audience IDs.
39
+ # @option opts [String] :languages Comma-separated language IDs.
40
+ # @option opts [String] :platform_versions Comma-separated platform version IDs.
41
+ # @option opts [String] :devices Comma-separated device IDs.
42
+ # @option opts [String] :behaviors Comma-separated behavior IDs.
43
+ # @option opts [String] :behaviors_expanded Comma-separated behaviors IDs.
44
+ # @option opts [String] :campaign engagement Campaign ID for Tweet Engager Retargeting.
45
+ # @option opts [String] :user_engagement Promoted User ID for Tweet Engager Retargeting.
46
+ # @option opts [String] :engagement_type engagement type for Tweet Engager Retargeting.
47
+ #
48
+ # @return [Hash] A hash containing count and infinite_bid_count.
49
+ #
50
+ # @since 0.2.0
51
+ # @see https://dev.twitter.com/ads/reference/get/accounts/%3Aaccount_id/reach_estimate
52
+ def fetch(account, product_type, objective, user_id, opts = {})
53
+ resource = "/0/accounts/#{account.id}/reach_estimate"
54
+ params = { product_type: product_type, objective: objective, user_id: user_id }.merge!(opts)
55
+
56
+ # The response value count is "bid sensitive", we default to bid_type=AUTO here to preserve
57
+ # expected behavior despite an API change that occurred in December 2015.
58
+ unless params.keys.include?(:bid_type) || params.keys.include?(:bid_amount_local_micro)
59
+ params = { bid_type: 'AUTO' }.merge!(params)
60
+ end
61
+
62
+ response = TwitterAds::Request.new(account.client, :get, resource, params: params).perform
63
+ response.body[:data]
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ module Utils
6
+
7
+ class << self
8
+
9
+ # Helper to convert objects into boolean values.
10
+ #
11
+ # @param object [Object] The object to be converted.
12
+ #
13
+ # @return [Boolean] The boolean result.
14
+ #
15
+ # @api private
16
+ # @since 0.1.0
17
+ def to_bool(object)
18
+ (object.to_s.downcase == 'false') ? false : !!object
19
+ end
20
+
21
+ # Helper to convert a time object according to a given granularity level.
22
+ #
23
+ # @param time [Time] A valid Time instance.
24
+ # @param granularity [Symbol] A symbol representing the desired granuarlity (eg. :hour).
25
+ #
26
+ # @return [Time] The formatted Time instance.
27
+ #
28
+ # @api private
29
+ # @since 0.1.0
30
+ def to_time(time, granularity = nil)
31
+ return time.iso8601 unless granularity
32
+ if granularity == :hour
33
+ Time.new(time.year, time.month, time.day, time.hour).iso8601
34
+ elsif granularity == :day
35
+ Time.new(time.year, time.month, time.day).iso8601
36
+ else
37
+ time.iso8601
38
+ end
39
+ end
40
+
41
+ # Converts key names to symbols on a given object.
42
+ #
43
+ # @param object [Object] The object to be converted.
44
+ #
45
+ # @return [Object] The symbolized, converted object.
46
+ #
47
+ # @api private
48
+ # @since 0.1.0
49
+ def symbolize!(object)
50
+ if object.is_a?(Array)
51
+ object.each_with_index { |value, index| object[index] = symbolize!(value) }
52
+ elsif object.is_a?(Hash)
53
+ object.keys.each { |key| object[key.to_sym] = symbolize!(object.delete(key)) }
54
+ end
55
+ object
56
+ end
57
+
58
+ # Creates a deprecation message.
59
+ #
60
+ # @param name [String] The name of the object or method being deprecated.
61
+ # @param replacement [String] The name of the new object or method (optional).
62
+ # @param refer [String] HTTP address with supporting information (optional).
63
+ #
64
+ # @api private
65
+ # @since 0.3.2
66
+ def deprecated(name, opts = {})
67
+ message = String.new("[DEPRECATED] #{name} has been deprecated")
68
+ message += opts[:replacement] ? " (please use #{opts[:replacement]})." : '.'
69
+ message += " Please see #{opts[:refer]} for more info." if opts[:refer]
70
+ warn message
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ # Copyright (C) 2015 Twitter, Inc.
3
+
4
+ module TwitterAds
5
+ VERSION = '0.3.4'.freeze
6
+ end
@@ -0,0 +1,65 @@
1
+ {
2
+ "request": {
3
+ "params": {}
4
+ },
5
+ "data": [
6
+ {
7
+ "name": "Schuppe-Casper",
8
+ "timezone": "America/Los_Angeles",
9
+ "timezone_switch_at": "2014-11-17T08:00:00Z",
10
+ "id": "2iqph",
11
+ "created_at": "2015-03-04T10:50:42Z",
12
+ "salt": "5ab2pizq7qxjjqrx3z67f4wbko61o7xs",
13
+ "updated_at": "2015-04-11T05:20:08Z",
14
+ "approval_status": "ACCEPTED",
15
+ "deleted": false
16
+ },
17
+ {
18
+ "name": "Schuppe-Casper",
19
+ "timezone": "America/Los_Angeles",
20
+ "timezone_switch_at": "2014-11-17T08:00:00Z",
21
+ "id": "pz6ec",
22
+ "created_at": "2015-05-29T00:52:16Z",
23
+ "salt": "39ku32xvhdt0jax8thps2c70e2fv3yok",
24
+ "updated_at": "2015-05-29T00:52:16Z",
25
+ "approval_status": "ACCEPTED",
26
+ "deleted": false
27
+ },
28
+ {
29
+ "name": "Kozey-Farrell",
30
+ "timezone": "America/Los_Angeles",
31
+ "timezone_switch_at": "2014-11-17T08:00:00Z",
32
+ "id": "j9ozo",
33
+ "created_at": "2015-05-01T12:08:10Z",
34
+ "salt": "winwfne3y6oyikl4tm84bj9r50waxj37",
35
+ "updated_at": "2015-05-01T12:08:10Z",
36
+ "approval_status": "ACCEPTED",
37
+ "deleted": false
38
+ },
39
+ {
40
+ "name": "Osinski, Quitzon and Hilll",
41
+ "timezone": "America/Los_Angeles",
42
+ "timezone_switch_at": "2014-11-17T08:00:00Z",
43
+ "id": "9ttgd",
44
+ "created_at": "2015-06-24T18:51:20Z",
45
+ "salt": "tj9hmt5xylm5zztrq05w7hwh4mkpkg5r",
46
+ "updated_at": "2015-06-26T06:13:24Z",
47
+ "approval_status": "ACCEPTED",
48
+ "deleted": false
49
+ },
50
+ {
51
+ "name": "Jakubowski-Aufderhar",
52
+ "timezone": "America/Los_Angeles",
53
+ "timezone_switch_at": "2013-05-22T07:00:00Z",
54
+ "id": "47d0v",
55
+ "created_at": "2015-05-28T05:42:03Z",
56
+ "salt": "1ms1mq1nww7zl7169865gwqt89s9127m",
57
+ "updated_at": "2015-05-28T05:42:03Z",
58
+ "approval_status": "ACCEPTED",
59
+ "deleted": false
60
+ }
61
+ ],
62
+ "data_type": "account",
63
+ "total_count": 5,
64
+ "next_cursor": null
65
+ }