@memberjunction/actions-bizapps-social
Version:
Social Media Actions for MemberJunction - Twitter, LinkedIn, Facebook, Instagram, TikTok, YouTube, HootSuite, Buffer
282 lines (251 loc) • 10.3 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 { SocialPost } from '../../../base/base-social.action';
import { BaseAction } from '@memberjunction/actions';
/**
* Retrieves posts from an Instagram Business account.
* Returns feed posts, carousels, reels, and IGTV videos with basic metrics.
*/
(BaseAction, 'Instagram - Get Business Posts')
export class InstagramGetBusinessPostsAction extends InstagramBaseAction {
protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
try {
const companyIntegrationId = this.getParamValue(params.Params, 'CompanyIntegrationID');
const limit = this.getParamValue(params.Params, 'Limit') || 25;
const mediaType = this.getParamValue(params.Params, 'MediaType');
const includeMetrics = this.getParamValue(params.Params, 'IncludeMetrics') !== false;
const afterCursor = this.getParamValue(params.Params, 'AfterCursor');
const startDate = this.getParamValue(params.Params, 'StartDate');
const endDate = this.getParamValue(params.Params, 'EndDate');
// Initialize OAuth
if (!await this.initializeOAuth(companyIntegrationId)) {
return {
Success: false,
Message: 'Failed to initialize Instagram authentication',
ResultCode: 'AUTH_FAILED'
};
}
// Build fields parameter
let fields = 'id,caption,media_type,media_url,thumbnail_url,permalink,timestamp';
if (includeMetrics) {
fields += ',like_count,comments_count,impressions,reach,saved,video_views';
}
// Build query parameters
const queryParams: any = {
fields,
access_token: this.getAccessToken(),
limit: Math.min(limit, 100) // Instagram max is 100 per request
};
if (afterCursor) {
queryParams.after = afterCursor;
}
// Add date filtering if provided
if (startDate) {
queryParams.since = Math.floor(new Date(startDate).getTime() / 1000);
}
if (endDate) {
queryParams.until = Math.floor(new Date(endDate).getTime() / 1000);
}
// Fetch posts
const response = await this.makeInstagramRequest<{
data: any[];
paging?: {
cursors: {
before: string;
after: string;
};
next?: string;
previous?: string;
};
}>(
`${this.instagramBusinessAccountId}/media`,
'GET',
null,
queryParams
);
// Filter by media type if specified
let posts = response.data || [];
if (mediaType) {
posts = posts.filter(post => post.media_type === mediaType);
}
// Transform to common format
const normalizedPosts: SocialPost[] = posts.map(post => this.normalizePost(post));
// Get additional insights if requested and available
if (includeMetrics && posts.length > 0) {
await this.enrichPostsWithInsights(normalizedPosts);
}
// Store result in output params
const outputParams = [...params.Params];
outputParams.push({
Name: 'ResultData',
Type: 'Output',
Value: JSON.stringify({
posts: normalizedPosts,
paging: {
hasNext: !!response.paging?.next,
hasPrevious: !!response.paging?.previous,
afterCursor: response.paging?.cursors?.after,
beforeCursor: response.paging?.cursors?.before
},
summary: {
totalPosts: normalizedPosts.length,
mediaTypes: this.summarizeMediaTypes(posts),
dateRange: this.getDateRange(normalizedPosts)
}
})
});
return {
Success: true,
Message: `Retrieved ${normalizedPosts.length} Instagram posts`,
ResultCode: 'SUCCESS',
Params: outputParams
};
} catch (error: any) {
LogError('Failed to retrieve Instagram business posts', 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 === 'INSUFFICIENT_PERMISSIONS') {
return {
Success: false,
Message: 'Insufficient permissions to access Instagram business posts',
ResultCode: 'INSUFFICIENT_PERMISSIONS'
};
}
return {
Success: false,
Message: `Failed to retrieve Instagram posts: ${error.message}`,
ResultCode: 'ERROR'
};
}
}
/**
* Enrich posts with additional insights data
*/
private async enrichPostsWithInsights(posts: SocialPost[]): Promise<void> {
// Instagram allows batch insights requests
const postIds = posts.map(p => p.id);
const batchSize = 50; // Instagram's batch limit
for (let i = 0; i < postIds.length; i += batchSize) {
const batch = postIds.slice(i, i + batchSize);
try {
// Get insights for this batch
const metrics = ['impressions', 'reach', 'engagement', 'saved', 'video_views'];
const insightsPromises = batch.map(postId =>
this.getInsights(postId, metrics, 'lifetime')
);
const insightsResults = await Promise.allSettled(insightsPromises);
// Map insights back to posts
insightsResults.forEach((result, index) => {
if (result.status === 'fulfilled' && result.value) {
const postIndex = i + index;
const post = posts[postIndex];
// Parse insights data
result.value.forEach((metric: any) => {
const value = metric.values?.[0]?.value || 0;
switch (metric.name) {
case 'impressions':
post.analytics!.impressions = value;
break;
case 'reach':
post.analytics!.reach = value;
break;
case 'engagement':
post.analytics!.engagements = value;
break;
case 'saved':
post.analytics!.saves = value;
break;
case 'video_views':
post.analytics!.videoViews = value;
break;
}
});
}
});
} catch (error) {
// Log but don't fail the whole operation
LogError(`Failed to get insights for batch starting at ${i}`, error);
}
}
}
/**
* Summarize media types in the response
*/
private summarizeMediaTypes(posts: any[]): Record<string, number> {
const summary: Record<string, number> = {
IMAGE: 0,
VIDEO: 0,
CAROUSEL_ALBUM: 0
};
posts.forEach(post => {
if (summary[post.media_type] !== undefined) {
summary[post.media_type]++;
}
});
return summary;
}
/**
* Get date range of posts
*/
private getDateRange(posts: SocialPost[]): { earliest: Date | null; latest: Date | null } {
if (posts.length === 0) {
return { earliest: null, latest: null };
}
const dates = posts.map(p => p.publishedAt.getTime());
return {
earliest: new Date(Math.min(...dates)),
latest: new Date(Math.max(...dates))
};
}
/**
* Define the parameters for this action
*/
public get Params(): ActionParam[] {
return [
...this.commonSocialParams,
{
Name: 'Limit',
Type: 'Input',
Value: 25
},
{
Name: 'MediaType',
Type: 'Input',
Value: null
},
{
Name: 'IncludeMetrics',
Type: 'Input',
Value: true
},
{
Name: 'AfterCursor',
Type: 'Input',
Value: null
},
{
Name: 'StartDate',
Type: 'Input',
Value: null
},
{
Name: 'EndDate',
Type: 'Input',
Value: null
}
];
}
/**
* Get the description for this action
*/
public get Description(): string {
return 'Retrieves posts from an Instagram Business account with optional metrics and filtering.';
}
}