UNPKG

@memberjunction/actions-bizapps-social

Version:

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

335 lines 13.4 kB
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; }; import { RegisterClass } from '@memberjunction/global'; import { InstagramBaseAction } from '../instagram-base.action.js'; import { LogError } from '@memberjunction/core'; import { BaseAction } from '@memberjunction/actions'; /** * Retrieves comments for an Instagram post, including nested replies. * Supports filtering, pagination, and sentiment analysis. */ let InstagramGetCommentsAction = class InstagramGetCommentsAction extends InstagramBaseAction { async InternalRunAction(params) { try { const companyIntegrationId = this.getParamValue(params.Params, 'CompanyIntegrationID'); const postId = this.getParamValue(params.Params, 'PostID'); const includeReplies = this.getParamValue(params.Params, 'IncludeReplies') !== false; const includeHidden = this.getParamValue(params.Params, 'IncludeHidden') || false; const limit = this.getParamValue(params.Params, 'Limit') || 50; const afterCursor = this.getParamValue(params.Params, 'AfterCursor'); // Initialize OAuth if (!await this.initializeOAuth(companyIntegrationId, params)) { return { Success: false, Message: 'Failed to initialize Instagram authentication', ResultCode: 'AUTH_FAILED' }; } // Validate inputs if (!postId) { return { Success: false, Message: 'PostID is required', ResultCode: 'MISSING_PARAMS' }; } // Build fields for comment data const fields = 'id,text,username,timestamp,like_count,replies{id,text,username,timestamp,like_count}'; // Build query parameters const queryParams = { fields, access_token: this.getAccessToken(), limit: Math.min(limit, 100) }; if (afterCursor) { queryParams.after = afterCursor; } // Get comments const response = await this.makeInstagramRequest(`${postId}/comments`, 'GET', null, queryParams); const comments = response.data || []; // Process comments const processedComments = await this.processComments(comments, includeReplies); // Get hidden comments if requested let hiddenComments = []; if (includeHidden) { hiddenComments = await this.getHiddenComments(postId); } // Calculate engagement metrics const metrics = this.calculateCommentMetrics(processedComments); // Analyze sentiment patterns const sentimentAnalysis = this.analyzeSentiment(processedComments); // Store result in output params const outputParams = [...params.Params]; outputParams.push({ Name: 'ResultData', Type: 'Output', Value: JSON.stringify({ postId, comments: processedComments, hiddenComments, metrics, sentimentAnalysis, paging: { hasNext: !!response.paging?.next, afterCursor: response.paging?.cursors?.after } }) }); return { Success: true, Message: `Retrieved ${processedComments.length} comments`, ResultCode: 'SUCCESS', Params: outputParams }; } catch (error) { LogError('Failed to retrieve Instagram comments', error); if (error.code === 'RATE_LIMIT') { return { Success: false, Message: 'Instagram API rate limit exceeded. Please try again later.', ResultCode: 'RATE_LIMIT' }; } if (error.code === 'POST_NOT_FOUND') { return { Success: false, Message: 'Instagram post not found or access denied', ResultCode: 'POST_NOT_FOUND' }; } return { Success: false, Message: `Failed to retrieve comments: ${error.message}`, ResultCode: 'ERROR' }; } } /** * Process comments and fetch replies if needed */ async processComments(comments, includeReplies) { const processed = []; for (const comment of comments) { const processedComment = { id: comment.id, text: comment.text, username: comment.username, timestamp: comment.timestamp, likeCount: comment.like_count || 0, replies: [], metrics: { wordCount: this.countWords(comment.text), hasEmojis: this.containsEmojis(comment.text), hasMentions: this.containsMentions(comment.text), hasHashtags: this.containsHashtags(comment.text) } }; // Process replies if they exist and are requested if (includeReplies && comment.replies?.data) { processedComment.replies = comment.replies.data.map((reply) => ({ id: reply.id, text: reply.text, username: reply.username, timestamp: reply.timestamp, likeCount: reply.like_count || 0, metrics: { wordCount: this.countWords(reply.text), hasEmojis: this.containsEmojis(reply.text), hasMentions: this.containsMentions(reply.text), hasHashtags: this.containsHashtags(reply.text) } })); } processed.push(processedComment); } return processed; } /** * Get hidden comments (comments hidden by the account) */ async getHiddenComments(postId) { try { const response = await this.makeInstagramRequest(`${postId}/comments`, 'GET', null, { fields: 'id,text,username,timestamp,hidden', access_token: this.getAccessToken(), filter: 'hidden' }); return response.data || []; } catch (error) { LogError('Failed to get hidden comments', error); return []; } } /** * Calculate comment metrics */ calculateCommentMetrics(comments) { const metrics = { totalComments: comments.length, totalReplies: 0, avgCommentLength: 0, avgLikesPerComment: 0, topCommenters: [], engagementRate: 0, responseRate: 0 }; if (comments.length === 0) return metrics; let totalLength = 0; let totalLikes = 0; const commenterCounts = {}; comments.forEach(comment => { totalLength += comment.metrics.wordCount; totalLikes += comment.likeCount; // Count commenters commenterCounts[comment.username] = (commenterCounts[comment.username] || 0) + 1; // Count replies metrics.totalReplies += comment.replies.length; // Add reply stats comment.replies.forEach((reply) => { totalLength += reply.metrics.wordCount; totalLikes += reply.likeCount; commenterCounts[reply.username] = (commenterCounts[reply.username] || 0) + 1; }); }); const totalInteractions = comments.length + metrics.totalReplies; metrics.avgCommentLength = Math.round(totalLength / totalInteractions); metrics.avgLikesPerComment = Math.round(totalLikes / totalInteractions); // Get top commenters metrics.topCommenters = Object.entries(commenterCounts) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([username, count]) => ({ username, count })); // Calculate response rate (comments with replies) const commentsWithReplies = comments.filter(c => c.replies.length > 0).length; metrics.responseRate = (commentsWithReplies / comments.length) * 100; return metrics; } /** * Analyze sentiment patterns in comments */ analyzeSentiment(comments) { const analysis = { positive: 0, negative: 0, neutral: 0, questions: 0, keywords: [], emojis: [] }; const keywordCounts = {}; const emojiCounts = {}; // Simple sentiment analysis based on keywords and patterns const positiveWords = ['love', 'amazing', 'beautiful', 'great', 'awesome', 'perfect', 'excellent', 'wonderful', '❤️', '😍', '🔥', '💯']; const negativeWords = ['hate', 'awful', 'terrible', 'bad', 'worst', 'ugly', 'disgusting', '😠', '😡', '👎']; const questionWords = ['?', 'what', 'where', 'when', 'how', 'why', 'who']; comments.forEach(comment => { const text = comment.text.toLowerCase(); // Check sentiment const hasPositive = positiveWords.some(word => text.includes(word)); const hasNegative = negativeWords.some(word => text.includes(word)); const hasQuestion = questionWords.some(word => text.includes(word)); if (hasQuestion) { analysis.questions++; } if (hasPositive && !hasNegative) { analysis.positive++; } else if (hasNegative && !hasPositive) { analysis.negative++; } else { analysis.neutral++; } // Extract keywords (simple word frequency) const words = text.split(/\s+/).filter(word => word.length > 3); words.forEach(word => { keywordCounts[word] = (keywordCounts[word] || 0) + 1; }); // Extract emojis const emojis = text.match(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]/gu) || []; emojis.forEach(emoji => { emojiCounts[emoji] = (emojiCounts[emoji] || 0) + 1; }); // Analyze replies too comment.replies.forEach((reply) => { // Similar analysis for replies... }); }); // Get top keywords and emojis analysis.keywords = Object.entries(keywordCounts) .sort(([, a], [, b]) => b - a) .slice(0, 20) .map(([keyword, count]) => ({ keyword, count })); analysis.emojis = Object.entries(emojiCounts) .sort(([, a], [, b]) => b - a) .slice(0, 10) .map(([emoji, count]) => ({ emoji, count })); return analysis; } /** * Helper methods for text analysis */ countWords(text) { return text.trim().split(/\s+/).length; } containsEmojis(text) { return /[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]/u.test(text); } containsMentions(text) { return /@\w+/.test(text); } containsHashtags(text) { return /#\w+/.test(text); } /** * Define the parameters for this action */ get Params() { return [ ...this.commonSocialParams, { Name: 'PostID', Type: 'Input', Value: null }, { Name: 'IncludeReplies', Type: 'Input', Value: true }, { Name: 'IncludeHidden', Type: 'Input', Value: false }, { Name: 'Limit', Type: 'Input', Value: 50 }, { Name: 'AfterCursor', Type: 'Input', Value: null } ]; } /** * Get the description for this action */ get Description() { return 'Retrieves comments for an Instagram post including replies, metrics, and sentiment analysis.'; } }; InstagramGetCommentsAction = __decorate([ RegisterClass(BaseAction, 'Instagram - Get Comments') ], InstagramGetCommentsAction); export { InstagramGetCommentsAction }; //# sourceMappingURL=get-comments.action.js.map