Bild Carousel
This commit is contained in:
@@ -52,8 +52,88 @@ export async function getValidTiktokTokens() {
|
||||
scope: tokens.scope || null,
|
||||
accessTokenExpiresAt: new Date(now + Number(tokens.expires_in || 0) * 1000),
|
||||
refreshTokenExpiresAt: tokens.refresh_expires_in
|
||||
? new Date(now + Number(tokens.refresh_expires_in) * 1000)
|
||||
? new Date(now + Number(tokens.refresh_expires_in || 0) * 1000)
|
||||
: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
||||
|
||||
export async function getLiveTiktokAccessToken() {
|
||||
const integration = await db.tiktokIntegration.findUnique({
|
||||
where: { accountKey: TIKTOK_ACCOUNT_KEY },
|
||||
});
|
||||
if (!integration) {
|
||||
const error = new Error('No TikTok account connected.');
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (integration.accessTokenExpiresAt.getTime() - Date.now() > REFRESH_BUFFER_MS) {
|
||||
return integration;
|
||||
}
|
||||
|
||||
const clientKey = process.env.TIKTOK_CLIENT_KEY;
|
||||
const clientSecret = process.env.TIKTOK_CLIENT_SECRET;
|
||||
if (!clientKey || !clientSecret) {
|
||||
const error = new Error('TikTok client credentials are not configured.');
|
||||
error.status = 500;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return getValidTiktokTokens();
|
||||
}
|
||||
|
||||
export async function tiktokApi(url: string, options: RequestInit = {}) {
|
||||
const tokens = await getLiveTiktokAccessToken();
|
||||
const accessToken = tokens.accessToken;
|
||||
|
||||
const fetchOptions: RequestInit = {
|
||||
...options,
|
||||
headers: {
|
||||
...(options.headers as Record<string, string> | undefined),
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
},
|
||||
};
|
||||
|
||||
const res = await fetch(url, fetchOptions);
|
||||
const text = await res.text();
|
||||
let data: Record<string, unknown>;
|
||||
try {
|
||||
data = JSON.parse(text) as Record<string, unknown>;
|
||||
} catch {
|
||||
data = { raw: text };
|
||||
}
|
||||
|
||||
if (!res.ok || (data?.error as Record<string, string> | undefined)?.code !== 'ok') {
|
||||
const message = (data?.error as Record<string, string> | undefined)?.message || (typeof data?.raw === 'string' ? data.raw : '') || `TikTok API error: ${res.status}`;
|
||||
const error = new Error(message);
|
||||
error.status = res.status;
|
||||
error.body = data;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function uploadBinaryToTiktok(uploadUrl: string, buffer: Buffer, mimeType = 'video/mp4') {
|
||||
const res = await fetch(uploadUrl, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
'Content-Length': String(buffer.length),
|
||||
},
|
||||
body: buffer,
|
||||
});
|
||||
|
||||
const text = await res.text();
|
||||
if (!res.ok && res.status !== 201) {
|
||||
const error = new Error(`TikTok binary upload failed: ${res.status} ${text}`);
|
||||
error.status = res.status;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return { status: res.status, body: text };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user