tiktok-open-sdk 0.3.0 → 0.5.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/.rubocop.yml +4 -1
- data/CHANGELOG.md +13 -1
- data/README.md +189 -16
- data/lib/tiktok/open/omniauth/strategies/tiktok_open_sdk.rb +177 -0
- data/lib/tiktok/open/sdk/config.rb +17 -2
- data/lib/tiktok/open/sdk/helpers/validators/post_info_validator.rb +178 -0
- data/lib/tiktok/open/sdk/helpers/validators/post_publish_validator.rb +57 -0
- data/lib/tiktok/open/sdk/helpers/validators/source_info_validator.rb +186 -0
- data/lib/tiktok/open/sdk/http_client.rb +5 -4
- data/lib/tiktok/open/sdk/open_api/auth/user.rb +1 -1
- data/lib/tiktok/open/sdk/open_api/post/publish.rb +54 -0
- data/lib/tiktok/open/sdk/version.rb +1 -1
- data/lib/tiktok/open/sdk.rb +68 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ea350ded46bc468a3b86a72f539c12a36ecc960b1e2476ac7e59c2c52424d8f1
|
|
4
|
+
data.tar.gz: 07fa578800d4d03d9431c39b043008d2185ed62ed2f93c8c20feea8bb331736d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61e3742ea46ddeb02a8a168d3485c7ecc7632698c6c533364e392b67058f6a5d7e325e0e546eebe9eee915070e309981dc8707aca7ff67097dfadd74610977d0
|
|
7
|
+
data.tar.gz: f19aa49f93036f0b02f0d5613d76b7bd86e7a00457a3642569c3f266ac895720a4106e7df27fec6f215581ccfd6fcedb71ac68638c83fdddcfc0dca8b56d5052
|
data/.rubocop.yml
CHANGED
|
@@ -7,6 +7,9 @@ AllCops:
|
|
|
7
7
|
NewCops: enable
|
|
8
8
|
SuggestExtensions: false
|
|
9
9
|
|
|
10
|
+
Naming/VariableNumber:
|
|
11
|
+
EnforcedStyle: snake_case
|
|
12
|
+
|
|
10
13
|
Metrics/MethodLength:
|
|
11
14
|
Max: 15
|
|
12
15
|
|
|
@@ -27,7 +30,7 @@ Style/ModuleFunction:
|
|
|
27
30
|
Enabled: false
|
|
28
31
|
|
|
29
32
|
RSpec/NestedGroups:
|
|
30
|
-
Max:
|
|
33
|
+
Max: 5
|
|
31
34
|
|
|
32
35
|
RSpec/MultipleExpectations:
|
|
33
36
|
Max: 5
|
data/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,19 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [
|
|
7
|
+
## [0.5.0] - 2026-02-14
|
|
8
|
+
### Added
|
|
9
|
+
- **Post API: video_init** - Initialize a video publish to obtain an upload URL (file upload) or to start a pull-from-URL flow. Accepts `access_token` and `params` (post_info, source_info). Optional config: `video_init_url`.
|
|
10
|
+
|
|
11
|
+
## [0.4.0] - 2025-10-06
|
|
12
|
+
### Added
|
|
13
|
+
- **OmniAuth Strategy** - Ready-to-use OmniAuth strategy for TikTok Open Platform integration in Rails applications
|
|
14
|
+
- Supports multiple TikTok OAuth scopes (user.info.basic, user.info.profile, user.info.stats)
|
|
15
|
+
- Automatic token handling and user info retrieval
|
|
16
|
+
- Rails/Devise integration examples and callbacks
|
|
17
|
+
- **Post API** - New module for interacting with TikTok's Post API endpoints
|
|
18
|
+
- Creator info query functionality for video publishing workflows
|
|
19
|
+
- Support for querying creator settings and capabilities
|
|
8
20
|
|
|
9
21
|
## [0.3.0] - 2025-09-20
|
|
10
22
|
### Added
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# TikTok Open SDK
|
|
2
2
|
|
|
3
|
-
[](https://rubygems.org/gems/tiktok-open-sdk)
|
|
4
4
|
[](https://www.ruby-lang.org/en/downloads/)
|
|
5
5
|
[](LICENSE.txt)
|
|
6
6
|
[](https://github.com/pochkuntaras/tiktok-open-sdk/actions/workflows/main.yml)
|
|
@@ -10,9 +10,11 @@ A comprehensive Ruby SDK for integrating with TikTok Open API. This gem provides
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
12
|
- **OAuth 2.0 Authentication** – Seamless OAuth flow for secure integration
|
|
13
|
+
- **OmniAuth Strategy** – Ready-to-use OmniAuth strategy for Rails applications
|
|
13
14
|
- **Client Authentication** – Server-to-server authentication with client credentials
|
|
14
15
|
- **Token Management** – Easy access token exchange and refresh
|
|
15
16
|
- **User API** – Convenient methods to access user information
|
|
17
|
+
- **Post API** – Methods for querying creator information and video publishing
|
|
16
18
|
- **HTTP Client** – Built-in client for interacting with TikTok APIs
|
|
17
19
|
|
|
18
20
|
## Installation
|
|
@@ -56,6 +58,11 @@ Tiktok::Open::Sdk.configure do |config|
|
|
|
56
58
|
config.user_auth.token_url = 'https://open.tiktokapis.com/v2/oauth/token/'
|
|
57
59
|
config.user_auth.revoke_token_url = 'https://open.tiktokapis.com/v2/oauth/revoke/'
|
|
58
60
|
config.user_info_url = 'https://open.tiktokapis.com/v2/user/info/'
|
|
61
|
+
config.creator_info_query_url = 'https://open.tiktokapis.com/v2/post/publish/creator_info/query/'
|
|
62
|
+
config.video_init_url = 'https://open.tiktokapis.com/v2/post/publish/video/init/'
|
|
63
|
+
|
|
64
|
+
# Optional: Enable OmniAuth strategy auto-loading
|
|
65
|
+
config.load_omniauth = true
|
|
59
66
|
end
|
|
60
67
|
```
|
|
61
68
|
|
|
@@ -158,13 +165,71 @@ end
|
|
|
158
165
|
|
|
159
166
|
**Note:** Client tokens are used for server-to-server authentication and have different scopes and permissions than user tokens.
|
|
160
167
|
|
|
168
|
+
### Using the Post API
|
|
169
|
+
|
|
170
|
+
The SDK provides convenient methods for interacting with TikTok's Post API:
|
|
171
|
+
|
|
172
|
+
#### Creator Info Query
|
|
173
|
+
|
|
174
|
+
Query creator information for video publishing:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
# Get creator information
|
|
178
|
+
response = Tiktok::Open::Sdk.post.creator_info_query(access_token: access_token)
|
|
179
|
+
|
|
180
|
+
if response[:success]
|
|
181
|
+
creator_data = response[:response][:data]
|
|
182
|
+
|
|
183
|
+
puts "Creator Avatar: #{creator_data[:creator_avatar_url]}"
|
|
184
|
+
puts "Creator Nickname: #{creator_data[:creator_nickname]}"
|
|
185
|
+
puts "Max Video Duration: #{creator_data[:max_video_post_duration_sec]} seconds"
|
|
186
|
+
puts "Privacy Options: #{creator_data[:privacy_level_options]}"
|
|
187
|
+
else
|
|
188
|
+
puts "Error: #{response[:response][:error][:message]}"
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Video Init
|
|
193
|
+
|
|
194
|
+
Initialize a video publish to obtain an upload URL (for file upload) or to start a pull-from-URL flow:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
params = {
|
|
198
|
+
post_info: {
|
|
199
|
+
title: 'My video #tiktok',
|
|
200
|
+
privacy_level: 'SELF_ONLY',
|
|
201
|
+
disable_duet: false,
|
|
202
|
+
disable_comment: false,
|
|
203
|
+
disable_stitch: false,
|
|
204
|
+
video_cover_timestamp_ms: 1000
|
|
205
|
+
},
|
|
206
|
+
source_info: {
|
|
207
|
+
source: 'FILE_UPLOAD',
|
|
208
|
+
video_type: 'video/mp4',
|
|
209
|
+
video_size: 7_340_032,
|
|
210
|
+
chunk_size: 7_340_032,
|
|
211
|
+
total_chunk_count: 1
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
response = Tiktok::Open::Sdk.post.video_init(access_token: access_token, params: params)
|
|
216
|
+
|
|
217
|
+
if response[:success]
|
|
218
|
+
publish_id = response[:response][:data][:publish_id]
|
|
219
|
+
upload_url = response[:response][:data][:upload_url]
|
|
220
|
+
# Use upload_url to upload video chunks (FILE_UPLOAD) or proceed with PULL_FROM_URL flow
|
|
221
|
+
else
|
|
222
|
+
puts "Error: #{response[:response][:error][:message]}"
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
161
226
|
### Using the User API
|
|
162
227
|
|
|
163
228
|
The SDK provides a convenient way to access user information:
|
|
164
229
|
|
|
165
230
|
```ruby
|
|
166
231
|
# Get user information
|
|
167
|
-
response = Tiktok::Open::Sdk
|
|
232
|
+
response = Tiktok::Open::Sdk.user.get_user_info(
|
|
168
233
|
access_token: access_token,
|
|
169
234
|
fields: %w[open_id union_id avatar_url display_name]
|
|
170
235
|
)
|
|
@@ -187,6 +252,73 @@ Available user fields include:
|
|
|
187
252
|
- `username` - User's username
|
|
188
253
|
- And more (see documentation for full list)
|
|
189
254
|
|
|
255
|
+
### Using OmniAuth Strategy
|
|
256
|
+
|
|
257
|
+
The SDK provides a ready-to-use OmniAuth strategy for Rails applications:
|
|
258
|
+
|
|
259
|
+
#### Rails Setup
|
|
260
|
+
|
|
261
|
+
Add the OmniAuth strategy to your Rails application:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# config/initializers/devise.rb
|
|
265
|
+
Devise.setup do |config|
|
|
266
|
+
config.omniauth(
|
|
267
|
+
:tiktok_open_sdk,
|
|
268
|
+
Rails.application.credentials.dig(:tiktok, :client_key),
|
|
269
|
+
Rails.application.credentials.dig(:tiktok, :client_secret),
|
|
270
|
+
scope: 'user.info.basic,user.info.profile,user.info.stats',
|
|
271
|
+
)
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Or use the SDK configuration:
|
|
276
|
+
|
|
277
|
+
```ruby
|
|
278
|
+
# config/initializers/tiktok_sdk.rb
|
|
279
|
+
Tiktok::Open::Sdk.configure do |config|
|
|
280
|
+
config.client_key = Rails.application.credentials.dig(:tiktok, :client_key)
|
|
281
|
+
config.client_secret = Rails.application.credentials.dig(:tiktok, :client_secret)
|
|
282
|
+
config.user_auth.scopes = %w[user.info.basic video.publish]
|
|
283
|
+
config.user_auth.redirect_uri = "#{Rails.application.config.action_mailer.asset_host.chomp('/')}/users/auth/tiktok_open_sdk/callback"
|
|
284
|
+
config.load_omniauth = true
|
|
285
|
+
end
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### OmniAuth Callback
|
|
289
|
+
|
|
290
|
+
Handle the OmniAuth callback in your controller:
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
# app/controllers/omniauth_callbacks_controller.rb
|
|
294
|
+
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
295
|
+
def tiktok_open_sdk
|
|
296
|
+
@auth = request.env['omniauth.auth']
|
|
297
|
+
|
|
298
|
+
# Find or create user based on TikTok auth data
|
|
299
|
+
@user = User.find_for_oauth(@auth)
|
|
300
|
+
|
|
301
|
+
# ...
|
|
302
|
+
|
|
303
|
+
if @user.persisted?
|
|
304
|
+
sign_in_and_redirect @user, event: :authentication
|
|
305
|
+
set_flash_message :notice, :success, kind: 'TikTok'
|
|
306
|
+
else
|
|
307
|
+
session['devise.provider_data'] = @auth.except('extra')
|
|
308
|
+
redirect_to new_user_registration_url
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Supported Scopes
|
|
315
|
+
|
|
316
|
+
The OmniAuth strategy supports the following TikTok scopes:
|
|
317
|
+
|
|
318
|
+
- `user.info.basic` - Basic user info: open_id, union_id, display_name, avatar URLs
|
|
319
|
+
- `user.info.profile` - Profile info: username, bio_description, profile_deep_link, is_verified
|
|
320
|
+
- `user.info.stats` - Statistics: follower_count, following_count, likes_count, video_count
|
|
321
|
+
|
|
190
322
|
### Using the HTTP Client
|
|
191
323
|
|
|
192
324
|
The SDK includes a flexible HTTP client for making API calls:
|
|
@@ -331,6 +463,8 @@ Tiktok::Open::Sdk.configure do |config|
|
|
|
331
463
|
config.user_auth.scopes = %w[user.info.basic] # Optional
|
|
332
464
|
config.user_auth.redirect_uri = 'https://...' # Optional
|
|
333
465
|
config.user_info_url = 'https://open.tiktokapis.com/v2/user/info/' # Optional
|
|
466
|
+
config.creator_info_query_url = 'https://open.tiktokapis.com/v2/post/publish/creator_info/query/' # Optional
|
|
467
|
+
config.video_init_url = 'https://open.tiktokapis.com/v2/post/publish/video/init/' # Optional
|
|
334
468
|
end
|
|
335
469
|
```
|
|
336
470
|
|
|
@@ -410,20 +544,59 @@ Retrieves user information from the TikTok Open API.
|
|
|
410
544
|
**Returns:** Hash with `:success`, `:code`, and `:response` keys
|
|
411
545
|
|
|
412
546
|
**Available Fields:**
|
|
413
|
-
- `open_id` -
|
|
414
|
-
- `union_id` -
|
|
415
|
-
- `avatar_url` -
|
|
416
|
-
- `avatar_url_100` -
|
|
417
|
-
- `avatar_large_url` -
|
|
418
|
-
- `display_name` - User's
|
|
419
|
-
- `bio_description` - User's
|
|
420
|
-
- `profile_deep_link` -
|
|
421
|
-
- `is_verified` -
|
|
422
|
-
- `username` - User's username
|
|
423
|
-
- `follower_count` -
|
|
424
|
-
- `following_count` -
|
|
425
|
-
- `likes_count` -
|
|
426
|
-
- `video_count` -
|
|
547
|
+
- `open_id` - Unique identifier for the user within the current application
|
|
548
|
+
- `union_id` - Persistent identifier for the user across different applications from the same developer
|
|
549
|
+
- `avatar_url` - URL to the user's profile image
|
|
550
|
+
- `avatar_url_100` - URL to the user's profile image in 100x100 pixel size
|
|
551
|
+
- `avatar_large_url` - URL to the user's profile image in higher resolution
|
|
552
|
+
- `display_name` - User's display name shown on their TikTok profile
|
|
553
|
+
- `bio_description` - User's biography text (if available)
|
|
554
|
+
- `profile_deep_link` - Direct link to the user's TikTok profile page
|
|
555
|
+
- `is_verified` - Boolean indicating if the account is verified by TikTok
|
|
556
|
+
- `username` - User's unique TikTok username
|
|
557
|
+
- `follower_count` - Number of followers the user has
|
|
558
|
+
- `following_count` - Number of accounts the user is following
|
|
559
|
+
- `likes_count` - Total number of likes received across all user's videos
|
|
560
|
+
- `video_count` - Total number of publicly posted videos by the user
|
|
561
|
+
|
|
562
|
+
### Post API
|
|
563
|
+
|
|
564
|
+
#### `creator_info_query(access_token:)`
|
|
565
|
+
|
|
566
|
+
Queries creator information from the TikTok Open API for video publishing.
|
|
567
|
+
|
|
568
|
+
**Parameters:**
|
|
569
|
+
- `access_token` (String, required) - OAuth2 access token for authentication
|
|
570
|
+
|
|
571
|
+
**Returns:** Hash with `:success`, `:code`, and `:response` keys
|
|
572
|
+
|
|
573
|
+
**Response Data:**
|
|
574
|
+
- `creator_avatar_url` - Creator's avatar URL
|
|
575
|
+
- `creator_nickname` - Creator's display name
|
|
576
|
+
- `creator_username` - Creator's username
|
|
577
|
+
- `stitch_disabled` - Whether stitch is disabled for the creator
|
|
578
|
+
- `comment_disabled` - Whether comments are disabled for the creator
|
|
579
|
+
- `duet_disabled` - Whether duet is disabled for the creator
|
|
580
|
+
- `max_video_post_duration_sec` - Maximum video duration in seconds
|
|
581
|
+
- `privacy_level_options` - Available privacy level options
|
|
582
|
+
|
|
583
|
+
#### `video_init(access_token:, params: {})`
|
|
584
|
+
|
|
585
|
+
Initializes a video publish and returns a publish ID and upload URL (for file upload) or starts a pull-from-URL flow.
|
|
586
|
+
|
|
587
|
+
**Parameters:**
|
|
588
|
+
- `access_token` (String, required) - OAuth2 access token for authentication
|
|
589
|
+
- `params` (Hash, required) - Request body with `post_info` and `source_info`
|
|
590
|
+
- `post_info` - Post metadata (title, privacy_level, disable_duet, disable_comment, disable_stitch, video_cover_timestamp_ms, etc.)
|
|
591
|
+
- `source_info` - Video source (e.g. `source: 'FILE_UPLOAD'` with video_size, chunk_size, total_chunk_count, video_type; or `source: 'PULL_FROM_URL'` with video_url)
|
|
592
|
+
|
|
593
|
+
**Returns:** Hash with `:success`, `:code`, and `:response` keys
|
|
594
|
+
|
|
595
|
+
**Response Data (on success):**
|
|
596
|
+
- `data.publish_id` - Publish session ID
|
|
597
|
+
- `data.upload_url` - URL for uploading video chunks (when using FILE_UPLOAD)
|
|
598
|
+
|
|
599
|
+
**Raises:** `Tiktok::Open::Sdk::RequestValidationError` if access token is invalid or params fail validation
|
|
427
600
|
|
|
428
601
|
### HTTP Client
|
|
429
602
|
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tiktok
|
|
4
|
+
module Open
|
|
5
|
+
module Omniauth
|
|
6
|
+
module Strategies
|
|
7
|
+
# OmniAuth strategy for TikTok Open Platform.
|
|
8
|
+
#
|
|
9
|
+
# Integrates TikTok OAuth2 authentication with OmniAuth.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# use OmniAuth::Builder do
|
|
13
|
+
# provider :tiktok_open, 'CLIENT_KEY', 'CLIENT_SECRET'
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# Supported scopes and their user info fields:
|
|
17
|
+
# - user.info.basic: open_id, union_id, display_name, avatar_url, avatar_url_100, avatar_large_url
|
|
18
|
+
# - user.info.profile: profile_deep_link, bio_description, is_verified, username
|
|
19
|
+
# - user.info.stats: follower_count, following_count, likes_count, video_count
|
|
20
|
+
class TiktokOpenSdk < ::OmniAuth::Strategies::OAuth2
|
|
21
|
+
# Custom access token class for TikTok.
|
|
22
|
+
class AccessToken < ::OAuth2::AccessToken; end
|
|
23
|
+
|
|
24
|
+
# Maps TikTok OAuth scopes to user info fields.
|
|
25
|
+
SCOPE_FIELDS = {
|
|
26
|
+
'user.info.basic' => %w[open_id union_id display_name avatar_url avatar_url_100 avatar_large_url],
|
|
27
|
+
'user.info.profile' => %w[profile_deep_link bio_description is_verified username],
|
|
28
|
+
'user.info.stats' => %w[follower_count following_count likes_count video_count]
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
private_constant :SCOPE_FIELDS
|
|
32
|
+
|
|
33
|
+
# The name of this OmniAuth strategy.
|
|
34
|
+
option :name, :tiktok_open_sdk
|
|
35
|
+
|
|
36
|
+
# OAuth2 client options for TikTok endpoints.
|
|
37
|
+
# - :site: TikTok Open API base URL
|
|
38
|
+
# - :authorize_url: TikTok OAuth2 authorization endpoint
|
|
39
|
+
# - :token_url: TikTok OAuth2 token endpoint
|
|
40
|
+
option :client_options, {
|
|
41
|
+
site: ::Tiktok::Open::Sdk::Config::OPEN_API_BASE_URL,
|
|
42
|
+
authorize_url: ::Tiktok::Open::Sdk.config.user_auth.auth_url,
|
|
43
|
+
token_url: ::Tiktok::Open::Sdk.config.user_auth.token_url,
|
|
44
|
+
auth_scheme: :request_body,
|
|
45
|
+
auth_token_class: AccessToken
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# List of parameters allowed in the authorization request.
|
|
49
|
+
option :authorize_options, %i[scope state redirect_uri]
|
|
50
|
+
|
|
51
|
+
# Default scope and redirect_uri from SDK config.
|
|
52
|
+
option :scope, ::Tiktok::Open::Sdk.config.user_auth.scopes.join(',')
|
|
53
|
+
option :redirect_uri, ::Tiktok::Open::Sdk.config.user_auth.redirect_uri
|
|
54
|
+
|
|
55
|
+
# Returns the unique TikTok user ID (open_id).
|
|
56
|
+
#
|
|
57
|
+
# @return [String] TikTok user's open_id.
|
|
58
|
+
uid { raw_info[:open_id].to_s }
|
|
59
|
+
|
|
60
|
+
# Returns a hash of user information.
|
|
61
|
+
#
|
|
62
|
+
# @return [Hash] User info with :name and :image keys, and profile fields if scope is present.
|
|
63
|
+
info do
|
|
64
|
+
{ name: raw_info[:display_name], image: raw_info[:avatar_url_100] }.tap do |info|
|
|
65
|
+
if request_scopes.include?('user.info.profile')
|
|
66
|
+
info.merge!(raw_info.slice(:username, :bio_description, :profile_deep_link))
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns extra raw user information from TikTok.
|
|
72
|
+
#
|
|
73
|
+
# @return [Hash] Raw user info data from TikTok API.
|
|
74
|
+
extra { raw_info }
|
|
75
|
+
|
|
76
|
+
# Returns the callback URL without query parameters.
|
|
77
|
+
#
|
|
78
|
+
# @return [String] Callback URL.
|
|
79
|
+
def callback_url
|
|
80
|
+
super.split('?').first
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Builds the access token from TikTok's token endpoint.
|
|
84
|
+
#
|
|
85
|
+
# @raise [OAuth2::Error] if the token response is unsuccessful.
|
|
86
|
+
# @return [AccessToken] OAuth2 access token object.
|
|
87
|
+
def build_access_token
|
|
88
|
+
response = fetch_access_token
|
|
89
|
+
validate_token_response(response)
|
|
90
|
+
create_access_token(response[:response])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Handles the initial OAuth2 request phase.
|
|
94
|
+
#
|
|
95
|
+
# @raise [ArgumentError] if client_secret is present in params.
|
|
96
|
+
def request_phase
|
|
97
|
+
params = authorize_params.merge('response_type' => 'code')
|
|
98
|
+
|
|
99
|
+
if params.key?(:client_secret) || params.key?('client_secret')
|
|
100
|
+
raise ArgumentError, 'client_secret is not allowed in authorize URL query params'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
redirect client.authorize_url(params)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Builds the authorization parameters for the OAuth2 request,
|
|
107
|
+
# adding the TikTok client_key.
|
|
108
|
+
#
|
|
109
|
+
# @return [Hash] Authorization parameters.
|
|
110
|
+
def authorize_params
|
|
111
|
+
super.tap do |params|
|
|
112
|
+
params[:client_key] = options.client_id
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
# Fetches access token from TikTok API
|
|
119
|
+
#
|
|
120
|
+
# @return [Hash] Token response from TikTok
|
|
121
|
+
def fetch_access_token
|
|
122
|
+
Tiktok::Open::Sdk.user_auth.fetch_access_token(
|
|
123
|
+
code: request.params['code'],
|
|
124
|
+
redirect_uri: callback_url
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Validates the token response
|
|
129
|
+
#
|
|
130
|
+
# @param response [Hash] Token response
|
|
131
|
+
# @raise [OAuth2::Error] if response is unsuccessful
|
|
132
|
+
def validate_token_response(response)
|
|
133
|
+
raise OAuth2::Error, response[:response] unless response[:success]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Creates AccessToken from response data
|
|
137
|
+
#
|
|
138
|
+
# @param data [Hash] Token data from response
|
|
139
|
+
# @return [AccessToken] OAuth2 access token object
|
|
140
|
+
def create_access_token(data)
|
|
141
|
+
AccessToken.from_hash(
|
|
142
|
+
client,
|
|
143
|
+
access_token: data[:access_token],
|
|
144
|
+
refresh_token: data[:refresh_token],
|
|
145
|
+
expires_at: Time.now.to_i + data[:expires_in].to_i
|
|
146
|
+
)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Returns the list of requested OAuth scopes.
|
|
150
|
+
#
|
|
151
|
+
# @return [Array<String>] List of scope strings.
|
|
152
|
+
def request_scopes
|
|
153
|
+
@request_scopes ||= request.params.fetch('scopes', 'user.info.basic').split(',')
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Returns the list of user info fields to request from TikTok,
|
|
157
|
+
# based on the requested scopes.
|
|
158
|
+
#
|
|
159
|
+
# @return [Array<String>] List of user info field names.
|
|
160
|
+
def user_info_fields
|
|
161
|
+
request_scopes.flat_map { |scope| SCOPE_FIELDS[scope] }.compact
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Fetches and memoizes the raw user info from the TikTok API.
|
|
165
|
+
#
|
|
166
|
+
# @return [Hash] Raw user info data, or empty hash if unavailable.
|
|
167
|
+
def raw_info
|
|
168
|
+
@raw_info ||= Tiktok::Open::Sdk.user.get_user_info(
|
|
169
|
+
access_token: access_token.token,
|
|
170
|
+
fields: user_info_fields
|
|
171
|
+
).dig(:response, :data, :user) || {}
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
@@ -29,14 +29,29 @@ module Tiktok
|
|
|
29
29
|
# @return [String] TikTok user info endpoint URL.
|
|
30
30
|
attr_accessor :user_info_url
|
|
31
31
|
|
|
32
|
+
# @!attribute [rw] creator_info_query_url
|
|
33
|
+
# @return [String] TikTok Query Creator Info endpoint URL.
|
|
34
|
+
attr_accessor :creator_info_query_url
|
|
35
|
+
|
|
36
|
+
# @!attribute [rw] video_init_url
|
|
37
|
+
# @return [String] TikTok Video Init endpoint URL.
|
|
38
|
+
attr_accessor :video_init_url
|
|
39
|
+
|
|
32
40
|
# @!attribute [rw] user_auth
|
|
33
41
|
# @return [UserAuth] User authentication configuration.
|
|
34
42
|
attr_accessor :user_auth
|
|
35
43
|
|
|
44
|
+
# @!attribute [rw] load_omniauth
|
|
45
|
+
# @return [Boolean] Whether to automatically load OmniAuth strategy.
|
|
46
|
+
attr_accessor :load_omniauth
|
|
47
|
+
|
|
36
48
|
# Create a new Config with default user authentication settings.
|
|
37
49
|
def initialize
|
|
38
|
-
@user_info_url
|
|
39
|
-
@
|
|
50
|
+
@user_info_url = "#{OPEN_API_BASE_URL}/v2/user/info/"
|
|
51
|
+
@creator_info_query_url = "#{OPEN_API_BASE_URL}/v2/post/publish/creator_info/query/"
|
|
52
|
+
@video_init_url = "#{OPEN_API_BASE_URL}/v2/post/publish/video/init/"
|
|
53
|
+
@user_auth = UserAuth.new
|
|
54
|
+
@load_omniauth = false
|
|
40
55
|
end
|
|
41
56
|
|
|
42
57
|
# User authentication configuration for TikTok Open SDK.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tiktok
|
|
4
|
+
module Open
|
|
5
|
+
module Sdk
|
|
6
|
+
module Helpers
|
|
7
|
+
module Validators
|
|
8
|
+
# Validates post information for TikTok video publishing.
|
|
9
|
+
#
|
|
10
|
+
# This module provides validation methods for post-related parameters including
|
|
11
|
+
# privacy level, title, boolean flags, and video cover timestamp.
|
|
12
|
+
#
|
|
13
|
+
# @example Including the validator in a class
|
|
14
|
+
# class VideoPublisher
|
|
15
|
+
# include Tiktok::Open::Sdk::Helpers::Validators::PostInfoValidator
|
|
16
|
+
#
|
|
17
|
+
# def publish(post_info)
|
|
18
|
+
# validate_post_info!(post_info)
|
|
19
|
+
# # proceed with publishing
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Validating post information
|
|
24
|
+
# post_info = {
|
|
25
|
+
# privacy_level: 'PUBLIC_TO_EVERYONE',
|
|
26
|
+
# title: 'My awesome video',
|
|
27
|
+
# disable_duet: false,
|
|
28
|
+
# brand_content_toggle: true
|
|
29
|
+
# }
|
|
30
|
+
# validate_post_info!(post_info)
|
|
31
|
+
module PostInfoValidator
|
|
32
|
+
# Valid privacy level options for TikTok videos
|
|
33
|
+
PRIVACY_LEVEL_OPTIONS = %w[
|
|
34
|
+
PUBLIC_TO_EVERYONE
|
|
35
|
+
MUTUAL_FOLLOW_FRIENDS
|
|
36
|
+
FOLLOWER_OF_CREATOR
|
|
37
|
+
SELF_ONLY
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
# Maximum allowed length for video title
|
|
41
|
+
MAX_TITLE_LENGTH = 2200
|
|
42
|
+
|
|
43
|
+
# Validates post information and raises an error if invalid.
|
|
44
|
+
#
|
|
45
|
+
# @param post_info [Hash, nil] The post information to validate
|
|
46
|
+
# @option post_info [String] :privacy_level Required. Must be one of PRIVACY_LEVEL_OPTIONS
|
|
47
|
+
# @option post_info [String] :title Optional. Maximum 2200 characters
|
|
48
|
+
# @option post_info [Boolean] :disable_duet Optional. Boolean flag
|
|
49
|
+
# @option post_info [Boolean] :disable_stitch Optional. Boolean flag
|
|
50
|
+
# @option post_info [Boolean] :disable_comment Optional. Boolean flag
|
|
51
|
+
# @option post_info [Boolean] :brand_content_toggle Optional. Boolean flag
|
|
52
|
+
# @option post_info [Boolean] :brand_organic_toggle Optional. Boolean flag
|
|
53
|
+
# @option post_info [Boolean] :is_aigc Optional. Boolean flag
|
|
54
|
+
# @option post_info [Integer] :video_cover_timestamp_ms Optional. Non-negative integer
|
|
55
|
+
#
|
|
56
|
+
# @return [void]
|
|
57
|
+
# @raise [Tiktok::Open::Sdk::RequestValidationError] If validation fails
|
|
58
|
+
#
|
|
59
|
+
# @example Valid post information
|
|
60
|
+
# validate_post_info!(privacy_level: 'PUBLIC_TO_EVERYONE')
|
|
61
|
+
#
|
|
62
|
+
# @example Invalid privacy level
|
|
63
|
+
# validate_post_info!(privacy_level: 'INVALID')
|
|
64
|
+
# # => raises RequestValidationError with privacy_level error
|
|
65
|
+
def validate_post_info!(post_info)
|
|
66
|
+
errors = {}
|
|
67
|
+
|
|
68
|
+
validate_post_info(post_info, errors)
|
|
69
|
+
|
|
70
|
+
raise_validation_errors!(errors)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# Raises validation errors if any exist.
|
|
76
|
+
#
|
|
77
|
+
# @param errors [Hash] The accumulated validation errors
|
|
78
|
+
# @return [void]
|
|
79
|
+
# @raise [Tiktok::Open::Sdk::RequestValidationError] If errors is not empty
|
|
80
|
+
def raise_validation_errors!(errors)
|
|
81
|
+
raise ::Tiktok::Open::Sdk::RequestValidationError, errors unless errors.empty?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Validates post information and accumulates errors.
|
|
85
|
+
#
|
|
86
|
+
# @param post_info [Hash, nil] The post information to validate
|
|
87
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
88
|
+
# @return [void]
|
|
89
|
+
def validate_post_info(post_info, errors)
|
|
90
|
+
return if post_info.nil?
|
|
91
|
+
return add_error errors, :post_info, 'must be a Hash' unless post_info.is_a?(Hash)
|
|
92
|
+
|
|
93
|
+
privacy_level = post_info[:privacy_level]
|
|
94
|
+
title = post_info[:title]
|
|
95
|
+
timestamp = post_info[:video_cover_timestamp_ms]
|
|
96
|
+
|
|
97
|
+
validate_privacy_level! privacy_level, errors
|
|
98
|
+
validate_title! title, errors
|
|
99
|
+
validate_boolean_fields! post_info, errors
|
|
100
|
+
validate_video_cover_timestamp! timestamp, errors
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Validates that privacy level is one of the allowed options.
|
|
104
|
+
#
|
|
105
|
+
# @param privacy_level [String, nil] The privacy level to validate
|
|
106
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
107
|
+
# @return [void]
|
|
108
|
+
def validate_privacy_level!(privacy_level, errors)
|
|
109
|
+
return if PRIVACY_LEVEL_OPTIONS.include?(privacy_level)
|
|
110
|
+
|
|
111
|
+
add_error errors, :privacy_level, "must be one of: #{PRIVACY_LEVEL_OPTIONS.join(", ")}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Validates video title length and type.
|
|
115
|
+
#
|
|
116
|
+
# @param title [String, nil] The title to validate
|
|
117
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
118
|
+
# @return [void]
|
|
119
|
+
def validate_title!(title, errors)
|
|
120
|
+
return if title.nil?
|
|
121
|
+
return add_error errors, :title, 'must be a String' unless title.is_a?(String)
|
|
122
|
+
return unless title.length > MAX_TITLE_LENGTH
|
|
123
|
+
|
|
124
|
+
add_error errors, :title, "must be less than #{MAX_TITLE_LENGTH} characters"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Validates that boolean fields contain boolean values.
|
|
128
|
+
#
|
|
129
|
+
# @param post_info [Hash] The post information containing boolean fields
|
|
130
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
131
|
+
# @return [void]
|
|
132
|
+
def validate_boolean_fields!(post_info, errors)
|
|
133
|
+
boolean_fields = %i[
|
|
134
|
+
disable_duet
|
|
135
|
+
disable_stitch
|
|
136
|
+
disable_comment
|
|
137
|
+
brand_content_toggle
|
|
138
|
+
brand_organic_toggle
|
|
139
|
+
is_aigc
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
boolean_fields.each do |field|
|
|
143
|
+
next unless post_info.key?(field)
|
|
144
|
+
|
|
145
|
+
value = post_info[field]
|
|
146
|
+
next if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
147
|
+
|
|
148
|
+
add_error errors, field, 'must be a boolean (true or false)'
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Validates video cover timestamp is a non-negative integer.
|
|
153
|
+
#
|
|
154
|
+
# @param timestamp [Integer, nil] The timestamp to validate
|
|
155
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
156
|
+
# @return [void]
|
|
157
|
+
def validate_video_cover_timestamp!(timestamp, errors)
|
|
158
|
+
return if timestamp.nil? || (timestamp.is_a?(Integer) && timestamp >= 0)
|
|
159
|
+
|
|
160
|
+
add_error errors, :video_cover_timestamp_ms, 'must be a non-negative integer'
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Adds an error message to the errors hash.
|
|
164
|
+
#
|
|
165
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
166
|
+
# @param field_name [Symbol] The field name that has an error
|
|
167
|
+
# @param message [String] The error message
|
|
168
|
+
# @return [Hash] The errors hash
|
|
169
|
+
def add_error(errors, field_name, message)
|
|
170
|
+
errors[field_name] ||= [] << message
|
|
171
|
+
errors
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'post_info_validator'
|
|
4
|
+
require_relative 'source_info_validator'
|
|
5
|
+
|
|
6
|
+
module Tiktok
|
|
7
|
+
module Open
|
|
8
|
+
module Sdk
|
|
9
|
+
module Helpers
|
|
10
|
+
module Validators
|
|
11
|
+
# Provides validation for video publishing initialization parameters.
|
|
12
|
+
#
|
|
13
|
+
# This module combines and applies the `PostInfoValidator` and `SourceInfoValidator` logic
|
|
14
|
+
# for validating `post_info` and `source_info` input hashes typically required for
|
|
15
|
+
# TikTok Open SDK video publishing workflows.
|
|
16
|
+
#
|
|
17
|
+
# @example Including and using PostPublishValidator
|
|
18
|
+
# class Publisher
|
|
19
|
+
# include Tiktok::Open::Sdk::Helpers::Validators::PostPublishValidator
|
|
20
|
+
#
|
|
21
|
+
# def publish_video(params)
|
|
22
|
+
# validate_video_init_info!(params)
|
|
23
|
+
# # ...proceed if valid
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# @see PostInfoValidator#validate_post_info
|
|
28
|
+
# @see SourceInfoValidator#validate_source_info
|
|
29
|
+
module PostPublishValidator
|
|
30
|
+
include PostInfoValidator
|
|
31
|
+
include SourceInfoValidator
|
|
32
|
+
|
|
33
|
+
# Validates combined video initialization parameters.
|
|
34
|
+
#
|
|
35
|
+
# Both `post_info` and `source_info` sub-hashes are validated using the appropriate
|
|
36
|
+
# validators. If any errors occur, a RequestValidationError (or similar) is raised.
|
|
37
|
+
#
|
|
38
|
+
# @param params [Hash] The full parameter hash.
|
|
39
|
+
# @option params [Hash] :post_info Info for video post, validated by PostInfoValidator
|
|
40
|
+
# @option params [Hash] :source_info Info on video source, validated by SourceInfoValidator
|
|
41
|
+
#
|
|
42
|
+
# @raise [RequestValidationError] Raised if any validation errors found in either section.
|
|
43
|
+
# @return [void]
|
|
44
|
+
def validate_video_init_info!(params)
|
|
45
|
+
errors = { post_info: {}, source_info: {} }
|
|
46
|
+
|
|
47
|
+
validate_post_info params[:post_info], errors[:post_info]
|
|
48
|
+
validate_source_info params[:source_info], errors[:source_info]
|
|
49
|
+
|
|
50
|
+
raise_validation_errors!(errors) unless errors.values.all?(&:empty?)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tiktok
|
|
4
|
+
module Open
|
|
5
|
+
module Sdk
|
|
6
|
+
module Helpers
|
|
7
|
+
module Validators
|
|
8
|
+
# Validates source information for TikTok video publishing.
|
|
9
|
+
#
|
|
10
|
+
# This module provides validation methods for video source parameters including
|
|
11
|
+
# source type (PULL_FROM_URL or FILE_UPLOAD) and their respective required fields.
|
|
12
|
+
#
|
|
13
|
+
# @example Including the validator in a class
|
|
14
|
+
# class VideoPublisher
|
|
15
|
+
# include Tiktok::Open::Sdk::Helpers::Validators::SourceInfoValidator
|
|
16
|
+
#
|
|
17
|
+
# def publish(source_info)
|
|
18
|
+
# validate_source_info!(source_info)
|
|
19
|
+
# # proceed with publishing
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Validating PULL_FROM_URL source
|
|
24
|
+
# source_info = {
|
|
25
|
+
# source: 'PULL_FROM_URL',
|
|
26
|
+
# video_url: 'https://example.com/video.mp4'
|
|
27
|
+
# }
|
|
28
|
+
# validate_source_info!(source_info)
|
|
29
|
+
#
|
|
30
|
+
# @example Validating FILE_UPLOAD source
|
|
31
|
+
# source_info = {
|
|
32
|
+
# source: 'FILE_UPLOAD',
|
|
33
|
+
# video_size: 10_485_760,
|
|
34
|
+
# chunk_size: 1_048_576,
|
|
35
|
+
# total_chunk_count: 10
|
|
36
|
+
# }
|
|
37
|
+
# validate_source_info!(source_info)
|
|
38
|
+
module SourceInfoValidator
|
|
39
|
+
# Valid source options for video uploads
|
|
40
|
+
SOURCE_OPTIONS = %w[
|
|
41
|
+
PULL_FROM_URL
|
|
42
|
+
FILE_UPLOAD
|
|
43
|
+
].freeze
|
|
44
|
+
|
|
45
|
+
# Validates source information and raises an error if invalid.
|
|
46
|
+
#
|
|
47
|
+
# @param source_info [Hash, nil] The source information to validate
|
|
48
|
+
# @option source_info [String] :source Required. Must be 'PULL_FROM_URL' or 'FILE_UPLOAD'
|
|
49
|
+
# @option source_info [String] :video_url Required for PULL_FROM_URL. Non-empty video URL
|
|
50
|
+
# @option source_info [Integer] :video_size Required for FILE_UPLOAD. Positive integer
|
|
51
|
+
# @option source_info [Integer] :chunk_size Optional for FILE_UPLOAD. Positive integer
|
|
52
|
+
# @option source_info [Integer] :total_chunk_count Optional for FILE_UPLOAD. Positive integer
|
|
53
|
+
#
|
|
54
|
+
# @return [void]
|
|
55
|
+
# @raise [Tiktok::Open::Sdk::RequestValidationError] If validation fails
|
|
56
|
+
#
|
|
57
|
+
# @example Valid PULL_FROM_URL source
|
|
58
|
+
# validate_source_info!(source: 'PULL_FROM_URL', video_url: 'https://example.com/video.mp4')
|
|
59
|
+
#
|
|
60
|
+
# @example Valid FILE_UPLOAD source
|
|
61
|
+
# validate_source_info!(source: 'FILE_UPLOAD', video_size: 10_485_760)
|
|
62
|
+
#
|
|
63
|
+
# @example Invalid source type
|
|
64
|
+
# validate_source_info!(source: 'INVALID')
|
|
65
|
+
# # => raises RequestValidationError with source error
|
|
66
|
+
def validate_source_info!(source_info)
|
|
67
|
+
errors = {}
|
|
68
|
+
|
|
69
|
+
validate_source_info(source_info, errors)
|
|
70
|
+
|
|
71
|
+
raise_validation_errors!(errors)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
# Raises validation errors if any exist.
|
|
77
|
+
#
|
|
78
|
+
# @param errors [Hash] The accumulated validation errors
|
|
79
|
+
# @return [void]
|
|
80
|
+
# @raise [Tiktok::Open::Sdk::RequestValidationError] If errors is not empty
|
|
81
|
+
def raise_validation_errors!(errors)
|
|
82
|
+
raise ::Tiktok::Open::Sdk::RequestValidationError, errors unless errors.empty?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Validates source information and accumulates errors.
|
|
86
|
+
#
|
|
87
|
+
# @param source_info [Hash, nil] The source information to validate
|
|
88
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
89
|
+
# @return [void]
|
|
90
|
+
def validate_source_info(source_info, errors)
|
|
91
|
+
if source_info.is_a?(Hash)
|
|
92
|
+
validate_source_specific_fields!(source_info, errors)
|
|
93
|
+
else
|
|
94
|
+
add_error errors, :source_info, 'must be a Hash'
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Validates source-specific required fields based on source type.
|
|
99
|
+
#
|
|
100
|
+
# @param source_info [Hash] The source information containing type-specific fields
|
|
101
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
102
|
+
# @return [void]
|
|
103
|
+
def validate_source_specific_fields!(source_info, errors)
|
|
104
|
+
case source_info[:source]
|
|
105
|
+
when 'PULL_FROM_URL'
|
|
106
|
+
validate_pull_from_url_fields!(source_info, errors)
|
|
107
|
+
when 'FILE_UPLOAD'
|
|
108
|
+
validate_file_upload_fields!(source_info, errors)
|
|
109
|
+
else
|
|
110
|
+
add_error errors, :source, "must be one of: #{SOURCE_OPTIONS.join(", ")}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Validates required fields for PULL_FROM_URL source type.
|
|
115
|
+
#
|
|
116
|
+
# @param source_info [Hash] The source information
|
|
117
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
118
|
+
# @return [void]
|
|
119
|
+
def validate_pull_from_url_fields!(source_info, errors)
|
|
120
|
+
video_url = source_info[:video_url]
|
|
121
|
+
|
|
122
|
+
return if video_url.is_a?(String) && !video_url.empty?
|
|
123
|
+
|
|
124
|
+
add_error errors, :video_url, 'is required and must be a non-empty string for PULL_FROM_URL'
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Validates required fields for FILE_UPLOAD source type.
|
|
128
|
+
#
|
|
129
|
+
# @param source_info [Hash] The source information
|
|
130
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
131
|
+
# @return [void]
|
|
132
|
+
def validate_file_upload_fields!(source_info, errors)
|
|
133
|
+
validate_video_size! source_info[:video_size], errors
|
|
134
|
+
validate_chunk_size! source_info[:chunk_size], errors
|
|
135
|
+
validate_total_chunk_count! source_info[:total_chunk_count], errors
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Validates video size for FILE_UPLOAD source.
|
|
139
|
+
#
|
|
140
|
+
# @param video_size [Integer, nil] The video size in bytes
|
|
141
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
142
|
+
# @return [void]
|
|
143
|
+
def validate_video_size!(video_size, errors)
|
|
144
|
+
return if video_size.is_a?(Integer) && video_size.positive?
|
|
145
|
+
|
|
146
|
+
add_error errors, :video_size, 'is required and must be a positive integer for FILE_UPLOAD'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Validates chunk size for FILE_UPLOAD source.
|
|
150
|
+
#
|
|
151
|
+
# @param value [Integer, nil] The chunk size in bytes
|
|
152
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
153
|
+
# @return [void]
|
|
154
|
+
def validate_chunk_size!(value, errors)
|
|
155
|
+
return if value.nil? || (value.is_a?(Integer) && value.positive?)
|
|
156
|
+
|
|
157
|
+
add_error errors, :chunk_size, 'must be a positive integer if provided'
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Validates total chunk count for FILE_UPLOAD source.
|
|
161
|
+
#
|
|
162
|
+
# @param value [Integer, nil] The total number of chunks
|
|
163
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
164
|
+
# @return [void]
|
|
165
|
+
def validate_total_chunk_count!(value, errors)
|
|
166
|
+
return if value.nil? || (value.is_a?(Integer) && value.positive?)
|
|
167
|
+
|
|
168
|
+
add_error errors, :total_chunk_count, 'must be a positive integer if provided'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Adds an error message to the errors hash.
|
|
172
|
+
#
|
|
173
|
+
# @param errors [Hash] The hash to accumulate errors into
|
|
174
|
+
# @param field_name [Symbol] The field name that has an error
|
|
175
|
+
# @param message [String] The error message
|
|
176
|
+
# @return [Hash] The errors hash
|
|
177
|
+
def add_error(errors, field_name, message)
|
|
178
|
+
errors[field_name] ||= [] << message
|
|
179
|
+
errors
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -85,7 +85,7 @@ module Tiktok
|
|
|
85
85
|
case content_type
|
|
86
86
|
when 'application/x-www-form-urlencoded'
|
|
87
87
|
request.set_form_data(body)
|
|
88
|
-
when 'application/json'
|
|
88
|
+
when 'application/json', 'application/json; charset=UTF-8'
|
|
89
89
|
request.body = body.to_json
|
|
90
90
|
else
|
|
91
91
|
raise ArgumentError, "Unsupported content type: #{content_type}"
|
|
@@ -102,10 +102,11 @@ module Tiktok
|
|
|
102
102
|
# @param body [Hash, nil] The request body.
|
|
103
103
|
# @return [Net::HTTPRequest] The HTTP request object.
|
|
104
104
|
def build_http_request(method, uri, headers, body)
|
|
105
|
-
klass
|
|
106
|
-
request
|
|
105
|
+
klass = Net::HTTP.const_get(method.capitalize)
|
|
106
|
+
request = klass.new(uri, headers)
|
|
107
|
+
content_type = headers[:'Content-Type'] || headers['Content-Type']
|
|
107
108
|
|
|
108
|
-
body ? assign_body!(request, body,
|
|
109
|
+
body ? assign_body!(request, body, content_type) : request
|
|
109
110
|
end
|
|
110
111
|
end
|
|
111
112
|
end
|
|
@@ -23,7 +23,7 @@ module Tiktok
|
|
|
23
23
|
# @return [URI] The constructed authorization URI.
|
|
24
24
|
def authorization_uri(params = {})
|
|
25
25
|
allowed_params = params.slice(:scope, :redirect_uri, :state)
|
|
26
|
-
uri = URI(Tiktok::Open::Sdk.config.user_auth.auth_url)
|
|
26
|
+
uri = URI.parse(Tiktok::Open::Sdk.config.user_auth.auth_url)
|
|
27
27
|
query_params = authorization_uri_default_params.merge(allowed_params)
|
|
28
28
|
uri.query = URI.encode_www_form(query_params)
|
|
29
29
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Provides methods to interact with TikTok Open API post endpoints.
|
|
4
|
+
module Tiktok
|
|
5
|
+
module Open
|
|
6
|
+
module Sdk
|
|
7
|
+
module OpenApi
|
|
8
|
+
module Post
|
|
9
|
+
# Provides methods for handling TikTok Open API post endpoints.
|
|
10
|
+
module Publish
|
|
11
|
+
extend self
|
|
12
|
+
|
|
13
|
+
include ::Tiktok::Open::Sdk::Helpers::ResponseHelper
|
|
14
|
+
include ::Tiktok::Open::Sdk::Helpers::Validators::TokenValidator
|
|
15
|
+
include ::Tiktok::Open::Sdk::Helpers::Validators::PostPublishValidator
|
|
16
|
+
|
|
17
|
+
# Queries creator information from the TikTok Open API.
|
|
18
|
+
#
|
|
19
|
+
# @param access_token [String] OAuth2 access token for authentication.
|
|
20
|
+
# @return [Hash] Parsed API response containing creator information.
|
|
21
|
+
# @raise [::Tiktok::Open::Sdk::RequestValidationError] If the access token is invalid.
|
|
22
|
+
#
|
|
23
|
+
# @example
|
|
24
|
+
# Tiktok::Open::Sdk.post.creator_info_query(access_token: 'your_access_token')
|
|
25
|
+
def creator_info_query(access_token:)
|
|
26
|
+
validate_token!(access_token)
|
|
27
|
+
|
|
28
|
+
render_response Tiktok::Open::Sdk::HttpClient.post(
|
|
29
|
+
Tiktok::Open::Sdk.config.creator_info_query_url,
|
|
30
|
+
headers: {
|
|
31
|
+
Authorization: "Bearer #{access_token}"
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def video_init(access_token:, params: {})
|
|
37
|
+
validate_token!(access_token)
|
|
38
|
+
validate_video_init_info!(params)
|
|
39
|
+
|
|
40
|
+
render_response Tiktok::Open::Sdk::HttpClient.post(
|
|
41
|
+
Tiktok::Open::Sdk.config.video_init_url,
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: "Bearer #{access_token}",
|
|
44
|
+
'Content-Type': 'application/json; charset=UTF-8'
|
|
45
|
+
},
|
|
46
|
+
body: params
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/tiktok/open/sdk.rb
CHANGED
|
@@ -4,8 +4,10 @@ require_relative 'sdk/helpers/string_utils_helper'
|
|
|
4
4
|
require_relative 'sdk/helpers/response_helper'
|
|
5
5
|
require_relative 'sdk/helpers/auth_helper'
|
|
6
6
|
require_relative 'sdk/helpers/validators/token_validator'
|
|
7
|
+
require_relative 'sdk/helpers/validators/post_publish_validator'
|
|
7
8
|
require_relative 'sdk/open_api/auth/user'
|
|
8
9
|
require_relative 'sdk/open_api/auth/client'
|
|
10
|
+
require_relative 'sdk/open_api/post/publish'
|
|
9
11
|
require_relative 'sdk/open_api/user'
|
|
10
12
|
require_relative 'sdk/version'
|
|
11
13
|
require_relative 'sdk/http_client'
|
|
@@ -21,7 +23,31 @@ module Tiktok
|
|
|
21
23
|
# raise Tiktok::Open::Sdk::Error, "Something went wrong"
|
|
22
24
|
class Error < StandardError; end
|
|
23
25
|
|
|
24
|
-
class
|
|
26
|
+
# Error class for request validation failures
|
|
27
|
+
#
|
|
28
|
+
# This error is raised when request validation fails, providing access to
|
|
29
|
+
# detailed validation error information.
|
|
30
|
+
#
|
|
31
|
+
# @attr_reader messages [Hash, nil] validation errors hash or nil if no specific errors
|
|
32
|
+
#
|
|
33
|
+
# @example
|
|
34
|
+
# raise RequestValidationError.new({ field: ['error message'] })
|
|
35
|
+
# raise RequestValidationError.new('General validation error')
|
|
36
|
+
class RequestValidationError < Error
|
|
37
|
+
attr_reader :messages
|
|
38
|
+
|
|
39
|
+
def initialize(messages = nil)
|
|
40
|
+
if messages.is_a?(Hash)
|
|
41
|
+
@messages = messages
|
|
42
|
+
|
|
43
|
+
super('Validation failed')
|
|
44
|
+
else
|
|
45
|
+
@messages = nil
|
|
46
|
+
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
25
51
|
|
|
26
52
|
class << self
|
|
27
53
|
# SDK configuration object
|
|
@@ -45,12 +71,15 @@ module Tiktok
|
|
|
45
71
|
# config.user_auth.token_url = 'https://open.tiktokapis.com/v2/oauth/token/'
|
|
46
72
|
# config.user_auth.scopes = %w[user.info.basic video.list]
|
|
47
73
|
# config.user_auth.redirect_uri = 'https://your-redirect-uri.example.com'
|
|
74
|
+
# config.load_omniauth = true
|
|
48
75
|
# end
|
|
49
76
|
def configure
|
|
50
77
|
self.config ||= Config.new
|
|
51
78
|
|
|
52
79
|
yield(config)
|
|
53
80
|
|
|
81
|
+
load_omniauth! if config.load_omniauth
|
|
82
|
+
|
|
54
83
|
config
|
|
55
84
|
end
|
|
56
85
|
|
|
@@ -73,6 +102,44 @@ module Tiktok
|
|
|
73
102
|
def client_auth
|
|
74
103
|
OpenApi::Auth::Client
|
|
75
104
|
end
|
|
105
|
+
|
|
106
|
+
# Convenience accessor for post publish functionality
|
|
107
|
+
#
|
|
108
|
+
# @return [OpenApi::Post::Publish] the Post publish module
|
|
109
|
+
#
|
|
110
|
+
# @example
|
|
111
|
+
# Tiktok::Open::Sdk.post.video_init(access_token: 'token', post_info: post_info, source_info: source_info)
|
|
112
|
+
def post
|
|
113
|
+
OpenApi::Post::Publish
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Convenience accessor for user functionality
|
|
117
|
+
#
|
|
118
|
+
# @return [OpenApi::User] the User module
|
|
119
|
+
#
|
|
120
|
+
# @example
|
|
121
|
+
# Tiktok::Open::Sdk.user.info(access_token: 'token')
|
|
122
|
+
def user
|
|
123
|
+
OpenApi::User
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
private
|
|
127
|
+
|
|
128
|
+
# Loads the OmniAuth strategy for TikTok Open Platform.
|
|
129
|
+
#
|
|
130
|
+
# Attempts to require the necessary OmniAuth dependencies for TikTok Open integration.
|
|
131
|
+
# Raises an error if the required gems are not available.
|
|
132
|
+
#
|
|
133
|
+
# @raise [Tiktok::Open::Sdk::Error] if 'omniauth-oauth2' or the TikTok strategy cannot be loaded
|
|
134
|
+
# @example
|
|
135
|
+
# Tiktok::Open::Sdk.load_omniauth!
|
|
136
|
+
def load_omniauth!
|
|
137
|
+
require 'omniauth-oauth2'
|
|
138
|
+
require 'tiktok/open/omniauth/strategies/tiktok_open_sdk'
|
|
139
|
+
rescue LoadError => e
|
|
140
|
+
raise ::Tiktok::Open::Sdk::Error,
|
|
141
|
+
"OmniAuth is not loaded! Error: #{e.message}"
|
|
142
|
+
end
|
|
76
143
|
end
|
|
77
144
|
end
|
|
78
145
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tiktok-open-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Taras Pochkun
|
|
@@ -26,15 +26,20 @@ files:
|
|
|
26
26
|
- LICENSE.txt
|
|
27
27
|
- README.md
|
|
28
28
|
- Rakefile
|
|
29
|
+
- lib/tiktok/open/omniauth/strategies/tiktok_open_sdk.rb
|
|
29
30
|
- lib/tiktok/open/sdk.rb
|
|
30
31
|
- lib/tiktok/open/sdk/config.rb
|
|
31
32
|
- lib/tiktok/open/sdk/helpers/auth_helper.rb
|
|
32
33
|
- lib/tiktok/open/sdk/helpers/response_helper.rb
|
|
33
34
|
- lib/tiktok/open/sdk/helpers/string_utils_helper.rb
|
|
35
|
+
- lib/tiktok/open/sdk/helpers/validators/post_info_validator.rb
|
|
36
|
+
- lib/tiktok/open/sdk/helpers/validators/post_publish_validator.rb
|
|
37
|
+
- lib/tiktok/open/sdk/helpers/validators/source_info_validator.rb
|
|
34
38
|
- lib/tiktok/open/sdk/helpers/validators/token_validator.rb
|
|
35
39
|
- lib/tiktok/open/sdk/http_client.rb
|
|
36
40
|
- lib/tiktok/open/sdk/open_api/auth/client.rb
|
|
37
41
|
- lib/tiktok/open/sdk/open_api/auth/user.rb
|
|
42
|
+
- lib/tiktok/open/sdk/open_api/post/publish.rb
|
|
38
43
|
- lib/tiktok/open/sdk/open_api/user.rb
|
|
39
44
|
- lib/tiktok/open/sdk/version.rb
|
|
40
45
|
- sig/tiktok/open/sdk.rbs
|
|
@@ -44,7 +49,7 @@ licenses:
|
|
|
44
49
|
metadata:
|
|
45
50
|
allowed_push_host: https://rubygems.org
|
|
46
51
|
homepage_uri: https://github.com/pochkuntaras/tiktok-open-sdk
|
|
47
|
-
source_code_uri: https://github.com/pochkuntaras/tiktok-open-sdk
|
|
52
|
+
source_code_uri: https://github.com/pochkuntaras/tiktok-open-sdk.git
|
|
48
53
|
changelog_uri: https://github.com/pochkuntaras/tiktok-open-sdk/blob/main/CHANGELOG.md
|
|
49
54
|
rubygems_mfa_required: 'true'
|
|
50
55
|
documentation_uri: https://rubydoc.info/gems/tiktok-open-sdk
|