From 398d5a0ad6c01c131dd6e87bd65d26df453163cf Mon Sep 17 00:00:00 2001 From: BillLeoutsakosvl346 Date: Sun, 1 Feb 2026 22:25:51 +0000 Subject: [PATCH] feat(tiktok): add TikTok integration with Display API support --- apps/sim/blocks/blocks/tiktok.ts | 161 ++++++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 8 ++ apps/sim/lib/auth/auth.ts | 55 ++++++++ apps/sim/lib/core/config/env.ts | 2 + apps/sim/lib/oauth/oauth.ts | 29 ++++ apps/sim/lib/oauth/types.ts | 2 + apps/sim/tools/registry.ts | 4 + apps/sim/tools/tiktok/get_user.ts | 185 ++++++++++++++++++++++++++ apps/sim/tools/tiktok/index.ts | 7 + apps/sim/tools/tiktok/list_videos.ts | 131 ++++++++++++++++++ apps/sim/tools/tiktok/query_videos.ts | 117 ++++++++++++++++ apps/sim/tools/tiktok/types.ts | 84 ++++++++++++ 13 files changed, 787 insertions(+) create mode 100644 apps/sim/blocks/blocks/tiktok.ts create mode 100644 apps/sim/tools/tiktok/get_user.ts create mode 100644 apps/sim/tools/tiktok/index.ts create mode 100644 apps/sim/tools/tiktok/list_videos.ts create mode 100644 apps/sim/tools/tiktok/query_videos.ts create mode 100644 apps/sim/tools/tiktok/types.ts diff --git a/apps/sim/blocks/blocks/tiktok.ts b/apps/sim/blocks/blocks/tiktok.ts new file mode 100644 index 000000000..1a6a1a4c5 --- /dev/null +++ b/apps/sim/blocks/blocks/tiktok.ts @@ -0,0 +1,161 @@ +import { TikTokIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' +import type { TikTokResponse } from '@/tools/tiktok/types' + +export const TikTokBlock: BlockConfig = { + type: 'tiktok', + name: 'TikTok', + description: 'Access TikTok user profiles and videos', + authMode: AuthMode.OAuth, + longDescription: + 'Integrate TikTok into your workflow. Get user profile information including follower counts and video statistics. List and query videos with cover images, embed links, and metadata.', + docsLink: 'https://docs.sim.ai/tools/tiktok', + category: 'tools', + bgColor: '#000000', + icon: TikTokIcon, + subBlocks: [ + // Operation selection + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Get User Info', id: 'get_user' }, + { label: 'List Videos', id: 'list_videos' }, + { label: 'Query Videos', id: 'query_videos' }, + ], + value: () => 'get_user', + }, + + // TikTok OAuth Authentication + { + id: 'credential', + title: 'TikTok Account', + type: 'oauth-input', + serviceId: 'tiktok', + placeholder: 'Select TikTok account', + required: true, + }, + + // Get User Info specific fields + { + id: 'fields', + title: 'Fields', + type: 'short-input', + placeholder: 'open_id,display_name,avatar_url,follower_count,video_count', + condition: { + field: 'operation', + value: 'get_user', + }, + }, + + // List Videos specific fields + { + id: 'maxCount', + title: 'Max Count', + type: 'short-input', + placeholder: '20', + condition: { + field: 'operation', + value: 'list_videos', + }, + }, + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + placeholder: 'Pagination cursor from previous response', + condition: { + field: 'operation', + value: 'list_videos', + }, + }, + + // Query Videos specific fields + { + id: 'videoIds', + title: 'Video IDs', + type: 'long-input', + placeholder: 'Comma-separated video IDs (e.g., 7077642457847994444,7080217258529732386)', + condition: { + field: 'operation', + value: 'query_videos', + }, + required: { + field: 'operation', + value: 'query_videos', + }, + }, + ], + tools: { + access: ['tiktok_get_user', 'tiktok_list_videos', 'tiktok_query_videos'], + config: { + tool: (inputs) => { + const operation = inputs.operation || 'get_user' + + switch (operation) { + case 'list_videos': + return 'tiktok_list_videos' + case 'query_videos': + return 'tiktok_query_videos' + default: + return 'tiktok_get_user' + } + }, + params: (inputs) => { + const operation = inputs.operation || 'get_user' + const { credential } = inputs + + switch (operation) { + case 'get_user': + return { + accessToken: credential, + ...(inputs.fields && { fields: inputs.fields }), + } + case 'list_videos': + return { + accessToken: credential, + ...(inputs.maxCount && { maxCount: Number(inputs.maxCount) }), + ...(inputs.cursor && { cursor: Number(inputs.cursor) }), + } + case 'query_videos': + return { + accessToken: credential, + videoIds: inputs.videoIds + ? inputs.videoIds.split(',').map((id: string) => id.trim()) + : [], + } + default: + return { + accessToken: credential, + } + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + credential: { type: 'string', description: 'TikTok access token' }, + fields: { type: 'string', description: 'Comma-separated list of user fields to return' }, + maxCount: { type: 'number', description: 'Maximum number of videos to return (1-20)' }, + cursor: { type: 'number', description: 'Pagination cursor from previous response' }, + videoIds: { type: 'string', description: 'Comma-separated list of video IDs to query' }, + }, + outputs: { + // Get User outputs + openId: { type: 'string', description: 'TikTok user ID' }, + displayName: { type: 'string', description: 'User display name' }, + avatarUrl: { type: 'string', description: 'Profile image URL' }, + bioDescription: { type: 'string', description: 'User bio' }, + followerCount: { type: 'number', description: 'Number of followers' }, + followingCount: { type: 'number', description: 'Number of accounts followed' }, + likesCount: { type: 'number', description: 'Total likes received' }, + videoCount: { type: 'number', description: 'Total public videos' }, + isVerified: { type: 'boolean', description: 'Whether account is verified' }, + // List/Query Videos outputs + videos: { type: 'json', description: 'Array of video objects' }, + cursor: { type: 'number', description: 'Cursor for next page' }, + hasMore: { type: 'boolean', description: 'Whether more videos are available' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 4fbaf2766..a38375031 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -131,6 +131,7 @@ import { TavilyBlock } from '@/blocks/blocks/tavily' import { TelegramBlock } from '@/blocks/blocks/telegram' import { TextractBlock } from '@/blocks/blocks/textract' import { ThinkingBlock } from '@/blocks/blocks/thinking' +import { TikTokBlock } from '@/blocks/blocks/tiktok' import { TinybirdBlock } from '@/blocks/blocks/tinybird' import { TranslateBlock } from '@/blocks/blocks/translate' import { TrelloBlock } from '@/blocks/blocks/trello' @@ -303,6 +304,7 @@ export const registry: Record = { supabase: SupabaseBlock, tavily: TavilyBlock, telegram: TelegramBlock, + tiktok: TikTokBlock, textract: TextractBlock, thinking: ThinkingBlock, tinybird: TinybirdBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 2e1e48778..c7d30360c 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -3472,6 +3472,14 @@ export function HumanInTheLoopIcon(props: SVGProps) { ) } +export function TikTokIcon(props: SVGProps) { + return ( + + + + ) +} + export function TrelloIcon(props: SVGProps) { return ( { + try { + logger.info('Fetching TikTok user profile') + + const response = await fetch( + 'https://open.tiktokapis.com/v2/user/info/?fields=open_id,union_id,avatar_url,display_name', + { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + } + ) + + if (!response.ok) { + logger.error('Failed to fetch TikTok user info', { + status: response.status, + statusText: response.statusText, + }) + throw new Error('Failed to fetch user info') + } + + const data = await response.json() + const profile = data.data?.user + + if (!profile) { + logger.error('No user data in TikTok response') + return null + } + + return { + id: `${profile.open_id}-${crypto.randomUUID()}`, + name: profile.display_name || 'TikTok User', + email: `${profile.open_id}@tiktok.user`, + emailVerified: false, + image: profile.avatar_url || undefined, + createdAt: new Date(), + updatedAt: new Date(), + } + } catch (error) { + logger.error('Error in TikTok getUserInfo:', { error }) + return null + } + }, + }, + // WordPress.com provider { providerId: 'wordpress', diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 6bd9df299..f663e2043 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -244,6 +244,8 @@ export const env = createEnv({ SPOTIFY_CLIENT_ID: z.string().optional(), // Spotify OAuth client ID SPOTIFY_CLIENT_SECRET: z.string().optional(), // Spotify OAuth client secret CALCOM_CLIENT_ID: z.string().optional(), // Cal.com OAuth client ID + TIKTOK_CLIENT_ID: z.string().optional(), // TikTok OAuth client ID + TIKTOK_CLIENT_SECRET: z.string().optional(), // TikTok OAuth client secret // E2B Remote Code Execution E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 7bb11ca35..113121ab8 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -32,6 +32,7 @@ import { ShopifyIcon, SlackIcon, SpotifyIcon, + TikTokIcon, TrelloIcon, VertexIcon, WealthboxIcon, @@ -796,6 +797,21 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'spotify', }, + tiktok: { + name: 'TikTok', + icon: TikTokIcon, + services: { + tiktok: { + name: 'TikTok', + description: 'Access TikTok user profiles and videos.', + providerId: 'tiktok', + icon: TikTokIcon, + baseProviderIcon: TikTokIcon, + scopes: ['user.info.basic', 'user.info.profile', 'user.info.stats', 'video.list'], + }, + }, + defaultService: 'tiktok', + }, } interface ProviderAuthConfig { @@ -1135,6 +1151,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { supportsRefreshTokenRotation: false, } } + case 'tiktok': { + const { clientId, clientSecret } = getCredentials( + env.TIKTOK_CLIENT_ID, + env.TIKTOK_CLIENT_SECRET + ) + return { + tokenEndpoint: 'https://open.tiktokapis.com/v2/oauth/token/', + clientId, + clientSecret, + useBasicAuth: false, + supportsRefreshTokenRotation: true, + } + } default: throw new Error(`Unsupported provider: ${provider}`) } diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 961c7a0b1..af06ad0e4 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -42,6 +42,7 @@ export type OAuthProvider = | 'wordpress' | 'spotify' | 'calcom' + | 'tiktok' export type OAuthService = | 'google' @@ -83,6 +84,7 @@ export type OAuthService = | 'wordpress' | 'spotify' | 'calcom' + | 'tiktok' export interface OAuthProviderConfig { name: string diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 6018a6f86..c34c3267e 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1625,6 +1625,7 @@ import { } from '@/tools/telegram' import { textractParserTool } from '@/tools/textract' import { thinkingTool } from '@/tools/thinking' +import { tiktokGetUserTool, tiktokListVideosTool, tiktokQueryVideosTool } from '@/tools/tiktok' import { tinybirdEventsTool, tinybirdQueryTool } from '@/tools/tinybird' import { trelloAddCommentTool, @@ -2731,6 +2732,9 @@ export const tools: Record = { telegram_send_photo: telegramSendPhotoTool, telegram_send_video: telegramSendVideoTool, telegram_send_document: telegramSendDocumentTool, + tiktok_get_user: tiktokGetUserTool, + tiktok_list_videos: tiktokListVideosTool, + tiktok_query_videos: tiktokQueryVideosTool, clay_populate: clayPopulateTool, clerk_list_users: clerkListUsersTool, clerk_get_user: clerkGetUserTool, diff --git a/apps/sim/tools/tiktok/get_user.ts b/apps/sim/tools/tiktok/get_user.ts new file mode 100644 index 000000000..ecc4b873b --- /dev/null +++ b/apps/sim/tools/tiktok/get_user.ts @@ -0,0 +1,185 @@ +import type { TikTokGetUserParams, TikTokGetUserResponse } from '@/tools/tiktok/types' +import type { ToolConfig } from '@/tools/types' + +export const tiktokGetUserTool: ToolConfig = { + id: 'tiktok_get_user', + name: 'TikTok Get User', + description: + 'Get the authenticated TikTok user profile information including display name, avatar, bio, follower count, and video statistics.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['user.info.basic'], + }, + + params: { + fields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + default: + 'open_id,union_id,avatar_url,avatar_url_100,avatar_large_url,display_name,bio_description,profile_deep_link,is_verified,username,follower_count,following_count,likes_count,video_count', + description: + 'Comma-separated list of fields to return. Available: open_id, union_id, avatar_url, avatar_url_100, avatar_large_url, display_name, bio_description, profile_deep_link, is_verified, username, follower_count, following_count, likes_count, video_count', + }, + }, + + request: { + url: (params: TikTokGetUserParams) => { + const fields = + params.fields || + 'open_id,union_id,avatar_url,avatar_url_100,avatar_large_url,display_name,bio_description,profile_deep_link,is_verified,username,follower_count,following_count,likes_count,video_count' + return `https://open.tiktokapis.com/v2/user/info/?fields=${encodeURIComponent(fields)}` + }, + method: 'GET', + headers: (params: TikTokGetUserParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response): Promise => { + const data = await response.json() + + if (data.error?.code !== 'ok' && data.error?.code) { + return { + success: false, + output: { + openId: '', + unionId: null, + displayName: '', + avatarUrl: null, + avatarUrl100: null, + avatarLargeUrl: null, + bioDescription: null, + profileDeepLink: null, + isVerified: null, + username: null, + followerCount: null, + followingCount: null, + likesCount: null, + videoCount: null, + }, + error: data.error?.message || 'Failed to fetch user info', + } + } + + const user = data.data?.user + + if (!user) { + return { + success: false, + output: { + openId: '', + unionId: null, + displayName: '', + avatarUrl: null, + avatarUrl100: null, + avatarLargeUrl: null, + bioDescription: null, + profileDeepLink: null, + isVerified: null, + username: null, + followerCount: null, + followingCount: null, + likesCount: null, + videoCount: null, + }, + error: 'No user data returned', + } + } + + return { + success: true, + output: { + openId: user.open_id ?? '', + unionId: user.union_id ?? null, + displayName: user.display_name ?? '', + avatarUrl: user.avatar_url ?? null, + avatarUrl100: user.avatar_url_100 ?? null, + avatarLargeUrl: user.avatar_large_url ?? null, + bioDescription: user.bio_description ?? null, + profileDeepLink: user.profile_deep_link ?? null, + isVerified: user.is_verified ?? null, + username: user.username ?? null, + followerCount: user.follower_count ?? null, + followingCount: user.following_count ?? null, + likesCount: user.likes_count ?? null, + videoCount: user.video_count ?? null, + }, + } + }, + + outputs: { + openId: { + type: 'string', + description: 'Unique TikTok user ID for this application', + }, + unionId: { + type: 'string', + description: 'Unique TikTok user ID across all apps from the same developer', + optional: true, + }, + displayName: { + type: 'string', + description: 'User display name', + }, + avatarUrl: { + type: 'string', + description: 'Profile image URL', + optional: true, + }, + avatarUrl100: { + type: 'string', + description: 'Profile image URL (100x100)', + optional: true, + }, + avatarLargeUrl: { + type: 'string', + description: 'Profile image URL (large)', + optional: true, + }, + bioDescription: { + type: 'string', + description: 'User bio description', + optional: true, + }, + profileDeepLink: { + type: 'string', + description: 'Deep link to user TikTok profile', + optional: true, + }, + isVerified: { + type: 'boolean', + description: 'Whether the account is verified', + optional: true, + }, + username: { + type: 'string', + description: 'TikTok username', + optional: true, + }, + followerCount: { + type: 'number', + description: 'Number of followers', + optional: true, + }, + followingCount: { + type: 'number', + description: 'Number of accounts the user follows', + optional: true, + }, + likesCount: { + type: 'number', + description: 'Total likes received across all videos', + optional: true, + }, + videoCount: { + type: 'number', + description: 'Total number of public videos', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/tiktok/index.ts b/apps/sim/tools/tiktok/index.ts new file mode 100644 index 000000000..e471cefea --- /dev/null +++ b/apps/sim/tools/tiktok/index.ts @@ -0,0 +1,7 @@ +import { tiktokGetUserTool } from '@/tools/tiktok/get_user' +import { tiktokListVideosTool } from '@/tools/tiktok/list_videos' +import { tiktokQueryVideosTool } from '@/tools/tiktok/query_videos' + +export { tiktokGetUserTool } +export { tiktokListVideosTool } +export { tiktokQueryVideosTool } diff --git a/apps/sim/tools/tiktok/list_videos.ts b/apps/sim/tools/tiktok/list_videos.ts new file mode 100644 index 000000000..4fd29d24a --- /dev/null +++ b/apps/sim/tools/tiktok/list_videos.ts @@ -0,0 +1,131 @@ +import type { + TikTokListVideosParams, + TikTokListVideosResponse, + TikTokVideo, +} from '@/tools/tiktok/types' +import type { ToolConfig } from '@/tools/types' + +export const tiktokListVideosTool: ToolConfig = { + id: 'tiktok_list_videos', + name: 'TikTok List Videos', + description: + "Get a list of the authenticated user's TikTok videos with cover images, titles, and metadata. Supports pagination.", + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['video.list'], + }, + + params: { + maxCount: { + type: 'number', + required: false, + visibility: 'user-or-llm', + default: 20, + description: 'Maximum number of videos to return (1-20)', + }, + cursor: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Cursor for pagination (from previous response)', + }, + }, + + request: { + url: () => + 'https://open.tiktokapis.com/v2/video/list/?fields=id,title,cover_image_url,embed_link,duration,create_time,share_url,video_description,width,height', + method: 'POST', + headers: (params: TikTokListVideosParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params: TikTokListVideosParams) => ({ + max_count: params.maxCount || 20, + ...(params.cursor !== undefined && { cursor: params.cursor }), + }), + }, + + transformResponse: async (response: Response): Promise => { + const data = await response.json() + + if (data.error?.code !== 'ok' && data.error?.code) { + return { + success: false, + output: { + videos: [], + cursor: null, + hasMore: false, + }, + error: data.error?.message || 'Failed to fetch videos', + } + } + + const videos: TikTokVideo[] = (data.data?.videos ?? []).map((video: any) => ({ + id: video.id ?? '', + title: video.title ?? null, + coverImageUrl: video.cover_image_url ?? null, + embedLink: video.embed_link ?? null, + duration: video.duration ?? null, + createTime: video.create_time ?? null, + shareUrl: video.share_url ?? null, + videoDescription: video.video_description ?? null, + width: video.width ?? null, + height: video.height ?? null, + })) + + return { + success: true, + output: { + videos, + cursor: data.data?.cursor ?? null, + hasMore: data.data?.has_more ?? false, + }, + } + }, + + outputs: { + videos: { + type: 'array', + description: 'List of TikTok videos', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Video ID' }, + title: { type: 'string', description: 'Video title', optional: true }, + coverImageUrl: { + type: 'string', + description: 'Cover image URL (may expire)', + optional: true, + }, + embedLink: { type: 'string', description: 'Embeddable video URL', optional: true }, + duration: { type: 'number', description: 'Video duration in seconds', optional: true }, + createTime: { + type: 'number', + description: 'Unix timestamp when video was created', + optional: true, + }, + shareUrl: { type: 'string', description: 'Shareable video URL', optional: true }, + videoDescription: { + type: 'string', + description: 'Video description/caption', + optional: true, + }, + width: { type: 'number', description: 'Video width in pixels', optional: true }, + height: { type: 'number', description: 'Video height in pixels', optional: true }, + }, + }, + }, + cursor: { + type: 'number', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + hasMore: { + type: 'boolean', + description: 'Whether there are more videos to fetch', + }, + }, +} diff --git a/apps/sim/tools/tiktok/query_videos.ts b/apps/sim/tools/tiktok/query_videos.ts new file mode 100644 index 000000000..29c174f86 --- /dev/null +++ b/apps/sim/tools/tiktok/query_videos.ts @@ -0,0 +1,117 @@ +import type { + TikTokQueryVideosParams, + TikTokQueryVideosResponse, + TikTokVideo, +} from '@/tools/tiktok/types' +import type { ToolConfig } from '@/tools/types' + +export const tiktokQueryVideosTool: ToolConfig = + { + id: 'tiktok_query_videos', + name: 'TikTok Query Videos', + description: + 'Query specific TikTok videos by their IDs to get fresh metadata including cover images, embed links, and video details.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['video.list'], + }, + + params: { + videoIds: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Array of video IDs to query (maximum 20)', + items: { + type: 'string', + description: 'TikTok video ID', + }, + }, + }, + + request: { + url: () => + 'https://open.tiktokapis.com/v2/video/query/?fields=id,title,cover_image_url,embed_link,duration,create_time,share_url,video_description,width,height', + method: 'POST', + headers: (params: TikTokQueryVideosParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params: TikTokQueryVideosParams) => ({ + filters: { + video_ids: params.videoIds, + }, + }), + }, + + transformResponse: async (response: Response): Promise => { + const data = await response.json() + + if (data.error?.code !== 'ok' && data.error?.code) { + return { + success: false, + output: { + videos: [], + }, + error: data.error?.message || 'Failed to query videos', + } + } + + const videos: TikTokVideo[] = (data.data?.videos ?? []).map((video: any) => ({ + id: video.id ?? '', + title: video.title ?? null, + coverImageUrl: video.cover_image_url ?? null, + embedLink: video.embed_link ?? null, + duration: video.duration ?? null, + createTime: video.create_time ?? null, + shareUrl: video.share_url ?? null, + videoDescription: video.video_description ?? null, + width: video.width ?? null, + height: video.height ?? null, + })) + + return { + success: true, + output: { + videos, + }, + } + }, + + outputs: { + videos: { + type: 'array', + description: 'List of queried TikTok videos', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Video ID' }, + title: { type: 'string', description: 'Video title', optional: true }, + coverImageUrl: { + type: 'string', + description: 'Cover image URL (fresh URL)', + optional: true, + }, + embedLink: { type: 'string', description: 'Embeddable video URL', optional: true }, + duration: { type: 'number', description: 'Video duration in seconds', optional: true }, + createTime: { + type: 'number', + description: 'Unix timestamp when video was created', + optional: true, + }, + shareUrl: { type: 'string', description: 'Shareable video URL', optional: true }, + videoDescription: { + type: 'string', + description: 'Video description/caption', + optional: true, + }, + width: { type: 'number', description: 'Video width in pixels', optional: true }, + height: { type: 'number', description: 'Video height in pixels', optional: true }, + }, + }, + }, + }, + } diff --git a/apps/sim/tools/tiktok/types.ts b/apps/sim/tools/tiktok/types.ts new file mode 100644 index 000000000..a5fc0b8a1 --- /dev/null +++ b/apps/sim/tools/tiktok/types.ts @@ -0,0 +1,84 @@ +import type { ToolResponse } from '@/tools/types' + +/** + * Base params that include OAuth access token + */ +export interface TikTokBaseParams { + accessToken: string +} + +/** + * Get User Info + */ +export interface TikTokGetUserParams extends TikTokBaseParams { + fields?: string +} + +export interface TikTokGetUserResponse extends ToolResponse { + output: { + openId: string + unionId: string | null + displayName: string + avatarUrl: string | null + avatarUrl100: string | null + avatarLargeUrl: string | null + bioDescription: string | null + profileDeepLink: string | null + isVerified: boolean | null + username: string | null + followerCount: number | null + followingCount: number | null + likesCount: number | null + videoCount: number | null + } +} + +/** + * List Videos + */ +export interface TikTokListVideosParams extends TikTokBaseParams { + maxCount?: number + cursor?: number +} + +export interface TikTokVideo { + id: string + title: string | null + coverImageUrl: string | null + embedLink: string | null + duration: number | null + createTime: number | null + shareUrl: string | null + videoDescription: string | null + width: number | null + height: number | null +} + +export interface TikTokListVideosResponse extends ToolResponse { + output: { + videos: TikTokVideo[] + cursor: number | null + hasMore: boolean + } +} + +/** + * Query Videos + */ +export interface TikTokQueryVideosParams extends TikTokBaseParams { + videoIds: string[] +} + +export interface TikTokQueryVideosResponse extends ToolResponse { + output: { + videos: TikTokVideo[] + } +} + +/** + * Union type of all TikTok responses + */ +export type TikTokResponse = + | TikTokGetUserResponse + | TikTokListVideosResponse + | TikTokQueryVideosResponse