@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
text/typescript
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.
*/
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.';
}
}