UNPKG

@memberjunction/actions-bizapps-social

Version:

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

294 lines (260 loc) 11.5 kB
import { RegisterClass } from '@memberjunction/global'; import { HootSuiteBaseAction, HootSuitePost } from '../hootsuite-base.action'; import { ActionParam, ActionResultSimple, RunActionParams } from '@memberjunction/actions-base'; import { LogStatus, LogError } from '@memberjunction/core'; import { MediaFile } from '../../../base/base-social.action'; import { BaseAction } from '@memberjunction/actions'; /** * Interface for bulk post data */ interface BulkPostData { content: string; scheduledTime?: string; profileIds?: string[]; mediaFiles?: MediaFile[]; tags?: string[]; location?: { latitude: number; longitude: number; }; } /** * Action to bulk schedule multiple posts in HootSuite */ @RegisterClass(BaseAction, 'HootSuiteBulkSchedulePostsAction') export class HootSuiteBulkSchedulePostsAction extends HootSuiteBaseAction { /** * Bulk schedule posts in HootSuite */ protected async InternalRunAction(params: RunActionParams): Promise<ActionResultSimple> { 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 parameters const posts = this.getParamValue(Params, 'Posts') as BulkPostData[]; const defaultProfileIds = this.getParamValue(Params, 'DefaultProfileIDs'); const scheduleInterval = this.getParamValue(Params, 'ScheduleInterval'); const startTime = this.getParamValue(Params, 'StartTime'); const skipOnError = this.getParamValue(Params, 'SkipOnError') || true; const validateOnly = this.getParamValue(Params, 'ValidateOnly') || false; // Validate required parameters if (!posts || !Array.isArray(posts) || posts.length === 0) { throw new Error('Posts array is required and must not be empty'); } // Get default profiles if not specified let defaultProfiles: string[] = []; if (defaultProfileIds && Array.isArray(defaultProfileIds)) { defaultProfiles = defaultProfileIds; } else if (defaultProfileIds && typeof defaultProfileIds === 'string') { defaultProfiles = [defaultProfileIds]; } else { // Get all available profiles as default const profiles = await this.getSocialProfiles(); if (profiles.length === 0) { throw new Error('No social profiles found. Please specify DefaultProfileIDs.'); } defaultProfiles = profiles.map(p => p.id); LogStatus(`Using ${defaultProfiles.length} default profiles`); } // Calculate schedule times if interval is specified let baseScheduleTime: Date | null = null; if (scheduleInterval && startTime) { baseScheduleTime = new Date(startTime); } // Process posts const results: any[] = []; const errors: any[] = []; let successCount = 0; let failureCount = 0; LogStatus(`Processing ${posts.length} posts for bulk scheduling...`); for (let i = 0; i < posts.length; i++) { const post = posts[i]; const postIndex = i + 1; try { // Validate post data this.validateBulkPost(post, postIndex); // Calculate scheduled time for this post let scheduledTime = post.scheduledTime; if (!scheduledTime && baseScheduleTime && scheduleInterval) { const offsetMinutes = i * scheduleInterval; const postScheduleTime = new Date(baseScheduleTime); postScheduleTime.setMinutes(postScheduleTime.getMinutes() + offsetMinutes); scheduledTime = postScheduleTime.toISOString(); } // Prepare profile IDs const profileIds = post.profileIds || defaultProfiles; // If validate only, just check the data if (validateOnly) { results.push({ index: postIndex, status: 'VALIDATED', content: post.content.substring(0, 50) + '...', scheduledTime: scheduledTime, profileCount: profileIds.length }); successCount++; continue; } // Upload media if provided let mediaIds: string[] = []; if (post.mediaFiles && Array.isArray(post.mediaFiles)) { LogStatus(`Uploading ${post.mediaFiles.length} media files for post ${postIndex}...`); mediaIds = await this.uploadMedia(post.mediaFiles); } // Create the post const postData: any = { text: post.content, socialProfileIds: profileIds, scheduledTime: scheduledTime ? this.formatHootSuiteDate(scheduledTime) : undefined, mediaIds: mediaIds.length > 0 ? mediaIds : undefined, tags: post.tags, location: post.location }; const response = await this.axiosInstance.post('/messages', postData); const createdPost = response.data; results.push({ index: postIndex, status: 'SUCCESS', postId: createdPost.id, content: post.content.substring(0, 50) + '...', scheduledTime: createdPost.scheduledTime, profileCount: profileIds.length }); successCount++; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; errors.push({ index: postIndex, content: post.content.substring(0, 50) + '...', error: errorMessage }); failureCount++; if (!skipOnError) { throw new Error(`Failed at post ${postIndex}: ${errorMessage}`); } } // Log progress every 10 posts if (i > 0 && (i + 1) % 10 === 0) { LogStatus(`Processed ${i + 1}/${posts.length} posts...`); } } // Create summary const summary = { totalPosts: posts.length, successCount: successCount, failureCount: failureCount, validationOnly: validateOnly, processingTime: new Date().toISOString(), scheduleRange: baseScheduleTime ? { start: baseScheduleTime.toISOString(), end: new Date(baseScheduleTime.getTime() + (posts.length - 1) * (scheduleInterval || 0) * 60000).toISOString() } : null }; // Update output parameters const outputParams = [...Params]; const resultsParam = outputParams.find(p => p.Name === 'Results'); if (resultsParam) resultsParam.Value = results; const errorsParam = outputParams.find(p => p.Name === 'Errors'); if (errorsParam) errorsParam.Value = errors; const summaryParam = outputParams.find(p => p.Name === 'Summary'); if (summaryParam) summaryParam.Value = summary; const resultCode = failureCount === 0 ? 'SUCCESS' : (successCount > 0 ? 'PARTIAL_SUCCESS' : 'FAILED'); const message = validateOnly ? `Validated ${successCount} posts, ${failureCount} failed validation` : `Successfully scheduled ${successCount} posts, ${failureCount} failed`; return { Success: failureCount === 0 || (skipOnError && successCount > 0), ResultCode: resultCode, Message: message, Params: outputParams }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { Success: false, ResultCode: 'ERROR', Message: `Failed to bulk schedule posts: ${errorMessage}`, Params }; } } /** * Validate bulk post data */ private validateBulkPost(post: BulkPostData, index: number): void { if (!post.content || typeof post.content !== 'string' || post.content.trim().length === 0) { throw new Error(`Post ${index}: Content is required and must not be empty`); } if (post.content.length > 10000) { throw new Error(`Post ${index}: Content exceeds maximum length of 10000 characters`); } if (post.scheduledTime && isNaN(Date.parse(post.scheduledTime))) { throw new Error(`Post ${index}: Invalid scheduled time format`); } if (post.location) { if (typeof post.location.latitude !== 'number' || typeof post.location.longitude !== 'number') { throw new Error(`Post ${index}: Location must have numeric latitude and longitude`); } } } /** * Define the parameters this action expects */ public get Params(): ActionParam[] { return [ ...this.oauthParams, { Name: 'Posts', Type: 'Input', Value: null }, { Name: 'DefaultProfileIDs', Type: 'Input', Value: null }, { Name: 'ScheduleInterval', Type: 'Input', Value: null }, { Name: 'StartTime', Type: 'Input', Value: null }, { Name: 'SkipOnError', Type: 'Input', Value: null }, { Name: 'ValidateOnly', Type: 'Input', Value: null }, { Name: 'Results', Type: 'Output', Value: null }, { Name: 'Errors', Type: 'Output', Value: null }, { Name: 'Summary', Type: 'Output', Value: null } ]; } /** * Get action description */ public get Description(): string { return 'Bulk schedules multiple posts to HootSuite with support for auto-scheduling intervals and validation'; } }