Skip to content

Commit de4acb3

Browse files
BillLeoutsakosvl346Bill Leoutsakoscursoragent
authored
chore(tiktok): simplify to draft-only Content Posting (#5703)
* Simplify TikTok to draft-only Content Posting Remove Direct Post stubs and legacy Share Kit webhook triggers that Sim does not ship, and fix draft upload response handling so real TikTok errors are not misreported as size-limit failures. Co-authored-by: Cursor <cursoragent@cursor.com> * fix TikTok cleanup lint and Greptile review findings Reject legacy mode:direct publish requests instead of silently drafting, keep deprecated Share Kit triggers registered for saved workflows, and apply biome format/import fixes. Co-authored-by: Cursor <cursoragent@cursor.com> * Finish TikTok greenfield draft-only surface Remove Query Creator Info and video.publish, drop Share Kit trigger stubs, rename publish-video to upload-video-draft, and strip leftover Direct Post mode from the contract. Co-authored-by: Cursor <cursoragent@cursor.com> * Hide TikTok from the toolbar until app review is approved. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Bill Leoutsakos <billleoutsakos@Bills-MacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent fb439c9 commit de4acb3

34 files changed

Lines changed: 104 additions & 813 deletions

apps/sim/app/api/tools/tiktok/publish-video/route.test.ts renamed to apps/sim/app/api/tools/tiktok/upload-video-draft/route.test.ts

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ vi.mock('@/app/api/files/authorization', () => ({
2424
assertToolFileAccess: mockAssertToolFileAccess,
2525
}))
2626

27-
vi.mock('@/app/api/tools/tiktok/publish-video/upload', () => ({
27+
vi.mock('@/app/api/tools/tiktok/upload-video-draft/upload', () => ({
2828
computeTikTokChunkPlan: mockComputeTikTokChunkPlan,
2929
getStoredVideoSize: mockGetStoredVideoSize,
3030
streamStoredVideoToTikTok: mockStreamStoredVideoToTikTok,
3131
TIKTOK_MAX_VIDEO_BYTES: 250 * 1024 * 1024,
3232
}))
3333

34-
import { POST } from '@/app/api/tools/tiktok/publish-video/route'
34+
import { POST } from '@/app/api/tools/tiktok/upload-video-draft/route'
3535

3636
const file = {
3737
key: 'workspace/workspace-1/video.mp4',
@@ -40,7 +40,7 @@ const file = {
4040
type: 'video/mp4',
4141
}
4242

43-
describe('POST /api/tools/tiktok/publish-video', () => {
43+
describe('POST /api/tools/tiktok/upload-video-draft', () => {
4444
beforeEach(() => {
4545
vi.clearAllMocks()
4646
hybridAuthMockFns.mockCheckInternalAuth.mockResolvedValue({
@@ -67,7 +67,6 @@ describe('POST /api/tools/tiktok/publish-video', () => {
6767
vi.stubGlobal('fetch', fetchMock)
6868
const request = createMockRequest('POST', {
6969
accessToken: 'access-token',
70-
mode: 'draft',
7170
file,
7271
})
7372

@@ -104,35 +103,6 @@ describe('POST /api/tools/tiktok/publish-video', () => {
104103
})
105104
})
106105

107-
it('keeps Direct Post unavailable until per-post approval exists', async () => {
108-
const fetchMock = vi.fn()
109-
vi.stubGlobal('fetch', fetchMock)
110-
111-
const response = await POST(
112-
createMockRequest('POST', {
113-
accessToken: 'access-token',
114-
mode: 'direct',
115-
file,
116-
musicUsageConsent: 'accepted',
117-
postInfo: {
118-
privacy_level: 'SELF_ONLY',
119-
disable_duet: true,
120-
disable_stitch: true,
121-
disable_comment: true,
122-
brand_content_toggle: false,
123-
},
124-
})
125-
)
126-
127-
expect(response.status).toBe(400)
128-
await expect(response.json()).resolves.toMatchObject({
129-
success: false,
130-
error: expect.stringMatching(/Direct Post is not available/),
131-
})
132-
expect(mockGetStoredVideoSize).not.toHaveBeenCalled()
133-
expect(fetchMock).not.toHaveBeenCalled()
134-
})
135-
136106
it('returns 413 when the storage object exceeds the relay limit', async () => {
137107
mockGetStoredVideoSize.mockRejectedValue(
138108
new PayloadSizeLimitError({
@@ -145,7 +115,6 @@ describe('POST /api/tools/tiktok/publish-video', () => {
145115
const response = await POST(
146116
createMockRequest('POST', {
147117
accessToken: 'access-token',
148-
mode: 'draft',
149118
file,
150119
})
151120
)

apps/sim/app/api/tools/tiktok/publish-video/route.ts renamed to apps/sim/app/api/tools/tiktok/upload-video-draft/route.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createLogger } from '@sim/logger'
22
import { getErrorMessage } from '@sim/utils/errors'
33
import { type NextRequest, NextResponse } from 'next/server'
4-
import { tiktokPublishVideoContract } from '@/lib/api/contracts/tiktok-tools'
4+
import { tiktokUploadVideoDraftContract } from '@/lib/api/contracts/tiktok-tools'
55
import { parseRequest } from '@/lib/api/server'
66
import { checkInternalAuth } from '@/lib/auth/hybrid'
77
import { generateRequestId } from '@/lib/core/utils/request'
@@ -19,15 +19,15 @@ import {
1919
getStoredVideoSize,
2020
streamStoredVideoToTikTok,
2121
TIKTOK_MAX_VIDEO_BYTES,
22-
} from '@/app/api/tools/tiktok/publish-video/upload'
22+
} from '@/app/api/tools/tiktok/upload-video-draft/upload'
2323
import type { UserFile } from '@/executor/types'
2424
import { tiktokPublishInitApiDataSchema } from '@/tools/tiktok/api-schemas'
2525
import { readTikTokApiResponse } from '@/tools/tiktok/utils'
2626

2727
export const dynamic = 'force-dynamic'
2828
export const maxDuration = 900
2929

30-
const logger = createLogger('TikTokPublishVideoAPI')
30+
const logger = createLogger('TikTokUploadVideoDraftAPI')
3131

3232
const TIKTOK_VIDEO_MIME_TYPES = new Set(['video/mp4', 'video/quicktime', 'video/webm'])
3333

@@ -43,28 +43,19 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
4343
try {
4444
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
4545
if (!authResult.success || !authResult.userId) {
46-
logger.warn(`[${requestId}] Unauthorized TikTok publish-video attempt: ${authResult.error}`)
46+
logger.warn(
47+
`[${requestId}] Unauthorized TikTok upload-video-draft attempt: ${authResult.error}`
48+
)
4749
return NextResponse.json(
4850
{ success: false, error: authResult.error || 'Authentication required' },
4951
{ status: 401 }
5052
)
5153
}
5254

53-
const parsed = await parseRequest(tiktokPublishVideoContract, request, {})
55+
const parsed = await parseRequest(tiktokUploadVideoDraftContract, request, {})
5456
if (!parsed.success) return parsed.response
5557
const data = parsed.data.body
5658

57-
if (data.mode === 'direct') {
58-
return NextResponse.json(
59-
{
60-
success: false,
61-
error:
62-
'TikTok Direct Post is not available until per-post human approval is implemented. Use Upload Video Draft instead.',
63-
},
64-
{ status: 400 }
65-
)
66-
}
67-
6859
let userFile: UserFile
6960
try {
7061
userFile = processSingleFileToUserFile(data.file, requestId, logger)
@@ -146,7 +137,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
146137
if (initError) {
147138
logger.error(`[${requestId}] TikTok init failed`, { error: initError })
148139
return NextResponse.json(
149-
{ success: false, error: initError.message || 'Failed to initialize TikTok upload' },
140+
{
141+
success: false,
142+
error: initError.message || initError.code || 'Failed to initialize TikTok upload',
143+
},
150144
{ status: initResponse.status >= 400 ? initResponse.status : 502 }
151145
)
152146
}
@@ -203,7 +197,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
203197
{ status: 499 }
204198
)
205199
}
206-
logger.error(`[${requestId}] Error publishing video to TikTok:`, error)
200+
logger.error(`[${requestId}] Error uploading TikTok video draft:`, error)
207201
return NextResponse.json(
208202
{ success: false, error: getErrorMessage(error, 'Internal server error') },
209203
{ status: 500 }

apps/sim/app/api/tools/tiktok/publish-video/upload.test.ts renamed to apps/sim/app/api/tools/tiktok/upload-video-draft/upload.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
getStoredVideoSize,
2929
streamStoredVideoToTikTok,
3030
TIKTOK_MAX_VIDEO_BYTES,
31-
} from '@/app/api/tools/tiktok/publish-video/upload'
31+
} from '@/app/api/tools/tiktok/upload-video-draft/upload'
3232

3333
const baseStreamOptions = {
3434
key: 'workspace/workspace-1/video.mp4',

apps/sim/app/api/tools/tiktok/publish-video/upload.ts renamed to apps/sim/app/api/tools/tiktok/upload-video-draft/upload.ts

File renamed without changes.

apps/sim/blocks/blocks/tiktok.test.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ describe('TikTokBlock', () => {
88
const buildParams = TikTokBlock.tools.config.params!
99
const selectTool = TikTokBlock.tools.config.tool!
1010

11-
it('keeps direct posting unavailable until one-use human approval is supported', () => {
12-
expect(TikTokBlock.tools.access).not.toContain('tiktok_direct_post_video')
13-
expect(() => selectTool({ operation: 'tiktok_direct_post_video' })).toThrow(
11+
it('rejects unsupported operations', () => {
12+
expect(() => selectTool({ operation: 'tiktok_unknown_operation' })).toThrow(
1413
'Unsupported TikTok operation'
1514
)
1615
})
@@ -37,21 +36,16 @@ describe('TikTokBlock', () => {
3736
const inputs = {
3837
operation: 'tiktok_upload_video_draft',
3938
file,
40-
privacyLevel: 'PUBLIC_TO_EVERYONE',
41-
musicUsageConsent: 'accepted',
4239
videoIds: 'stale-video-id',
4340
}
4441
const finalInputs = { ...inputs, ...buildParams(inputs) }
4542

4643
expect(finalInputs.file).toEqual(file)
47-
expect(finalInputs.privacyLevel).toBeUndefined()
48-
expect(finalInputs.musicUsageConsent).toBeUndefined()
4944
expect(finalInputs.videoIds).toBeUndefined()
5045
})
5146

5247
it('declares file-like outputs with canonical block output types', () => {
5348
expect(TikTokBlock.outputs.avatarFile.type).toBe('file')
54-
expect(TikTokBlock.outputs.creatorAvatarFile.type).toBe('file')
5549
expect(TikTokBlock.outputs.videos.type).toBe('array')
5650
expect(TikTokBlock.outputs.publiclyAvailablePostId.type).toBe('array')
5751
})

apps/sim/blocks/blocks/tiktok.ts

Lines changed: 6 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import { normalizeFileInput } from '@/blocks/utils'
66
import type { TikTokResponse } from '@/tools/tiktok/types'
77
import { getTrigger } from '@/triggers'
88

9-
const VIDEO_UPLOAD_OPERATIONS = ['tiktok_upload_video_draft']
109
const TIKTOK_TOOL_IDS = new Set([
1110
'tiktok_get_user',
1211
'tiktok_list_videos',
1312
'tiktok_query_videos',
14-
'tiktok_query_creator_info',
1513
'tiktok_upload_video_draft',
1614
'tiktok_get_post_status',
1715
])
@@ -22,16 +20,6 @@ const TIKTOK_OPERATION_INPUT_KEYS = [
2220
'videoIds',
2321
'file',
2422
'publishId',
25-
'title',
26-
'privacyLevel',
27-
'disableDuet',
28-
'disableStitch',
29-
'disableComment',
30-
'videoCoverTimestampMs',
31-
'isAigc',
32-
'brandContentToggle',
33-
'brandOrganicToggle',
34-
'musicUsageConsent',
3523
] as const
3624

3725
export const TikTokBlock: BlockConfig<TikTokResponse> = {
@@ -58,7 +46,6 @@ export const TikTokBlock: BlockConfig<TikTokResponse> = {
5846
{ label: 'Get User Info', id: 'tiktok_get_user' },
5947
{ label: 'List Videos', id: 'tiktok_list_videos' },
6048
{ label: 'Query Videos', id: 'tiktok_query_videos' },
61-
{ label: 'Query Creator Info', id: 'tiktok_query_creator_info' },
6249
{ label: 'Upload Video Draft', id: 'tiktok_upload_video_draft' },
6350
{ label: 'Get Post Status', id: 'tiktok_get_post_status' },
6451
],
@@ -140,8 +127,8 @@ export const TikTokBlock: BlockConfig<TikTokResponse> = {
140127
multiple: false,
141128
maxSize: 250,
142129
description: 'MP4, MOV, or WebM video up to 250 MB.',
143-
condition: { field: 'operation', value: VIDEO_UPLOAD_OPERATIONS },
144-
required: { field: 'operation', value: VIDEO_UPLOAD_OPERATIONS },
130+
condition: { field: 'operation', value: 'tiktok_upload_video_draft' },
131+
required: { field: 'operation', value: 'tiktok_upload_video_draft' },
145132
},
146133
{
147134
id: 'videoFileRef',
@@ -150,8 +137,8 @@ export const TikTokBlock: BlockConfig<TikTokResponse> = {
150137
canonicalParamId: 'file',
151138
mode: 'advanced',
152139
placeholder: 'Reference a video from a previous block',
153-
condition: { field: 'operation', value: VIDEO_UPLOAD_OPERATIONS },
154-
required: { field: 'operation', value: VIDEO_UPLOAD_OPERATIONS },
140+
condition: { field: 'operation', value: 'tiktok_upload_video_draft' },
141+
required: { field: 'operation', value: 'tiktok_upload_video_draft' },
155142
},
156143

157144
{
@@ -168,8 +155,6 @@ export const TikTokBlock: BlockConfig<TikTokResponse> = {
168155
...getTrigger('tiktok_post_inbox_delivered').subBlocks,
169156
...getTrigger('tiktok_post_publicly_available').subBlocks,
170157
...getTrigger('tiktok_post_no_longer_public').subBlocks,
171-
...getTrigger('tiktok_video_publish_completed').subBlocks,
172-
...getTrigger('tiktok_video_upload_failed').subBlocks,
173158
...getTrigger('tiktok_authorization_removed').subBlocks,
174159
],
175160

@@ -181,8 +166,6 @@ export const TikTokBlock: BlockConfig<TikTokResponse> = {
181166
'tiktok_post_inbox_delivered',
182167
'tiktok_post_publicly_available',
183168
'tiktok_post_no_longer_public',
184-
'tiktok_video_publish_completed',
185-
'tiktok_video_upload_failed',
186169
'tiktok_authorization_removed',
187170
],
188171
},
@@ -192,7 +175,6 @@ export const TikTokBlock: BlockConfig<TikTokResponse> = {
192175
'tiktok_get_user',
193176
'tiktok_list_videos',
194177
'tiktok_query_videos',
195-
'tiktok_query_creator_info',
196178
'tiktok_upload_video_draft',
197179
'tiktok_get_post_status',
198180
],
@@ -227,8 +209,6 @@ export const TikTokBlock: BlockConfig<TikTokResponse> = {
227209
.map((id: string) => id.trim())
228210
.filter(Boolean)
229211
break
230-
case 'tiktok_query_creator_info':
231-
break
232212
case 'tiktok_upload_video_draft': {
233213
result.file = normalizeFileInput(params.file, { single: true })
234214
break
@@ -340,59 +320,12 @@ export const TikTokBlock: BlockConfig<TikTokResponse> = {
340320
condition: { field: 'operation', value: 'tiktok_list_videos' },
341321
},
342322

343-
creatorAvatarUrl: {
344-
type: 'string',
345-
description: 'Provider URL of the creator avatar; expires after two hours',
346-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
347-
},
348-
creatorAvatarFile: {
349-
type: 'file',
350-
description:
351-
'Canonical workflow file containing the creator avatar, suitable for downstream attachments',
352-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
353-
},
354-
creatorUsername: {
355-
type: 'string',
356-
description: 'TikTok username of the creator',
357-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
358-
},
359-
creatorNickname: {
360-
type: 'string',
361-
description: 'Display name/nickname of the creator',
362-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
363-
},
364-
privacyLevelOptions: {
365-
type: 'array',
366-
description: 'Available privacy levels for posting (array of strings)',
367-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
368-
},
369-
commentDisabled: {
370-
type: 'boolean',
371-
description: 'Whether the creator disabled comments by default',
372-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
373-
},
374-
duetDisabled: {
375-
type: 'boolean',
376-
description: 'Whether the creator disabled duets by default',
377-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
378-
},
379-
stitchDisabled: {
380-
type: 'boolean',
381-
description: 'Whether the creator disabled stitches by default',
382-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
383-
},
384-
maxVideoPostDurationSec: {
385-
type: 'number',
386-
description: 'Maximum allowed video duration in seconds',
387-
condition: { field: 'operation', value: 'tiktok_query_creator_info' },
388-
},
389-
390323
publishId: {
391324
type: 'string',
392-
description: 'Publish ID for tracking post status',
325+
description: 'Draft upload ID for tracking post status',
393326
condition: {
394327
field: 'operation',
395-
value: VIDEO_UPLOAD_OPERATIONS,
328+
value: 'tiktok_upload_video_draft',
396329
},
397330
},
398331

0 commit comments

Comments
 (0)