UNPKG

@memberjunction/actions-bizapps-social

Version:

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

339 lines (292 loc) 12 kB
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. */ @RegisterClass(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; } }