Skip to content

Commit 9a9970b

Browse files
committed
feat: tiktok retries
1 parent 10901ab commit 9a9970b

File tree

1 file changed

+99
-78
lines changed

1 file changed

+99
-78
lines changed

libraries/nestjs-libraries/src/integrations/social/tiktok.provider.ts

Lines changed: 99 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -240,87 +240,108 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
240240
integration: Integration
241241
): Promise<PostResponse[]> {
242242
const [firstPost, ...comments] = postDetails;
243+
const maxRetries = 3;
244+
let lastError: Error | null = null;
243245

244-
console.log(firstPost);
245-
const {
246-
data: { publish_id },
247-
} = await (
248-
await this.fetch(
249-
`https://open.tiktokapis.com/v2/post/publish${this.postingMethod(
250-
firstPost.settings.content_posting_method,
251-
(firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) === -1
252-
)}`,
253-
{
254-
method: 'POST',
255-
headers: {
256-
'Content-Type': 'application/json; charset=UTF-8',
257-
Authorization: `Bearer ${accessToken}`,
246+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
247+
try {
248+
console.log(`TikTok post attempt ${attempt}/${maxRetries}`, firstPost);
249+
250+
const {
251+
data: { publish_id },
252+
} = await (
253+
await this.fetch(
254+
`https://open.tiktokapis.com/v2/post/publish${this.postingMethod(
255+
firstPost.settings.content_posting_method,
256+
(firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) === -1
257+
)}`,
258+
{
259+
method: 'POST',
260+
headers: {
261+
'Content-Type': 'application/json; charset=UTF-8',
262+
Authorization: `Bearer ${accessToken}`,
263+
},
264+
body: JSON.stringify({
265+
...((firstPost?.settings?.content_posting_method ||
266+
'DIRECT_POST') === 'DIRECT_POST'
267+
? {
268+
post_info: {
269+
title: firstPost.message,
270+
privacy_level:
271+
firstPost.settings.privacy_level || 'PUBLIC_TO_EVERYONE',
272+
disable_duet: !firstPost.settings.duet || false,
273+
disable_comment: !firstPost.settings.comment || false,
274+
disable_stitch: !firstPost.settings.stitch || false,
275+
brand_content_toggle:
276+
firstPost.settings.brand_content_toggle || false,
277+
brand_organic_toggle:
278+
firstPost.settings.brand_organic_toggle || false,
279+
...((firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) ===
280+
-1
281+
? {
282+
auto_add_music:
283+
firstPost.settings.autoAddMusic === 'yes',
284+
}
285+
: {}),
286+
},
287+
}
288+
: {}),
289+
...((firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) > -1
290+
? {
291+
source_info: {
292+
source: 'PULL_FROM_URL',
293+
video_url: firstPost?.media?.[0]?.path!,
294+
...(firstPost?.media?.[0]?.thumbnailTimestamp!
295+
? {
296+
video_cover_timestamp_ms:
297+
firstPost?.media?.[0]?.thumbnailTimestamp!,
298+
}
299+
: {}),
300+
},
301+
}
302+
: {
303+
source_info: {
304+
source: 'PULL_FROM_URL',
305+
photo_cover_index: 0,
306+
photo_images: firstPost.media?.map((p) => p.path),
307+
},
308+
post_mode: 'DIRECT_POST',
309+
media_type: 'PHOTO',
310+
}),
311+
}),
312+
}
313+
)
314+
).json();
315+
316+
const { url, id: videoId } = await this.uploadedVideoSuccess(
317+
integration.profile!,
318+
publish_id,
319+
accessToken
320+
);
321+
322+
return [
323+
{
324+
id: firstPost.id,
325+
releaseURL: url,
326+
postId: String(videoId),
327+
status: 'success',
258328
},
259-
body: JSON.stringify({
260-
...((firstPost?.settings?.content_posting_method ||
261-
'DIRECT_POST') === 'DIRECT_POST'
262-
? {
263-
post_info: {
264-
title: firstPost.message,
265-
privacy_level:
266-
firstPost.settings.privacy_level || 'PUBLIC_TO_EVERYONE',
267-
disable_duet: !firstPost.settings.duet || false,
268-
disable_comment: !firstPost.settings.comment || false,
269-
disable_stitch: !firstPost.settings.stitch || false,
270-
brand_content_toggle:
271-
firstPost.settings.brand_content_toggle || false,
272-
brand_organic_toggle:
273-
firstPost.settings.brand_organic_toggle || false,
274-
...((firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) ===
275-
-1
276-
? {
277-
auto_add_music:
278-
firstPost.settings.autoAddMusic === 'yes',
279-
}
280-
: {}),
281-
},
282-
}
283-
: {}),
284-
...((firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) > -1
285-
? {
286-
source_info: {
287-
source: 'PULL_FROM_URL',
288-
video_url: firstPost?.media?.[0]?.path!,
289-
...(firstPost?.media?.[0]?.thumbnailTimestamp!
290-
? {
291-
video_cover_timestamp_ms:
292-
firstPost?.media?.[0]?.thumbnailTimestamp!,
293-
}
294-
: {}),
295-
},
296-
}
297-
: {
298-
source_info: {
299-
source: 'PULL_FROM_URL',
300-
photo_cover_index: 0,
301-
photo_images: firstPost.media?.map((p) => p.path),
302-
},
303-
post_mode: 'DIRECT_POST',
304-
media_type: 'PHOTO',
305-
}),
306-
}),
329+
];
330+
} catch (error) {
331+
lastError = error as Error;
332+
console.log(`TikTok post attempt ${attempt} failed:`, error);
333+
334+
// If it's the last attempt, throw the error
335+
if (attempt === maxRetries) {
336+
throw error;
307337
}
308-
)
309-
).json();
310-
311-
const { url, id: videoId } = await this.uploadedVideoSuccess(
312-
integration.profile!,
313-
publish_id,
314-
accessToken
315-
);
338+
339+
// Wait before retrying (exponential backoff)
340+
await timer(attempt * 2000);
341+
}
342+
}
316343

317-
return [
318-
{
319-
id: firstPost.id,
320-
releaseURL: url,
321-
postId: String(videoId),
322-
status: 'success',
323-
},
324-
];
344+
// This should never be reached, but just in case
345+
throw lastError || new Error('TikTok post failed after all retries');
325346
}
326347
}

0 commit comments

Comments
 (0)