@inite/n8n-nodes-instagram-private-api
Version:
n8n node for Instagram Private API with 2FA support
633 lines (632 loc) • 29.2 kB
JavaScript
;
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;