UNPKG

@memberjunction/actions-bizapps-social

Version:

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

400 lines 17.1 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TwitterGetAnalyticsAction = void 0; const global_1 = require("@memberjunction/global"); const twitter_base_action_1 = require("../twitter-base.action"); const core_1 = require("@memberjunction/core"); const actions_1 = require("@memberjunction/actions"); /** * Action to get analytics for tweets or user account from Twitter/X */ let TwitterGetAnalyticsAction = class TwitterGetAnalyticsAction extends twitter_base_action_1.TwitterBaseAction { /** * Get analytics from Twitter */ async InternalRunAction(params) { 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 analyticsType = this.getParamValue(Params, 'AnalyticsType') || 'account'; // 'account' or 'tweets' const tweetIds = this.getParamValue(Params, 'TweetIDs'); const startDate = this.getParamValue(Params, 'StartDate'); const endDate = this.getParamValue(Params, 'EndDate'); const granularity = this.getParamValue(Params, 'Granularity') || 'day'; // 'hour', 'day', 'total' if (analyticsType === 'tweets') { // Get analytics for specific tweets if (!tweetIds || !Array.isArray(tweetIds) || tweetIds.length === 0) { throw new Error('TweetIDs array is required for tweet analytics'); } return await this.getTweetAnalytics(Params, tweetIds); } else { // Get account-level analytics return await this.getAccountAnalytics(Params, startDate, endDate, granularity); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { Success: false, ResultCode: this.getErrorCode(error), Message: `Failed to get analytics: ${errorMessage}`, Params }; } } /** * Get analytics for specific tweets */ async getTweetAnalytics(params, tweetIds) { try { (0, core_1.LogStatus)(`Getting analytics for ${tweetIds.length} tweets...`); // Twitter API v2 requires organic metrics scope for detailed analytics // We'll get public metrics for each tweet const tweetAnalytics = []; // Process in batches of 100 (API limit) const batchSize = 100; for (let i = 0; i < tweetIds.length; i += batchSize) { const batch = tweetIds.slice(i, i + batchSize); const ids = batch.join(','); const response = await this.axiosInstance.get('/tweets', { params: { 'ids': ids, 'tweet.fields': 'id,text,created_at,public_metrics,organic_metrics,promoted_metrics', 'expansions': 'author_id', 'user.fields': 'id,username' } }); if (response.data.data) { for (const tweet of response.data.data) { const metrics = { impression_count: 0, engagement_count: 0, retweet_count: 0, reply_count: 0, like_count: 0, quote_count: 0, bookmark_count: 0, url_link_clicks: 0, user_profile_clicks: 0 }; // Combine public and organic metrics if available if (tweet.public_metrics) { Object.assign(metrics, tweet.public_metrics); } if (tweet.organic_metrics) { Object.assign(metrics, tweet.organic_metrics); } // Calculate engagement count metrics.engagement_count = metrics.retweet_count + metrics.reply_count + metrics.like_count + metrics.quote_count + metrics.url_link_clicks + metrics.user_profile_clicks; const normalizedAnalytics = this.normalizeAnalytics(metrics); tweetAnalytics.push({ tweetId: tweet.id, text: tweet.text.substring(0, 100) + (tweet.text.length > 100 ? '...' : ''), createdAt: tweet.created_at, metrics: normalizedAnalytics, engagementRate: metrics.impression_count > 0 ? ((metrics.engagement_count / metrics.impression_count) * 100).toFixed(2) + '%' : '0%' }); } } } // Calculate aggregate metrics const aggregateMetrics = this.calculateAggregateMetrics(tweetAnalytics); // Update output parameters const outputParams = [...params]; const analyticsParam = outputParams.find(p => p.Name === 'Analytics'); if (analyticsParam) analyticsParam.Value = tweetAnalytics; const aggregateParam = outputParams.find(p => p.Name === 'AggregateMetrics'); if (aggregateParam) aggregateParam.Value = aggregateMetrics; return { Success: true, ResultCode: 'SUCCESS', Message: `Successfully retrieved analytics for ${tweetAnalytics.length} tweets`, Params: outputParams }; } catch (error) { throw error; } } /** * Get account-level analytics */ async getAccountAnalytics(params, startDate, endDate, granularity) { try { // Get current user const currentUser = await this.getCurrentUser(); (0, core_1.LogStatus)(`Getting account analytics for @${currentUser.username}...`); // For account analytics, we'll analyze recent tweets performance const queryParams = { 'tweet.fields': 'id,text,created_at,public_metrics,organic_metrics', 'max_results': 100 }; if (startDate) { queryParams['start_time'] = this.formatTwitterDate(startDate); } if (endDate) { queryParams['end_time'] = this.formatTwitterDate(endDate); } // Get user's tweets const tweets = await this.getPaginatedTweets(`/users/${currentUser.id}/tweets`, queryParams, 200 // Get up to 200 tweets for analysis ); // Calculate time-based analytics const timeBasedAnalytics = this.calculateTimeBasedAnalytics(tweets, granularity || 'day'); // Calculate overall metrics const overallMetrics = { totalTweets: tweets.length, totalImpressions: 0, totalEngagements: 0, totalLikes: 0, totalRetweets: 0, totalReplies: 0, totalQuotes: 0, averageEngagementRate: 0, topPerformingTweets: [] }; tweets.forEach(tweet => { if (tweet.public_metrics) { overallMetrics.totalImpressions += tweet.public_metrics.impression_count || 0; overallMetrics.totalLikes += tweet.public_metrics.like_count || 0; overallMetrics.totalRetweets += tweet.public_metrics.retweet_count || 0; overallMetrics.totalReplies += tweet.public_metrics.reply_count || 0; overallMetrics.totalQuotes += tweet.public_metrics.quote_count || 0; const engagement = (tweet.public_metrics.like_count || 0) + (tweet.public_metrics.retweet_count || 0) + (tweet.public_metrics.reply_count || 0) + (tweet.public_metrics.quote_count || 0); overallMetrics.totalEngagements += engagement; } }); // Calculate average engagement rate if (overallMetrics.totalImpressions > 0) { overallMetrics.averageEngagementRate = parseFloat(((overallMetrics.totalEngagements / overallMetrics.totalImpressions) * 100).toFixed(2)); } // Get top performing tweets overallMetrics.topPerformingTweets = tweets .filter(t => t.public_metrics) .sort((a, b) => { const aEngagement = this.calculateTweetEngagement(a.public_metrics); const bEngagement = this.calculateTweetEngagement(b.public_metrics); return bEngagement - aEngagement; }) .slice(0, 5) .map(tweet => ({ id: tweet.id, text: tweet.text.substring(0, 100) + (tweet.text.length > 100 ? '...' : ''), createdAt: tweet.created_at, metrics: tweet.public_metrics, engagement: this.calculateTweetEngagement(tweet.public_metrics) })); // Update output parameters const outputParams = [...params]; const overallParam = outputParams.find(p => p.Name === 'OverallMetrics'); if (overallParam) overallParam.Value = overallMetrics; const timeBasedParam = outputParams.find(p => p.Name === 'TimeBasedAnalytics'); if (timeBasedParam) timeBasedParam.Value = timeBasedAnalytics; return { Success: true, ResultCode: 'SUCCESS', Message: `Successfully retrieved account analytics for ${tweets.length} tweets`, Params: outputParams }; } catch (error) { throw error; } } /** * Calculate aggregate metrics from tweet analytics */ calculateAggregateMetrics(tweetAnalytics) { const aggregate = { totalImpressions: 0, totalEngagements: 0, totalLikes: 0, totalRetweets: 0, totalReplies: 0, averageEngagementRate: 0, bestPerformingTweet: null, worstPerformingTweet: null }; let bestEngagement = -1; let worstEngagement = Infinity; tweetAnalytics.forEach(analytics => { const metrics = analytics.metrics; aggregate.totalImpressions += metrics.impressions; aggregate.totalEngagements += metrics.engagements; aggregate.totalLikes += metrics.likes; aggregate.totalRetweets += metrics.shares; aggregate.totalReplies += metrics.comments; if (metrics.engagements > bestEngagement) { bestEngagement = metrics.engagements; aggregate.bestPerformingTweet = analytics; } if (metrics.engagements < worstEngagement) { worstEngagement = metrics.engagements; aggregate.worstPerformingTweet = analytics; } }); if (aggregate.totalImpressions > 0) { aggregate.averageEngagementRate = parseFloat(((aggregate.totalEngagements / aggregate.totalImpressions) * 100).toFixed(2)); } return aggregate; } /** * Calculate time-based analytics */ calculateTimeBasedAnalytics(tweets, granularity) { const buckets = new Map(); tweets.forEach(tweet => { const date = new Date(tweet.created_at); let bucketKey; switch (granularity) { case 'hour': bucketKey = `${date.toISOString().slice(0, 13)}:00:00Z`; break; case 'day': bucketKey = date.toISOString().slice(0, 10); break; case 'total': bucketKey = 'total'; break; default: bucketKey = date.toISOString().slice(0, 10); } if (!buckets.has(bucketKey)) { buckets.set(bucketKey, { period: bucketKey, tweets: 0, impressions: 0, engagements: 0, likes: 0, retweets: 0, replies: 0 }); } const bucket = buckets.get(bucketKey); bucket.tweets++; if (tweet.public_metrics) { bucket.impressions += tweet.public_metrics.impression_count || 0; bucket.likes += tweet.public_metrics.like_count || 0; bucket.retweets += tweet.public_metrics.retweet_count || 0; bucket.replies += tweet.public_metrics.reply_count || 0; bucket.engagements += this.calculateTweetEngagement(tweet.public_metrics); } }); return Array.from(buckets.values()).sort((a, b) => a.period.localeCompare(b.period)); } /** * Calculate tweet engagement from public metrics */ calculateTweetEngagement(metrics) { return (metrics.like_count || 0) + (metrics.retweet_count || 0) + (metrics.reply_count || 0) + (metrics.quote_count || 0); } /** * Get error code based on error type */ getErrorCode(error) { 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 */ get Params() { return [ ...this.commonSocialParams, { Name: 'AnalyticsType', Type: 'Input', Value: 'account' // 'account' or 'tweets' }, { Name: 'TweetIDs', Type: 'Input', Value: null }, { Name: 'StartDate', Type: 'Input', Value: null }, { Name: 'EndDate', Type: 'Input', Value: null }, { Name: 'Granularity', Type: 'Input', Value: 'day' // 'hour', 'day', 'total' }, { Name: 'Analytics', Type: 'Output', Value: null }, { Name: 'AggregateMetrics', Type: 'Output', Value: null }, { Name: 'OverallMetrics', Type: 'Output', Value: null }, { Name: 'TimeBasedAnalytics', Type: 'Output', Value: null } ]; } /** * Get action description */ get Description() { return 'Gets analytics data from Twitter/X for specific tweets or account-level metrics with time-based analysis'; } }; exports.TwitterGetAnalyticsAction = TwitterGetAnalyticsAction; exports.TwitterGetAnalyticsAction = TwitterGetAnalyticsAction = __decorate([ (0, global_1.RegisterClass)(actions_1.BaseAction, 'TwitterGetAnalyticsAction') ], TwitterGetAnalyticsAction); //# sourceMappingURL=get-analytics.action.js.map