@memberjunction/actions-bizapps-social
Version:
Social Media Actions for MemberJunction - Twitter, LinkedIn, Facebook, Instagram, TikTok, YouTube, HootSuite, Buffer
291 lines • 11.6 kB
JavaScript
;
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.GetCommentsAction = void 0;
const global_1 = require("@memberjunction/global");
const tiktok_base_action_1 = require("../tiktok-base.action");
const actions_1 = require("@memberjunction/actions");
/**
* Action to get comments from TikTok videos
*/
let GetCommentsAction = class GetCommentsAction extends tiktok_base_action_1.TikTokBaseAction {
/**
* Get comments from TikTok videos
*/
async InternalRunAction(params) {
const { Params } = params;
try {
// Initialize OAuth
const companyIntegrationId = this.getParamValue(Params, 'CompanyIntegrationID');
if (!companyIntegrationId) {
throw new Error('CompanyIntegrationID is required');
}
await this.initializeOAuth(companyIntegrationId);
// Extract parameters
const videoId = this.getParamValue(Params, 'VideoID');
const maxComments = this.getParamValue(Params, 'MaxComments') || 100;
const includeReplies = this.getParamValue(Params, 'IncludeReplies') !== false;
const sortBy = this.getParamValue(Params, 'SortBy') || 'time'; // time or likes
if (!videoId) {
throw new Error('VideoID is required');
}
// Get comments from TikTok API
const response = await this.makeTikTokRequest(`/v2/video/comment/list/`, 'GET', undefined, {
video_id: videoId,
max_count: Math.min(maxComments, 100), // API limit
sort_by: sortBy
});
const comments = response.data?.comments || [];
// Process comments
const processedComments = comments.map(comment => ({
id: comment.comment_id,
text: comment.text,
author: {
id: comment.user.open_id,
username: comment.user.display_name,
avatarUrl: comment.user.avatar_url
},
createdAt: new Date(comment.create_time * 1000),
likes: comment.like_count,
replies: comment.reply_count,
isReply: !!comment.parent_comment_id,
parentCommentId: comment.parent_comment_id,
sentiment: this.analyzeSentiment(comment.text),
containsQuestion: this.containsQuestion(comment.text),
length: comment.text.length
}));
// Separate top-level comments and replies
const topLevelComments = processedComments.filter(c => !c.isReply);
const replies = processedComments.filter(c => c.isReply);
// Calculate engagement metrics
const engagementMetrics = {
totalComments: comments.length,
topLevelComments: topLevelComments.length,
totalReplies: replies.length,
averageLikes: comments.length > 0
? Math.round(comments.reduce((sum, c) => sum + c.like_count, 0) / comments.length)
: 0,
mostLikedComment: processedComments.length > 0
? processedComments.reduce((max, c) => c.likes > max.likes ? c : max)
: null,
sentimentBreakdown: this.calculateSentimentBreakdown(processedComments),
questionsCount: processedComments.filter(c => c.containsQuestion).length,
averageCommentLength: processedComments.length > 0
? Math.round(processedComments.reduce((sum, c) => sum + c.length, 0) / processedComments.length)
: 0
};
// Identify notable comments (high engagement, questions, etc.)
const notableComments = this.identifyNotableComments(processedComments);
// Create summary
const summary = {
videoId,
totalCommentsRetrieved: processedComments.length,
hasMoreComments: response.data?.has_more || false,
engagementMetrics,
notableComments,
topCommenters: this.getTopCommenters(processedComments),
timeDistribution: this.analyzeTimeDistribution(processedComments)
};
// Update output parameters
const outputParams = [...Params];
const commentsParam = outputParams.find(p => p.Name === 'Comments');
if (commentsParam)
commentsParam.Value = includeReplies ? processedComments : topLevelComments;
const summaryParam = outputParams.find(p => p.Name === 'Summary');
if (summaryParam)
summaryParam.Value = summary;
const rawDataParam = outputParams.find(p => p.Name === 'RawData');
if (rawDataParam)
rawDataParam.Value = comments;
return {
Success: true,
ResultCode: 'SUCCESS',
Message: `Retrieved ${processedComments.length} comments from TikTok video`,
Params: outputParams
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return {
Success: false,
ResultCode: this.isAuthError(error) ? 'INVALID_TOKEN' : 'ERROR',
Message: `Failed to get TikTok comments: ${errorMessage}`,
Params
};
}
}
/**
* Simple sentiment analysis
*/
analyzeSentiment(text) {
const lowerText = text.toLowerCase();
// Positive indicators
const positiveWords = ['love', 'amazing', 'great', 'awesome', 'fantastic', 'excellent', 'good', '❤️', '😍', '🔥', '👏', '💯'];
const negativeWords = ['hate', 'terrible', 'awful', 'bad', 'worst', 'disappointing', 'trash', '👎', '😠', '😡'];
const positiveScore = positiveWords.filter(word => lowerText.includes(word)).length;
const negativeScore = negativeWords.filter(word => lowerText.includes(word)).length;
if (positiveScore > negativeScore)
return 'positive';
if (negativeScore > positiveScore)
return 'negative';
return 'neutral';
}
/**
* Check if comment contains a question
*/
containsQuestion(text) {
return text.includes('?') ||
/\b(what|when|where|who|why|how|is|are|can|could|would|should)\b/i.test(text);
}
/**
* Calculate sentiment breakdown
*/
calculateSentimentBreakdown(comments) {
const breakdown = {
positive: 0,
negative: 0,
neutral: 0
};
comments.forEach(comment => {
breakdown[comment.sentiment]++;
});
return breakdown;
}
/**
* Identify notable comments
*/
identifyNotableComments(comments) {
const notable = [];
// High engagement comments
const avgLikes = comments.length > 0
? comments.reduce((sum, c) => sum + c.likes, 0) / comments.length
: 0;
comments.forEach(comment => {
const reasons = [];
if (comment.likes > avgLikes * 2) {
reasons.push('high_engagement');
}
if (comment.containsQuestion) {
reasons.push('contains_question');
}
if (comment.replies > 5) {
reasons.push('many_replies');
}
if (comment.text.length > 200) {
reasons.push('detailed_feedback');
}
if (reasons.length > 0) {
notable.push({
...comment,
notableReasons: reasons
});
}
});
// Return top 10 notable comments
return notable.sort((a, b) => b.likes - a.likes).slice(0, 10);
}
/**
* Get top commenters
*/
getTopCommenters(comments) {
const commenterMap = new Map();
comments.forEach(comment => {
const key = comment.author.id;
if (!commenterMap.has(key)) {
commenterMap.set(key, {
...comment.author,
commentCount: 0,
totalLikes: 0
});
}
const commenter = commenterMap.get(key);
commenter.commentCount++;
commenter.totalLikes += comment.likes;
});
return Array.from(commenterMap.values())
.sort((a, b) => b.commentCount - a.commentCount)
.slice(0, 5);
}
/**
* Analyze time distribution of comments
*/
analyzeTimeDistribution(comments) {
if (comments.length === 0)
return null;
const now = new Date();
const hourBuckets = {};
comments.forEach(comment => {
const hoursSincePost = Math.floor((now.getTime() - comment.createdAt.getTime()) / (1000 * 60 * 60));
let bucket;
if (hoursSincePost < 1)
bucket = '< 1 hour';
else if (hoursSincePost < 24)
bucket = '1-24 hours';
else if (hoursSincePost < 168)
bucket = '1-7 days';
else
bucket = '> 7 days';
hourBuckets[bucket] = (hourBuckets[bucket] || 0) + 1;
});
return hourBuckets;
}
/**
* Define the parameters this action expects
*/
get Params() {
return [
...this.commonSocialParams,
{
Name: 'VideoID',
Type: 'Input',
Value: null
},
{
Name: 'MaxComments',
Type: 'Input',
Value: 100
},
{
Name: 'IncludeReplies',
Type: 'Input',
Value: true
},
{
Name: 'SortBy',
Type: 'Input',
Value: 'time'
},
{
Name: 'Comments',
Type: 'Output',
Value: null
},
{
Name: 'Summary',
Type: 'Output',
Value: null
},
{
Name: 'RawData',
Type: 'Output',
Value: null
}
];
}
/**
* Metadata about this action
*/
get Description() {
return 'Retrieves and analyzes comments from TikTok videos including sentiment, engagement metrics, and notable comments';
}
};
exports.GetCommentsAction = GetCommentsAction;
exports.GetCommentsAction = GetCommentsAction = __decorate([
(0, global_1.RegisterClass)(actions_1.BaseAction, 'GetCommentsAction')
], GetCommentsAction);
//# sourceMappingURL=get-comments.action.js.map