UNPKG

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

Version:
281 lines (280 loc) 12.4 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.InstagramTrigger = void 0; 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 InstagramTrigger { constructor() { this.description = { displayName: 'Instagram Trigger', name: 'instagramTrigger', icon: 'file:instagram.svg', group: ['trigger'], version: 1, // subtitle: '={{$parameter["event"]}}', // Keep commented out from previous steps description: 'Triggers when Instagram events occur', defaults: { name: 'Instagram Trigger', }, inputs: ['main'], outputs: ['main'], credentials: [ { name: 'instagramPrivateApi', required: true, }, ], // Restore properties definition properties: [ { displayName: 'Event', name: 'event', type: 'options', noDataExpression: true, options: [ { name: 'New Direct Message', value: 'newDirectMessage', description: 'Triggers when a new direct message is received', }, ], default: 'newDirectMessage', }, { displayName: 'Polling Interval', name: 'pollingInterval', type: 'number', default: 60, description: 'How often to poll for new messages (in seconds)', // Keep displayOptions commented out from previous steps // displayOptions: { // show: { // event: ['newDirectMessage'], // }, // }, }, ], }; // Remove the poll method entirely // async poll(...) { ... } } // Refactor trigger method async trigger() { console.log('--- Trigger Method Start (Self-Polling) ---'); // Get parameters - ACCESS PARAMETERS HERE const event = this.getNodeParameter('event', 0); const pollingIntervalSeconds = this.getNodeParameter('pollingInterval', 0); // Get credentials const credentials = await this.getCredentials('instagramPrivateApi'); console.log(`Credentials obtained for user: ${credentials.username}`); // --- Instagram Client & Session Setup (Copied from original poll method) --- const ig = new instagram_private_api_1.IgApiClient(); ig.state.generateDevice(credentials.username); const sessionDir = path.join(os.homedir(), '.n8n', 'instagram-sessions'); const sessionFilePath = path.join(sessionDir, `${credentials.username}.json`); if (!fs.existsSync(sessionDir)) { fs.mkdirSync(sessionDir, { recursive: true }); } let capturedSessionData = null; // Session loading function (to avoid duplicating in interval) const loadAndVerifySession = async () => { let sessionData; let isAuthenticated = false; if (credentials.sessionData) { sessionData = credentials.sessionData; } 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 (sessionData) { try { await ig.state.deserialize(sessionData); console.log('Session loaded successfully'); try { await ig.account.currentUser(); console.log('Session is valid'); isAuthenticated = true; } catch (error) { console.log('Session is invalid, re-authenticating...'); 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:', error); } } 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); // Handle auth failure appropriately - maybe stop the interval? throw new Error('Failed to authenticate with Instagram. Please check your credentials.'); } } return isAuthenticated; }; // Initial session load/auth await loadAndVerifySession(); // Session capture setup ig.request.end$.subscribe(async () => { try { const serialized = await ig.state.serialize(); delete serialized.constants; capturedSessionData = serialized; try { fs.writeFileSync(sessionFilePath, JSON.stringify({ sessionData: capturedSessionData }), 'utf8'); // console.log('Session data saved to file'); // Reduce logging noise } catch (error) { console.error('Failed to save session to file:', error); } } catch (error) { console.error('Failed to serialize session:', error); } }); // --- Polling Logic (Moved from poll method) --- let isRunning = false; // Prevent overlapping runs const pollMessages = async () => { if (isRunning) { console.log('Polling already in progress, skipping this interval.'); return; } isRunning = true; console.log('Polling for new messages...'); try { // Ensure session is still valid before polling if (!(await loadAndVerifySession())) { console.error('Authentication failed, cannot poll messages.'); isRunning = false; return; // Stop polling if auth fails } const messages = await ig.feed.directInbox().items(); const unreadMessages = messages.filter((message) => message.read_state > 0); console.log(`Found ${unreadMessages.length} unread messages`); if (unreadMessages.length > 0) { const outputDataArray = []; for (const message of unreadMessages) { const lastItem = message.last_permanent_item; if (!lastItem) { console.log(`No last item found for thread ${message.thread_id}`); continue; } const outputData = { json: { threadId: message.thread_id, messageId: lastItem.item_id, message: lastItem.text, senderId: lastItem.user_id, timestamp: lastItem.timestamp, threadTitle: message.thread_title, sessionData: capturedSessionData, sessionFilePath, }, }; outputDataArray.push(outputData); // Mark as seen try { if (message.thread_id) { await ig.direct.markItemSeen(message.thread_id, lastItem.item_id); console.log(`Marked message ${lastItem.item_id} as seen in thread ${message.thread_id}`); } else { console.log(`Cannot mark message as seen: thread_id is null or undefined`); } } catch (markError) { console.error(`Failed to mark message as seen:`, markError); } } // Emit the data - IMPORTANT: Use this.emit() for self-managed triggers if (outputDataArray.length > 0) { console.log(`Emitting ${outputDataArray.length} messages`); this.emit([outputDataArray]); } } } catch (error) { console.error('Error during polling:', error); // Consider stopping the interval on certain errors? } finally { isRunning = false; } }; // Start the interval timer const intervalId = setInterval(pollMessages, pollingIntervalSeconds * 1000); console.log(`Polling interval set for ${pollingIntervalSeconds} seconds.`); // For self-managed triggers like setInterval, return an empty object or specific response if needed // The 'close' function is typically used when n8n manages the polling via the 'poll' method // Returning a function to clear the interval IS useful if n8n calls it on deactivation // Let's keep the close function for cleanup return { closeFunction: async () => { console.log('--- Clearing Polling Interval --- '); clearInterval(intervalId); }, }; } } exports.InstagramTrigger = InstagramTrigger;