UNPKG

@memberjunction/actions-bizapps-social

Version:

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

270 lines (235 loc) 7.94 kB
import { BaseOAuthAction } from '@memberjunction/actions'; import { ActionParam } from '@memberjunction/actions-base'; import { LogStatus, LogError } from '@memberjunction/core'; /** * Common interfaces for social media actions */ export interface SocialPost { id: string; platform: string; profileId: string; content: string; mediaUrls: string[]; publishedAt: Date; scheduledFor?: Date; analytics?: SocialAnalytics; platformSpecificData: Record<string, any>; } export interface SocialAnalytics { impressions: number; engagements: number; clicks: number; shares: number; comments: number; likes: number; reach: number; saves?: number; videoViews?: number; platformMetrics: Record<string, any>; } export interface SearchParams { query?: string; hashtags?: string[]; startDate?: Date; endDate?: Date; limit?: number; offset?: number; } export interface MediaFile { filename: string; mimeType: string; data: Buffer | string; // Base64 or buffer size: number; } export enum SocialMediaErrorCode { RATE_LIMIT_EXCEEDED = 'RATE_LIMIT', INVALID_TOKEN = 'INVALID_TOKEN', TOKEN_EXPIRED = 'TOKEN_EXPIRED', PLATFORM_ERROR = 'PLATFORM_ERROR', INVALID_MEDIA = 'INVALID_MEDIA', POST_NOT_FOUND = 'POST_NOT_FOUND', INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS' } /** * Base class for all social media actions. * Provides common functionality for authentication, media handling, * analytics normalization, and rate limiting. */ export abstract class BaseSocialMediaAction extends BaseOAuthAction { /** * Common parameters for all social media actions */ protected get commonSocialParams(): ActionParam[] { return [ ...this.oauthParams, { Name: 'ProfileID', Type: 'Input', Value: null } ]; } /** * Get the platform name (e.g., 'Twitter', 'LinkedIn') */ protected abstract get platformName(): string; /** * Get the API base URL for the platform */ protected abstract get apiBaseUrl(): string; /** * Normalize platform-specific analytics to common format */ protected normalizeAnalytics(platformData: any): SocialAnalytics { // Default implementation - override in platform-specific classes return { impressions: platformData.impressions || 0, engagements: platformData.engagements || 0, clicks: platformData.clicks || 0, shares: platformData.shares || 0, comments: platformData.comments || 0, likes: platformData.likes || 0, reach: platformData.reach || 0, saves: platformData.saves, videoViews: platformData.videoViews, platformMetrics: platformData }; } /** * Upload media files to the platform */ protected async uploadMedia(files: MediaFile[]): Promise<string[]> { const uploadedUrls: string[] = []; for (const file of files) { // Validate file this.validateMediaFile(file); // Upload file (platform-specific implementation) const url = await this.uploadSingleMedia(file); uploadedUrls.push(url); } return uploadedUrls; } /** * Platform-specific media upload implementation */ protected abstract uploadSingleMedia(file: MediaFile): Promise<string>; /** * Validate media file meets platform requirements */ protected validateMediaFile(file: MediaFile): void { const maxSizes: Record<string, number> = { 'image/jpeg': 5 * 1024 * 1024, // 5MB 'image/png': 5 * 1024 * 1024, // 5MB 'image/gif': 15 * 1024 * 1024, // 15MB 'video/mp4': 512 * 1024 * 1024 // 512MB }; const maxSize = maxSizes[file.mimeType]; if (!maxSize) { throw new Error(`Unsupported media type: ${file.mimeType}`); } if (file.size > maxSize) { throw new Error(`File size exceeds limit. Max ${maxSize} bytes, got ${file.size} bytes`); } } /** * Handle rate limiting with exponential backoff */ protected async handleRateLimit(retryAfter?: number): Promise<void> { const waitTime = retryAfter || 60; // Default to 60 seconds LogStatus(`Rate limit hit. Waiting ${waitTime} seconds...`); await new Promise(resolve => setTimeout(resolve, waitTime * 1000)); } /** * Search for posts on the platform */ protected abstract searchPosts(params: SearchParams): Promise<SocialPost[]>; /** * Convert platform-specific post data to common format */ protected abstract normalizePost(platformPost: any): SocialPost; /** * Parse rate limit headers from API response */ protected parseRateLimitHeaders(headers: any): { remaining: number; reset: Date; limit: number; } | null { // Common header patterns across platforms const remaining = headers['x-rate-limit-remaining'] || headers['x-ratelimit-remaining'] || headers['rate-limit-remaining']; const reset = headers['x-rate-limit-reset'] || headers['x-ratelimit-reset'] || headers['rate-limit-reset']; const limit = headers['x-rate-limit-limit'] || headers['x-ratelimit-limit'] || headers['rate-limit-limit']; if (remaining !== undefined && reset && limit) { return { remaining: parseInt(remaining), reset: new Date(parseInt(reset) * 1000), // Usually Unix timestamp limit: parseInt(limit) }; } return null; } /** * Build common API headers including authentication */ protected buildHeaders(additionalHeaders?: Record<string, string>): Record<string, string> { const token = this.getAccessToken(); if (!token) { throw new Error('No access token available'); } return { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'Accept': 'application/json', ...additionalHeaders }; } /** * Format date for API requests (ISO 8601) */ protected formatDate(date: Date | string): string { if (typeof date === 'string') { date = new Date(date); } return date.toISOString(); } /** * Parse date from API response */ protected parseDate(dateString: string): Date { return new Date(dateString); } /** * Get profile ID from parameters or default from integration */ protected getProfileId(params: any): string { return params.ProfileID || this.getCustomAttribute(1) || ''; } /** * Log API request for debugging */ protected logApiRequest(method: string, url: string, data?: any): void { LogStatus(`${this.platformName} API Request: ${method} ${url}`); if (data) { LogStatus(`Request Data: ${JSON.stringify(data, null, 2)}`); } } /** * Log API response for debugging */ protected logApiResponse(response: any): void { LogStatus(`${this.platformName} API Response: ${JSON.stringify(response, null, 2)}`); } /** * Helper to get parameter value from params array */ protected getParamValue(params: ActionParam[], paramName: string): any { const param = params.find(p => p.Name === paramName); return param?.Value; } }