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