@memberjunction/actions-bizapps-social
Version:
Social Media Actions for MemberJunction - Twitter, LinkedIn, Facebook, Instagram, TikTok, YouTube, HootSuite, Buffer
339 lines (292 loc) • 12 kB
text/typescript
import { RegisterClass } from '@memberjunction/global';
import { FacebookBaseAction, FacebookInsight } from '../facebook-base.action';
import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
import { SocialMediaErrorCode } from '../../../base/base-social.action';
import { LogStatus, LogError } from '@memberjunction/core';
import axios from 'axios';
import { BaseAction } from '@memberjunction/actions';
/**
* Retrieves detailed insights and analytics for a specific Facebook post.
* Provides metrics like reach, impressions, engagement, and more.
*/
(BaseAction, 'FacebookGetPostInsightsAction')
export class FacebookGetPostInsightsAction extends FacebookBaseAction {
/**
* Get action description
*/
public get Description(): string {
return 'Retrieves detailed analytics and insights for a specific Facebook post including reach, impressions, and engagement metrics';
}
/**
* Define the parameters for this action
*/
public get Params(): ActionParam[] {
return [
...this.commonSocialParams,
{
Name: 'PostID',
Type: 'Input',
Value: null,
},
{
Name: 'MetricTypes',
Type: 'Input',
Value: null,
},
{
Name: 'Period',
Type: 'Input',
Value: 'lifetime',
},
{
Name: 'IncludeVideoMetrics',
Type: 'Input',
Value: true,
},
{
Name: 'IncludeDemographics',
Type: 'Input',
Value: true,
}
];
}
/**
* Execute the action
*/
protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
const { Params, ContextUser } = params;
try {
// Validate required parameters
const companyIntegrationId = this.getParamValue(Params, 'CompanyIntegrationID');
const postId = this.getParamValue(Params, 'PostID');
if (!companyIntegrationId) {
return {
Success: false,
Message: 'CompanyIntegrationID is required',
ResultCode: 'INVALID_TOKEN'
};
}
if (!postId) {
return {
Success: false,
Message: 'PostID is required',
ResultCode: 'MISSING_REQUIRED_PARAM'
};
}
// Initialize OAuth
if (!await this.initializeOAuth(companyIntegrationId)) {
return {
Success: false,
Message: 'Failed to initialize Facebook OAuth connection',
ResultCode: 'INVALID_TOKEN'
};
}
// Get parameters
const metricTypes = this.getParamValue(Params, 'MetricTypes') as string[];
const period = this.getParamValue(Params, 'Period') as string || 'lifetime';
const includeVideoMetrics = this.getParamValue(Params, 'IncludeVideoMetrics') !== false;
const includeDemographics = this.getParamValue(Params, 'IncludeDemographics') !== false;
LogStatus(`Retrieving insights for Facebook post ${postId}...`);
// Extract page ID from post ID (format: pageId_postId)
const pageId = postId.split('_')[0];
// Get page access token
const pageToken = await this.getPageAccessToken(pageId);
// Build metrics list
const metrics = metricTypes && metricTypes.length > 0
? metricTypes
: this.getDefaultPostMetrics(includeVideoMetrics);
// Get post insights
const insightsResponse = await axios.get(
`${this.apiBaseUrl}/${postId}/insights`,
{
params: {
access_token: pageToken,
metric: metrics.join(','),
period: period
}
}
);
const insights: FacebookInsight[] = insightsResponse.data.data || [];
// Get additional engagement data
const engagementResponse = await axios.get(
`${this.apiBaseUrl}/${postId}`,
{
params: {
access_token: pageToken,
fields: 'reactions.summary(true).limit(0),comments.summary(true).limit(0),shares,likes.summary(true).limit(0)'
}
}
);
const engagementData = engagementResponse.data;
// Get demographic insights if requested
let demographics = null;
if (includeDemographics) {
demographics = await this.getPostDemographics(postId, pageToken);
}
// Get post details for context
const postDetails = await this.getPost(postId);
// Process and organize insights
const processedInsights = this.processInsights(insights);
// Build comprehensive analytics object
const analytics = {
postId,
postDetails: {
message: postDetails.message,
createdTime: postDetails.created_time,
type: postDetails.attachments?.data?.[0]?.type || 'status',
permalinkUrl: postDetails.permalink_url
},
metrics: processedInsights,
engagement: {
reactions: {
total: engagementData.reactions?.summary?.total_count || 0,
likes: engagementData.likes?.summary?.total_count || 0
},
comments: engagementData.comments?.summary?.total_count || 0,
shares: engagementData.shares?.count || 0
},
demographics,
period,
retrievedAt: new Date().toISOString()
};
// Calculate engagement rate if we have reach data
const reach = processedInsights.post_impressions_unique || processedInsights.post_reach;
if (reach && reach > 0) {
const totalEngagements = analytics.engagement.reactions.total +
analytics.engagement.comments +
analytics.engagement.shares;
(analytics as any).engagementRate = (totalEngagements / reach) * 100;
}
LogStatus(`Successfully retrieved insights for post ${postId}`);
// Update output parameters
const outputParams = [...Params];
// TODO: Set output parameters based on result
return {
Success: true,
Message: 'Post insights retrieved successfully',
ResultCode: 'SUCCESS',
Params: outputParams
};
} catch (error) {
LogError(`Failed to get Facebook post insights: ${error instanceof Error ? error.message : 'Unknown error'}`);
if (this.isAuthError(error)) {
return this.handleOAuthError(error);
}
// Check if it's a permissions error
if (error instanceof Error && error.message.includes('permissions')) {
return {
Success: false,
Message: 'Insufficient permissions to access post insights. Ensure the page token has insights permissions.',
ResultCode: 'INSUFFICIENT_PERMISSIONS'
};
}
return {
Success: false,
Message: error instanceof Error ? error.message : 'Unknown error occurred',
ResultCode: 'ERROR'
};
}
}
/**
* Get default post metrics based on post type
*/
private getDefaultPostMetrics(includeVideo: boolean): string[] {
const metrics = [
// Reach and impressions
'post_impressions',
'post_impressions_unique',
'post_impressions_paid',
'post_impressions_paid_unique',
'post_impressions_fan',
'post_impressions_fan_unique',
'post_impressions_organic',
'post_impressions_organic_unique',
// Engagement
'post_engaged_users',
'post_engaged_fan',
'post_clicks',
'post_clicks_unique',
// Reactions
'post_reactions_by_type_total',
// Negative feedback
'post_negative_feedback',
'post_negative_feedback_unique',
// Activity
'post_activity',
'post_activity_by_action_type'
];
if (includeVideo) {
metrics.push(
'post_video_views',
'post_video_views_unique',
'post_video_views_10s',
'post_video_views_10s_unique',
'post_video_avg_time_watched',
'post_video_complete_views_30s',
'post_video_complete_views_30s_unique',
'post_video_retention_graph',
'post_video_view_time',
'post_video_view_time_by_age_bucket_and_gender',
'post_video_view_time_by_region_id'
);
}
return metrics;
}
/**
* Get demographic insights for a post
*/
private async getPostDemographics(postId: string, pageToken: string): Promise<any> {
try {
const demographicMetrics = [
'post_impressions_by_age_gender_unique',
'post_engaged_users_by_age_gender',
'post_clicks_by_age_gender_unique'
];
const response = await axios.get(
`${this.apiBaseUrl}/${postId}/insights`,
{
params: {
access_token: pageToken,
metric: demographicMetrics.join(',')
}
}
);
const insights = response.data.data || [];
const demographics: Record<string, any> = {};
for (const insight of insights) {
if (insight.values?.[0]?.value) {
demographics[insight.name] = insight.values[0].value;
}
}
return demographics;
} catch (error) {
LogError(`Failed to get demographic insights: ${error}`);
return null;
}
}
/**
* Process raw insights into a more usable format
*/
private processInsights(insights: FacebookInsight[]): Record<string, any> {
const processed: Record<string, any> = {};
for (const insight of insights) {
const value = insight.values?.[0]?.value;
if (value !== undefined) {
// Handle different value types
if (typeof value === 'object' && !Array.isArray(value)) {
// For metrics like reactions_by_type
processed[insight.name] = value;
} else {
processed[insight.name] = value;
}
// Add metadata
processed[`${insight.name}_meta`] = {
title: insight.title,
description: insight.description,
period: insight.period
};
}
}
return processed;
}
}