snapchat_api 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 43c8269298615c2858d5ddcdc32484970aa6ba2a6fe1df256d8cb309e04ba16e
4
+ data.tar.gz: f0131210e97ae51caaddf6c042b3575eb88d5c08e9c075e0b770b0cef0c6c144
5
+ SHA512:
6
+ metadata.gz: 3db8ca2e98368796ea19ca8554e538aaa47d3b827d78b66bad841b74d1bbafdba30c1574d3bd51a3b317936a790f37685fd270f6d40af378795d412438e286f9
7
+ data.tar.gz: 976a5ffd1d911639c0859db349dc4aadcc239dc230921fd0145bfaa2e545f0e4652d739f519f6d2e2ad332dbf73a633d273fecddc744f304cdb66c5a824a3247
data/CHANGELOG.md ADDED
@@ -0,0 +1,31 @@
1
+ # Changelog
2
+
3
+ ## [0.1.4](https://github.com/k0va1/snapchat_api/compare/v0.1.3...v0.1.4) (2025-07-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * rename license file ([5b1ab3e](https://github.com/k0va1/snapchat_api/commit/5b1ab3eef2d934c67a1c3895070c13303a9ef802))
9
+
10
+ ## [0.1.3](https://github.com/k0va1/snapchat_api/compare/v0.1.2...v0.1.3) (2025-07-29)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * add MIT licence ([5f054ea](https://github.com/k0va1/snapchat_api/commit/5f054eadb61351fa960f4b2d7568b59c2ef5706a))
16
+
17
+ ## [0.1.2](https://github.com/k0va1/snapchat_api/compare/v0.1.1...v0.1.2) (2025-07-29)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * changelog styling ([361d93c](https://github.com/k0va1/snapchat_api/commit/361d93c8886ed017f8910aa7773264a9cd91383a))
23
+
24
+ ## [0.1.1](https://github.com/k0va1/snapchat_api/compare/v0.1.0...v0.1.1) (2025-07-29)
25
+
26
+
27
+ ### Features
28
+
29
+ * add Ad Squads ([717ba38](https://github.com/k0va1/snapchat_api/commit/717ba38c91e7dcd21677e0269891677e80a23e02))
30
+ * add media support ([3433e6f](https://github.com/k0va1/snapchat_api/commit/3433e6f248a7c5a535fb99ba083d14b0f98bb75e))
31
+ * campaigns & specs ([cb6aab5](https://github.com/k0va1/snapchat_api/commit/cb6aab52d01aaab4e2f8390749d0356288c1384f))
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Alex Koval
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,289 @@
1
+ # SnapchatApi
2
+
3
+ A Ruby gem that provides a comprehensive wrapper for the Snapchat Ads API, supporting OAuth2 authentication and full CRUD operations for campaigns, ad squads, ads, creatives, and media.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'snapchat_api'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install snapchat_api
20
+
21
+ ## Usage
22
+
23
+ ### Creating a Client
24
+
25
+ ```ruby
26
+ require 'snapchat_api'
27
+
28
+ client = SnapchatApi::Client.new(
29
+ client_id: "your-client-id",
30
+ client_secret: "your-client-secret",
31
+ redirect_uri: "your-redirect-uri",
32
+ access_token: "your-access-token",
33
+ refresh_token: "your-refresh-token",
34
+ debug: false # Optional: enable debug logging
35
+ )
36
+ ```
37
+
38
+ ### OAuth2 Authentication
39
+
40
+ ```ruby
41
+ # Get authorization URL
42
+ auth_url = client.get_authorization_url(scope: "snapchat-marketing-api")
43
+ # => "https://accounts.snapchat.com/accounts/oauth2/auth?client_id=your-client-id&redirect_uri=your-redirect-uri&response_type=code&scope=snapchat-marketing-api"
44
+
45
+ # Exchange authorization code for tokens
46
+ tokens = client.get_tokens(code: "your-authorization-code")
47
+ # => {"access_token" => "...", "refresh_token" => "...", "expires_in" => 3600}
48
+
49
+ # Refresh access tokens
50
+ client.refresh_tokens!
51
+ ```
52
+
53
+ ## API Resources
54
+
55
+ ### Accounts (`SnapchatApi::Resources::Account`)
56
+
57
+ Manage ad accounts within organizations.
58
+
59
+ ```ruby
60
+ # List all ad accounts for an organization
61
+ accounts = client.accounts.list_all(organization_id: "org-id", params: {limit: 50})
62
+ ```
63
+
64
+ ### Campaigns (`SnapchatApi::Resources::Campaign`)
65
+
66
+ Full CRUD operations for campaigns.
67
+
68
+ ```ruby
69
+ # List all campaigns for an ad account
70
+ campaigns = client.campaigns.list_all(ad_account_id: "account-id", params: {limit: 50})
71
+
72
+ # Get a specific campaign
73
+ campaign = client.campaigns.get(ad_account_id: "account-id", campaign_id: "campaign-id")
74
+
75
+ # Create a new campaign
76
+ new_campaign = client.campaigns.create(
77
+ ad_account_id: "account-id",
78
+ params: {
79
+ name: "My Campaign",
80
+ status: "ACTIVE",
81
+ objective: "AWARENESS"
82
+ }
83
+ )
84
+
85
+ # Update a campaign
86
+ updated_campaign = client.campaigns.update(
87
+ ad_account_id: "account-id",
88
+ campaign_id: "campaign-id",
89
+ params: {name: "Updated Campaign Name"}
90
+ )
91
+
92
+ # Delete a campaign
93
+ client.campaigns.delete(campaign_id: "campaign-id")
94
+
95
+ # Get campaign statistics
96
+ stats = client.campaigns.get_stats(campaign_id: "campaign-id", params: {granularity: "DAY"})
97
+ ```
98
+
99
+ ### Ad Squads (`SnapchatApi::Resources::AdSquad`)
100
+
101
+ Manage ad squads within campaigns.
102
+
103
+ ```ruby
104
+ # List all ad squads for an ad account
105
+ ad_squads = client.ad_squads.list_all(ad_account_id: "account-id", params: {limit: 50})
106
+
107
+ # Get a specific ad squad
108
+ ad_squad = client.ad_squads.get(ad_squad_id: "squad-id")
109
+
110
+ # Create a new ad squad
111
+ new_ad_squad = client.ad_squads.create(
112
+ campaign_id: "campaign-id",
113
+ params: {
114
+ name: "My Ad Squad",
115
+ status: "ACTIVE",
116
+ type: "SNAP_ADS"
117
+ }
118
+ )
119
+
120
+ # Update an ad squad
121
+ updated_ad_squad = client.ad_squads.update(
122
+ campaign_id: "campaign-id",
123
+ ad_squad_id: "squad-id",
124
+ params: {name: "Updated Ad Squad"}
125
+ )
126
+
127
+ # Delete an ad squad
128
+ client.ad_squads.delete(ad_squad_id: "squad-id")
129
+
130
+ # Get ad squad statistics
131
+ stats = client.ad_squads.get_stats(ad_squad_id: "squad-id", params: {granularity: "DAY"})
132
+ ```
133
+
134
+ ### Ads (`SnapchatApi::Resources::Ad`)
135
+
136
+ Create and manage individual ads.
137
+
138
+ ```ruby
139
+ # Create a new ad
140
+ new_ad = client.ads.create(
141
+ ad_squad_id: "squad-id",
142
+ params: {
143
+ name: "My Ad",
144
+ creative_id: "creative-id",
145
+ status: "ACTIVE"
146
+ }
147
+ )
148
+
149
+ # List ads by different entities
150
+ ads_by_squad = client.ads.list_all_by(entity_id: "squad-id", entity: :ad_squad)
151
+ ads_by_account = client.ads.list_all_by(entity_id: "account-id", entity: :ad_account)
152
+ ads_by_campaign = client.ads.list_all_by(entity_id: "campaign-id", entity: :campaign)
153
+
154
+ # Get a specific ad
155
+ ad = client.ads.get(ad_id: "ad-id")
156
+
157
+ # Update an ad
158
+ updated_ad = client.ads.update(
159
+ ad_squad_id: "squad-id",
160
+ params: {id: "ad-id", name: "Updated Ad Name"}
161
+ )
162
+
163
+ # Delete an ad
164
+ client.ads.delete(ad_id: "ad-id")
165
+
166
+ # Get ad statistics
167
+ stats = client.ads.get_stats(ad_id: "ad-id", params: {granularity: "DAY"})
168
+ ```
169
+
170
+ ### Creatives (`SnapchatApi::Resources::Creative`)
171
+
172
+ Manage creative assets for ads.
173
+
174
+ ```ruby
175
+ # List all creatives for an ad account
176
+ creatives = client.creatives.list_all(ad_account_id: "account-id", params: {limit: 50})
177
+
178
+ # Get a specific creative
179
+ creative = client.creatives.get(creative_id: "creative-id")
180
+
181
+ # Create a new creative
182
+ new_creative = client.creatives.create(
183
+ ad_account_id: "account-id",
184
+ params: {
185
+ name: "My Creative",
186
+ type: "IMAGE",
187
+ top_snap_media_id: "media-id"
188
+ }
189
+ )
190
+
191
+ # Update a creative
192
+ updated_creative = client.creatives.update(
193
+ ad_account_id: "account-id",
194
+ creative_id: "creative-id",
195
+ params: {name: "Updated Creative Name"}
196
+ )
197
+ ```
198
+
199
+ ### Media (`SnapchatApi::Resources::Media`)
200
+
201
+ Upload and manage media files.
202
+
203
+ ```ruby
204
+ # List all media for an ad account
205
+ media_items = client.media.list_all(ad_account_id: "account-id", params: {limit: 50})
206
+
207
+ # Get a specific media item
208
+ media = client.media.get(media_id: "media-id")
209
+
210
+ # Create a new media placeholder
211
+ new_media = client.media.create(
212
+ ad_account_id: "account-id",
213
+ params: {
214
+ name: "My Media",
215
+ type: "IMAGE"
216
+ }
217
+ )
218
+
219
+ # Upload a file to the media placeholder
220
+ result = client.media.upload(
221
+ media_id: "media-id",
222
+ file_path: "/path/to/image.jpg"
223
+ )
224
+ ```
225
+
226
+ ## Error Handling
227
+
228
+ The gem provides specific error classes for different API error scenarios:
229
+
230
+ - `SnapchatApi::AuthenticationError` (401) - Invalid or expired access token
231
+ - `SnapchatApi::AuthorizationError` (403) - Insufficient permissions
232
+ - `SnapchatApi::InvalidRequestError` (400-499) - Bad request or client error
233
+ - `SnapchatApi::RateLimitError` (429) - Rate limit exceeded
234
+ - `SnapchatApi::ApiError` (500-599) - Server error
235
+ - `SnapchatApi::Error` - Base error class
236
+
237
+ ```ruby
238
+ begin
239
+ campaigns = client.campaigns.list_all(ad_account_id: "invalid-id")
240
+ rescue SnapchatApi::AuthenticationError => e
241
+ puts "Authentication failed: #{e.message}"
242
+ client.refresh_tokens! # Try refreshing tokens
243
+ rescue SnapchatApi::RateLimitError => e
244
+ puts "Rate limit exceeded, waiting..."
245
+ sleep(60)
246
+ retry
247
+ rescue SnapchatApi::Error => e
248
+ puts "API error: #{e.message} (Status: #{e.status_code})"
249
+ end
250
+ ```
251
+
252
+ ## Configuration
253
+
254
+ ### Debug Mode
255
+
256
+ Enable debug mode to see detailed HTTP request/response logs:
257
+
258
+ ```ruby
259
+ client = SnapchatApi::Client.new(
260
+ client_id: "your-client-id",
261
+ client_secret: "your-client-secret",
262
+ debug: true
263
+ )
264
+ ```
265
+
266
+ ### Connection Settings
267
+
268
+ The client uses Faraday with the following default settings:
269
+ - Request timeout: 60 seconds
270
+ - Open timeout: 30 seconds
271
+ - Maximum retries: 3
272
+ - Multipart support for file uploads
273
+ - Automatic redirect following
274
+
275
+ ## Development
276
+
277
+ ```bash
278
+ make install
279
+ make test
280
+ ```
281
+
282
+ To run linting and fix issues automatically:
283
+ ```bash
284
+ make lint-fix
285
+ ```
286
+
287
+ ## Contributing
288
+
289
+ Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration.
data/bin/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "snapchat_api"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require "irb"
10
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,162 @@
1
+ require "faraday"
2
+ require "faraday/multipart"
3
+ require "faraday/follow_redirects"
4
+ require "faraday/retry"
5
+ require "snapchat_api/error"
6
+
7
+ module SnapchatApi
8
+ class Client
9
+ attr_accessor :client_id, :client_secret, :access_token, :refresh_token, :debug, :redirect_uri
10
+
11
+ ADS_HOST = "https://adsapi.snapchat.com"
12
+ ACCOUNTS_HOST = "https://accounts.snapchat.com"
13
+
14
+ ADS_CURRENT_API_PATH = "v1"
15
+ ADS_URL = "#{ADS_HOST}/#{ADS_CURRENT_API_PATH}"
16
+
17
+ def initialize(client_id:, client_secret:, redirect_uri: nil, access_token: nil, refresh_token: nil, debug: false)
18
+ @client_id = client_id
19
+ @client_secret = client_secret
20
+ @redirect_uri = redirect_uri
21
+ @access_token = access_token
22
+ @refresh_token = refresh_token
23
+ @debug = debug
24
+ end
25
+
26
+ def connection
27
+ @connection ||= Faraday.new(url: ADS_URL) do |conn|
28
+ conn.headers["Authorization"] = "Bearer #{@access_token}"
29
+ conn.headers["Content-Type"] = "application/json"
30
+
31
+ conn.options.timeout = 60
32
+ conn.options.open_timeout = 30
33
+
34
+ conn.request :multipart
35
+ conn.use Faraday::FollowRedirects::Middleware
36
+ conn.use Faraday::Retry::Middleware, max: 3
37
+
38
+ conn.response :json
39
+
40
+ conn.response :logger if @debug
41
+
42
+ conn.adapter Faraday.default_adapter
43
+ end
44
+ end
45
+
46
+ def request(method, path, params = {}, headers = {})
47
+ response = connection.run_request(method, path, nil, headers) do |req|
48
+ case method
49
+ when :get, :delete
50
+ req.params = params
51
+ when :post, :put
52
+ if headers["Content-Type"] == "multipart/form-data"
53
+ req.options.timeout = 120
54
+ req.body = {}
55
+ params.each do |key, value|
56
+ req.body[key.to_sym] = value
57
+ end
58
+ else
59
+ req.body = JSON.generate(params) unless params.empty?
60
+ end
61
+ end
62
+ end
63
+
64
+ handle_response(response)
65
+ end
66
+
67
+ def refresh_tokens!
68
+ response = Faraday.post("#{ACCOUNTS_HOST}/login/oauth2/access_token") do |req|
69
+ req.headers["Content-Type"] = "application/x-www-form-urlencoded"
70
+ req.body = URI.encode_www_form(
71
+ client_id: @client_id,
72
+ client_secret: @client_secret,
73
+ refresh_token: @refresh_token,
74
+ grant_type: "refresh_token"
75
+ )
76
+ end
77
+ handle_response(response)
78
+ body = JSON.parse(response.body)
79
+
80
+ @access_token = body["access_token"]
81
+ @refresh_token = body["refresh_token"]
82
+ end
83
+
84
+ def get_authorization_url(scope: "snapchat-marketing-api")
85
+ params = {
86
+ client_id: @client_id,
87
+ redirect_uri: redirect_uri,
88
+ response_type: "code",
89
+ scope: scope,
90
+ state: state
91
+ }
92
+
93
+ "https://accounts.snapchat.com/accounts/oauth2/auth?#{URI.encode_www_form(params)}"
94
+ end
95
+
96
+ def get_tokens(code:)
97
+ response = Faraday.post("#{ACCOUNTS_HOST}/login/oauth2/access_token") do |req|
98
+ req.headers["Content-Type"] = "application/x-www-form-urlencoded"
99
+ req.body = URI.encode_www_form({
100
+ client_id: @client_id,
101
+ client_secret: @client_secret,
102
+ code: code,
103
+ grant_type: "authorization_code",
104
+ redirect_uri: redirect_uri
105
+ })
106
+ end
107
+ handle_response(response)
108
+ JSON.parse(response.body)
109
+ end
110
+
111
+ def accounts
112
+ @accounts ||= SnapchatApi::Resources::Account.new(self)
113
+ end
114
+
115
+ def campaigns
116
+ @campaigns ||= SnapchatApi::Resources::Campaign.new(self)
117
+ end
118
+
119
+ def ad_squads
120
+ @ad_squads ||= SnapchatApi::Resources::AdSquad.new(self)
121
+ end
122
+
123
+ def media
124
+ @media ||= SnapchatApi::Resources::Media.new(self)
125
+ end
126
+
127
+ def creatives
128
+ @creatives ||= SnapchatApi::Resources::Creative.new(self)
129
+ end
130
+
131
+ def ads
132
+ @ads ||= SnapchatApi::Resources::Ad.new(self)
133
+ end
134
+
135
+ private
136
+
137
+ def handle_response(response)
138
+ return response if response.success?
139
+
140
+ status = response.status
141
+ body = response.body
142
+ error_message = body.is_a?(Hash) ? body&.dig("message") : body
143
+
144
+ klass = case status
145
+ when 401
146
+ SnapchatApi::AuthenticationError
147
+ when 403
148
+ SnapchatApi::AuthorizationError
149
+ when 429
150
+ SnapchatApi::RateLimitError
151
+ when 400..499
152
+ SnapchatApi::InvalidRequestError
153
+ when 500..599
154
+ SnapchatApi::ApiError
155
+ else
156
+ SnapchatApi::Error
157
+ end
158
+
159
+ raise klass.new(error_message || "HTTP #{status}", status, body)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,22 @@
1
+ module SnapchatApi
2
+ class Error < StandardError
3
+ attr_reader :status_code
4
+ attr_reader :body
5
+
6
+ def initialize(message = nil, status_code = nil, body = nil)
7
+ @status_code = status_code
8
+ @body = body
9
+ super(message)
10
+ end
11
+ end
12
+
13
+ class AuthenticationError < Error; end
14
+
15
+ class AuthorizationError < Error; end
16
+
17
+ class InvalidRequestError < Error; end
18
+
19
+ class ApiError < Error; end
20
+
21
+ class RateLimitError < Error; end
22
+ end
@@ -0,0 +1,21 @@
1
+ module SnapchatApi
2
+ module Resources
3
+ class Account < Base
4
+ def list_all(organization_id:, params: {})
5
+ params[:limit] ||= 50
6
+
7
+ accounts = []
8
+ next_link = "organizations/#{organization_id}/adaccounts?limit=#{params[:limit]}"
9
+
10
+ loop do
11
+ response = client.request(:get, next_link)
12
+ next_link = response.body["paging"]["next_link"]
13
+ accounts.concat(response.body["adaccounts"].map { |el| el["adaccount"] })
14
+ break if next_link.nil?
15
+ end
16
+
17
+ accounts
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ module SnapchatApi
2
+ module Resources
3
+ class Ad < Base
4
+ def create(ad_squad_id:, params:)
5
+ ads_data = {ads: [**params]}
6
+ response = client.request(:post, "adsquads/#{ad_squad_id}/ads", ads_data)
7
+
8
+ response.body["ads"].first["ad"]
9
+ end
10
+
11
+ def list_all_by(entity_id:, entity: :ad_squad, params: {})
12
+ params[:limit] ||= 50
13
+
14
+ ads = []
15
+
16
+ next_link = case entity
17
+ when :ad_squad
18
+ "adsquads/#{entity_id}/ads?limit=#{params[:limit]}"
19
+ when :ad_account
20
+ "adaccounts/#{entity_id}/ads?limit=#{params[:limit]}"
21
+ when :campaign
22
+ "campaigns/#{entity_id}/ads?limit=#{params[:limit]}"
23
+ else
24
+ raise ArgumentError, "Invalid entity type: #{entity}. Must be :ad_squad, :ad_account, or :campaign."
25
+ end
26
+
27
+ loop do
28
+ response = client.request(:get, next_link)
29
+ next_link = response.body["paging"]["next_link"]
30
+ ads.concat(response.body["ads"].map { |el| el["ad"] })
31
+ break if next_link.nil?
32
+ end
33
+
34
+ ads
35
+ end
36
+
37
+ def get(ad_id:)
38
+ response = client.request(:get, "ads/#{ad_id}")
39
+ response.body["ads"].first["ad"]
40
+ end
41
+
42
+ def delete(ad_id:)
43
+ response = client.request(:delete, "ads/#{ad_id}")
44
+ response.success?
45
+ end
46
+
47
+ def update(ad_squad_id:, params:)
48
+ ad_data = {
49
+ ads: [**params.merge(ad_squad_id: ad_squad_id)]
50
+ }
51
+
52
+ response = client.request(:put, "adsquads/#{ad_squad_id}/ads", ad_data)
53
+ response.body["ads"].first["ad"]
54
+ end
55
+
56
+ def get_stats(ad_id:, params: {})
57
+ response = client.request(:get, "ads/#{ad_id}/stats", params)
58
+ response.body
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,54 @@
1
+ module SnapchatApi
2
+ module Resources
3
+ class AdSquad < Base
4
+ def list_all(ad_account_id:, params: {})
5
+ params[:limit] ||= 50
6
+
7
+ ad_squads = []
8
+ next_link = "adaccounts/#{ad_account_id}/adsquads?limit=#{params[:limit]}"
9
+
10
+ loop do
11
+ response = client.request(:get, next_link)
12
+ next_link = response.body["paging"]["next_link"]
13
+ ad_squads.concat(response.body["adsquads"].map { |el| el["adsquad"] })
14
+ break if next_link.nil?
15
+ end
16
+
17
+ ad_squads
18
+ end
19
+
20
+ def get(ad_squad_id:)
21
+ response = client.request(:get, "adsquads/#{ad_squad_id}")
22
+ response.body["adsquads"].first["adsquad"]
23
+ end
24
+
25
+ def create(campaign_id:, params: {})
26
+ ad_squads_data = {
27
+ adsquads: [**params.merge(campaign_id: campaign_id)]
28
+ }
29
+
30
+ response = client.request(:post, "campaigns/#{campaign_id}/adsquads", ad_squads_data)
31
+ response.body["adsquads"].first["adsquad"]
32
+ end
33
+
34
+ def update(campaign_id:, ad_squad_id:, params: {})
35
+ ad_squads_data = {
36
+ adsquads: [**params.merge(id: ad_squad_id, campaign_id:)]
37
+ }
38
+
39
+ response = client.request(:put, "campaigns/#{campaign_id}/adsquads", ad_squads_data)
40
+ response.body["adsquads"].first["adsquad"]
41
+ end
42
+
43
+ def delete(ad_squad_id:)
44
+ response = client.request(:delete, "adsquads/#{ad_squad_id}")
45
+ response.success?
46
+ end
47
+
48
+ def get_stats(ad_squad_id:, params: {})
49
+ response = client.request(:get, "adsquads/#{ad_squad_id}/stats", params)
50
+ response.body
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ module SnapchatApi
2
+ module Resources
3
+ class Base
4
+ attr_reader :client
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,54 @@
1
+ module SnapchatApi
2
+ module Resources
3
+ class Campaign < Base
4
+ def list_all(ad_account_id:, params: {})
5
+ params[:limit] ||= 50
6
+
7
+ campaigns = []
8
+ next_link = "adaccounts/#{ad_account_id}/campaigns?limit=#{params[:limit]}"
9
+
10
+ loop do
11
+ response = client.request(:get, next_link)
12
+ next_link = response.body["paging"]["next_link"]
13
+ campaigns.concat(response.body["campaigns"].map { |el| el["campaign"] })
14
+ break if next_link.nil?
15
+ end
16
+
17
+ campaigns
18
+ end
19
+
20
+ def get(ad_account_id:, campaign_id:)
21
+ response = client.request(:get, "campaigns/#{campaign_id}")
22
+ response.body["campaigns"].first["campaign"]
23
+ end
24
+
25
+ def create(ad_account_id:, params: {})
26
+ campaigns_data = {
27
+ campaigns: [**params]
28
+ }
29
+
30
+ response = client.request(:post, "adaccounts/#{ad_account_id}/campaigns", campaigns_data)
31
+ response.body["campaigns"].first["campaign"]
32
+ end
33
+
34
+ def update(ad_account_id:, campaign_id:, params: {})
35
+ campaigns_data = {
36
+ campaigns: [**params.merge(id: campaign_id, ad_account_id:)]
37
+ }
38
+
39
+ response = client.request(:put, "adaccounts/#{ad_account_id}/campaigns", campaigns_data)
40
+ response.body["campaigns"].first["campaign"]
41
+ end
42
+
43
+ def delete(campaign_id:)
44
+ response = client.request(:delete, "campaigns/#{campaign_id}")
45
+ response.success?
46
+ end
47
+
48
+ def get_stats(campaign_id:, params: {})
49
+ response = client.request(:get, "campaigns/#{campaign_id}/stats", params)
50
+ response.body
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ module SnapchatApi
2
+ module Resources
3
+ class Creative < Base
4
+ def list_all(ad_account_id:, params: {})
5
+ params[:limit] ||= 50
6
+
7
+ creatives = []
8
+ next_link = "adaccounts/#{ad_account_id}/creatives?limit=#{params[:limit]}"
9
+
10
+ loop do
11
+ response = client.request(:get, next_link)
12
+ next_link = response.body["paging"]["next_link"]
13
+ creatives.concat(response.body["creatives"].map { |el| el["creative"] })
14
+ break if next_link.nil?
15
+ end
16
+
17
+ creatives
18
+ end
19
+
20
+ def get(creative_id:)
21
+ response = client.request(:get, "creatives/#{creative_id}")
22
+ response.body["creatives"].first["creative"]
23
+ end
24
+
25
+ def create(ad_account_id:, params: {})
26
+ creatives_data = {
27
+ creatives: [**params]
28
+ }
29
+
30
+ response = client.request(:post, "adaccounts/#{ad_account_id}/creatives", creatives_data)
31
+ response.body["creatives"].first["creative"]
32
+ end
33
+
34
+ def update(ad_account_id:, creative_id:, params: {})
35
+ creatives_data = {
36
+ creatives: [**params.merge(id: creative_id, ad_account_id:)]
37
+ }
38
+
39
+ response = client.request(:put, "adaccounts/#{ad_account_id}/creatives", creatives_data)
40
+ response.body["creatives"].first["creative"]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ require "stringio"
2
+ require "mime/types"
3
+
4
+ module SnapchatApi
5
+ module Resources
6
+ class Media < Base
7
+ def list_all(ad_account_id:, params: {})
8
+ params[:limit] ||= 50
9
+
10
+ media_items = []
11
+ next_link = "adaccounts/#{ad_account_id}/media?limit=#{params[:limit]}"
12
+
13
+ loop do
14
+ response = client.request(:get, next_link)
15
+ next_link = response.body["paging"]["next_link"]
16
+ media_items.concat(response.body["media"].map { |el| el["media"] })
17
+ break if next_link.nil?
18
+ end
19
+
20
+ media_items
21
+ end
22
+
23
+ def get(media_id:)
24
+ response = client.request(:get, "media/#{media_id}")
25
+ response.body["media"].first["media"]
26
+ end
27
+
28
+ def create(ad_account_id:, params: {})
29
+ media_data = {
30
+ media: [params]
31
+ }
32
+
33
+ response = client.request(:post, "adaccounts/#{ad_account_id}/media", media_data)
34
+ response.body["media"].first["media"]
35
+ end
36
+
37
+ def upload(media_id:, file_path:, params: {})
38
+ mime_type = MIME::Types.type_for(file_path).first.content_type
39
+
40
+ upload_params = {
41
+ file: Faraday::UploadIO.new(file_path, mime_type, File.basename(file_path))
42
+ }
43
+
44
+ response = client.request(:post, "media/#{media_id}/upload", upload_params, {"Content-Type" => "multipart/form-data"})
45
+ response.body["result"]
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SnapchatApi
4
+ VERSION = "0.1.4"
5
+ end
@@ -0,0 +1,8 @@
1
+ require_relative "snapchat_api/version"
2
+
3
+ require "zeitwerk"
4
+ loader = Zeitwerk::Loader.for_gem
5
+ loader.setup
6
+
7
+ module SnapchatApi
8
+ end
metadata ADDED
@@ -0,0 +1,240 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: snapchat_api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Alex Koval
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: oauth2
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: faraday-multipart
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: faraday-follow_redirects
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.3'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.3'
68
+ - !ruby/object:Gem::Dependency
69
+ name: faraday-retry
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '2.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: mime-types
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.1'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '3.1'
96
+ - !ruby/object:Gem::Dependency
97
+ name: zeitwerk
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '2.6'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '2.6'
110
+ - !ruby/object:Gem::Dependency
111
+ name: bundler
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '2.0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '2.0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rake
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '13.0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '13.0'
138
+ - !ruby/object:Gem::Dependency
139
+ name: rspec
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '3.0'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '3.0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: standard
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - '='
157
+ - !ruby/object:Gem::Version
158
+ version: 1.49.0
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - '='
164
+ - !ruby/object:Gem::Version
165
+ version: 1.49.0
166
+ - !ruby/object:Gem::Dependency
167
+ name: webmock
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '3.0'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '3.0'
180
+ - !ruby/object:Gem::Dependency
181
+ name: vcr
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '6.0'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: '6.0'
194
+ description: A comprehensive Ruby library for interacting with the Snapchat API
195
+ email:
196
+ - al3xander.koval@gmail.com
197
+ executables: []
198
+ extensions: []
199
+ extra_rdoc_files: []
200
+ files:
201
+ - CHANGELOG.md
202
+ - LICENSE.txt
203
+ - README.md
204
+ - bin/console
205
+ - bin/setup
206
+ - lib/snapchat_api.rb
207
+ - lib/snapchat_api/client.rb
208
+ - lib/snapchat_api/error.rb
209
+ - lib/snapchat_api/resources/account.rb
210
+ - lib/snapchat_api/resources/ad.rb
211
+ - lib/snapchat_api/resources/ad_squad.rb
212
+ - lib/snapchat_api/resources/base.rb
213
+ - lib/snapchat_api/resources/campaign.rb
214
+ - lib/snapchat_api/resources/creative.rb
215
+ - lib/snapchat_api/resources/media.rb
216
+ - lib/snapchat_api/version.rb
217
+ homepage: https://github.com/k0va1/snapchat_api
218
+ licenses: []
219
+ metadata:
220
+ homepage_uri: https://github.com/k0va1/snapchat_api
221
+ source_code_uri: https://github.com/k0va1/snapchat_api
222
+ changelog_uri: https://github.com/k0va1/snapchat_api/blob/main/CHANGELOG.md
223
+ rdoc_options: []
224
+ require_paths:
225
+ - lib
226
+ required_ruby_version: !ruby/object:Gem::Requirement
227
+ requirements:
228
+ - - ">="
229
+ - !ruby/object:Gem::Version
230
+ version: 3.2.0
231
+ required_rubygems_version: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '0'
236
+ requirements: []
237
+ rubygems_version: 3.6.9
238
+ specification_version: 4
239
+ summary: Ruby wrapper for the Snapchat API
240
+ test_files: []