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.
@@ -3,111 +3,17 @@
3
3
  module TiktokBusinessApi
4
4
  module Resources
5
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']
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
- # 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']
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TiktokBusinessApi
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -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