UNPKG

@memberjunction/actions-bizapps-social

Version:

Social Media Actions for MemberJunction - Twitter, LinkedIn, Facebook, Instagram, TikTok, YouTube, HootSuite, Buffer

322 lines (293 loc) 12.2 kB
import { RegisterClass } from '@memberjunction/global'; import { YouTubeBaseAction } from '../youtube-base.action'; import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base'; import { BaseAction } from '@memberjunction/actions'; /** * Action to update metadata for a YouTube video */ @RegisterClass(BaseAction, 'YouTubeUpdateVideoMetadataAction') export class YouTubeUpdateVideoMetadataAction extends YouTubeBaseAction { /** * Update video metadata on YouTube */ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> { const { Params, ContextUser } = params; try { // Initialize OAuth const companyIntegrationId = this.getParamValue(Params, 'CompanyIntegrationID'); if (!companyIntegrationId) { throw new Error('CompanyIntegrationID is required'); } const initialized = await this.initializeOAuth(companyIntegrationId); if (!initialized) { throw new Error('Failed to initialize YouTube OAuth connection'); } // Extract parameters const videoId = this.getParamValue(Params, 'VideoID'); const title = this.getParamValue(Params, 'Title'); const description = this.getParamValue(Params, 'Description'); const tags = this.getParamValue(Params, 'Tags'); const categoryId = this.getParamValue(Params, 'CategoryID'); const privacyStatus = this.getParamValue(Params, 'PrivacyStatus'); const embeddable = this.getParamValue(Params, 'Embeddable'); const publicStatsViewable = this.getParamValue(Params, 'PublicStatsViewable'); const publishAt = this.getParamValue(Params, 'PublishAt'); const license = this.getParamValue(Params, 'License'); const recordingDate = this.getParamValue(Params, 'RecordingDate'); const thumbnailFile = this.getParamValue(Params, 'ThumbnailFile'); // Validate required parameters if (!videoId) { throw new Error('VideoID is required'); } // First, get the current video details to preserve unchanged fields const currentVideo = await this.makeYouTubeRequest<any>( '/videos', 'GET', undefined, { part: 'snippet,status', id: videoId }, ContextUser ); if (!currentVideo.items || currentVideo.items.length === 0) { throw new Error(`Video not found: ${videoId}`); } const video = currentVideo.items[0]; const currentSnippet = video.snippet; const currentStatus = video.status; // Build update request with only changed fields const updateData: any = { id: videoId, snippet: { title: title !== undefined ? title : currentSnippet.title, description: description !== undefined ? description : currentSnippet.description, tags: tags !== undefined ? tags : currentSnippet.tags, categoryId: categoryId !== undefined ? categoryId : currentSnippet.categoryId }, status: { privacyStatus: privacyStatus !== undefined ? privacyStatus : currentStatus.privacyStatus, embeddable: embeddable !== undefined ? embeddable : currentStatus.embeddable, publicStatsViewable: publicStatsViewable !== undefined ? publicStatsViewable : currentStatus.publicStatsViewable, selfDeclaredMadeForKids: currentStatus.selfDeclaredMadeForKids // Required field } }; // Add optional fields if provided if (publishAt) { updateData.status.publishAt = this.formatDate(publishAt); } if (license) { updateData.status.license = license; } if (recordingDate) { updateData.recordingDetails = { recordingDate: this.formatDate(recordingDate) }; } // Update the video const updateResponse = await this.makeYouTubeRequest<any>( '/videos', 'PUT', updateData, { part: 'snippet,status' + (recordingDate ? ',recordingDetails' : '') }, ContextUser ); // Update thumbnail if provided if (thumbnailFile) { try { await this.uploadThumbnail(videoId, thumbnailFile); } catch (error) { // Log thumbnail upload failure but don't fail the entire operation console.error('Failed to update thumbnail:', error); } } // Get updated video details const updatedVideo = await this.makeYouTubeRequest<any>( '/videos', 'GET', undefined, { part: 'snippet,status,statistics,contentDetails', id: videoId }, ContextUser ); const finalVideo = updatedVideo.items[0]; // Prepare result const result = { videoId: finalVideo.id, videoUrl: `https://www.youtube.com/watch?v=${finalVideo.id}`, updatedFields: this.getUpdatedFields(currentSnippet, currentStatus, finalVideo.snippet, finalVideo.status), title: finalVideo.snippet.title, description: finalVideo.snippet.description, tags: finalVideo.snippet.tags, categoryId: finalVideo.snippet.categoryId, privacyStatus: finalVideo.status.privacyStatus, publishAt: finalVideo.status.publishAt, embeddable: finalVideo.status.embeddable, publicStatsViewable: finalVideo.status.publicStatsViewable, thumbnails: finalVideo.snippet.thumbnails, statistics: finalVideo.statistics, quotaCost: this.getQuotaCost('videos.update') }; // Update output parameters const outputParams = [...Params]; const videoDetailsParam = outputParams.find(p => p.Name === 'VideoDetails'); if (videoDetailsParam) videoDetailsParam.Value = result; const updatedFieldsParam = outputParams.find(p => p.Name === 'UpdatedFields'); if (updatedFieldsParam) updatedFieldsParam.Value = result.updatedFields; return { Success: true, ResultCode: 'SUCCESS', Message: `Video metadata updated successfully. ${result.updatedFields.length} fields changed.`, Params: outputParams }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { Success: false, ResultCode: this.getErrorCode(errorMessage), Message: `Failed to update video metadata: ${errorMessage}`, Params }; } } /** * Upload thumbnail for a video */ private async uploadThumbnail(videoId: string, thumbnailFile: any): Promise<void> { const thumbnailData = Buffer.isBuffer(thumbnailFile.data) ? thumbnailFile.data : Buffer.from(thumbnailFile.data, 'base64'); // Validate thumbnail const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp']; if (!allowedTypes.includes(thumbnailFile.mimeType)) { throw new Error('Invalid thumbnail format. Supported formats: JPEG, PNG, GIF, BMP'); } const maxSize = 2 * 1024 * 1024; // 2MB if (thumbnailData.length > maxSize) { throw new Error(`Thumbnail too large. Maximum size is 2MB`); } await this.makeYouTubeRequest( '/thumbnails/set', 'POST', thumbnailData, { videoId: videoId } ); } /** * Determine which fields were updated */ private getUpdatedFields(oldSnippet: any, oldStatus: any, newSnippet: any, newStatus: any): string[] { const updated: string[] = []; if (oldSnippet.title !== newSnippet.title) updated.push('title'); if (oldSnippet.description !== newSnippet.description) updated.push('description'); if (JSON.stringify(oldSnippet.tags) !== JSON.stringify(newSnippet.tags)) updated.push('tags'); if (oldSnippet.categoryId !== newSnippet.categoryId) updated.push('categoryId'); if (oldStatus.privacyStatus !== newStatus.privacyStatus) updated.push('privacyStatus'); if (oldStatus.embeddable !== newStatus.embeddable) updated.push('embeddable'); if (oldStatus.publicStatsViewable !== newStatus.publicStatsViewable) updated.push('publicStatsViewable'); if (oldStatus.publishAt !== newStatus.publishAt) updated.push('publishAt'); return updated; } /** * Get error code from error message */ private getErrorCode(message: string): string { if (message.includes('quota')) return 'QUOTA_EXCEEDED'; if (message.includes('401') || message.includes('unauthorized')) return 'INVALID_TOKEN'; if (message.includes('403') || message.includes('forbidden')) return 'INSUFFICIENT_PERMISSIONS'; if (message.includes('404')) return 'NOT_FOUND'; if (message.includes('rate limit')) return 'RATE_LIMIT'; return 'ERROR'; } /** * Define the parameters this action expects */ public get Params(): ActionParam[] { const baseParams = this.commonSocialParams; const specificParams: ActionParam[] = [ { Name: 'VideoID', Type: 'Input', Value: null }, { Name: 'Title', Type: 'Input', Value: null }, { Name: 'Description', Type: 'Input', Value: null }, { Name: 'Tags', Type: 'Input', Value: null }, { Name: 'CategoryID', Type: 'Input', Value: null }, { Name: 'PrivacyStatus', Type: 'Input', Value: null }, { Name: 'Embeddable', Type: 'Input', Value: null }, { Name: 'PublicStatsViewable', Type: 'Input', Value: null }, { Name: 'PublishAt', Type: 'Input', Value: null }, { Name: 'License', Type: 'Input', Value: null }, { Name: 'RecordingDate', Type: 'Input', Value: null }, { Name: 'ThumbnailFile', Type: 'Input', Value: null }, { Name: 'VideoDetails', Type: 'Output', Value: null }, { Name: 'UpdatedFields', Type: 'Output', Value: null } ]; return [...baseParams, ...specificParams]; } /** * Metadata about this action */ public get Description(): string { return 'Updates metadata for a YouTube video including title, description, tags, privacy settings, and thumbnail'; } }