tiktok_business_api 0.1.0
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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +242 -0
- data/bin/console +11 -0
- data/bin/publish_gem +29 -0
- data/lib/tiktok_ads_api.rb +40 -0
- data/lib/tiktok_business_api/auth.rb +116 -0
- data/lib/tiktok_business_api/client.rb +162 -0
- data/lib/tiktok_business_api/config.rb +38 -0
- data/lib/tiktok_business_api/errors.rb +84 -0
- data/lib/tiktok_business_api/resources/base_resource.rb +100 -0
- data/lib/tiktok_business_api/resources/campaign.rb +113 -0
- data/lib/tiktok_business_api/version.rb +5 -0
- data/lib/tiktok_business_api.rb +40 -0
- metadata +183 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ad55f88f2cd5e40133b2b5e9e50f531ab05d42c1481dbcb2bc10cce04e32b36c
|
4
|
+
data.tar.gz: 1d9c0d9cd1d66d5f9b18ed7cafb300554f838d60fa8b3f1e674e6add14b51d16
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f62aad982847e47734da79d75d187819d67f518dbdc82232d2d85671bde0df713e022c930c544cb6a4976b21093f397d2aed810208deed395e1a16e846f70bac
|
7
|
+
data.tar.gz: 136d73e3995bd71017e27579a5923b93c5e92141ae5828ee479d97a4eeaed835742919251c41d5f512a14e3b0311142a34f184c103d84eee2bdf113d47783e2c
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
# TikTok Business API Ruby Gem
|
2
|
+
|
3
|
+
A Ruby interface to the TikTok Business API with support for campaigns, ad groups, ads, and more.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'tiktok_business_api'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
```bash
|
16
|
+
$ bundle install
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
```bash
|
22
|
+
$ gem install tiktok_business_api
|
23
|
+
```
|
24
|
+
|
25
|
+
## Getting Started
|
26
|
+
|
27
|
+
### Configuration
|
28
|
+
|
29
|
+
You need to configure the client with your TikTok app credentials:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
TiktokBusinessApi.configure do |config|
|
33
|
+
config.app_id = 'your_app_id'
|
34
|
+
config.secret = 'your_app_secret'
|
35
|
+
config.debug = true # Enable request/response logging
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
If you want to use a custom logger:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
require 'logger'
|
43
|
+
logger = Logger.new(STDOUT)
|
44
|
+
logger.level = Logger::DEBUG
|
45
|
+
|
46
|
+
TiktokBusinessApi.configure do |config|
|
47
|
+
config.logger = logger
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
### Authentication
|
52
|
+
|
53
|
+
#### Generate an authorization URL
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
client = TiktokBusinessApi.client
|
57
|
+
auth_url = client.auth.authorization_url('https://your-redirect-url.com/callback')
|
58
|
+
```
|
59
|
+
|
60
|
+
After the user authorizes your app, TikTok will redirect to your callback URL with an authorization code.
|
61
|
+
|
62
|
+
#### Generate an access token
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
auth_code = 'auth_code_from_callback'
|
66
|
+
redirect_uri = 'https://your-redirect-url.com/callback'
|
67
|
+
|
68
|
+
response = client.auth.generate_access_token(auth_code, redirect_uri)
|
69
|
+
access_token = response['data']['access_token']
|
70
|
+
```
|
71
|
+
|
72
|
+
The access token will automatically be stored in your client configuration for future requests.
|
73
|
+
|
74
|
+
### Working with Campaigns
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
# Create a new client instance
|
78
|
+
client = TiktokBusinessApi.client(access_token: 'your_access_token')
|
79
|
+
|
80
|
+
# Create a campaign
|
81
|
+
advertiser_id = '6000000000000'
|
82
|
+
campaign_params = {
|
83
|
+
campaign_name: 'My First Campaign',
|
84
|
+
objective_type: 'TRAFFIC',
|
85
|
+
budget_mode: 'BUDGET_MODE_TOTAL',
|
86
|
+
budget: 1000
|
87
|
+
}
|
88
|
+
|
89
|
+
campaign = client.campaigns.create(advertiser_id, campaign_params)
|
90
|
+
campaign_id = campaign['campaign_id']
|
91
|
+
|
92
|
+
# Get a list of campaigns
|
93
|
+
campaigns = client.campaigns.list(advertiser_id)
|
94
|
+
|
95
|
+
# Get a specific campaign
|
96
|
+
campaign = client.campaigns.get(advertiser_id, campaign_id)
|
97
|
+
|
98
|
+
# Update a campaign
|
99
|
+
update_params = {
|
100
|
+
campaign_name: 'Updated Campaign Name'
|
101
|
+
}
|
102
|
+
client.campaigns.update(advertiser_id, campaign_id, update_params)
|
103
|
+
|
104
|
+
# Enable/disable a campaign
|
105
|
+
client.campaigns.update_status(advertiser_id, campaign_id, 'DISABLE')
|
106
|
+
|
107
|
+
# Delete a campaign
|
108
|
+
client.campaigns.delete(advertiser_id, campaign_id)
|
109
|
+
|
110
|
+
# List all campaigns with pagination (yields each campaign to a block)
|
111
|
+
client.campaigns.list_all(advertiser_id) do |campaign|
|
112
|
+
puts "Campaign: #{campaign['campaign_name']}"
|
113
|
+
end
|
114
|
+
```
|
115
|
+
|
116
|
+
## Logging Requests and Responses
|
117
|
+
|
118
|
+
You can enable debug logging to see all API requests and responses:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
TiktokBusinessApi.configure do |config|
|
122
|
+
config.debug = true
|
123
|
+
config.logger = Logger.new(STDOUT)
|
124
|
+
end
|
125
|
+
```
|
126
|
+
|
127
|
+
## Error Handling
|
128
|
+
|
129
|
+
The gem raises specific exceptions for different error types:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
begin
|
133
|
+
client.campaigns.create(advertiser_id, campaign_params)
|
134
|
+
rescue TiktokBusinessApi::AuthenticationError => e
|
135
|
+
puts "Authentication failed: #{e.message}"
|
136
|
+
rescue TiktokBusinessApi::RateLimitError => e
|
137
|
+
puts "Rate limit exceeded: #{e.message}"
|
138
|
+
rescue TiktokBusinessApi::ApiError => e
|
139
|
+
puts "API error: #{e.message}, Status code: #{e.status_code}, Body: #{e.body}"
|
140
|
+
rescue TiktokBusinessApi::Error => e
|
141
|
+
puts "General error: #{e.message}"
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
## Development
|
146
|
+
|
147
|
+
### With Docker (recommended)
|
148
|
+
|
149
|
+
This gem includes a Docker-based development environment to ensure consistent development and testing.
|
150
|
+
|
151
|
+
#### Prerequisites
|
152
|
+
|
153
|
+
- Docker and Docker Compose
|
154
|
+
- Make (optional, but recommended)
|
155
|
+
|
156
|
+
#### Setup using Make
|
157
|
+
|
158
|
+
```bash
|
159
|
+
# Set up the development environment
|
160
|
+
make setup
|
161
|
+
|
162
|
+
# Run tests
|
163
|
+
make test
|
164
|
+
|
165
|
+
# Open a shell in the Docker container
|
166
|
+
make shell
|
167
|
+
|
168
|
+
# Start a console with the gem loaded
|
169
|
+
make console
|
170
|
+
|
171
|
+
# Run RuboCop linting
|
172
|
+
make lint
|
173
|
+
|
174
|
+
# Generate documentation
|
175
|
+
make docs
|
176
|
+
|
177
|
+
# Build the gem
|
178
|
+
make build
|
179
|
+
|
180
|
+
# Prepare a new release
|
181
|
+
make release
|
182
|
+
|
183
|
+
# Publish to RubyGems
|
184
|
+
make publish
|
185
|
+
|
186
|
+
# View all available commands
|
187
|
+
make help
|
188
|
+
```
|
189
|
+
|
190
|
+
#### Without Make
|
191
|
+
|
192
|
+
If you prefer to use Docker Compose directly:
|
193
|
+
|
194
|
+
```bash
|
195
|
+
# Set up the development environment
|
196
|
+
docker-compose build
|
197
|
+
|
198
|
+
# Run tests
|
199
|
+
docker-compose run --rm test
|
200
|
+
|
201
|
+
# Start a console with the gem loaded
|
202
|
+
docker-compose run --rm console
|
203
|
+
|
204
|
+
# Open a shell in the Docker container
|
205
|
+
docker-compose run --rm gem bash
|
206
|
+
```
|
207
|
+
|
208
|
+
### Without Docker
|
209
|
+
|
210
|
+
If you prefer to develop without Docker:
|
211
|
+
|
212
|
+
```bash
|
213
|
+
# Install dependencies
|
214
|
+
bundle install
|
215
|
+
|
216
|
+
# Run tests
|
217
|
+
bundle exec rake spec
|
218
|
+
|
219
|
+
# Run linting
|
220
|
+
bundle exec rubocop
|
221
|
+
|
222
|
+
# Generate documentation
|
223
|
+
bundle exec yard
|
224
|
+
|
225
|
+
# Build the gem
|
226
|
+
bundle exec rake build
|
227
|
+
|
228
|
+
# Start a console with the gem loaded
|
229
|
+
bin/console
|
230
|
+
```
|
231
|
+
|
232
|
+
## Contributing
|
233
|
+
|
234
|
+
1. Fork it
|
235
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
236
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
237
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
238
|
+
5. Create a new Pull Request
|
239
|
+
|
240
|
+
## License
|
241
|
+
|
242
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "tiktok_business_api"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require "irb"
|
11
|
+
IRB.start(__FILE__)
|
data/bin/publish_gem
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -e
|
3
|
+
|
4
|
+
# This script publishes the gem to RubyGems without requiring Git operations
|
5
|
+
|
6
|
+
# Build the gem if needed
|
7
|
+
if [ ! -f "pkg/tiktok_business_api-$(ruby -r ./lib/tiktok_business_api/version -e 'print TiktokBusinessApi::VERSION').gem" ]; then
|
8
|
+
echo "Building gem..."
|
9
|
+
bundle exec rake build
|
10
|
+
fi
|
11
|
+
|
12
|
+
# Get the current version
|
13
|
+
CURRENT_VERSION=$(ruby -r ./lib/tiktok_business_api/version -e 'print TiktokBusinessApi::VERSION')
|
14
|
+
echo "Publishing tiktok_business_api version $CURRENT_VERSION to RubyGems..."
|
15
|
+
|
16
|
+
# Prompt for RubyGems credentials if needed
|
17
|
+
if [ -z "$RUBYGEMS_API_KEY" ]; then
|
18
|
+
if [ ! -f ~/.gem/credentials ]; then
|
19
|
+
echo "RubyGems credentials not found. You need to log in to RubyGems."
|
20
|
+
gem push --help
|
21
|
+
read -p "Press Enter after you've set up your credentials..." _
|
22
|
+
fi
|
23
|
+
fi
|
24
|
+
|
25
|
+
# Push to RubyGems
|
26
|
+
echo "Pushing gem to RubyGems..."
|
27
|
+
gem push "pkg/tiktok_business_api-$CURRENT_VERSION.gem"
|
28
|
+
|
29
|
+
echo "Gem successfully published!"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday/retry'
|
5
|
+
require 'faraday/follow_redirects'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
require_relative 'tiktok_ads_api/version'
|
9
|
+
require_relative 'tiktok_ads_api/config'
|
10
|
+
require_relative 'tiktok_ads_api/errors'
|
11
|
+
require_relative 'tiktok_ads_api/client'
|
12
|
+
require_relative 'tiktok_ads_api/auth'
|
13
|
+
|
14
|
+
# Resources
|
15
|
+
require_relative 'tiktok_ads_api/resources/base_resource'
|
16
|
+
require_relative 'tiktok_ads_api/resources/campaign'
|
17
|
+
|
18
|
+
module TiktokAdsApi
|
19
|
+
class << self
|
20
|
+
attr_accessor :config
|
21
|
+
|
22
|
+
# Configure the TikTok Ads API client
|
23
|
+
#
|
24
|
+
# @yield [config] Configuration object that can be modified
|
25
|
+
# @return [TiktokAdsApi::Config] The configuration object
|
26
|
+
def configure
|
27
|
+
self.config ||= Config.new
|
28
|
+
yield(config) if block_given?
|
29
|
+
config
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a new client instance
|
33
|
+
#
|
34
|
+
# @param options [Hash] Optional configuration overrides
|
35
|
+
# @return [TiktokAdsApi::Client] A new client instance
|
36
|
+
def client(options = {})
|
37
|
+
TiktokAdsApi::Client.new(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
# Handles authentication with the TikTok Business API
|
5
|
+
class Auth
|
6
|
+
# @return [TiktokBusinessApi::Client] Client instance
|
7
|
+
attr_reader :client
|
8
|
+
|
9
|
+
# Initialize the Auth handler
|
10
|
+
#
|
11
|
+
# @param client [TiktokBusinessApi::Client] Client instance
|
12
|
+
def initialize(client)
|
13
|
+
@client = client
|
14
|
+
end
|
15
|
+
|
16
|
+
# Generate an access token using an authorization code
|
17
|
+
#
|
18
|
+
# @param auth_code [String] Authorization code from TikTok
|
19
|
+
# @param redirect_uri [String] Redirect URI used in the authorization request
|
20
|
+
# @return [Hash] Access token response
|
21
|
+
def generate_access_token(auth_code, redirect_uri)
|
22
|
+
params = {
|
23
|
+
app_id: client.config.app_id,
|
24
|
+
secret: client.config.secret,
|
25
|
+
auth_code: auth_code,
|
26
|
+
grant_type: 'auth_code'
|
27
|
+
}
|
28
|
+
|
29
|
+
# Add redirect_uri if provided
|
30
|
+
params[:redirect_uri] = redirect_uri if redirect_uri
|
31
|
+
|
32
|
+
response = client.request(:post, 'v1.3/oauth2/access_token/', params)
|
33
|
+
|
34
|
+
# Store the access token in the client config
|
35
|
+
client.config.access_token = response['data']['access_token'] if response['data'] && response['data']['access_token']
|
36
|
+
|
37
|
+
response
|
38
|
+
end
|
39
|
+
|
40
|
+
# Refresh an access token (for TikTok account access tokens)
|
41
|
+
#
|
42
|
+
# @param refresh_token [String] Refresh token
|
43
|
+
# @return [Hash] New access token response
|
44
|
+
def refresh_access_token(refresh_token)
|
45
|
+
params = {
|
46
|
+
app_id: client.config.app_id,
|
47
|
+
secret: client.config.secret,
|
48
|
+
refresh_token: refresh_token,
|
49
|
+
grant_type: 'refresh_token'
|
50
|
+
}
|
51
|
+
|
52
|
+
response = client.request(:post, 'v1.3/tt_user/oauth2/refresh_token/', params)
|
53
|
+
|
54
|
+
# Update the access token in the client config
|
55
|
+
client.config.access_token = response['data']['access_token'] if response['data'] && response['data']['access_token']
|
56
|
+
|
57
|
+
response
|
58
|
+
end
|
59
|
+
|
60
|
+
# Revoke an access token
|
61
|
+
#
|
62
|
+
# @param token [String] Access token to revoke (defaults to the current access token)
|
63
|
+
# @return [Hash] Response
|
64
|
+
def revoke_access_token(token = nil)
|
65
|
+
token ||= client.config.access_token
|
66
|
+
|
67
|
+
params = {
|
68
|
+
app_id: client.config.app_id,
|
69
|
+
secret: client.config.secret,
|
70
|
+
access_token: token
|
71
|
+
}
|
72
|
+
|
73
|
+
response = client.request(:post, 'v1.3/oauth2/revoke_token/', params)
|
74
|
+
|
75
|
+
# Clear the access token if it was revoked
|
76
|
+
client.config.access_token = nil if response['code'] == 0
|
77
|
+
|
78
|
+
response
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get a list of authorized advertiser accounts
|
82
|
+
#
|
83
|
+
# @param app_id [String] App ID (defaults to the configured app_id)
|
84
|
+
# @param secret [String] App secret (defaults to the configured secret)
|
85
|
+
# @param access_token [String] Access token (defaults to the configured access_token)
|
86
|
+
# @return [Hash] List of authorized advertisers
|
87
|
+
def get_authorized_advertisers(app_id = nil, secret = nil, access_token = nil)
|
88
|
+
params = {}
|
89
|
+
|
90
|
+
# Use provided values or fallback to client config
|
91
|
+
headers = {}
|
92
|
+
headers['Access-Token'] = access_token || client.config.access_token if access_token || client.config.access_token
|
93
|
+
|
94
|
+
client.request(:get, 'v1.3/oauth2/advertiser/get/', params, headers)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Generate authorization URL for TikTok Business API
|
98
|
+
#
|
99
|
+
# @param redirect_uri [String] Redirect URI where the authorization code will be sent
|
100
|
+
# @param state [String] Optional state parameter for security
|
101
|
+
# @param scope [Array<String>] Optional array of permission scopes
|
102
|
+
# @return [String] Authorization URL
|
103
|
+
def authorization_url(redirect_uri, state = nil, scope = nil)
|
104
|
+
params = {
|
105
|
+
app_id: client.config.app_id,
|
106
|
+
redirect_uri: redirect_uri
|
107
|
+
}
|
108
|
+
|
109
|
+
params[:state] = state if state
|
110
|
+
params[:scope] = scope.join(',') if scope && !scope.empty?
|
111
|
+
|
112
|
+
query = params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
|
113
|
+
"https://ads.tiktok.com/marketing_api/auth?#{query}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
# Main client for interacting with the TikTok Business API
|
5
|
+
class Client
|
6
|
+
# @return [TiktokBusinessApi::Config] Client configuration
|
7
|
+
attr_reader :config
|
8
|
+
|
9
|
+
# @return [TiktokBusinessApi::Auth] Authentication handler
|
10
|
+
attr_reader :auth
|
11
|
+
|
12
|
+
# Initialize a new client
|
13
|
+
#
|
14
|
+
# @param options [Hash] Override configuration options
|
15
|
+
def initialize(options = {})
|
16
|
+
@config = TiktokBusinessApi.config.dup
|
17
|
+
|
18
|
+
# Override config with options
|
19
|
+
options.each do |key, value|
|
20
|
+
@config.send("#{key}=", value) if @config.respond_to?("#{key}=")
|
21
|
+
end
|
22
|
+
|
23
|
+
@auth = Auth.new(self)
|
24
|
+
@resources = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get or create a resource instance
|
28
|
+
#
|
29
|
+
# @param resource_name [Symbol] Name of the resource
|
30
|
+
# @return [BaseResource] Resource instance
|
31
|
+
def resource(resource_name)
|
32
|
+
@resources[resource_name] ||= begin
|
33
|
+
# Convert resource_name to class name (e.g., :campaign => Campaign)
|
34
|
+
class_name = resource_name.to_s.split('_').map(&:capitalize).join
|
35
|
+
|
36
|
+
# Get the resource class
|
37
|
+
resource_class = TiktokBusinessApi::Resources.const_get(class_name)
|
38
|
+
|
39
|
+
# Create an instance
|
40
|
+
resource_class.new(self)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Make an HTTP request to the TikTok Business API
|
45
|
+
#
|
46
|
+
# @param method [Symbol] HTTP method (:get, :post, :put, :delete)
|
47
|
+
# @param path [String] API endpoint path
|
48
|
+
# @param params [Hash] URL parameters for GET, or body parameters for POST/PUT
|
49
|
+
# @param headers [Hash] Additional HTTP headers
|
50
|
+
# @return [Hash] Parsed API response
|
51
|
+
def request(method, path, params = {}, headers = {})
|
52
|
+
url = File.join(@config.api_base_url, path)
|
53
|
+
|
54
|
+
# Set up default headers
|
55
|
+
default_headers = {
|
56
|
+
'Content-Type' => 'application/json'
|
57
|
+
}
|
58
|
+
|
59
|
+
# Add access token if available
|
60
|
+
if @config.access_token
|
61
|
+
default_headers['Access-Token'] = @config.access_token
|
62
|
+
end
|
63
|
+
|
64
|
+
# Merge with custom headers
|
65
|
+
headers = default_headers.merge(headers)
|
66
|
+
|
67
|
+
# Log the request
|
68
|
+
log_request(method, url, params, headers)
|
69
|
+
|
70
|
+
# Build the request
|
71
|
+
response = connection.run_request(method, url, nil, headers) do |req|
|
72
|
+
case method
|
73
|
+
when :get, :delete
|
74
|
+
req.params = params
|
75
|
+
when :post, :put
|
76
|
+
req.body = JSON.generate(params) unless params.empty?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Parse and handle the response
|
81
|
+
handle_response(response)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Access to campaign resource
|
85
|
+
#
|
86
|
+
# @return [TiktokBusinessApi::Resources::Campaign] Campaign resource
|
87
|
+
def campaigns
|
88
|
+
resource(:campaign)
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# Set up Faraday connection
|
94
|
+
#
|
95
|
+
# @return [Faraday::Connection] Faraday connection
|
96
|
+
def connection
|
97
|
+
@connection ||= Faraday.new do |conn|
|
98
|
+
conn.options.timeout = @config.timeout
|
99
|
+
conn.options.open_timeout = @config.open_timeout
|
100
|
+
|
101
|
+
# Set up middleware
|
102
|
+
conn.use Faraday::Response::Logger, @config.logger if @config.logger
|
103
|
+
conn.use Faraday::FollowRedirects::Middleware
|
104
|
+
conn.use Faraday::Retry::Middleware, max: 3
|
105
|
+
|
106
|
+
conn.adapter Faraday.default_adapter
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Parse and handle the API response
|
111
|
+
#
|
112
|
+
# @param response [Faraday::Response] HTTP response
|
113
|
+
# @return [Hash] Parsed response body
|
114
|
+
# @raise [TiktokBusinessApi::Error] If the response indicates an error
|
115
|
+
def handle_response(response)
|
116
|
+
# Log the response
|
117
|
+
log_response(response)
|
118
|
+
|
119
|
+
# Parse the response body
|
120
|
+
body = if response.body && !response.body.empty?
|
121
|
+
begin
|
122
|
+
JSON.parse(response.body)
|
123
|
+
rescue JSON::ParserError
|
124
|
+
{ error: "Invalid JSON response: #{response.body}" }
|
125
|
+
end
|
126
|
+
else
|
127
|
+
{}
|
128
|
+
end
|
129
|
+
|
130
|
+
# Check for API errors
|
131
|
+
if !response.success? || (body.is_a?(Hash) && body['code'] != 0)
|
132
|
+
raise ErrorFactory.from_response(response)
|
133
|
+
end
|
134
|
+
|
135
|
+
body
|
136
|
+
end
|
137
|
+
|
138
|
+
# Log the request details
|
139
|
+
#
|
140
|
+
# @param method [Symbol] HTTP method
|
141
|
+
# @param url [String] Request URL
|
142
|
+
# @param params [Hash] Request parameters
|
143
|
+
# @param headers [Hash] Request headers
|
144
|
+
def log_request(method, url, params, headers)
|
145
|
+
return unless @config.debug && @config.logger
|
146
|
+
|
147
|
+
@config.logger.debug "[TiktokBusinessApi] Request: #{method.upcase} #{url}"
|
148
|
+
@config.logger.debug "[TiktokBusinessApi] Parameters: #{params.inspect}"
|
149
|
+
@config.logger.debug "[TiktokBusinessApi] Headers: #{headers.inspect}"
|
150
|
+
end
|
151
|
+
|
152
|
+
# Log the response details
|
153
|
+
#
|
154
|
+
# @param response [Faraday::Response] HTTP response
|
155
|
+
def log_response(response)
|
156
|
+
return unless @config.debug && @config.logger
|
157
|
+
|
158
|
+
@config.logger.debug "[TiktokBusinessApi] Response Status: #{response.status}"
|
159
|
+
@config.logger.debug "[TiktokBusinessApi] Response Body: #{response.body}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
# Configuration class for TikTok Business API
|
5
|
+
class Config
|
6
|
+
# @return [String] TikTok API app ID
|
7
|
+
attr_accessor :app_id
|
8
|
+
|
9
|
+
# @return [String] TikTok API app secret
|
10
|
+
attr_accessor :secret
|
11
|
+
|
12
|
+
# @return [String] TikTok API access token
|
13
|
+
attr_accessor :access_token
|
14
|
+
|
15
|
+
# @return [String] Base URL for the TikTok API
|
16
|
+
attr_accessor :api_base_url
|
17
|
+
|
18
|
+
# @return [Boolean] Whether to enable debug logging
|
19
|
+
attr_accessor :debug
|
20
|
+
|
21
|
+
# @return [Object] Custom logger instance
|
22
|
+
attr_accessor :logger
|
23
|
+
|
24
|
+
# @return [Integer] Request timeout in seconds
|
25
|
+
attr_accessor :timeout
|
26
|
+
|
27
|
+
# @return [Integer] Open timeout in seconds
|
28
|
+
attr_accessor :open_timeout
|
29
|
+
|
30
|
+
# Initialize configuration with default values
|
31
|
+
def initialize
|
32
|
+
@api_base_url = 'https://business-api.tiktok.com/open_api/'
|
33
|
+
@debug = false
|
34
|
+
@timeout = 60
|
35
|
+
@open_timeout = 30
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
# Base error class for the TikTok Business API
|
5
|
+
class Error < StandardError
|
6
|
+
# @return [Integer] HTTP status code
|
7
|
+
attr_reader :status_code
|
8
|
+
|
9
|
+
# @return [Hash] Full response body
|
10
|
+
attr_reader :body
|
11
|
+
|
12
|
+
# @return [Hash] Request that caused the error
|
13
|
+
attr_reader :request
|
14
|
+
|
15
|
+
# Initialize a new error
|
16
|
+
#
|
17
|
+
# @param message [String] Error message
|
18
|
+
# @param status_code [Integer] HTTP status code
|
19
|
+
# @param body [Hash] Response body
|
20
|
+
# @param request [Hash] Request that caused the error
|
21
|
+
def initialize(message = nil, status_code = nil, body = nil, request = nil)
|
22
|
+
@status_code = status_code
|
23
|
+
@body = body
|
24
|
+
@request = request
|
25
|
+
super(message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Error raised when authentication fails
|
30
|
+
class AuthenticationError < Error; end
|
31
|
+
|
32
|
+
# Error raised when authorization fails
|
33
|
+
class AuthorizationError < Error; end
|
34
|
+
|
35
|
+
# Error raised for invalid requests
|
36
|
+
class InvalidRequestError < Error; end
|
37
|
+
|
38
|
+
# Error raised for API errors
|
39
|
+
class ApiError < Error; end
|
40
|
+
|
41
|
+
# Error raised for rate limit errors
|
42
|
+
class RateLimitError < Error; end
|
43
|
+
|
44
|
+
# Factory for creating the appropriate error object
|
45
|
+
class ErrorFactory
|
46
|
+
# Create an error object based on the response
|
47
|
+
#
|
48
|
+
# @param response [Faraday::Response] The HTTP response
|
49
|
+
# @param request [Hash] The request that caused the error
|
50
|
+
# @return [TiktokBusinessApi::Error] The appropriate error object
|
51
|
+
def self.from_response(response, request = nil)
|
52
|
+
status = response.status
|
53
|
+
body = response.body
|
54
|
+
|
55
|
+
# Parse TikTok API response which has its own error structure
|
56
|
+
error_code = body.is_a?(Hash) ? body['code'] : nil
|
57
|
+
error_message = body.is_a?(Hash) ? body['message'] : nil
|
58
|
+
|
59
|
+
# Determine the error class based on status and error code
|
60
|
+
klass = case status
|
61
|
+
when 401
|
62
|
+
AuthenticationError
|
63
|
+
when 403
|
64
|
+
AuthorizationError
|
65
|
+
when 429
|
66
|
+
RateLimitError
|
67
|
+
when 400..499
|
68
|
+
InvalidRequestError
|
69
|
+
when 500..599
|
70
|
+
ApiError
|
71
|
+
else
|
72
|
+
Error
|
73
|
+
end
|
74
|
+
|
75
|
+
# Create and return the error
|
76
|
+
klass.new(
|
77
|
+
error_message || "HTTP #{status}",
|
78
|
+
status,
|
79
|
+
body,
|
80
|
+
request
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
module Resources
|
5
|
+
# Base class for all API resources
|
6
|
+
class BaseResource
|
7
|
+
# @return [TiktokBusinessApi::Client] Client instance
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
# Initialize a new resource
|
11
|
+
#
|
12
|
+
# @param client [TiktokBusinessApi::Client] Client instance
|
13
|
+
def initialize(client)
|
14
|
+
@client = client
|
15
|
+
end
|
16
|
+
|
17
|
+
# Get the resource name (used for endpoint paths)
|
18
|
+
#
|
19
|
+
# @return [String] Resource name
|
20
|
+
def resource_name
|
21
|
+
self.class.name.split('::').last.downcase
|
22
|
+
end
|
23
|
+
|
24
|
+
# Get the API version
|
25
|
+
#
|
26
|
+
# @return [String] API version
|
27
|
+
def api_version
|
28
|
+
'v1.3'
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the base path for this resource
|
32
|
+
#
|
33
|
+
# @return [String] Base path
|
34
|
+
def base_path
|
35
|
+
"#{api_version}/#{resource_name}/"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Make a GET request to the resource
|
39
|
+
#
|
40
|
+
# @param path [String] Path relative to the resource base path
|
41
|
+
# @param params [Hash] Query parameters
|
42
|
+
# @param headers [Hash] Custom headers
|
43
|
+
# @return [Hash] Response data
|
44
|
+
def get(path, params = {}, headers = {})
|
45
|
+
full_path = File.join(base_path, path)
|
46
|
+
client.request(:get, full_path, params, headers)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Make a POST request to the resource
|
50
|
+
#
|
51
|
+
# @param path [String] Path relative to the resource base path
|
52
|
+
# @param params [Hash] Body parameters
|
53
|
+
# @param headers [Hash] Custom headers
|
54
|
+
# @return [Hash] Response data
|
55
|
+
def post(path, params = {}, headers = {})
|
56
|
+
full_path = File.join(base_path, path)
|
57
|
+
client.request(:post, full_path, params, headers)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Handle pagination for list endpoints
|
61
|
+
#
|
62
|
+
# @param path [String] Path relative to the resource base path
|
63
|
+
# @param params [Hash] Query parameters
|
64
|
+
# @param headers [Hash] Custom headers
|
65
|
+
# @param data_key [String] Key in the response that contains the data array
|
66
|
+
# @yield [item] Block to process each item
|
67
|
+
# @yieldparam item [Hash] Item from the response
|
68
|
+
# @return [Array] All items if no block is given
|
69
|
+
def paginate(path, params = {}, headers = {}, data_key = 'data')
|
70
|
+
items = []
|
71
|
+
page = 1
|
72
|
+
page_size = params[:page_size] || 10
|
73
|
+
has_more = true
|
74
|
+
|
75
|
+
while has_more
|
76
|
+
params[:page] = page
|
77
|
+
params[:page_size] = page_size
|
78
|
+
|
79
|
+
response = get(path, params, headers)
|
80
|
+
|
81
|
+
# Extract data from the response
|
82
|
+
current_items = response.dig('data', data_key) || []
|
83
|
+
|
84
|
+
if block_given?
|
85
|
+
current_items.each { |item| yield(item) }
|
86
|
+
else
|
87
|
+
items.concat(current_items)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Check if there are more pages
|
91
|
+
page_info = response.dig('data', 'page_info') || {}
|
92
|
+
has_more = page_info['has_more'] == true
|
93
|
+
page += 1
|
94
|
+
end
|
95
|
+
|
96
|
+
block_given? ? nil : items
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
module Resources
|
5
|
+
# Campaign resource for the TikTok Business API
|
6
|
+
class Campaign < BaseResource
|
7
|
+
# Create a new campaign
|
8
|
+
#
|
9
|
+
# @param advertiser_id [String] Advertiser ID
|
10
|
+
# @param params [Hash] Campaign parameters
|
11
|
+
# @return [Hash] New campaign data
|
12
|
+
def create(advertiser_id, params = {})
|
13
|
+
# Ensure advertiser_id is included in the params
|
14
|
+
params = params.merge(advertiser_id: advertiser_id)
|
15
|
+
|
16
|
+
response = post('create/', params)
|
17
|
+
response['data']
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get a list of campaigns
|
21
|
+
#
|
22
|
+
# @param advertiser_id [String] Advertiser ID
|
23
|
+
# @param params [Hash] Filter parameters
|
24
|
+
# @return [Hash] Campaign list response
|
25
|
+
def list(advertiser_id, params = {})
|
26
|
+
# Ensure advertiser_id is included in the params
|
27
|
+
params = params.merge(advertiser_id: advertiser_id)
|
28
|
+
|
29
|
+
response = get('get/', params)
|
30
|
+
response['data']
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get a list of campaigns with pagination support
|
34
|
+
#
|
35
|
+
# @param advertiser_id [String] Advertiser ID
|
36
|
+
# @param params [Hash] Filter parameters
|
37
|
+
# @yield [campaign] Block to process each campaign
|
38
|
+
# @yieldparam campaign [Hash] Campaign from the response
|
39
|
+
# @return [Array] All campaigns if no block is given
|
40
|
+
def list_all(advertiser_id, params = {}, &block)
|
41
|
+
# Ensure advertiser_id is included in the params
|
42
|
+
params = params.merge(advertiser_id: advertiser_id)
|
43
|
+
|
44
|
+
paginate('get/', params, {}, 'list')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get a campaign by ID
|
48
|
+
#
|
49
|
+
# @param advertiser_id [String] Advertiser ID
|
50
|
+
# @param campaign_id [String] Campaign ID
|
51
|
+
# @return [Hash] Campaign data
|
52
|
+
def get(advertiser_id, campaign_id)
|
53
|
+
params = {
|
54
|
+
advertiser_id: advertiser_id,
|
55
|
+
campaign_ids: [campaign_id]
|
56
|
+
}
|
57
|
+
|
58
|
+
response = get('get/', params)
|
59
|
+
campaigns = response.dig('data', 'list') || []
|
60
|
+
campaigns.first
|
61
|
+
end
|
62
|
+
|
63
|
+
# Update a campaign
|
64
|
+
#
|
65
|
+
# @param advertiser_id [String] Advertiser ID
|
66
|
+
# @param campaign_id [String] Campaign ID
|
67
|
+
# @param params [Hash] Campaign parameters to update
|
68
|
+
# @return [Hash] Updated campaign data
|
69
|
+
def update(advertiser_id, campaign_id, params = {})
|
70
|
+
# Ensure required parameters are included
|
71
|
+
params = params.merge(
|
72
|
+
advertiser_id: advertiser_id,
|
73
|
+
campaign_id: campaign_id
|
74
|
+
)
|
75
|
+
|
76
|
+
response = post('update/', params)
|
77
|
+
response['data']
|
78
|
+
end
|
79
|
+
|
80
|
+
# Update campaign status (enable/disable)
|
81
|
+
#
|
82
|
+
# @param advertiser_id [String] Advertiser ID
|
83
|
+
# @param campaign_id [String] Campaign ID
|
84
|
+
# @param status [String] New status ('ENABLE' or 'DISABLE')
|
85
|
+
# @return [Hash] Result
|
86
|
+
def update_status(advertiser_id, campaign_id, status)
|
87
|
+
params = {
|
88
|
+
advertiser_id: advertiser_id,
|
89
|
+
campaign_ids: [campaign_id],
|
90
|
+
operation_status: status
|
91
|
+
}
|
92
|
+
|
93
|
+
response = post('status/update/', params)
|
94
|
+
response['data']
|
95
|
+
end
|
96
|
+
|
97
|
+
# Delete a campaign
|
98
|
+
#
|
99
|
+
# @param advertiser_id [String] Advertiser ID
|
100
|
+
# @param campaign_id [String] Campaign ID
|
101
|
+
# @return [Hash] Result
|
102
|
+
def delete(advertiser_id, campaign_id)
|
103
|
+
params = {
|
104
|
+
advertiser_id: advertiser_id,
|
105
|
+
campaign_ids: [campaign_id]
|
106
|
+
}
|
107
|
+
|
108
|
+
response = post('delete/', params)
|
109
|
+
response['data']
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'faraday'
|
4
|
+
require 'faraday/retry'
|
5
|
+
require 'faraday/follow_redirects'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
require_relative 'tiktok_business_api/version'
|
9
|
+
require_relative 'tiktok_business_api/config'
|
10
|
+
require_relative 'tiktok_business_api/errors'
|
11
|
+
require_relative 'tiktok_business_api/client'
|
12
|
+
require_relative 'tiktok_business_api/auth'
|
13
|
+
|
14
|
+
# Resources
|
15
|
+
require_relative 'tiktok_business_api/resources/base_resource'
|
16
|
+
require_relative 'tiktok_business_api/resources/campaign'
|
17
|
+
|
18
|
+
module TiktokBusinessApi
|
19
|
+
class << self
|
20
|
+
attr_accessor :config
|
21
|
+
|
22
|
+
# Configure the TikTok Business API client
|
23
|
+
#
|
24
|
+
# @yield [config] Configuration object that can be modified
|
25
|
+
# @return [TiktokBusinessApi::Config] The configuration object
|
26
|
+
def configure
|
27
|
+
self.config ||= Config.new
|
28
|
+
yield(config) if block_given?
|
29
|
+
config
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a new client instance
|
33
|
+
#
|
34
|
+
# @param options [Hash] Optional configuration overrides
|
35
|
+
# @return [TiktokBusinessApi::Client] A new client instance
|
36
|
+
def client(options = {})
|
37
|
+
TiktokBusinessApi::Client.new(options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,183 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tiktok_business_api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Your Name
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-03-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday-retry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faraday-follow_redirects
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '13.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '13.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.10'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '3.10'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webmock
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.14'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.14'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.21'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.21'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.9'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.9'
|
139
|
+
description: A Ruby interface to the TikTok Business API with support for campaigns,
|
140
|
+
ad groups, ads, and more
|
141
|
+
email:
|
142
|
+
- your.email@example.com
|
143
|
+
executables: []
|
144
|
+
extensions: []
|
145
|
+
extra_rdoc_files: []
|
146
|
+
files:
|
147
|
+
- LICENSE.txt
|
148
|
+
- README.md
|
149
|
+
- bin/console
|
150
|
+
- bin/publish_gem
|
151
|
+
- lib/tiktok_ads_api.rb
|
152
|
+
- lib/tiktok_business_api.rb
|
153
|
+
- lib/tiktok_business_api/auth.rb
|
154
|
+
- lib/tiktok_business_api/client.rb
|
155
|
+
- lib/tiktok_business_api/config.rb
|
156
|
+
- lib/tiktok_business_api/errors.rb
|
157
|
+
- lib/tiktok_business_api/resources/base_resource.rb
|
158
|
+
- lib/tiktok_business_api/resources/campaign.rb
|
159
|
+
- lib/tiktok_business_api/version.rb
|
160
|
+
homepage: https://github.com/yourusername/tiktok_business_api
|
161
|
+
licenses:
|
162
|
+
- MIT
|
163
|
+
metadata: {}
|
164
|
+
post_install_message:
|
165
|
+
rdoc_options: []
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 2.6.0
|
173
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - ">="
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
requirements: []
|
179
|
+
rubygems_version: 3.4.19
|
180
|
+
signing_key:
|
181
|
+
specification_version: 4
|
182
|
+
summary: Ruby client for the TikTok Business API
|
183
|
+
test_files: []
|