@memberjunction/actions-bizapps-social
Version:
Social Media Actions for MemberJunction - Twitter, LinkedIn, Facebook, Instagram, TikTok, YouTube, HootSuite, Buffer
275 lines • 11.2 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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HootSuiteBaseAction = void 0;
const global_1 = require("@memberjunction/global");
const base_social_action_1 = require("../../base/base-social.action");
const axios_1 = __importDefault(require("axios"));
const core_1 = require("@memberjunction/core");
const actions_1 = require("@memberjunction/actions");
/**
* Base class for all HootSuite actions.
* Handles HootSuite-specific authentication, API interactions, and rate limiting.
*/
let HootSuiteBaseAction = class HootSuiteBaseAction extends base_social_action_1.BaseSocialMediaAction {
get platformName() {
return 'HootSuite';
}
get apiBaseUrl() {
return 'https://platform.hootsuite.com/v1';
}
/**
* Axios instance for making HTTP requests
*/
_axiosInstance = null;
/**
* Get or create axios instance with interceptors
*/
get axiosInstance() {
if (!this._axiosInstance) {
this._axiosInstance = axios_1.default.create({
baseURL: this.apiBaseUrl,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
// Add request interceptor for auth
this._axiosInstance.interceptors.request.use((config) => {
const token = this.getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (error) => Promise.reject(error));
// Add response interceptor for rate limit handling
this._axiosInstance.interceptors.response.use((response) => {
// Log rate limit info
const rateLimitInfo = this.parseRateLimitHeaders(response.headers);
if (rateLimitInfo) {
(0, core_1.LogStatus)(`HootSuite Rate Limit - Remaining: ${rateLimitInfo.remaining}/${rateLimitInfo.limit}, Reset: ${rateLimitInfo.reset}`);
}
return response;
}, async (error) => {
if (error.response?.status === 429) {
// Rate limit exceeded
const retryAfter = error.response.headers['retry-after'];
const waitTime = retryAfter ? parseInt(retryAfter) : 60;
await this.handleRateLimit(waitTime);
// Retry the request
return this._axiosInstance.request(error.config);
}
return Promise.reject(error);
});
}
return this._axiosInstance;
}
/**
* Refresh the access token using the refresh token
*/
async refreshAccessToken() {
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
throw new Error('No refresh token available for HootSuite');
}
try {
const response = await axios_1.default.post('https://platform.hootsuite.com/oauth2/token', {
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: this.getCustomAttribute(2), // Client ID stored in CustomAttribute2
client_secret: this.getCustomAttribute(3) // Client Secret stored in CustomAttribute3
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
const { access_token, refresh_token: newRefreshToken, expires_in } = response.data;
// Update stored tokens
await this.updateStoredTokens(access_token, newRefreshToken || refreshToken, expires_in);
(0, core_1.LogStatus)('HootSuite access token refreshed successfully');
}
catch (error) {
(0, core_1.LogError)(`Failed to refresh HootSuite access token: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw error;
}
}
/**
* Upload media to HootSuite
*/
async uploadSingleMedia(file) {
try {
// First, request an upload URL
const uploadRequest = await this.axiosInstance.post('/media', {
mimeType: file.mimeType,
sizeBytes: file.size
});
const { uploadUrl, mediaId } = uploadRequest.data;
// Upload the file to the provided URL
const fileData = typeof file.data === 'string'
? Buffer.from(file.data, 'base64')
: file.data;
await axios_1.default.put(uploadUrl, fileData, {
headers: {
'Content-Type': file.mimeType,
'Content-Length': file.size.toString()
}
});
// Wait for processing
await this.waitForMediaProcessing(mediaId);
return mediaId;
}
catch (error) {
(0, core_1.LogError)(`Failed to upload media to HootSuite: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw error;
}
}
/**
* Wait for media to finish processing
*/
async waitForMediaProcessing(mediaId, maxAttempts = 30) {
for (let i = 0; i < maxAttempts; i++) {
try {
const response = await this.axiosInstance.get(`/media/${mediaId}`);
const { state } = response.data;
if (state === 'READY') {
return;
}
else if (state === 'FAILED') {
throw new Error('Media processing failed');
}
// Wait 2 seconds before next check
await new Promise(resolve => setTimeout(resolve, 2000));
}
catch (error) {
if (i === maxAttempts - 1) {
throw new Error(`Media processing timeout for ${mediaId}`);
}
}
}
}
/**
* Get social profiles for the authenticated user
*/
async getSocialProfiles() {
try {
const response = await this.axiosInstance.get('/socialProfiles');
return response.data.data || [];
}
catch (error) {
(0, core_1.LogError)(`Failed to get social profiles: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw error;
}
}
/**
* Make a paginated request to HootSuite API
*/
async makePaginatedRequest(endpoint, params = {}) {
const results = [];
let cursor = null;
const limit = params.limit || 50;
do {
const queryParams = { ...params, limit };
if (cursor) {
queryParams.cursor = cursor;
}
const response = await this.axiosInstance.get(endpoint, { params: queryParams });
const data = response.data;
if (data.data && Array.isArray(data.data)) {
results.push(...data.data);
}
cursor = data.cursor || null;
// Check if we've reached the desired number of results
if (params.maxResults && results.length >= params.maxResults) {
return results.slice(0, params.maxResults);
}
} while (cursor);
return results;
}
/**
* Format date for HootSuite API (ISO 8601)
*/
formatHootSuiteDate(date) {
if (typeof date === 'string') {
date = new Date(date);
}
return date.toISOString();
}
/**
* Parse HootSuite date string
*/
parseHootSuiteDate(dateString) {
return new Date(dateString);
}
/**
* Convert HootSuite post to common format
*/
normalizePost(hootsuitePost) {
return {
id: hootsuitePost.id,
platform: 'HootSuite',
profileId: hootsuitePost.socialProfileIds.join(','), // Multiple profiles possible
content: hootsuitePost.text,
mediaUrls: hootsuitePost.mediaIds || [],
publishedAt: this.parseHootSuiteDate(hootsuitePost.createdTime),
scheduledFor: hootsuitePost.scheduledTime ? this.parseHootSuiteDate(hootsuitePost.scheduledTime) : undefined,
platformSpecificData: {
state: hootsuitePost.state,
tags: hootsuitePost.tags,
location: hootsuitePost.location,
socialProfileIds: hootsuitePost.socialProfileIds
}
};
}
/**
* Search for posts - implemented in search action
*/
async searchPosts(params) {
// This is implemented in the search-posts.action.ts
throw new Error('Search posts is implemented in HootSuiteSearchPostsAction');
}
/**
* Handle HootSuite-specific errors
*/
handleHootSuiteError(error) {
if (error.response) {
const { status, data } = error.response;
const errorData = data;
switch (status) {
case 400:
throw new Error(`Bad Request: ${errorData.message || 'Invalid request parameters'}`);
case 401:
throw new Error('Unauthorized: Invalid or expired access token');
case 403:
throw new Error('Forbidden: Insufficient permissions');
case 404:
throw new Error('Not Found: Resource does not exist');
case 429:
throw new Error('Rate Limit Exceeded: Too many requests');
case 500:
throw new Error('Internal Server Error: HootSuite service error');
default:
throw new Error(`HootSuite API Error (${status}): ${errorData.message || 'Unknown error'}`);
}
}
else if (error.request) {
throw new Error('Network Error: No response from HootSuite');
}
else {
throw new Error(`Request Error: ${error.message}`);
}
}
};
exports.HootSuiteBaseAction = HootSuiteBaseAction;
exports.HootSuiteBaseAction = HootSuiteBaseAction = __decorate([
(0, global_1.RegisterClass)(actions_1.BaseAction, 'HootSuiteBaseAction')
], HootSuiteBaseAction);
//# sourceMappingURL=hootsuite-base.action.js.map