UNPKG

@inite/n8n-nodes-instagram-private-api

Version:
633 lines (632 loc) 29.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstagramPrivateApi = void 0; const n8n_workflow_1 = require("n8n-workflow"); const instagram_private_api_1 = require("instagram-private-api"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const os = __importStar(require("os")); // Define error types locally since they're not exported from the package class IgLoginRequiredError extends Error { constructor(message) { super(message); this.name = 'IgLoginRequiredError'; } } class IgCheckpointError extends Error { constructor(message) { super(message); this.name = 'IgCheckpointError'; } } class IgNotFoundError extends Error { constructor(message) { super(message); this.name = 'IgNotFoundError'; } } class InstagramPrivateApi { constructor() { this.description = { displayName: 'Instagram Private API', name: 'instagramPrivateApi', icon: 'file:instagram.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["operation"]}}', description: 'Interact with Instagram using the private API', defaults: { name: 'Instagram Private API', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'instagramPrivateApi', required: true, }, ], properties: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true, options: [ { name: 'Send DM by Username', value: 'sendDMByUsername', description: 'Send a direct message to a user by their username', action: 'Send a direct message by username', }, { name: 'Send DM by User ID', value: 'sendDMByUserId', description: 'Send a direct message to a user by their ID', action: 'Send a direct message by user ID', }, { name: 'Search User', value: 'searchUser', description: 'Search for a user by username', action: 'Search for a user', }, { name: 'Get User Info', value: 'getUserInfo', description: 'Get information about a user', action: 'Get user information', }, { name: 'Get User Feed', value: 'getUserFeed', description: 'Get a user\'s feed', action: 'Get user feed', }, { name: 'Get User Stories', value: 'getUserStories', description: 'Get a user\'s stories', action: 'Get user stories', }, { name: 'Manage Session', value: 'manageSession', description: 'Manage Instagram session data', action: 'Manage Instagram session', }, ], default: 'sendDMByUsername', }, { displayName: 'Username', name: 'username', type: 'string', default: '', description: 'Username to send message to', displayOptions: { show: { operation: ['sendDMByUsername'], }, }, }, { displayName: 'User ID', name: 'userId', type: 'string', default: '', description: 'User ID to send message to', displayOptions: { show: { operation: ['sendDMByUserId'], }, }, }, { displayName: 'Message', name: 'message', type: 'string', default: '', description: 'Message to send', displayOptions: { show: { operation: ['sendDMByUsername', 'sendDMByUserId'], }, }, }, { displayName: 'Username', name: 'username', type: 'string', default: '', description: 'Username to search for', displayOptions: { show: { operation: ['searchUser'], }, }, }, { displayName: 'User ID', name: 'userId', type: 'string', default: '', description: 'User ID to get information for', displayOptions: { show: { operation: ['getUserInfo', 'getUserFeed', 'getUserStories'], }, }, }, { displayName: 'Session Action', name: 'sessionAction', type: 'options', noDataExpression: true, options: [ { name: 'Get Current Session', value: 'getSession', description: 'Get the current session data', action: 'Get current session data', }, { name: 'Login and Get Session', value: 'loginAndGetSession', description: 'Login to Instagram and get session data', action: 'Login and get session data', }, ], default: 'getSession', displayOptions: { show: { operation: ['manageSession'], }, }, }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; const operation = this.getNodeParameter('operation', 0); const credentials = await this.getCredentials('instagramPrivateApi'); // Create Instagram client const ig = new instagram_private_api_1.IgApiClient(); ig.state.generateDevice(credentials.username); // Define session file path const sessionDir = path.join(os.homedir(), '.n8n', 'instagram-sessions'); const sessionFilePath = path.join(sessionDir, `${credentials.username}.json`); // Ensure session directory exists if (!fs.existsSync(sessionDir)) { fs.mkdirSync(sessionDir, { recursive: true }); } // Try to load session from file let sessionData; let isAuthenticated = false; let capturedSessionData = null; // First try to load from credentials (for backward compatibility) if (credentials.sessionData) { sessionData = credentials.sessionData; } // Then try to load from file if (fs.existsSync(sessionFilePath)) { try { const fileData = fs.readFileSync(sessionFilePath, 'utf8'); const parsedData = JSON.parse(fileData); sessionData = parsedData.sessionData; console.log('Session loaded from file'); } catch (error) { console.error('Failed to load session from file:', error); } } // If we have session data, load it if (sessionData) { try { await ig.state.deserialize(sessionData); console.log('Session loaded successfully from credentials'); // Verify the session is still valid try { await ig.account.currentUser(); console.log('Session is valid'); isAuthenticated = true; } catch (error) { console.log('Session is invalid, re-authenticating...'); // Session is invalid, re-authenticate await ig.account.login(credentials.username, credentials.password); console.log('Successfully re-authenticated with Instagram'); isAuthenticated = true; } } catch (error) { console.error('Failed to load session from credentials:', error); // Continue with a fresh session if loading fails } } // If we're not authenticated yet, do it now if (!isAuthenticated) { console.log('Authenticating with Instagram...'); try { await ig.account.login(credentials.username, credentials.password); console.log('Successfully authenticated with Instagram'); isAuthenticated = true; } catch (error) { console.error('Failed to authenticate with Instagram:', error); throw new Error('Failed to authenticate with Instagram. Please check your credentials.'); } } // Set up session capture ig.request.end$.subscribe(async () => { try { const serialized = await ig.state.serialize(); delete serialized.constants; // Use library's version info capturedSessionData = serialized; // Save session data to file try { fs.writeFileSync(sessionFilePath, JSON.stringify({ sessionData: capturedSessionData }), 'utf8'); console.log('Session data saved to file'); } catch (error) { console.error('Failed to save session to file:', error); } } catch (error) { console.error('Failed to serialize session:', error); } }); try { switch (operation) { case 'sendDMByUsername': { const username = this.getNodeParameter('username', 0); const message = this.getNodeParameter('message', 0); if (!username) { throw new Error('Username is required'); } if (!message) { throw new Error('Message is required'); } // Search for the user const userResult = await ig.user.searchExact(username); if (!userResult || !userResult.pk) { throw new Error(`User "${username}" not found`); } const userId = userResult.pk; try { // Send the message directly using entity.directThread try { await ig.entity.directThread([userId.toString()]).broadcastText(message); console.log('Successfully sent message using direct approach'); } catch (error) { // If we get a 500 error, try alternative methods if (error instanceof Error && (error.message.includes('500 Internal Server Error') || error.message.includes('We\'re sorry, but something went wrong'))) { console.log('Received 500 error from Instagram API, trying alternative method...'); // Try using direct.sendText as a fallback try { await ig.direct.sendText({ threadIds: [userId.toString()], text: message, }); console.log('Successfully sent message using fallback method'); } catch (fallbackError) { // If that also fails, try one more approach console.log('Fallback method also failed, trying one more approach...'); // Try using direct.broadcastText as a last resort await ig.direct.broadcastText({ threadIds: [userId.toString()], text: message, }); console.log('Successfully sent message using last resort method'); } } else { // If it's not a 500 error, rethrow it throw error; } } returnData.push({ success: true, username, userId, message, sessionData: capturedSessionData, // Include session data in output }); } catch (error) { if (error instanceof Error) { if (error.name === 'IgLoginRequiredError') { throw new Error('Login required. Please authenticate with Instagram first.'); } if (error.name === 'IgCheckpointError') { throw new Error('Checkpoint required. Please verify your account.'); } if (error.name === 'IgNotFoundError') { throw new Error(`User "${username}" not found or cannot be messaged.`); } } throw error; } break; } case 'sendDMByUserId': { const userId = this.getNodeParameter('userId', 0); const message = this.getNodeParameter('message', 0); if (!userId) { throw new Error('User ID is required'); } if (!message) { throw new Error('Message is required'); } try { // Send the message directly using entity.directThread try { await ig.entity.directThread([userId.toString()]).broadcastText(message); console.log('Successfully sent message using direct approach'); } catch (error) { // If we get a 500 error, try alternative methods if (error instanceof Error && (error.message.includes('500 Internal Server Error') || error.message.includes('We\'re sorry, but something went wrong'))) { console.log('Received 500 error from Instagram API, trying alternative method...'); // Try using direct.sendText as a fallback try { await ig.direct.sendText({ threadIds: [userId.toString()], text: message, }); console.log('Successfully sent message using fallback method'); } catch (fallbackError) { // If that also fails, try one more approach console.log('Fallback method also failed, trying one more approach...'); // Try using direct.broadcastText as a last resort await ig.direct.broadcastText({ threadIds: [userId.toString()], text: message, }); console.log('Successfully sent message using last resort method'); } } else { // If it's not a 500 error, rethrow it throw error; } } returnData.push({ success: true, userId, message, sessionData: capturedSessionData, // Include session data in output }); } catch (error) { if (error instanceof Error) { if (error.name === 'IgLoginRequiredError') { throw new Error('Login required. Please authenticate with Instagram first.'); } if (error.name === 'IgCheckpointError') { throw new Error('Checkpoint required. Please verify your account.'); } if (error.name === 'IgNotFoundError') { throw new Error(`User with ID "${userId}" not found or cannot be messaged.`); } } throw error; } break; } case 'searchUser': { const username = this.getNodeParameter('username', 0); if (!username) { throw new Error('Username is required'); } try { // Search for the user const userResult = await ig.user.searchExact(username); returnData.push({ success: true, username, sessionData: capturedSessionData, user: userResult, }); } catch (error) { console.error('Error searching for user:', error); // Check if it's an authentication error if (error instanceof Error && error.message.includes('login_required')) { // Try to re-authenticate try { console.log('Re-authenticating with Instagram...'); await ig.account.login(credentials.username, credentials.password); console.log('Successfully re-authenticated with Instagram'); // Try the search again const userResult = await ig.user.searchExact(username); returnData.push({ success: true, username, sessionData: capturedSessionData, user: userResult, }); } catch (loginError) { throw new Error('Login required. Please authenticate with Instagram first.'); } } else if (error instanceof Error) { if (error.name === 'IgCheckpointError') { throw new Error('Checkpoint required. Please verify your account.'); } else if (error.name === 'IgNotFoundError') { throw new Error(`User "${username}" not found.`); } else { throw error; } } else { throw error; } } break; } case 'getUserInfo': { const userId = this.getNodeParameter('userId', 0); if (!userId) { throw new Error('User ID is required'); } // Get user info const userInfo = await ig.user.info(userId); returnData.push({ success: true, userId, sessionData: capturedSessionData, userInfo, }); break; } case 'getUserFeed': { const userId = this.getNodeParameter('userId', 0); if (!userId) { throw new Error('User ID is required'); } // Get user feed const userFeed = await ig.feed.user(userId).items(); returnData.push({ success: true, userId, sessionData: capturedSessionData, userFeed, }); break; } case 'getUserStories': { const userId = this.getNodeParameter('userId', 0); if (!userId) { throw new Error('User ID is required'); } // Get user stories const userStories = await ig.feed.userStory(userId).items(); returnData.push({ success: true, userId, sessionData: capturedSessionData, userStories, }); break; } case 'manageSession': { const sessionAction = this.getNodeParameter('sessionAction', 0); if (sessionAction === 'getSession') { // If we have session data, return it if (sessionData) { returnData.push({ success: true, message: 'Session data retrieved successfully', sessionData, instructions: 'Copy this session data and update your Instagram credentials with it to maintain your session between runs.', }); } else { returnData.push({ success: false, message: 'No session data found in credentials', instructions: 'Use the "Login and Get Session" action to create a new session.', }); } } else if (sessionAction === 'loginAndGetSession') { // Login to Instagram console.log('Authenticating with Instagram...'); try { await ig.account.login(credentials.username, credentials.password); console.log('Successfully authenticated with Instagram'); // Wait a moment for the session to be captured await new Promise(resolve => setTimeout(resolve, 1000)); // Get the captured session data if (capturedSessionData) { returnData.push({ success: true, message: 'Successfully logged in and captured session data', sessionData: capturedSessionData, instructions: 'Copy this session data and update your Instagram credentials with it to maintain your session between runs.', }); } else { returnData.push({ success: false, message: 'Login successful but failed to capture session data', instructions: 'Try again or check the logs for more information.', }); } } catch (error) { console.error('Failed to authenticate with Instagram:', error); throw new Error('Failed to authenticate with Instagram. Please check your credentials.'); } } break; } default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Operation "${operation}" not supported`); } } catch (error) { if (this.continueOnFail()) { returnData.push({ success: false, error: error instanceof Error ? error.message : 'An unknown error occurred', }); } else { throw error; } } // Always return data, even if empty if (returnData.length === 0) { returnData.push({ success: false, message: 'No data returned from operation', }); } // At the end of the execute method, add the session data to the return data if (capturedSessionData) { returnData.push({ sessionData: capturedSessionData, sessionFilePath, }); } return [this.helpers.returnJsonArray(returnData)]; } } exports.InstagramPrivateApi = InstagramPrivateApi;