@memberjunction/actions-bizapps-social
Version:
Social Media Actions for MemberJunction - Twitter, LinkedIn, Facebook, Instagram, TikTok, YouTube, HootSuite, Buffer
234 lines (211 loc) • 8.9 kB
text/typescript
import { RegisterClass } from '@memberjunction/global';
import { TwitterBaseAction, Tweet } from '../twitter-base.action';
import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base';
import { LogStatus, LogError } from '@memberjunction/core';
import { SocialPost } from '../../../base/base-social.action';
import { BaseAction } from '@memberjunction/actions';
/**
* Action to get mentions (tweets that mention the authenticated user) from Twitter/X
*/
(BaseAction, 'TwitterGetMentionsAction')
export class TwitterGetMentionsAction extends TwitterBaseAction {
/**
* Get mentions from Twitter
*/
protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> {
const { Params, ContextUser } = params;
try {
// Initialize OAuth
const companyIntegrationId = this.getParamValue(Params, 'CompanyIntegrationID');
if (!await this.initializeOAuth(companyIntegrationId)) {
throw new Error('Failed to initialize OAuth connection');
}
// Extract parameters
const maxResults = this.getParamValue(Params, 'MaxResults') || 100;
const startTime = this.getParamValue(Params, 'StartTime');
const endTime = this.getParamValue(Params, 'EndTime');
const sinceId = this.getParamValue(Params, 'SinceID');
const untilId = this.getParamValue(Params, 'UntilID');
const includeRetweets = this.getParamValue(Params, 'IncludeRetweets') !== false; // Default true
// Get current user info
const currentUser = await this.getCurrentUser();
// Build query parameters
const queryParams: Record<string, any> = {
'tweet.fields': 'id,text,created_at,author_id,conversation_id,public_metrics,attachments,entities,referenced_tweets,in_reply_to_user_id',
'user.fields': 'id,name,username,profile_image_url,description,created_at,verified',
'media.fields': 'url,preview_image_url,type,width,height',
'expansions': 'author_id,attachments.media_keys,referenced_tweets.id,referenced_tweets.id.author_id,in_reply_to_user_id',
'max_results': Math.min(maxResults, 100) // API limit per request
};
// Add time-based filters if provided
if (startTime) {
queryParams['start_time'] = this.formatTwitterDate(startTime);
}
if (endTime) {
queryParams['end_time'] = this.formatTwitterDate(endTime);
}
if (sinceId) {
queryParams['since_id'] = sinceId;
}
if (untilId) {
queryParams['until_id'] = untilId;
}
// Get mentions using the mentions timeline endpoint
const endpoint = `/users/${currentUser.id}/mentions`;
LogStatus(`Getting mentions for @${currentUser.username}...`);
const mentions = await this.getPaginatedTweets(endpoint, queryParams, maxResults);
// Filter out retweets if requested
let filteredMentions = mentions;
if (!includeRetweets) {
filteredMentions = mentions.filter(tweet =>
!tweet.referenced_tweets ||
!tweet.referenced_tweets.some(ref => ref.type === 'retweeted')
);
}
// Convert to normalized format
const normalizedPosts: SocialPost[] = filteredMentions.map(tweet => this.normalizePost(tweet));
// Group mentions by type
const mentionTypes = {
directMentions: 0,
replies: 0,
retweets: 0,
quotes: 0
};
filteredMentions.forEach(tweet => {
if (tweet.referenced_tweets) {
const refTypes = tweet.referenced_tweets.map(ref => ref.type);
if (refTypes.includes('replied_to')) {
mentionTypes.replies++;
} else if (refTypes.includes('retweeted')) {
mentionTypes.retweets++;
} else if (refTypes.includes('quoted')) {
mentionTypes.quotes++;
}
} else {
mentionTypes.directMentions++;
}
});
// Calculate engagement statistics
const stats = {
totalMentions: normalizedPosts.length,
mentionTypes,
totalEngagement: 0,
averageEngagement: 0,
topMentions: [] as any[]
};
// Calculate total engagement
normalizedPosts.forEach(post => {
if (post.analytics) {
stats.totalEngagement += post.analytics.engagements;
}
});
// Calculate average engagement
if (normalizedPosts.length > 0) {
stats.averageEngagement = Math.round(stats.totalEngagement / normalizedPosts.length);
}
// Get top 5 mentions by engagement
stats.topMentions = normalizedPosts
.filter(post => post.analytics)
.sort((a, b) => (b.analytics?.engagements || 0) - (a.analytics?.engagements || 0))
.slice(0, 5)
.map(post => ({
id: post.id,
content: post.content.substring(0, 100) + (post.content.length > 100 ? '...' : ''),
engagement: post.analytics?.engagements,
author: filteredMentions.find(t => t.id === post.id)?.author_id
}));
// Update output parameters
const outputParams = [...Params];
const postsParam = outputParams.find(p => p.Name === 'Mentions');
if (postsParam) postsParam.Value = normalizedPosts;
const tweetsParam = outputParams.find(p => p.Name === 'Tweets');
if (tweetsParam) tweetsParam.Value = filteredMentions;
const statsParam = outputParams.find(p => p.Name === 'Statistics');
if (statsParam) statsParam.Value = stats;
return {
Success: true,
ResultCode: 'SUCCESS',
Message: `Successfully retrieved ${normalizedPosts.length} mentions`,
Params: outputParams
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
Success: false,
ResultCode: this.getErrorCode(error),
Message: `Failed to get mentions: ${errorMessage}`,
Params
};
}
}
/**
* Get error code based on error type
*/
private getErrorCode(error: any): string {
if (error instanceof Error) {
if (error.message.includes('Rate Limit')) return 'RATE_LIMIT';
if (error.message.includes('Unauthorized')) return 'INVALID_TOKEN';
if (error.message.includes('Forbidden')) return 'INSUFFICIENT_PERMISSIONS';
}
return 'ERROR';
}
/**
* Define the parameters this action expects
*/
public get Params(): ActionParam[] {
return [
...this.commonSocialParams,
{
Name: 'MaxResults',
Type: 'Input',
Value: 100
},
{
Name: 'StartTime',
Type: 'Input',
Value: null
},
{
Name: 'EndTime',
Type: 'Input',
Value: null
},
{
Name: 'SinceID',
Type: 'Input',
Value: null
},
{
Name: 'UntilID',
Type: 'Input',
Value: null
},
{
Name: 'IncludeRetweets',
Type: 'Input',
Value: true
},
{
Name: 'Mentions',
Type: 'Output',
Value: null
},
{
Name: 'Tweets',
Type: 'Output',
Value: null
},
{
Name: 'Statistics',
Type: 'Output',
Value: null
}
];
}
/**
* Get action description
*/
public get Description(): string {
return 'Gets tweets that mention the authenticated user on Twitter/X with engagement statistics and filtering options';
}
}