UNPKG

@memberjunction/actions-bizapps-social

Version:

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

427 lines 18.2 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 { YouTubeBaseAction } from '../youtube-base.action.js'; import { BaseAction } from '@memberjunction/actions'; /** * Action to get analytics for a YouTube channel */ let YouTubeGetChannelAnalyticsAction = class YouTubeGetChannelAnalyticsAction extends YouTubeBaseAction { /** * Get analytics for a YouTube channel */ async InternalRunAction(params) { const { Params, ContextUser } = params; try { // Initialize OAuth const companyIntegrationId = this.getParamValue(Params, 'CompanyIntegrationID'); if (!companyIntegrationId) { throw new Error('CompanyIntegrationID is required'); } const initialized = await this.initializeOAuth(companyIntegrationId, params); if (!initialized) { throw new Error('Failed to initialize YouTube OAuth connection'); } // Extract parameters const channelId = this.getParamValue(Params, 'ChannelID') || this.getCustomAttribute(1); const dateRange = this.getParamValue(Params, 'DateRange') || 'last30Days'; const startDate = this.getParamValue(Params, 'StartDate'); const endDate = this.getParamValue(Params, 'EndDate'); const includeVideos = this.getParamValue(Params, 'IncludeVideos') ?? true; const videoLimit = this.getParamValue(Params, 'VideoLimit') || 50; // If no channel ID provided, get the authenticated user's channel let actualChannelId = channelId; if (!actualChannelId) { const channelResponse = await this.makeYouTubeRequest('/channels', 'GET', undefined, { part: 'id,snippet,statistics,contentDetails,brandingSettings', mine: true }, ContextUser); if (!channelResponse.items || channelResponse.items.length === 0) { throw new Error('No channel found for authenticated user'); } actualChannelId = channelResponse.items[0].id; } else { // Get channel details for specified channel const channelResponse = await this.makeYouTubeRequest('/channels', 'GET', undefined, { part: 'snippet,statistics,contentDetails,brandingSettings', id: actualChannelId }, ContextUser); if (!channelResponse.items || channelResponse.items.length === 0) { throw new Error(`Channel not found: ${actualChannelId}`); } } // Get channel details const channelData = await this.getChannelDetails(actualChannelId, ContextUser); const channel = channelData.items[0]; // Calculate date range const { start, end } = this.calculateDateRange(dateRange, startDate, endDate); // Get videos for the date range if requested let videos = []; let videoAnalytics = {}; if (includeVideos) { videos = await this.getChannelVideosForDateRange(actualChannelId, start, end, videoLimit, ContextUser); if (videos.length > 0) { videoAnalytics = await this.aggregateVideoAnalytics(videos); } } // Prepare channel analytics const channelAnalytics = { channelId: channel.id, channelTitle: channel.snippet.title, channelDescription: channel.snippet.description, channelUrl: `https://www.youtube.com/channel/${channel.id}`, customUrl: channel.snippet.customUrl, country: channel.snippet.country, publishedAt: channel.snippet.publishedAt, thumbnails: channel.snippet.thumbnails, // Overall statistics statistics: { viewCount: parseInt(channel.statistics.viewCount || '0'), subscriberCount: parseInt(channel.statistics.subscriberCount || '0'), videoCount: parseInt(channel.statistics.videoCount || '0'), // Note: hiddenSubscriberCount indicates if subscriber count is public subscribersHidden: channel.statistics.hiddenSubscriberCount }, // Date range specific analytics dateRangeAnalytics: { dateRange: { start: start.toISOString(), end: end.toISOString(), days: Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) }, videosPublished: videos.length, totalViews: videoAnalytics.totalViews || 0, totalLikes: videoAnalytics.totalLikes || 0, totalComments: videoAnalytics.totalComments || 0, averageViewsPerVideo: videoAnalytics.averageViews || 0, topPerformingVideos: videoAnalytics.topVideos || [] }, // Growth metrics (if we had historical data) growth: { note: 'Growth metrics require YouTube Analytics API access', subscriberGrowth: 'N/A', viewGrowth: 'N/A' }, // Content details contentDetails: { relatedPlaylists: channel.contentDetails.relatedPlaylists, uploadPlaylistId: channel.contentDetails.relatedPlaylists.uploads }, // Branding branding: { keywords: channel.brandingSettings?.channel?.keywords, unsubscribedTrailer: channel.brandingSettings?.channel?.unsubscribedTrailer, featuredChannelsUrls: channel.brandingSettings?.channel?.featuredChannelsUrls }, quotaCost: this.getQuotaCost('channels.list') + (videos.length > 0 ? this.getQuotaCost('search.list') + this.getQuotaCost('videos.list') : 0) }; // Prepare summary const summary = { channelHealth: this.calculateChannelHealth(channel, videoAnalytics), recommendations: this.generateRecommendations(channel, videoAnalytics), performanceIndicators: { engagementRate: this.calculateEngagementRate(channel.statistics, videoAnalytics), averageViewsPerSubscriber: this.calculateViewsPerSubscriber(channel.statistics), uploadFrequency: this.calculateUploadFrequency(videos, start, end) } }; // Update output parameters const outputParams = [...Params]; const analyticsParam = outputParams.find(p => p.Name === 'Analytics'); if (analyticsParam) analyticsParam.Value = channelAnalytics; const summaryParam = outputParams.find(p => p.Name === 'Summary'); if (summaryParam) summaryParam.Value = summary; const videosParam = outputParams.find(p => p.Name === 'Videos'); if (videosParam) videosParam.Value = videos; return { Success: true, ResultCode: 'SUCCESS', Message: `Retrieved analytics for channel: ${channel.snippet.title}`, Params: outputParams }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { Success: false, ResultCode: this.getErrorCode(errorMessage), Message: `Failed to get channel analytics: ${errorMessage}`, Params }; } } /** * Get channel details */ async getChannelDetails(channelId, contextUser) { return await this.makeYouTubeRequest('/channels', 'GET', undefined, { part: 'snippet,statistics,contentDetails,brandingSettings', id: channelId }, contextUser); } /** * Get videos for a date range */ async getChannelVideosForDateRange(channelId, startDate, endDate, limit, contextUser) { const searchResponse = await this.makeYouTubeRequest('/search', 'GET', undefined, { part: 'id', channelId: channelId, type: 'video', publishedAfter: startDate.toISOString(), publishedBefore: endDate.toISOString(), maxResults: Math.min(limit, 50), order: 'date' }, contextUser); if (!searchResponse.items || searchResponse.items.length === 0) { return []; } const videoIds = searchResponse.items.map((item) => item.id.videoId).join(','); const videosResponse = await this.makeYouTubeRequest('/videos', 'GET', undefined, { part: 'snippet,statistics,contentDetails', id: videoIds }, contextUser); return videosResponse.items || []; } /** * Aggregate video analytics */ aggregateVideoAnalytics(videos) { let totalViews = 0; let totalLikes = 0; let totalComments = 0; let totalDislikes = 0; for (const video of videos) { const stats = video.statistics || {}; totalViews += parseInt(stats.viewCount || '0'); totalLikes += parseInt(stats.likeCount || '0'); totalComments += parseInt(stats.commentCount || '0'); totalDislikes += parseInt(stats.dislikeCount || '0'); } const topVideos = [...videos] .sort((a, b) => parseInt(b.statistics?.viewCount || '0') - parseInt(a.statistics?.viewCount || '0')) .slice(0, 5) .map(v => ({ id: v.id, title: v.snippet.title, views: parseInt(v.statistics?.viewCount || '0'), likes: parseInt(v.statistics?.likeCount || '0'), comments: parseInt(v.statistics?.commentCount || '0'), publishedAt: v.snippet.publishedAt, url: `https://www.youtube.com/watch?v=${v.id}` })); return { totalViews, totalLikes, totalComments, totalDislikes, averageViews: videos.length > 0 ? Math.round(totalViews / videos.length) : 0, averageLikes: videos.length > 0 ? Math.round(totalLikes / videos.length) : 0, topVideos }; } /** * Calculate date range */ calculateDateRange(dateRange, startDate, endDate) { const end = endDate ? new Date(endDate) : new Date(); let start; if (startDate) { start = new Date(startDate); } else { switch (dateRange) { case 'last7Days': start = new Date(end.getTime() - 7 * 24 * 60 * 60 * 1000); break; case 'last30Days': start = new Date(end.getTime() - 30 * 24 * 60 * 60 * 1000); break; case 'last90Days': start = new Date(end.getTime() - 90 * 24 * 60 * 60 * 1000); break; case 'lastYear': start = new Date(end.getTime() - 365 * 24 * 60 * 60 * 1000); break; default: start = new Date(end.getTime() - 30 * 24 * 60 * 60 * 1000); } } return { start, end }; } /** * Calculate channel health score */ calculateChannelHealth(channel, videoAnalytics) { const stats = channel.statistics; const subscribers = parseInt(stats.subscriberCount || '0'); const views = parseInt(stats.viewCount || '0'); const videos = parseInt(stats.videoCount || '0'); // Simple health score based on engagement let score = 0; let factors = []; // Subscriber to view ratio if (subscribers > 0 && views > subscribers * 100) { score += 25; factors.push('Good view-to-subscriber ratio'); } // Video count if (videos > 50) { score += 25; factors.push('Active content creation'); } // Recent engagement if (videoAnalytics.averageViews > subscribers * 0.1) { score += 25; factors.push('Strong recent engagement'); } // Upload consistency if (videoAnalytics.topVideos?.length >= 3) { score += 25; factors.push('Consistent uploads'); } return { score: Math.min(score, 100), rating: score >= 75 ? 'Excellent' : score >= 50 ? 'Good' : score >= 25 ? 'Fair' : 'Needs Improvement', factors }; } /** * Generate recommendations */ generateRecommendations(channel, videoAnalytics) { const recommendations = []; const stats = channel.statistics; const subscribers = parseInt(stats.subscriberCount || '0'); if (videoAnalytics.averageViews < subscribers * 0.05) { recommendations.push('Consider improving video titles and thumbnails to increase click-through rate'); } if (videoAnalytics.topVideos?.length < 3) { recommendations.push('Increase upload frequency to maintain audience engagement'); } if (!channel.brandingSettings?.channel?.keywords) { recommendations.push('Add channel keywords to improve discoverability'); } if (!channel.snippet.customUrl) { recommendations.push('Claim a custom URL for easier channel sharing'); } return recommendations; } /** * Calculate engagement rate */ calculateEngagementRate(stats, videoAnalytics) { const views = videoAnalytics.totalViews || parseInt(stats.viewCount || '0'); const engagements = (videoAnalytics.totalLikes || 0) + (videoAnalytics.totalComments || 0); return views > 0 ? Math.round((engagements / views) * 10000) / 100 : 0; } /** * Calculate views per subscriber */ calculateViewsPerSubscriber(stats) { const views = parseInt(stats.viewCount || '0'); const subscribers = parseInt(stats.subscriberCount || '0'); return subscribers > 0 ? Math.round((views / subscribers) * 100) / 100 : 0; } /** * Calculate upload frequency */ calculateUploadFrequency(videos, start, end) { const days = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); const videosPerDay = videos.length / days; if (videosPerDay >= 1) return `${Math.round(videosPerDay * 10) / 10} videos/day`; if (videosPerDay >= 1 / 7) return `${Math.round(videosPerDay * 7 * 10) / 10} videos/week`; return `${Math.round(videosPerDay * 30 * 10) / 10} videos/month`; } /** * Get error code from error message */ getErrorCode(message) { if (message.includes('quota')) return 'QUOTA_EXCEEDED'; if (message.includes('401') || message.includes('unauthorized')) return 'INVALID_TOKEN'; if (message.includes('403') || message.includes('forbidden')) return 'INSUFFICIENT_PERMISSIONS'; if (message.includes('404')) return 'NOT_FOUND'; if (message.includes('rate limit')) return 'RATE_LIMIT'; return 'ERROR'; } /** * Define the parameters this action expects */ get Params() { const baseParams = this.commonSocialParams; const specificParams = [ { Name: 'ChannelID', Type: 'Input', Value: null }, { Name: 'DateRange', Type: 'Input', Value: 'last30Days' }, { Name: 'StartDate', Type: 'Input', Value: null }, { Name: 'EndDate', Type: 'Input', Value: null }, { Name: 'IncludeVideos', Type: 'Input', Value: true }, { Name: 'VideoLimit', Type: 'Input', Value: 50 }, { Name: 'Analytics', Type: 'Output', Value: null }, { Name: 'Summary', Type: 'Output', Value: null }, { Name: 'Videos', Type: 'Output', Value: null } ]; return [...baseParams, ...specificParams]; } /** * Metadata about this action */ get Description() { return 'Gets comprehensive analytics for a YouTube channel including statistics, growth metrics, and performance indicators'; } }; YouTubeGetChannelAnalyticsAction = __decorate([ RegisterClass(BaseAction, 'YouTubeGetChannelAnalyticsAction') ], YouTubeGetChannelAnalyticsAction); export { YouTubeGetChannelAnalyticsAction }; //# sourceMappingURL=get-channel-analytics.action.js.map