feat: better error messages, retry fix

This commit is contained in:
Nevo David 2025-07-09 20:53:49 +07:00
parent 159f65a127
commit 4ddea7977f
9 changed files with 570 additions and 272 deletions

View File

@ -163,11 +163,11 @@ export class IntegrationService {
await this.informAboutRefreshError(orgId, integration);
}
async informAboutRefreshError(orgId: string, integration: Integration) {
async informAboutRefreshError(orgId: string, integration: Integration, err = '') {
await this._notificationService.inAppNotification(
orgId,
`Could not refresh your ${integration.providerIdentifier} channel`,
`Could not refresh your ${integration.providerIdentifier} channel. Please go back to the system and connect it again ${process.env.FRONTEND_URL}/launches`,
`Could not refresh your ${integration.providerIdentifier} channel ${err}`,
`Could not refresh your ${integration.providerIdentifier} channel ${err}. Please go back to the system and connect it again ${process.env.FRONTEND_URL}/launches`,
true
);
}

View File

@ -299,16 +299,10 @@ export class PostsService {
}
try {
const finalPost =
firstPost.integration?.type === 'article'
? await this.postArticle(firstPost.integration!, [
firstPost,
...morePosts,
])
: await this.postSocial(firstPost.integration!, [
firstPost,
...morePosts,
]);
const finalPost = await this.postSocial(firstPost.integration!, [
firstPost,
...morePosts,
]);
if (firstPost?.intervalInDays) {
this._workerServiceProducer.emit('post', {
@ -333,31 +327,16 @@ export class PostsService {
return;
}
if (firstPost.submittedForOrderId) {
this._workerServiceProducer.emit('submit', {
payload: {
id: firstPost.id,
releaseURL: finalPost.releaseURL,
},
});
}
} catch (err: any) {
await this._postRepository.changeState(firstPost.id, 'ERROR', err);
await this._notificationService.inAppNotification(
firstPost.organizationId,
`Error posting on ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name}`,
`An error occurred while posting on ${
firstPost.integration?.providerIdentifier
} ${
!process.env.NODE_ENV || process.env.NODE_ENV === 'development'
? err
: ''
}`,
true
);
if (err instanceof BadBody) {
await this._notificationService.inAppNotification(
firstPost.organizationId,
`Error posting on ${firstPost.integration?.providerIdentifier} for ${firstPost?.integration?.name}`,
`An error occurred while posting on ${firstPost.integration?.providerIdentifier}${err?.message ? `: ${err?.message}` : ``}`,
true
);
console.error(
'[Error] posting on',
firstPost.integration?.providerIdentifier,
@ -366,15 +345,9 @@ export class PostsService {
err.body,
err
);
return;
}
console.error(
'[Error] posting on',
firstPost.integration?.providerIdentifier,
err
);
return;
}
}
@ -403,7 +376,8 @@ export class PostsService {
private async postSocial(
integration: Integration,
posts: Post[],
forceRefresh = false
forceRefresh = false,
err = ''
): Promise<Partial<{ postId: string; releaseURL: string }>> {
const getIntegration = this._integrationManager.getSocialIntegration(
integration.providerIdentifier
@ -533,7 +507,7 @@ export class PostsService {
};
} catch (err) {
if (err instanceof RefreshToken) {
return this.postSocial(integration, posts, true);
return this.postSocial(integration, posts, true, err?.message || '');
}
throw err;
@ -627,41 +601,6 @@ export class PostsService {
}
}
private async postArticle(
integration: Integration,
posts: Post[]
): Promise<any> {
const getIntegration = this._integrationManager.getArticlesIntegration(
integration.providerIdentifier
);
if (!getIntegration) {
return;
}
const newPosts = await this.updateTags(integration.organizationId, posts);
const { postId, releaseURL } = await getIntegration.post(
integration.token,
newPosts.map((p) => p.content).join('\n\n'),
JSON.parse(newPosts[0].settings || '{}')
);
await this._notificationService.inAppNotification(
integration.organizationId,
`Your article has been published on ${capitalize(
integration.providerIdentifier
)}`,
`Your article has been published at ${releaseURL}`,
true
);
await this._postRepository.updatePost(newPosts[0].id, postId, releaseURL);
return {
postId,
releaseURL,
};
}
async deletePost(orgId: string, group: string) {
const post = await this._postRepository.deletePost(orgId, group);
if (post?.id) {
@ -738,7 +677,7 @@ export class PostsService {
);
if (!posts?.length) {
return;
return [] as any[];
}
await this._workerServiceProducer.delete(

View File

@ -89,7 +89,7 @@ export class IntegrationManager {
plugs: (
Reflect.getMetadata('custom:plug', p.constructor.prototype) || []
)
.filter((f) => !f.disabled)
.filter((f: any) => !f.disabled)
.map((p: any) => ({
...p,
fields: p.fields.map((c: any) => ({
@ -111,7 +111,7 @@ export class IntegrationManager {
'custom:internal_plug',
p.constructor.prototype
) || []
).filter((f) => !f.disabled) || [],
).filter((f: any) => !f.disabled) || [],
};
}

View File

@ -6,7 +6,7 @@ export class RefreshToken {
public identifier: string,
public json: string,
public body: BodyInit,
public message = 'refresh account',
public message = '',
) {}
}
export class BadBody {
@ -14,7 +14,7 @@ export class BadBody {
public identifier: string,
public json: string,
public body: BodyInit,
public message = 'error occurred'
public message = ''
) {}
}
@ -24,7 +24,7 @@ export class NotEnoughScopes {
const pThrottleInstance = pThrottle({
limit: 1,
interval: 2000
interval: 5000
});
export abstract class SocialAbstract {
@ -32,7 +32,7 @@ export abstract class SocialAbstract {
(url: RequestInfo, options?: RequestInit) => fetch(url, options)
);
protected handleErrors(body: string): {type: 'refresh-token' | 'bad-body', value: string}|undefined {
public handleErrors(body: string): {type: 'refresh-token' | 'bad-body', value: string}|undefined {
return {type: 'bad-body', value: 'bad request'};
}
@ -61,7 +61,7 @@ export abstract class SocialAbstract {
}
if (json.includes('rate_limit_exceeded') || json.includes('Rate limit')) {
await timer(2000);
await timer(5000);
return this.fetch(url, options, identifier, totalRetries + 1);
}
@ -71,24 +71,11 @@ export abstract class SocialAbstract {
throw new RefreshToken(identifier, json, options.body!, handleError?.value);
}
// if (
// request.status === 401 ||
// (json.includes('OAuthException') &&
// !json.includes('The user is not an Instagram Business') &&
// !json.includes('Unsupported format') &&
// !json.includes('2207018') &&
// !json.includes('352') &&
// !json.includes('REVOKED_ACCESS_TOKEN'))
// ) {
// throw new RefreshToken(identifier, json, options.body!);
// }
if (totalRetries < 2) {
await timer(2000);
return this.fetch(url, options, identifier, totalRetries + 1);
}
throw new BadBody(identifier, json, options.body!, handleError.value);
throw new BadBody(identifier, json, options.body!, handleError?.value);
}
checkScopes(required: string[], got: string | string[]) {

View File

@ -21,6 +21,110 @@ export class FacebookProvider extends SocialAbstract implements SocialProvider {
'pages_read_engagement',
'read_insights',
];
override handleErrors(body: string): {
type: 'refresh-token' | 'bad-body';
value: string;
} | undefined {
// Access token validation errors - require re-authentication
if (body.indexOf('Error validating access token') > -1) {
return {
type: 'refresh-token' as const,
value: 'Please re-authenticate your Facebook account',
};
}
if (body.indexOf('490') > -1) {
return {
type: 'refresh-token' as const,
value: 'Access token expired, please re-authenticate',
};
}
if (body.indexOf('REVOKED_ACCESS_TOKEN') > -1) {
return {
type: 'refresh-token' as const,
value: 'Access token has been revoked, please re-authenticate',
};
}
if (body.indexOf('1390008') > -1) {
return {
type: 'bad-body' as const,
value: 'You are posting too fast, please slow down',
};
}
// Content policy violations
if (body.indexOf('1346003') > -1) {
return {
type: 'bad-body' as const,
value: 'Content flagged as abusive by Facebook',
};
}
if (body.indexOf('1404102') > -1) {
return {
type: 'bad-body' as const,
value: 'Content violates Facebook Community Standards',
};
}
// Permission errors
if (body.indexOf('1404078') > -1) {
return {
type: 'refresh-token' as const,
value: 'Page publishing authorization required, please re-authenticate',
};
}
if (body.indexOf('1609008') > -1) {
return {
type: 'bad-body' as const,
value: 'Cannot post Facebook.com links',
};
}
// Parameter validation errors
if (body.indexOf('2061006') > -1) {
return {
type: 'bad-body' as const,
value: 'Invalid URL format in post content',
};
}
if (body.indexOf('1349125') > -1) {
return {
type: 'bad-body' as const,
value: 'Invalid content format',
};
}
if (body.indexOf('Name parameter too long') > -1) {
return {
type: 'bad-body' as const,
value: 'Post content is too long',
};
}
// Service errors - checking specific subcodes first
if (body.indexOf('1363047') > -1) {
return {
type: 'bad-body' as const,
value: 'Facebook service temporarily unavailable',
};
}
if (body.indexOf('1609010') > -1) {
return {
type: 'bad-body' as const,
value: 'Facebook service temporarily unavailable',
};
}
return undefined;
}
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
return {
refreshToken: '',

View File

@ -43,17 +43,28 @@ export class InstagramProvider
};
}
protected override handleErrors(body: string): {
type: 'refresh-token' | 'bad-body';
value: string;
} | undefined {
if (body.indexOf('2207018') > -1) {
public override handleErrors(body: string):
| {
type: 'refresh-token' | 'bad-body';
value: string;
}
| undefined {
if (body.indexOf('2207018') > -1 || body.indexOf('REVOKED_ACCESS_TOKEN')) {
return {
type: 'refresh-token' as const,
value:
'Something is wrong with your connected user, please re-authenticate',
};
}
if (body.indexOf('The user is not an Instagram Business') > -1) {
return {
type: 'refresh-token' as const,
value:
'Your Instagram account is not a business account, please convert it to a business account',
};
}
if (body.indexOf('Error validating access token') > -1) {
return {
type: 'refresh-token' as const,
@ -68,6 +79,143 @@ export class InstagramProvider
};
}
// Media download/upload errors
if (body.indexOf('2207003') > -1) {
return {
type: 'bad-body' as const,
value: 'Timeout downloading media, please try again',
};
}
if (body.indexOf('2207020') > -1) {
return {
type: 'bad-body' as const,
value: 'Media expired, please upload again',
};
}
if (body.indexOf('2207032') > -1) {
return {
type: 'bad-body' as const,
value: 'Failed to create media, please try again',
};
}
if (body.indexOf('2207053') > -1) {
return {
type: 'bad-body' as const,
value: 'Unknown upload error, please try again',
};
}
if (body.indexOf('2207052') > -1) {
return {
type: 'bad-body' as const,
value: 'Media fetch failed, please try again',
};
}
if (body.indexOf('2207057') > -1) {
return {
type: 'bad-body' as const,
value: 'Invalid thumbnail offset for video',
};
}
if (body.indexOf('2207026') > -1) {
return {
type: 'bad-body' as const,
value: 'Unsupported video format',
};
}
if (body.indexOf('2207023') > -1) {
return {
type: 'bad-body' as const,
value: 'Unknown media type',
};
}
if (body.indexOf('2207006') > -1) {
return {
type: 'bad-body' as const,
value: 'Media not found, please upload again',
};
}
if (body.indexOf('2207008') > -1) {
return {
type: 'bad-body' as const,
value: 'Media builder expired, please try again',
};
}
// Content validation errors
if (body.indexOf('2207028') > -1) {
return {
type: 'bad-body' as const,
value: 'Carousel validation failed',
};
}
if (body.indexOf('2207010') > -1) {
return {
type: 'bad-body' as const,
value: 'Caption is too long',
};
}
// Product tagging errors
if (body.indexOf('2207035') > -1) {
return {
type: 'bad-body' as const,
value: 'Product tag positions not supported for videos',
};
}
if (body.indexOf('2207036') > -1) {
return {
type: 'bad-body' as const,
value: 'Product tag positions required for photos',
};
}
if (body.indexOf('2207037') > -1) {
return {
type: 'bad-body' as const,
value: 'Product tag validation failed',
};
}
if (body.indexOf('2207040') > -1) {
return {
type: 'bad-body' as const,
value: 'Too many product tags',
};
}
// Image format/size errors
if (body.indexOf('2207004') > -1) {
return {
type: 'bad-body' as const,
value: 'Image is too large',
};
}
if (body.indexOf('2207005') > -1) {
return {
type: 'bad-body' as const,
value: 'Unsupported image format',
};
}
if (body.indexOf('2207009') > -1) {
return {
type: 'bad-body' as const,
value: 'Aspect ratio not supported, must be between 4:5 to 1.91:1',
};
}
if (body.indexOf('Page request limit reached') > -1) {
return {
type: 'bad-body' as const,
@ -75,6 +223,14 @@ export class InstagramProvider
};
}
if (body.indexOf('2207042') > -1) {
return {
type: 'bad-body' as const,
value:
'You have reached the maximum of 25 posts per day, allowed for your account',
};
}
if (body.indexOf('Not enough permissions to post') > -1) {
return {
type: 'bad-body' as const,
@ -96,26 +252,10 @@ export class InstagramProvider
};
}
if (body.indexOf('2207027') > -1) {
return {
type: 'bad-body' as const,
value: 'Unknown error, please try again later or contact support',
};
}
if (body.indexOf('2207042') > -1) {
return {
type: 'bad-body' as const,
value:
'You have reached the maximum of 25 posts per day, allowed for your account',
};
}
if (body.indexOf('2207051') > -1) {
return {
type: 'bad-body' as const,
value:
'Instagram blocked your request',
value: 'Instagram blocked your request',
};
}
@ -127,6 +267,13 @@ export class InstagramProvider
};
}
if (body.indexOf('2207027') > -1) {
return {
type: 'bad-body' as const,
value: 'Unknown error, please try again later or contact support',
};
}
return undefined;
}

View File

@ -27,6 +27,10 @@ export class InstagramStandaloneProvider
'instagram_business_manage_insights',
];
public override handleErrors(body: string): { type: "refresh-token" | "bad-body"; value: string } | undefined {
return instagramProvider.handleErrors(body);
}
async refreshToken(refresh_token: string): Promise<AuthTokenDetails> {
const { access_token } = await (
await this.fetch(

View File

@ -25,6 +25,125 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
'user.info.profile',
];
override handleErrors(body: string):
| {
type: 'refresh-token' | 'bad-body';
value: string;
}
| undefined {
// Authentication/Authorization errors - require re-authentication
if (body.indexOf('access_token_invalid') > -1) {
return {
type: 'refresh-token' as const,
value:
'Access token invalid, please re-authenticate your TikTok account',
};
}
if (body.indexOf('scope_not_authorized') > -1) {
return {
type: 'refresh-token' as const,
value:
'Missing required permissions, please re-authenticate with all scopes',
};
}
if (body.indexOf('scope_permission_missed') > -1) {
return {
type: 'refresh-token' as const,
value: 'Additional permissions required, please re-authenticate',
};
}
// Rate limiting errors
if (body.indexOf('rate_limit_exceeded') > -1) {
return {
type: 'bad-body' as const,
value: 'TikTok API rate limit exceeded, please try again later',
};
}
// Spam/Policy errors
if (body.indexOf('spam_risk_too_many_posts') > -1) {
return {
type: 'bad-body' as const,
value: 'Daily post limit reached, please try again tomorrow',
};
}
if (body.indexOf('spam_risk_user_banned_from_posting') > -1) {
return {
type: 'bad-body' as const,
value:
'Account banned from posting, please check TikTok account status',
};
}
if (body.indexOf('reached_active_user_cap') > -1) {
return {
type: 'bad-body' as const,
value: 'Daily active user quota reached, please try again later',
};
}
if (
body.indexOf('unaudited_client_can_only_post_to_private_accounts') > -1
) {
return {
type: 'bad-body' as const,
value: 'App not approved for public posting, contact support',
};
}
if (body.indexOf('url_ownership_unverified') > -1) {
return {
type: 'bad-body' as const,
value: 'URL ownership not verified, please verify domain ownership',
};
}
if (body.indexOf('privacy_level_option_mismatch') > -1) {
return {
type: 'bad-body' as const,
value: 'Privacy level mismatch, please check privacy settings',
};
}
// Content/Format validation errors
if (body.indexOf('invalid_file_upload') > -1) {
return {
type: 'bad-body' as const,
value: 'Invalid file format or specifications not met',
};
}
if (body.indexOf('invalid_params') > -1) {
return {
type: 'bad-body' as const,
value: 'Invalid request parameters, please check content format',
};
}
// Server errors
if (body.indexOf('internal_error') > -1) {
return {
type: 'bad-body' as const,
value: 'TikTok server error, please try again later',
};
}
// Generic TikTok API errors
if (body.indexOf('TikTok API error') > -1) {
return {
type: 'bad-body' as const,
value: 'TikTok API error, please try again',
};
}
// Fall back to parent class error handling
return undefined;
}
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
const value = {
client_key: process.env.TIKTOK_CLIENT_ID!,
@ -240,108 +359,85 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
integration: Integration
): Promise<PostResponse[]> {
const [firstPost, ...comments] = postDetails;
const maxRetries = 3;
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`TikTok post attempt ${attempt}/${maxRetries}`, firstPost);
const {
data: { publish_id },
} = await (
await this.fetch(
`https://open.tiktokapis.com/v2/post/publish${this.postingMethod(
firstPost.settings.content_posting_method,
(firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) === -1
)}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
...((firstPost?.settings?.content_posting_method ||
'DIRECT_POST') === 'DIRECT_POST'
? {
post_info: {
title: firstPost.message,
privacy_level:
firstPost.settings.privacy_level || 'PUBLIC_TO_EVERYONE',
disable_duet: !firstPost.settings.duet || false,
disable_comment: !firstPost.settings.comment || false,
disable_stitch: !firstPost.settings.stitch || false,
brand_content_toggle:
firstPost.settings.brand_content_toggle || false,
brand_organic_toggle:
firstPost.settings.brand_organic_toggle || false,
...((firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) ===
-1
? {
auto_add_music:
firstPost.settings.autoAddMusic === 'yes',
}
: {}),
},
}
: {}),
...((firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) > -1
? {
source_info: {
source: 'PULL_FROM_URL',
video_url: firstPost?.media?.[0]?.path!,
...(firstPost?.media?.[0]?.thumbnailTimestamp!
? {
video_cover_timestamp_ms:
firstPost?.media?.[0]?.thumbnailTimestamp!,
}
: {}),
},
}
: {
source_info: {
source: 'PULL_FROM_URL',
photo_cover_index: 0,
photo_images: firstPost.media?.map((p) => p.path),
},
post_mode: 'DIRECT_POST',
media_type: 'PHOTO',
}),
}),
}
)
).json();
const { url, id: videoId } = await this.uploadedVideoSuccess(
integration.profile!,
publish_id,
accessToken
);
return [
{
id: firstPost.id,
releaseURL: url,
postId: String(videoId),
status: 'success',
const {
data: { publish_id },
} = await (
await this.fetch(
`https://open.tiktokapis.com/v2/post/publish${this.postingMethod(
firstPost.settings.content_posting_method,
(firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) === -1
)}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Authorization: `Bearer ${accessToken}`,
},
];
} catch (error) {
lastError = error as Error;
console.log(`TikTok post attempt ${attempt} failed:`, error);
// If it's the last attempt, throw the error
if (attempt === maxRetries) {
throw error;
body: JSON.stringify({
...((firstPost?.settings?.content_posting_method ||
'DIRECT_POST') === 'DIRECT_POST'
? {
post_info: {
title: firstPost.message,
privacy_level: firstPost.settings.privacy_level || 'PUBLIC_TO_EVERYONE',
disable_duet: !firstPost.settings.duet || false,
disable_comment: !firstPost.settings.comment || false,
disable_stitch: !firstPost.settings.stitch || false,
brand_content_toggle:
firstPost.settings.brand_content_toggle || false,
brand_organic_toggle:
firstPost.settings.brand_organic_toggle || false,
...((firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) ===
-1
? {
auto_add_music:
firstPost.settings.autoAddMusic === 'yes',
}
: {}),
},
}
: {}),
...((firstPost?.media?.[0]?.path?.indexOf('mp4') || -1) > -1
? {
source_info: {
source: 'PULL_FROM_URL',
video_url: firstPost?.media?.[0]?.path!,
...(firstPost?.media?.[0]?.thumbnailTimestamp!
? {
video_cover_timestamp_ms:
firstPost?.media?.[0]?.thumbnailTimestamp!,
}
: {}),
},
}
: {
source_info: {
source: 'PULL_FROM_URL',
photo_cover_index: 0,
photo_images: firstPost.media?.map((p) => p.path),
},
post_mode: 'DIRECT_POST',
media_type: 'PHOTO',
}),
}),
}
// Wait before retrying (exponential backoff)
await timer(attempt * 2000);
}
}
)
).json();
// This should never be reached, but just in case
throw lastError || new Error('TikTok post failed after all retries');
const { url, id: videoId } = await this.uploadedVideoSuccess(
integration.profile!,
publish_id,
accessToken
);
return [
{
id: firstPost.id,
releaseURL: url,
postId: String(videoId),
status: 'success',
},
];
}
}

View File

@ -6,14 +6,18 @@ import {
SocialProvider,
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
import { google } from 'googleapis';
import { google, youtube_v3 } from 'googleapis';
import { OAuth2Client } from 'google-auth-library/build/src/auth/oauth2client';
import axios from 'axios';
import { YoutubeSettingsDto } from '@gitroom/nestjs-libraries/dtos/posts/providers-settings/youtube.settings.dto';
import { SocialAbstract } from '@gitroom/nestjs-libraries/integrations/social.abstract';
import {
BadBody,
SocialAbstract,
} from '@gitroom/nestjs-libraries/integrations/social.abstract';
import * as process from 'node:process';
import dayjs from 'dayjs';
import { GaxiosError } from 'gaxios/build/src/common';
import { GaxiosResponse } from 'gaxios/build/src/common';
import Schema$Video = youtube_v3.Schema$Video;
const clientAndYoutube = () => {
const client = new google.auth.OAuth2({
@ -147,8 +151,9 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider {
responseType: 'stream',
});
let all: GaxiosResponse<Schema$Video>;
try {
const all = await youtubeClient.videos.insert({
all = await youtubeClient.videos.insert({
part: ['id', 'snippet', 'status'],
notifySubscribers: true,
requestBody: {
@ -158,15 +163,6 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider {
...(settings?.tags?.length
? { tags: settings.tags.map((p) => p.label) }
: {}),
// ...(settings?.thumbnail?.url
// ? {
// thumbnails: {
// default: {
// url: settings?.thumbnail?.url,
// },
// },
// }
// : {}),
},
status: {
privacyStatus: settings.type,
@ -176,58 +172,83 @@ export class YoutubeProvider extends SocialAbstract implements SocialProvider {
body: response.data,
},
});
if (settings?.thumbnail?.path) {
try {
const allb = await youtubeClient.thumbnails.set({
videoId: all?.data?.id!,
media: {
body: (
await axios({
url: settings?.thumbnail?.path,
method: 'GET',
responseType: 'stream',
})
).data,
},
});
} catch (err: any) {
if (
err.response?.data?.error?.errors?.[0]?.domain ===
'youtube.thumbnail'
) {
throw 'Your account is not verified, we have uploaded your video but we could not set the thumbnail. Please verify your account and try again.';
}
}
}
return [
{
id: firstPost.id,
releaseURL: `https://www.youtube.com/watch?v=${all?.data?.id}`,
postId: all?.data?.id!,
status: 'success',
},
];
} catch (err: any) {
if (
err.response?.data?.error?.errors?.[0]?.reason === 'failedPrecondition'
) {
throw 'We have uploaded your video but we could not set the thumbnail. Thumbnail size is too large';
throw new BadBody(
'youtube',
JSON.stringify(err.response.data),
JSON.stringify(err.response.data),
'We have uploaded your video but we could not set the thumbnail. Thumbnail size is too large.'
);
}
if (
err.response?.data?.error?.errors?.[0]?.reason === 'uploadLimitExceeded'
) {
throw 'You have reached your daily upload limit, please try again tomorrow.';
throw new BadBody(
'youtube',
JSON.stringify(err.response.data),
JSON.stringify(err.response.data),
'You have reached your daily upload limit, please try again tomorrow.'
);
}
if (
err.response?.data?.error?.errors?.[0]?.reason ===
'youtubeSignupRequired'
) {
throw 'You have to link your youtube account to your google account first.';
throw new BadBody(
'youtube',
JSON.stringify(err.response.data),
JSON.stringify(err.response.data),
'You have to link your youtube account to your google account first.'
);
}
throw new BadBody(
'youtube',
JSON.stringify(err.response.data),
JSON.stringify(err.response.data),
'An error occurred while uploading your video, please try again later.'
);
}
if (settings?.thumbnail?.path) {
try {
await youtubeClient.thumbnails.set({
videoId: all?.data?.id!,
media: {
body: (
await axios({
url: settings?.thumbnail?.path,
method: 'GET',
responseType: 'stream',
})
).data,
},
});
} catch (err: any) {
if (
err.response?.data?.error?.errors?.[0]?.domain === 'youtube.thumbnail'
) {
throw new BadBody(
'',
JSON.stringify(err.response.data),
JSON.stringify(err.response.data),
'Your account is not verified, we have uploaded your video but we could not set the thumbnail. Please verify your account and try again.'
);
}
}
}
return [];
return [
{
id: firstPost.id,
releaseURL: `https://www.youtube.com/watch?v=${all?.data?.id}`,
postId: all?.data?.id!,
status: 'success',
},
];
}
async analytics(