UNPKG

@memberjunction/actions-bizapps-social

Version:

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

330 lines (287 loc) 11.1 kB
import { RegisterClass } from '@memberjunction/global'; import { InstagramBaseAction } from '../instagram-base.action'; import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base'; import { LogError } from '@memberjunction/core'; import { MediaFile } from '../../../base/base-social.action'; import { BaseAction } from '@memberjunction/actions'; /** * Creates a new Instagram post (feed post, carousel, or reel). * Supports images and videos with captions, hashtags, and location tagging. */ @RegisterClass(BaseAction, 'Instagram - Create Post') export class InstagramCreatePostAction extends InstagramBaseAction { protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> { try { const companyIntegrationId = this.getParamValue(params.Params, 'CompanyIntegrationID'); const content = this.getParamValue(params.Params, 'Content'); const mediaFiles = this.getParamValue(params.Params, 'MediaFiles') as MediaFile[]; const postType = this.getParamValue(params.Params, 'PostType') || 'FEED'; const locationId = this.getParamValue(params.Params, 'LocationID'); const taggedUsers = this.getParamValue(params.Params, 'TaggedUsers') as string[]; const scheduledTime = this.getParamValue(params.Params, 'ScheduledTime'); // Initialize OAuth if (!await this.initializeOAuth(companyIntegrationId)) { return { Success: false, Message: 'Failed to initialize Instagram authentication', ResultCode: 'AUTH_FAILED' }; } // Validate inputs if (!mediaFiles || mediaFiles.length === 0) { return { Success: false, Message: 'At least one media file is required for Instagram posts', ResultCode: 'MISSING_MEDIA' }; } // Instagram has specific requirements for different post types if (postType === 'CAROUSEL' && mediaFiles.length < 2) { return { Success: false, Message: 'Carousel posts require at least 2 media files', ResultCode: 'INVALID_CAROUSEL' }; } if (postType === 'REELS' && (!mediaFiles[0].mimeType.startsWith('video/') || mediaFiles.length > 1)) { return { Success: false, Message: 'Reels require exactly one video file', ResultCode: 'INVALID_REEL' }; } // Handle scheduled posts differently if (scheduledTime) { const scheduleDate = new Date(scheduledTime); if (scheduleDate <= new Date()) { return { Success: false, Message: 'Scheduled time must be in the future', ResultCode: 'INVALID_SCHEDULE_TIME' }; } // Instagram requires Facebook Creator Studio for scheduling return { Success: false, Message: 'Instagram post scheduling requires Facebook Creator Studio integration', ResultCode: 'SCHEDULING_NOT_SUPPORTED' }; } let postId: string; if (postType === 'CAROUSEL') { postId = await this.createCarouselPost(mediaFiles, content, locationId, taggedUsers); } else if (postType === 'REELS') { postId = await this.createReelPost(mediaFiles[0], content, locationId, taggedUsers); } else { postId = await this.createFeedPost(mediaFiles[0], content, locationId, taggedUsers); } // Store result in output params const outputParams = [...params.Params]; outputParams.push({ Name: 'PostID', Type: 'Output', Value: postId }); outputParams.push({ Name: 'Permalink', Type: 'Output', Value: `https://www.instagram.com/p/${postId}/` }); outputParams.push({ Name: 'PostType', Type: 'Output', Value: postType }); return { Success: true, Message: `Instagram ${postType.toLowerCase()} created successfully`, ResultCode: 'SUCCESS', Params: outputParams }; } catch (error: any) { LogError('Failed to create Instagram post', error); if (error.code === 'RATE_LIMIT') { return { Success: false, Message: 'Instagram API rate limit exceeded. Please try again later.', ResultCode: 'RATE_LIMIT' }; } if (error.code === 'INVALID_MEDIA') { return { Success: false, Message: error.message, ResultCode: 'INVALID_MEDIA' }; } return { Success: false, Message: `Failed to create Instagram post: ${error.message}`, ResultCode: 'ERROR' }; } } /** * Create a standard feed post (single image or video) */ private async createFeedPost( mediaFile: MediaFile, caption: string, locationId?: string, taggedUsers?: string[] ): Promise<string> { // Add metadata to media file (mediaFile as any).metadata = { caption }; // Upload media and get container ID const containerId = await this.uploadSingleMedia(mediaFile); // Wait for media to be processed await this.waitForMediaContainer(containerId); // Add additional parameters if provided const publishParams: any = { creation_id: containerId, access_token: this.getAccessToken() }; if (locationId) { publishParams.location_id = locationId; } if (taggedUsers && taggedUsers.length > 0) { publishParams.user_tags = taggedUsers.map(userId => ({ username: userId, x: 0.5, // Center of image y: 0.5 })); } // Publish the post const response = await this.makeInstagramRequest<{ id: string }>( `${this.instagramBusinessAccountId}/media_publish`, 'POST', publishParams ); return response.id; } /** * Create a carousel post (multiple images/videos) */ private async createCarouselPost( mediaFiles: MediaFile[], caption: string, locationId?: string, taggedUsers?: string[] ): Promise<string> { // Upload all media items as carousel items const containerIds: string[] = []; for (const file of mediaFiles.slice(0, 10)) { // Instagram allows max 10 items file.filename = `carousel_${file.filename}`; // Mark as carousel item const containerId = await this.uploadSingleMedia(file); containerIds.push(containerId); } // Wait for all media to be processed await Promise.all(containerIds.map(id => this.waitForMediaContainer(id))); // Create carousel container const carouselParams: any = { media_type: 'CAROUSEL', children: containerIds, caption: caption, access_token: this.getAccessToken() }; if (locationId) { carouselParams.location_id = locationId; } const carouselResponse = await this.makeInstagramRequest<{ id: string }>( `${this.instagramBusinessAccountId}/media`, 'POST', carouselParams ); // Wait for carousel container to be ready await this.waitForMediaContainer(carouselResponse.id); // Publish the carousel const publishResponse = await this.makeInstagramRequest<{ id: string }>( `${this.instagramBusinessAccountId}/media_publish`, 'POST', { creation_id: carouselResponse.id, access_token: this.getAccessToken() } ); return publishResponse.id; } /** * Create a Reel post */ private async createReelPost( videoFile: MediaFile, caption: string, locationId?: string, taggedUsers?: string[] ): Promise<string> { // Validate video duration (Reels must be 90 seconds or less) // In production, you'd check the actual video duration // Add metadata (videoFile as any).metadata = { caption, media_type: 'REELS' }; // Upload video const containerId = await this.uploadSingleMedia(videoFile); // Wait for video to be processed (videos take longer) await this.waitForMediaContainer(containerId, 300000); // 5 minute timeout for videos // Publish the reel const publishParams: any = { creation_id: containerId, access_token: this.getAccessToken() }; if (locationId) { publishParams.location_id = locationId; } const response = await this.makeInstagramRequest<{ id: string }>( `${this.instagramBusinessAccountId}/media_publish`, 'POST', publishParams ); return response.id; } /** * Define the parameters for this action */ public get Params(): ActionParam[] { return [ ...this.commonSocialParams, { Name: 'Content', Type: 'Input', Value: null, }, { Name: 'MediaFiles', Type: 'Input', Value: null, }, { Name: 'PostType', Type: 'Input', Value: 'FEED', }, { Name: 'LocationID', Type: 'Input', Value: null, }, { Name: 'TaggedUsers', Type: 'Input', Value: null, }, { Name: 'ScheduledTime', Type: 'Input', Value: null, } ]; } /** * Get the description for this action */ public get Description(): string { return 'Creates a new Instagram post with images or videos. Supports feed posts, carousels, and reels.'; } }