UNPKG

@memberjunction/actions-bizapps-social

Version:

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

242 lines (211 loc) 8.61 kB
import { RegisterClass } from '@memberjunction/global'; import { FacebookBaseAction, GetPagePostsParams } 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 { BaseAction } from '@memberjunction/actions'; /** * Retrieves posts from a Facebook page with optional date filtering. * Includes post content, media, and basic engagement metrics. */ @RegisterClass(BaseAction, 'FacebookGetPagePostsAction') export class FacebookGetPagePostsAction extends FacebookBaseAction { /** * Get action description */ public get Description(): string { return 'Retrieves posts from a Facebook page with optional date range filtering and pagination'; } /** * Define the parameters for this action */ public get Params(): ActionParam[] { return [ ...this.commonSocialParams, { Name: 'PageID', Type: 'Input', Value: null, }, { Name: 'StartDate', Type: 'Input', Value: null, }, { Name: 'EndDate', Type: 'Input', Value: null, }, { Name: 'Limit', Type: 'Input', Value: 100, }, { Name: 'MaxResults', Type: 'Input', Value: null, }, { Name: 'IncludeUnpublished', Type: 'Input', Value: false, }, { Name: 'IncludeInsights', 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 pageId = this.getParamValue(Params, 'PageID'); if (!companyIntegrationId) { return { Success: false, Message: 'CompanyIntegrationID is required', ResultCode: 'INVALID_TOKEN' }; } if (!pageId) { return { Success: false, Message: 'PageID 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 startDate = this.getParamValue(Params, 'StartDate') as string; const endDate = this.getParamValue(Params, 'EndDate') as string; const limit = this.getParamValue(Params, 'Limit') as number || 100; const maxResults = this.getParamValue(Params, 'MaxResults') as number; const includeUnpublished = this.getParamValue(Params, 'IncludeUnpublished') as boolean; const includeInsights = this.getParamValue(Params, 'IncludeInsights') as boolean; // Build query parameters const queryParams: GetPagePostsParams = { limit: Math.min(limit, 100), // Facebook max is 100 per request published: includeUnpublished ? undefined : true }; if (startDate) { queryParams.since = new Date(startDate); } if (endDate) { queryParams.until = new Date(endDate); } LogStatus(`Retrieving posts from Facebook page ${pageId}...`); // Get posts with pagination support let allPosts = await this.getPagePosts(pageId, queryParams); // If we need more posts and have a maxResults, use pagination if (maxResults && allPosts.length < maxResults && allPosts.length === queryParams.limit) { LogStatus(`Fetching additional pages of posts...`); const pageToken = await this.getPageAccessToken(pageId); const fields = includeInsights ? this.getPostFields() : this.getPostFieldsWithoutInsights(); allPosts = await this.getPaginatedResults( `${this.apiBaseUrl}/${pageId}/posts`, { access_token: pageToken, fields, ...queryParams, since: queryParams.since ? Math.floor(queryParams.since.getTime() / 1000) : undefined, until: queryParams.until ? Math.floor(queryParams.until.getTime() / 1000) : undefined }, maxResults ); } // Limit results if maxResults specified if (maxResults && allPosts.length > maxResults) { allPosts = allPosts.slice(0, maxResults); } LogStatus(`Retrieved ${allPosts.length} posts from Facebook page`); // Normalize posts to common format const normalizedPosts = allPosts.map(post => this.normalizePost(post)); // Calculate summary statistics const summary = { totalPosts: normalizedPosts.length, dateRange: { earliest: normalizedPosts.length > 0 ? normalizedPosts[normalizedPosts.length - 1].publishedAt : null, latest: normalizedPosts.length > 0 ? normalizedPosts[0].publishedAt : null }, postTypes: this.categorizePostTypes(allPosts), totalEngagements: normalizedPosts.reduce((sum, post) => sum + (post.analytics?.engagements || 0), 0 ), totalImpressions: normalizedPosts.reduce((sum, post) => sum + (post.analytics?.impressions || 0), 0 ) }; // Update output parameters const outputParams = [...Params]; // TODO: Set output parameters based on result return { Success: true, Message: `Successfully retrieved ${normalizedPosts.length} posts`, ResultCode: 'SUCCESS', Params: outputParams }; } catch (error) { LogError(`Failed to get Facebook page posts: ${error instanceof Error ? error.message : 'Unknown error'}`); if (this.isAuthError(error)) { return this.handleOAuthError(error); } return { Success: false, Message: error instanceof Error ? error.message : 'Unknown error occurred', ResultCode: 'ERROR' }; } } /** * Get post fields without insights (for faster queries or when insights not available) */ private getPostFieldsWithoutInsights(): string { return 'id,message,created_time,updated_time,from,story,permalink_url,attachments,shares,reactions.summary(true),comments.summary(true)'; } /** * Categorize posts by type based on attachments */ private categorizePostTypes(posts: any[]): Record<string, number> { const types: Record<string, number> = { status: 0, photo: 0, video: 0, link: 0, event: 0, offer: 0, album: 0 }; for (const post of posts) { if (post.attachments?.data?.[0]?.type) { const type = post.attachments.data[0].type.toLowerCase(); if (type in types) { types[type]++; } else { types.status++; } } else { types.status++; } } return types; } }