tiktok_business_api 0.1.0 → 0.2.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 +4 -4
- data/README.md +50 -0
- data/lib/tiktok_business_api/client.rb +84 -44
- data/lib/tiktok_business_api/errors.rb +10 -2
- data/lib/tiktok_business_api/resources/ad.rb +102 -0
- data/lib/tiktok_business_api/resources/adgroup.rb +92 -0
- data/lib/tiktok_business_api/resources/base_resource.rb +15 -15
- data/lib/tiktok_business_api/resources/campaign.rb +9 -103
- data/lib/tiktok_business_api/resources/crud_resource.rb +202 -0
- data/lib/tiktok_business_api/resources/identity.rb +122 -0
- data/lib/tiktok_business_api/resources/image.rb +151 -0
- data/lib/tiktok_business_api/utils.rb +58 -0
- data/lib/tiktok_business_api/version.rb +1 -1
- data/lib/tiktok_business_api.rb +7 -0
- metadata +42 -11
- data/bin/console +0 -11
- data/bin/publish_gem +0 -29
- data/lib/tiktok_ads_api.rb +0 -40
@@ -3,111 +3,17 @@
|
|
3
3
|
module TiktokBusinessApi
|
4
4
|
module Resources
|
5
5
|
# Campaign resource for the TikTok Business API
|
6
|
-
class Campaign <
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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']
|
6
|
+
class Campaign < CrudResource
|
7
|
+
RESOURCE_NAME = 'campaign'
|
8
|
+
|
9
|
+
def get(advertiser_id:, campaign_id:)
|
10
|
+
list(advertiser_id: advertiser_id, filtering: {campaign_ids: [campaign_id]}).first
|
95
11
|
end
|
96
|
-
|
97
|
-
|
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']
|
12
|
+
|
13
|
+
def list(advertiser_id:, filtering: {}, page_size: nil, page: nil, **other_params, &block)
|
14
|
+
super(filtering: filtering, page_size: page_size, page: page, **other_params.merge(advertiser_id: advertiser_id), &block)
|
110
15
|
end
|
16
|
+
|
111
17
|
end
|
112
18
|
end
|
113
19
|
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
module Resources
|
5
|
+
# Base CRUD class for all API resources
|
6
|
+
class CrudResource < BaseResource
|
7
|
+
# Default path for create operations
|
8
|
+
def create_path
|
9
|
+
'create/'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Default path for read/list operations
|
13
|
+
def list_path
|
14
|
+
'get/'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Default path for update operations
|
18
|
+
def update_path
|
19
|
+
'update/'
|
20
|
+
end
|
21
|
+
|
22
|
+
# Default path for delete operations
|
23
|
+
def delete_path
|
24
|
+
'delete/'
|
25
|
+
end
|
26
|
+
|
27
|
+
# Default path for status updates
|
28
|
+
def status_update_path
|
29
|
+
'status/update/'
|
30
|
+
end
|
31
|
+
|
32
|
+
def resource_name
|
33
|
+
self::class::RESOURCE_NAME
|
34
|
+
end
|
35
|
+
|
36
|
+
# Default ID parameter name
|
37
|
+
def id_param_name
|
38
|
+
"#{resource_name}_id"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Default IDs parameter name for bulk operations
|
42
|
+
def ids_param_name
|
43
|
+
"#{resource_name}_ids"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Create a new resource
|
47
|
+
#
|
48
|
+
# @param owner_id [String] ID of the resource owner (e.g., advertiser_id)
|
49
|
+
# @param params [Hash] Resource parameters
|
50
|
+
# @param owner_param_name [String] Parameter name for the owner ID
|
51
|
+
# @return [Hash] New resource data
|
52
|
+
def create(owner_id, params = {}, owner_param_name = 'advertiser_id')
|
53
|
+
params = params.merge(owner_param_name => owner_id)
|
54
|
+
response = _http_post(create_path, params)
|
55
|
+
response['data']
|
56
|
+
end
|
57
|
+
|
58
|
+
def list(filtering: {}, page_size: nil, page: nil, **other_params)
|
59
|
+
# Build request paramss
|
60
|
+
request_params = other_params.merge(filtering: filtering.to_json)
|
61
|
+
|
62
|
+
page_size ||= 100
|
63
|
+
page ||= 1
|
64
|
+
request_params[:page_size] = [page_size, 100].min # Most APIs limit page size
|
65
|
+
request_params[:page] = page
|
66
|
+
|
67
|
+
# Make the request
|
68
|
+
response = _http_get(list_path, request_params)
|
69
|
+
|
70
|
+
# If a block is given, yield each item
|
71
|
+
if block_given?
|
72
|
+
items = response.dig('data', 'list') || []
|
73
|
+
items.each { |item| yield(item) }
|
74
|
+
|
75
|
+
# Return the response for method chaining
|
76
|
+
response
|
77
|
+
else
|
78
|
+
# Just return the data otherwise
|
79
|
+
response['data']['list']
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# List all resources with automatic pagination
|
84
|
+
#
|
85
|
+
# @param owner_id [String] ID of the resource owner (e.g., advertiser_id)
|
86
|
+
# @param params [Hash] Filter parameters
|
87
|
+
# @param owner_param_name [String] Parameter name for the owner ID
|
88
|
+
# @param list_key [String] Key in the response that contains the data array
|
89
|
+
# @yield [resource] Block to process each resource
|
90
|
+
# @yieldparam resource [Hash] Resource from the response
|
91
|
+
# @return [Array] All resources if no block is given
|
92
|
+
def list_all(owner_id, params = {}, owner_param_name = 'advertiser_id', list_key = 'list')
|
93
|
+
items = []
|
94
|
+
page = 1
|
95
|
+
page_size = params[:page_size] || 10
|
96
|
+
has_more = true
|
97
|
+
|
98
|
+
# Ensure owner_id is included in the params
|
99
|
+
request_params = params.merge(owner_param_name => owner_id)
|
100
|
+
|
101
|
+
while has_more
|
102
|
+
request_params[:page] = page
|
103
|
+
request_params[:page_size] = page_size
|
104
|
+
|
105
|
+
response = _http_get(list_path, request_params)
|
106
|
+
|
107
|
+
# Extract data from the response
|
108
|
+
current_items = response.dig('data', list_key) || []
|
109
|
+
|
110
|
+
if block_given?
|
111
|
+
current_items.each { |item| yield(item) }
|
112
|
+
else
|
113
|
+
items.concat(current_items)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Check if there are more pages
|
117
|
+
page_info = response.dig('data', 'page_info') || {}
|
118
|
+
total_number = page_info['total_number'] || 0
|
119
|
+
total_fetched = page * page_size
|
120
|
+
|
121
|
+
has_more = page_info['has_more'] == true ||
|
122
|
+
(total_number > 0 && total_fetched < total_number)
|
123
|
+
page += 1
|
124
|
+
|
125
|
+
# Break if we've reached the end or there's an empty result
|
126
|
+
break if current_items.empty?
|
127
|
+
end
|
128
|
+
|
129
|
+
block_given? ? nil : items
|
130
|
+
end
|
131
|
+
|
132
|
+
# Get a resource by ID
|
133
|
+
#
|
134
|
+
# @param owner_id [String] ID of the resource owner (e.g., advertiser_id)
|
135
|
+
# @param resource_id [String] Resource ID
|
136
|
+
# @param owner_param_name [String] Parameter name for the owner ID
|
137
|
+
# @return [Hash] Resource data
|
138
|
+
def get(owner_id, resource_id, owner_param_name = 'advertiser_id')
|
139
|
+
params = {
|
140
|
+
owner_param_name => owner_id,
|
141
|
+
ids_param_name => [resource_id]
|
142
|
+
}
|
143
|
+
|
144
|
+
response = _http_get(list_path, params)
|
145
|
+
items = response.dig('data', 'list') || []
|
146
|
+
items.first
|
147
|
+
end
|
148
|
+
|
149
|
+
# Update a resource
|
150
|
+
#
|
151
|
+
# @param owner_id [String] ID of the resource owner (e.g., advertiser_id)
|
152
|
+
# @param resource_id [String] Resource ID
|
153
|
+
# @param params [Hash] Resource parameters to update
|
154
|
+
# @param owner_param_name [String] Parameter name for the owner ID
|
155
|
+
# @return [Hash] Updated resource data
|
156
|
+
def update(owner_id, resource_id, params = {}, owner_param_name = 'advertiser_id')
|
157
|
+
# Ensure required parameters are included
|
158
|
+
params = params.merge(
|
159
|
+
owner_param_name => owner_id,
|
160
|
+
id_param_name => resource_id
|
161
|
+
)
|
162
|
+
|
163
|
+
response = _http_post(update_path, params)
|
164
|
+
response['data']
|
165
|
+
end
|
166
|
+
|
167
|
+
# Update resource status
|
168
|
+
#
|
169
|
+
# @param owner_id [String] ID of the resource owner (e.g., advertiser_id)
|
170
|
+
# @param resource_id [String] Resource ID
|
171
|
+
# @param status [String] New status
|
172
|
+
# @param owner_param_name [String] Parameter name for the owner ID
|
173
|
+
# @return [Hash] Result
|
174
|
+
def update_status(owner_id, resource_id, status, owner_param_name = 'advertiser_id')
|
175
|
+
params = {
|
176
|
+
owner_param_name => owner_id,
|
177
|
+
ids_param_name => [resource_id],
|
178
|
+
'operation_status' => status
|
179
|
+
}
|
180
|
+
|
181
|
+
response = _http_post(status_update_path, params)
|
182
|
+
response['data']
|
183
|
+
end
|
184
|
+
|
185
|
+
# Delete a resource
|
186
|
+
#
|
187
|
+
# @param owner_id [String] ID of the resource owner (e.g., advertiser_id)
|
188
|
+
# @param resource_id [String] Resource ID
|
189
|
+
# @param owner_param_name [String] Parameter name for the owner ID
|
190
|
+
# @return [Hash] Result
|
191
|
+
def delete(owner_id, resource_id, owner_param_name = 'advertiser_id')
|
192
|
+
params = {
|
193
|
+
owner_param_name => owner_id,
|
194
|
+
ids_param_name => [resource_id]
|
195
|
+
}
|
196
|
+
|
197
|
+
response = _http_post(delete_path, params)
|
198
|
+
response['data']
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
module Resources
|
5
|
+
# Identity resource for the TikTok Business API
|
6
|
+
class Identity < BaseResource
|
7
|
+
RESOURCE_NAME = 'identity'
|
8
|
+
|
9
|
+
# Get a list of identities
|
10
|
+
#
|
11
|
+
# @param advertiser_id [String] Advertiser ID
|
12
|
+
# @param options [Hash] Additional options for the request
|
13
|
+
# @option options [String] :identity_type Identity type. Enum values: CUSTOMIZED_USER, AUTH_CODE, TT_USER, BC_AUTH_TT
|
14
|
+
# @option options [String] :identity_authorized_bc_id ID of the Business Center (required when identity_type is BC_AUTH_TT)
|
15
|
+
# @option options [Hash] :filtering Filtering conditions (valid only for CUSTOMIZED_USER or when identity_type not specified)
|
16
|
+
# @option options [Integer] :page Page number
|
17
|
+
# @option options [Integer] :page_size Number of results per page
|
18
|
+
# @return [Hash] Response with list of identities and pagination info
|
19
|
+
def list(advertiser_id:, **options)
|
20
|
+
params = {
|
21
|
+
advertiser_id: advertiser_id
|
22
|
+
}
|
23
|
+
|
24
|
+
# Add optional parameters if provided
|
25
|
+
params[:identity_type] = options[:identity_type] if options[:identity_type]
|
26
|
+
params[:identity_authorized_bc_id] = options[:identity_authorized_bc_id] if options[:identity_authorized_bc_id]
|
27
|
+
params[:filtering] = options[:filtering].to_json if options[:filtering]
|
28
|
+
params[:page] = options[:page] if options[:page]
|
29
|
+
params[:page_size] = options[:page_size] if options[:page_size]
|
30
|
+
|
31
|
+
response = client.request(:get, "#{base_path}get/", params)
|
32
|
+
|
33
|
+
if block_given? && response['data']['identity_list']
|
34
|
+
response['data']['identity_list'].each { |identity| yield(identity) }
|
35
|
+
response['data']
|
36
|
+
else
|
37
|
+
response['data']
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Get information about a specific identity
|
42
|
+
#
|
43
|
+
# @param advertiser_id [String] Advertiser ID
|
44
|
+
# @param identity_id [String] Identity ID
|
45
|
+
# @param identity_type [String] Identity type. Enum values: CUSTOMIZED_USER, AUTH_CODE, TT_USER, BC_AUTH_TT
|
46
|
+
# @param identity_authorized_bc_id [String] ID of the Business Center (required when identity_type is BC_AUTH_TT)
|
47
|
+
# @return [Hash] Identity information
|
48
|
+
def get_info(advertiser_id:, identity_id:, identity_type:, identity_authorized_bc_id: nil)
|
49
|
+
params = {
|
50
|
+
advertiser_id: advertiser_id,
|
51
|
+
identity_id: identity_id,
|
52
|
+
identity_type: identity_type
|
53
|
+
}
|
54
|
+
|
55
|
+
# Add Business Center ID if provided (required for BC_AUTH_TT)
|
56
|
+
params[:identity_authorized_bc_id] = identity_authorized_bc_id if identity_authorized_bc_id
|
57
|
+
|
58
|
+
response = client.request(:get, "#{base_path}info/", params)
|
59
|
+
response['data']['identity_info']
|
60
|
+
end
|
61
|
+
|
62
|
+
# Create a new Custom User identity
|
63
|
+
#
|
64
|
+
# @param advertiser_id [String] Advertiser ID
|
65
|
+
# @param display_name [String] Display name (max 100 characters)
|
66
|
+
# @param image_uri [String] The ID of the avatar image (optional)
|
67
|
+
# @return [Hash] Response containing the new identity ID
|
68
|
+
def create(advertiser_id:, display_name:, image_uri: nil)
|
69
|
+
params = {
|
70
|
+
advertiser_id: advertiser_id,
|
71
|
+
display_name: display_name
|
72
|
+
}
|
73
|
+
|
74
|
+
# Add image URI if provided
|
75
|
+
params[:image_uri] = image_uri if image_uri
|
76
|
+
|
77
|
+
response = client.request(:post, "#{base_path}create/", params)
|
78
|
+
response['data']
|
79
|
+
end
|
80
|
+
|
81
|
+
# List all identities with automatic pagination
|
82
|
+
#
|
83
|
+
# @param advertiser_id [String] Advertiser ID
|
84
|
+
# @param options [Hash] Additional options for the request
|
85
|
+
# @yield [identity] Block to process each identity
|
86
|
+
# @yieldparam identity [Hash] Identity from the response
|
87
|
+
# @return [Array, nil] All identities if no block is given, nil otherwise
|
88
|
+
def list_all(advertiser_id:, **options, &block)
|
89
|
+
page = options[:page] || 1
|
90
|
+
page_size = options[:page_size] || 100
|
91
|
+
all_identities = []
|
92
|
+
has_more = true
|
93
|
+
|
94
|
+
while has_more
|
95
|
+
current_options = options.merge(page: page, page_size: page_size)
|
96
|
+
response = list(advertiser_id: advertiser_id, **current_options)
|
97
|
+
|
98
|
+
identities = response['identity_list'] || []
|
99
|
+
|
100
|
+
if block_given?
|
101
|
+
identities.each { |identity| yield(identity) }
|
102
|
+
else
|
103
|
+
all_identities.concat(identities)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Check pagination info
|
107
|
+
page_info = response['page_info'] || {}
|
108
|
+
current_page = page_info['page'].to_i
|
109
|
+
total_pages = page_info['total_page'].to_i
|
110
|
+
|
111
|
+
has_more = current_page < total_pages
|
112
|
+
page += 1
|
113
|
+
|
114
|
+
# Break if empty result or no more pages
|
115
|
+
break if identities.empty? || !has_more
|
116
|
+
end
|
117
|
+
|
118
|
+
block_given? ? nil : all_identities
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TiktokBusinessApi
|
4
|
+
module Resources
|
5
|
+
# Image resource for the TikTok Business API
|
6
|
+
class Image < BaseResource
|
7
|
+
# Get the resource name (used for endpoint paths)
|
8
|
+
#
|
9
|
+
# @return [String] Resource name
|
10
|
+
def resource_name
|
11
|
+
'file/image/ad'
|
12
|
+
end
|
13
|
+
|
14
|
+
# Upload an image
|
15
|
+
#
|
16
|
+
# @param advertiser_id [String] Advertiser ID
|
17
|
+
# @param options [Hash] Upload options
|
18
|
+
# @option options [String] :upload_type Upload type ('UPLOAD_BY_FILE', 'UPLOAD_BY_URL', 'UPLOAD_BY_FILE_ID')
|
19
|
+
# @option options [String] :file_name Image file name
|
20
|
+
# @option options [File] :image_file Image file (required when upload_type is 'UPLOAD_BY_FILE')
|
21
|
+
# @option options [String] :image_signature MD5 of image file (optional when upload_type is 'UPLOAD_BY_FILE', will be calculated if not provided)
|
22
|
+
# @option options [String] :image_url Image URL (required when upload_type is 'UPLOAD_BY_URL')
|
23
|
+
# @option options [String] :file_id File ID (required when upload_type is 'UPLOAD_BY_FILE_ID')
|
24
|
+
# @return [Hash] Upload result with image ID
|
25
|
+
def upload(advertiser_id:, **options)
|
26
|
+
upload_type = options[:upload_type] || 'UPLOAD_BY_FILE'
|
27
|
+
|
28
|
+
params = {
|
29
|
+
advertiser_id: advertiser_id,
|
30
|
+
upload_type: upload_type
|
31
|
+
}
|
32
|
+
|
33
|
+
# Add file_name if provided
|
34
|
+
params[:file_name] = options[:file_name] if options[:file_name]
|
35
|
+
|
36
|
+
case upload_type
|
37
|
+
when 'UPLOAD_BY_FILE'
|
38
|
+
raise ArgumentError, 'image_file is required for UPLOAD_BY_FILE' unless options[:image_file]
|
39
|
+
|
40
|
+
# Auto-calculate image signature if not provided
|
41
|
+
if !options[:image_signature]
|
42
|
+
options[:image_signature] = TiktokBusinessApi::Utils.calculate_md5(options[:image_file])
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create a FilePart for multipart file upload
|
46
|
+
params[:image_file] = Faraday::Multipart::FilePart.new(
|
47
|
+
options[:image_file],
|
48
|
+
TiktokBusinessApi::Utils.detect_content_type(options[:image_file])
|
49
|
+
)
|
50
|
+
params[:image_signature] = options[:image_signature]
|
51
|
+
|
52
|
+
# For file uploads, we need to use multipart/form-data
|
53
|
+
headers = { 'Content-Type' => 'multipart/form-data' }
|
54
|
+
response = client.request(:post, "#{base_path}/upload/", params, headers)
|
55
|
+
when 'UPLOAD_BY_URL'
|
56
|
+
raise ArgumentError, 'image_url is required for UPLOAD_BY_URL' unless options[:image_url]
|
57
|
+
|
58
|
+
params[:image_url] = options[:image_url]
|
59
|
+
response = client.request(:post, "#{base_path}/upload/", params)
|
60
|
+
when 'UPLOAD_BY_FILE_ID'
|
61
|
+
raise ArgumentError, 'file_id is required for UPLOAD_BY_FILE_ID' unless options[:file_id]
|
62
|
+
|
63
|
+
params[:file_id] = options[:file_id]
|
64
|
+
response = client.request(:post, "#{base_path}/upload/", params)
|
65
|
+
else
|
66
|
+
raise ArgumentError, "Invalid upload_type: #{upload_type}"
|
67
|
+
end
|
68
|
+
|
69
|
+
response['data']
|
70
|
+
end
|
71
|
+
|
72
|
+
# Get image info by image ID
|
73
|
+
#
|
74
|
+
# @param advertiser_id [String] Advertiser ID
|
75
|
+
# @param image_id [String] Image ID
|
76
|
+
# @return [Hash] Image info
|
77
|
+
def get_info(advertiser_id, image_id)
|
78
|
+
params = {
|
79
|
+
advertiser_id: advertiser_id,
|
80
|
+
image_ids: [image_id]
|
81
|
+
}
|
82
|
+
|
83
|
+
response = client.request(:get, "#{base_path}/info/", params)
|
84
|
+
images = response.dig('data', 'list') || []
|
85
|
+
images.first
|
86
|
+
end
|
87
|
+
|
88
|
+
# Search for images
|
89
|
+
#
|
90
|
+
# @param advertiser_id [String] Advertiser ID
|
91
|
+
# @param options [Hash] Search options
|
92
|
+
# @option options [Integer] :page Current page number (default: 1)
|
93
|
+
# @option options [Integer] :page_size Page size (default: 20)
|
94
|
+
# @option options [String] :image_ids Image IDs, comma-separated
|
95
|
+
# @option options [String] :material_ids Material IDs, comma-separated
|
96
|
+
# @option options [Integer] :width Image width
|
97
|
+
# @option options [Integer] :height Image height
|
98
|
+
# @option options [String] :signature Image MD5 hash
|
99
|
+
# @option options [Integer] :start_time Start time, in seconds
|
100
|
+
# @option options [Integer] :end_time End time, in seconds
|
101
|
+
# @option options [Boolean] :displayable Whether image can be displayed
|
102
|
+
# @yield [image] Block to process each image
|
103
|
+
# @yieldparam image [Hash] Image data from the response
|
104
|
+
# @return [Hash, Array] Image list response or processed images
|
105
|
+
def search(advertiser_id:, **options)
|
106
|
+
# Set up default values
|
107
|
+
page = options[:page] || 1
|
108
|
+
page_size = options[:page_size] || 20
|
109
|
+
|
110
|
+
params = {
|
111
|
+
advertiser_id: advertiser_id,
|
112
|
+
page: page,
|
113
|
+
page_size: page_size
|
114
|
+
}
|
115
|
+
|
116
|
+
# Add optional parameters if provided
|
117
|
+
search_fields = [:image_ids, :material_ids, :width, :height, :signature,
|
118
|
+
:start_time, :end_time, :displayable]
|
119
|
+
|
120
|
+
search_fields.each do |field|
|
121
|
+
params[field] = options[field] if options.key?(field)
|
122
|
+
end
|
123
|
+
|
124
|
+
response = client.request(:get, "#{base_path}/search/", params)
|
125
|
+
image_list = response.dig('data', 'list') || []
|
126
|
+
|
127
|
+
if block_given?
|
128
|
+
image_list.each { |image| yield(image) }
|
129
|
+
response['data']
|
130
|
+
else
|
131
|
+
image_list
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Check if an image file name is already in use
|
136
|
+
#
|
137
|
+
# @param advertiser_id [String] Advertiser ID
|
138
|
+
# @param file_names [Array<String>] List of file names to check
|
139
|
+
# @return [Hash] Result with available and unavailable file names
|
140
|
+
def check_name(advertiser_id, file_names)
|
141
|
+
params = {
|
142
|
+
advertiser_id: advertiser_id,
|
143
|
+
file_names: file_names
|
144
|
+
}
|
145
|
+
|
146
|
+
response = client.request(:post, 'file/name/check/', params)
|
147
|
+
response['data']
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
module TiktokBusinessApi
|
5
|
+
# Utility methods for TikTok Business API
|
6
|
+
module Utils
|
7
|
+
# Calculate MD5 hash of the file
|
8
|
+
#
|
9
|
+
# @param file [File, String] File object or file path
|
10
|
+
# @return [String] MD5 hash
|
11
|
+
def self.calculate_md5(file)
|
12
|
+
if file.is_a?(String) && File.exist?(file)
|
13
|
+
# File path was provided
|
14
|
+
Digest::MD5.file(file).hexdigest
|
15
|
+
elsif file.respond_to?(:path) && File.exist?(file.path)
|
16
|
+
# File object with path
|
17
|
+
Digest::MD5.file(file.path).hexdigest
|
18
|
+
elsif file.respond_to?(:read)
|
19
|
+
# IO-like object
|
20
|
+
content = file.read
|
21
|
+
file.rewind if file.respond_to?(:rewind) # Reset the file pointer
|
22
|
+
Digest::MD5.hexdigest(content)
|
23
|
+
else
|
24
|
+
raise ArgumentError, "Unable to calculate MD5: invalid file object"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Detect content type based on file extension
|
29
|
+
#
|
30
|
+
# @param file [File, String] File object or file path
|
31
|
+
# @return [String] MIME type
|
32
|
+
def self.detect_content_type(file)
|
33
|
+
file_path = if file.is_a?(String)
|
34
|
+
file
|
35
|
+
elsif file.respond_to?(:path)
|
36
|
+
file.path
|
37
|
+
else
|
38
|
+
return "application/octet-stream" # Default if we can't determine
|
39
|
+
end
|
40
|
+
|
41
|
+
# Simple extension to MIME type mapping for common image formats
|
42
|
+
case File.extname(file_path).downcase
|
43
|
+
when '.jpg', '.jpeg'
|
44
|
+
'image/jpeg'
|
45
|
+
when '.png'
|
46
|
+
'image/png'
|
47
|
+
when '.gif'
|
48
|
+
'image/gif'
|
49
|
+
when '.bmp'
|
50
|
+
'image/bmp'
|
51
|
+
when '.webp'
|
52
|
+
'image/webp'
|
53
|
+
else
|
54
|
+
'application/octet-stream'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/tiktok_business_api.rb
CHANGED
@@ -3,17 +3,24 @@
|
|
3
3
|
require 'faraday'
|
4
4
|
require 'faraday/retry'
|
5
5
|
require 'faraday/follow_redirects'
|
6
|
+
require 'faraday/multipart'
|
6
7
|
require 'json'
|
7
8
|
|
8
9
|
require_relative 'tiktok_business_api/version'
|
9
10
|
require_relative 'tiktok_business_api/config'
|
10
11
|
require_relative 'tiktok_business_api/errors'
|
12
|
+
require_relative 'tiktok_business_api/utils'
|
11
13
|
require_relative 'tiktok_business_api/client'
|
12
14
|
require_relative 'tiktok_business_api/auth'
|
13
15
|
|
14
16
|
# Resources
|
15
17
|
require_relative 'tiktok_business_api/resources/base_resource'
|
18
|
+
require_relative 'tiktok_business_api/resources/crud_resource'
|
16
19
|
require_relative 'tiktok_business_api/resources/campaign'
|
20
|
+
require_relative 'tiktok_business_api/resources/adgroup'
|
21
|
+
require_relative 'tiktok_business_api/resources/ad'
|
22
|
+
require_relative 'tiktok_business_api/resources/image'
|
23
|
+
require_relative 'tiktok_business_api/resources/identity'
|
17
24
|
|
18
25
|
module TiktokBusinessApi
|
19
26
|
class << self
|