TikTok V6
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getValidTiktokTokens, getLiveTiktokAccessToken, tiktokApi, uploadBinaryToTiktok } from '@/lib/tiktok';
|
||||
import {
|
||||
getValidTiktokTokens,
|
||||
tiktokApi,
|
||||
uploadBinaryToTiktok,
|
||||
planTiktokChunks,
|
||||
sniffVideoMimeType,
|
||||
pollTiktokPublishStatus,
|
||||
} from '@/lib/tiktok';
|
||||
|
||||
const isAdminRequest = (request: NextRequest) => {
|
||||
const adminKey = process.env.TIKTOK_ADMIN_KEY;
|
||||
@@ -33,12 +40,29 @@ export async function POST(request: NextRequest) {
|
||||
if (typeof json?.videoBufferBase64 === 'string') {
|
||||
const videoBuffer = Buffer.from(json.videoBufferBase64, 'base64');
|
||||
const videoSize = videoBuffer.length;
|
||||
if (!videoSize) {
|
||||
return NextResponse.json(
|
||||
{ error: 'videoBufferBase64 decoded to an empty file.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Validate by magic bytes, not by a client-supplied MIME string.
|
||||
const mimeType = sniffVideoMimeType(videoBuffer);
|
||||
if (!mimeType) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Video must be MP4, MOV, or WebM (magic-byte check failed).' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const { chunkSize, totalChunkCount } = planTiktokChunks(videoSize);
|
||||
const initBody = {
|
||||
source_info: {
|
||||
source: 'FILE_UPLOAD',
|
||||
video_size: videoSize,
|
||||
chunk_size: videoSize,
|
||||
total_chunk_count: 1,
|
||||
chunk_size: chunkSize,
|
||||
total_chunk_count: totalChunkCount,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -61,7 +85,7 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
await uploadBinaryToTiktok(uploadUrl, videoBuffer, 'video/mp4');
|
||||
await uploadBinaryToTiktok(uploadUrl, videoBuffer, mimeType);
|
||||
} else if (Array.isArray(json?.photos)) {
|
||||
const photos = json.photos as Array<{ url?: string } | string>;
|
||||
if (!photos.length) {
|
||||
@@ -94,6 +118,18 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const title = typeof json?.title === 'string' ? json.title.trim() : '';
|
||||
const description = typeof json?.description === 'string' ? json.description.trim() : '';
|
||||
if (title.length > 90) {
|
||||
return NextResponse.json(
|
||||
{ error: 'title must be at most 90 characters for TikTok photo posts.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
if (description.length > 4000) {
|
||||
return NextResponse.json(
|
||||
{ error: 'description must be at most 4000 characters for TikTok photo posts.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const initBody = {
|
||||
media_type: 'PHOTO',
|
||||
@@ -143,9 +179,16 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// INITIATED is not delivery. Poll until the draft reaches the creator's
|
||||
// inbox (SEND_TO_USER_INBOX) or fails, bounded so the request finishes.
|
||||
const finalStatus = await pollTiktokPublishStatus(publishId, { maxWaitMs: 45_000 });
|
||||
status = finalStatus.status || status;
|
||||
|
||||
return NextResponse.json({
|
||||
publish_id: publishId,
|
||||
status,
|
||||
delivered: status === 'SEND_TO_USER_INBOX',
|
||||
...(finalStatus.failReason ? { fail_reason: finalStatus.failReason } : {}),
|
||||
});
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Unknown error';
|
||||
|
||||
@@ -23,7 +23,7 @@ export function generateMetadata({ params }: { params: { slug: string } }): Meta
|
||||
}
|
||||
|
||||
const alternates = buildLanguageAlternates({
|
||||
en: `/use-cases/${page.enSlug}`,
|
||||
en: page.enPath ?? `/use-cases/${page.enSlug}`,
|
||||
de: page.href,
|
||||
});
|
||||
|
||||
@@ -101,6 +101,7 @@ export default function UseCaseDetailPageDe({
|
||||
heroImageAlt={page.heroImageAlt}
|
||||
authoritySignals={page.authoritySignals}
|
||||
directAnswer={page.directAnswer}
|
||||
enableScrollReveal={page.enableScrollReveal}
|
||||
labels={{
|
||||
...useCaseTemplateLabelsDe,
|
||||
faqTitle: `FAQ: ${page.title}`,
|
||||
|
||||
Reference in New Issue
Block a user