UNPKG

@memberjunction/actions-bizapps-social

Version:

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

354 lines 14.3 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.HootSuiteSearchPostsAction = void 0; const global_1 = require("@memberjunction/global"); const hootsuite_base_action_1 = require("../hootsuite-base.action"); const core_1 = require("@memberjunction/core"); const actions_1 = require("@memberjunction/actions"); /** * Action to search historical posts in HootSuite */ let HootSuiteSearchPostsAction = class HootSuiteSearchPostsAction extends hootsuite_base_action_1.HootSuiteBaseAction { /** * Search for posts in HootSuite */ 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 search parameters const query = this.getParamValue(Params, 'Query'); const hashtags = this.getParamValue(Params, 'Hashtags'); const startDate = this.getParamValue(Params, 'StartDate'); const endDate = this.getParamValue(Params, 'EndDate'); const profileId = this.getParamValue(Params, 'ProfileID'); const postState = this.getParamValue(Params, 'PostState') || 'PUBLISHED'; const limit = this.getParamValue(Params, 'Limit') || 100; const includeAnalytics = this.getParamValue(Params, 'IncludeAnalytics') || false; const sortBy = this.getParamValue(Params, 'SortBy') || 'publishedTime'; const sortOrder = this.getParamValue(Params, 'SortOrder') || 'DESC'; // Build search parameters const searchParams = { query: query, hashtags: hashtags, startDate: startDate ? new Date(startDate) : undefined, endDate: endDate ? new Date(endDate) : undefined, limit: limit }; // Perform the search (0, core_1.LogStatus)('Searching for posts...'); const posts = await this.searchPosts(searchParams); // Filter by profile if specified let filteredPosts = posts; if (profileId) { filteredPosts = posts.filter(p => p.profileId.includes(profileId) || (p.platformSpecificData?.socialProfileIds?.includes(profileId))); } // Sort results filteredPosts = this.sortPosts(filteredPosts, sortBy, sortOrder); // Limit results if (limit && filteredPosts.length > limit) { filteredPosts = filteredPosts.slice(0, limit); } // Optionally include analytics if (includeAnalytics) { (0, core_1.LogStatus)('Fetching analytics for posts...'); filteredPosts = await this.enrichPostsWithAnalytics(filteredPosts); } // Analyze content patterns const contentAnalysis = this.analyzePostContent(filteredPosts); // Create summary const summary = { totalResults: filteredPosts.length, dateRange: { start: startDate || 'Not specified', end: endDate || 'Not specified', actualStart: filteredPosts.length > 0 ? filteredPosts[filteredPosts.length - 1].publishedAt.toISOString() : null, actualEnd: filteredPosts.length > 0 ? filteredPosts[0].publishedAt.toISOString() : null }, byProfile: this.groupByProfile(filteredPosts), byMonth: this.groupByMonth(filteredPosts), contentAnalysis: contentAnalysis, topHashtags: this.extractTopHashtags(filteredPosts), performanceStats: includeAnalytics ? this.calculatePerformanceStats(filteredPosts) : null }; // Update output parameters const outputParams = [...Params]; const postsParam = outputParams.find(p => p.Name === 'Posts'); if (postsParam) postsParam.Value = filteredPosts; const summaryParam = outputParams.find(p => p.Name === 'Summary'); if (summaryParam) summaryParam.Value = summary; return { Success: true, ResultCode: 'SUCCESS', Message: `Found ${filteredPosts.length} posts matching search criteria`, Params: outputParams }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { Success: false, ResultCode: 'ERROR', Message: `Failed to search posts: ${errorMessage}`, Params }; } } /** * Search for posts implementation */ async searchPosts(params) { const queryParams = { limit: Math.min(params.limit || 100, 100), maxResults: params.limit }; // Add state filter - default to published for historical search queryParams.state = 'PUBLISHED'; // Add date filters if (params.startDate) { queryParams.publishedAfter = this.formatHootSuiteDate(params.startDate); } if (params.endDate) { queryParams.publishedBefore = this.formatHootSuiteDate(params.endDate); } // Add text search if (params.query) { queryParams.text = params.query; } // Add hashtag search if (params.hashtags && params.hashtags.length > 0) { // HootSuite API may require hashtags in the text query const hashtagQuery = params.hashtags.map(tag => tag.startsWith('#') ? tag : `#${tag}`).join(' '); queryParams.text = params.query ? `${params.query} ${hashtagQuery}` : hashtagQuery; } // Make paginated request const hootSuitePosts = await this.makePaginatedRequest('/messages', queryParams); // Convert to common format return hootSuitePosts.map(post => this.normalizePost(post)); } /** * Enrich posts with analytics data */ async enrichPostsWithAnalytics(posts) { const enrichedPosts = []; for (const post of posts) { try { const response = await this.axiosInstance.get(`/analytics/posts/${post.id}`); const analytics = response.data; enrichedPosts.push({ ...post, analytics: this.normalizeAnalytics(analytics.metrics) }); } catch (error) { // If analytics fail, include post without analytics (0, core_1.LogStatus)(`Could not get analytics for post ${post.id}`); enrichedPosts.push(post); } } return enrichedPosts; } /** * Sort posts by specified criteria */ sortPosts(posts, sortBy, sortOrder) { return posts.sort((a, b) => { let compareValue = 0; switch (sortBy) { case 'publishedTime': compareValue = a.publishedAt.getTime() - b.publishedAt.getTime(); break; case 'engagements': compareValue = (a.analytics?.engagements || 0) - (b.analytics?.engagements || 0); break; case 'impressions': compareValue = (a.analytics?.impressions || 0) - (b.analytics?.impressions || 0); break; default: compareValue = a.publishedAt.getTime() - b.publishedAt.getTime(); } return sortOrder === 'DESC' ? -compareValue : compareValue; }); } /** * Analyze post content patterns */ analyzePostContent(posts) { const totalPosts = posts.length; if (totalPosts === 0) return null; const contentLengths = posts.map(p => p.content.length); const avgLength = contentLengths.reduce((a, b) => a + b, 0) / totalPosts; const withMedia = posts.filter(p => p.mediaUrls && p.mediaUrls.length > 0).length; const withHashtags = posts.filter(p => p.content.includes('#')).length; const withLinks = posts.filter(p => p.content.includes('http://') || p.content.includes('https://')).length; return { averageLength: Math.round(avgLength), minLength: Math.min(...contentLengths), maxLength: Math.max(...contentLengths), withMedia: withMedia, withMediaPercentage: (withMedia / totalPosts) * 100, withHashtags: withHashtags, withHashtagsPercentage: (withHashtags / totalPosts) * 100, withLinks: withLinks, withLinksPercentage: (withLinks / totalPosts) * 100 }; } /** * Extract top hashtags from posts */ extractTopHashtags(posts) { const hashtagCounts = {}; posts.forEach(post => { const hashtags = post.content.match(/#\w+/g) || []; hashtags.forEach(tag => { const normalized = tag.toLowerCase(); hashtagCounts[normalized] = (hashtagCounts[normalized] || 0) + 1; }); }); return Object.entries(hashtagCounts) .map(([hashtag, count]) => ({ hashtag, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); } /** * Calculate performance statistics */ calculatePerformanceStats(posts) { const postsWithAnalytics = posts.filter(p => p.analytics); if (postsWithAnalytics.length === 0) return null; const totalEngagements = postsWithAnalytics.reduce((sum, p) => sum + (p.analytics?.engagements || 0), 0); const totalImpressions = postsWithAnalytics.reduce((sum, p) => sum + (p.analytics?.impressions || 0), 0); const avgEngagements = totalEngagements / postsWithAnalytics.length; const avgImpressions = totalImpressions / postsWithAnalytics.length; const engagementRate = totalImpressions > 0 ? (totalEngagements / totalImpressions) * 100 : 0; return { postsAnalyzed: postsWithAnalytics.length, averageEngagements: Math.round(avgEngagements), averageImpressions: Math.round(avgImpressions), totalEngagements: totalEngagements, totalImpressions: totalImpressions, engagementRate: engagementRate.toFixed(2) }; } /** * Group posts by profile */ groupByProfile(posts) { const groups = {}; posts.forEach(post => { const profiles = post.platformSpecificData?.socialProfileIds || [post.profileId]; profiles.forEach((profileId) => { groups[profileId] = (groups[profileId] || 0) + 1; }); }); return groups; } /** * Group posts by month */ groupByMonth(posts) { const groups = {}; posts.forEach(post => { const monthKey = post.publishedAt.toISOString().substring(0, 7); groups[monthKey] = (groups[monthKey] || 0) + 1; }); return groups; } /** * Define the parameters this action expects */ get Params() { return [ ...this.commonSocialParams, { Name: 'Query', Type: 'Input', Value: null }, { Name: 'Hashtags', Type: 'Input', Value: null }, { Name: 'StartDate', Type: 'Input', Value: null }, { Name: 'EndDate', Type: 'Input', Value: null }, { Name: 'PostState', Type: 'Input', Value: null }, { Name: 'Limit', Type: 'Input', Value: null }, { Name: 'IncludeAnalytics', Type: 'Input', Value: null }, { Name: 'SortBy', Type: 'Input', Value: null }, { Name: 'SortOrder', Type: 'Input', Value: null }, { Name: 'Posts', Type: 'Output', Value: null }, { Name: 'Summary', Type: 'Output', Value: null } ]; } /** * Get action description */ get Description() { return 'Searches historical posts in HootSuite with support for text queries, hashtags, date ranges, and content analysis'; } }; exports.HootSuiteSearchPostsAction = HootSuiteSearchPostsAction; exports.HootSuiteSearchPostsAction = HootSuiteSearchPostsAction = __decorate([ (0, global_1.RegisterClass)(actions_1.BaseAction, 'HootSuiteSearchPostsAction') ], HootSuiteSearchPostsAction); //# sourceMappingURL=search-posts.action.js.map