← back to Dear Bubbe Nextjs
lib/instagram-poster.js
473 lines
#!/usr/bin/env node
const { chromium } = require('playwright');
const fs = require('fs').promises;
const path = require('path');
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const { generateInstagramImage, generateCaption } = require('./instagram-image-generator');
const { anonymizeMessage } = require('./anonymous-user-poster');
// Configuration
const CONFIG = {
INSTAGRAM_USERNAME: 'dearBubbe',
INSTAGRAM_PASSWORD: '*Instaaccess911*',
COOKIES_PATH: '/root/Projects/dear-bubbe-nextjs/lib/instagram-cookies.json',
STATE_FILE: '/root/Projects/dear-bubbe-admin/logs/instagram-state.json',
POSTS_PER_DAY: 3,
MIN_INTERVAL_HOURS: 6
};
// Get user messages
async function getUserMessages() {
try {
const { stdout } = await execPromise(
`pm2 logs bubbe-ai --lines 1000 --nostream | grep -E "message:" | grep -v "__init__" | grep -v "__returning" | head -50`
);
const messages = [];
const lines = stdout.split('\n').filter(line => line.trim());
for (const line of lines) {
const match = line.match(/message:\s*["'](.+?)["']/);
if (match && match[1]) {
const msg = match[1].trim();
if (msg.length > 20 && !msg.includes('__') && !msg.startsWith('test') && !msg.includes('favicon')) {
messages.push(msg);
}
}
}
return [...new Set(messages)];
} catch (error) {
console.error('Error getting messages:', error);
return [];
}
}
// Load state
async function loadState() {
try {
const data = await fs.readFile(CONFIG.STATE_FILE, 'utf8');
return JSON.parse(data);
} catch {
return {
postedMessages: [],
lastPostTime: null,
totalPosts: 0,
dailyPosts: 0,
lastResetDate: new Date().toDateString()
};
}
}
// Save state
async function saveState(state) {
await fs.writeFile(CONFIG.STATE_FILE, JSON.stringify(state, null, 2));
}
// Load cookies
async function loadCookies(context) {
try {
const cookies = JSON.parse(await fs.readFile(CONFIG.COOKIES_PATH, 'utf8'));
await context.addCookies(cookies);
return true;
} catch {
return false;
}
}
// Save cookies
async function saveCookies(context) {
const cookies = await context.cookies();
await fs.writeFile(CONFIG.COOKIES_PATH, JSON.stringify(cookies, null, 2));
}
// Post to Instagram
async function postToInstagram(imagePath, caption) {
const browser = await chromium.launch({
headless: true, // Run headless on server
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--no-first-run',
'--no-zygote',
'--single-process'
]
});
try {
const context = await browser.newContext({
viewport: { width: 390, height: 844 }, // iPhone 14 Pro viewport
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
isMobile: true,
hasTouch: true,
deviceScaleFactor: 3,
locale: 'en-US'
});
const page = await context.newPage();
// Enable console logging for debugging
page.on('console', msg => {
if (msg.type() === 'error') {
console.log('Page error:', msg.text());
}
});
// Load cookies
const cookiesLoaded = await loadCookies(context);
// Go to Instagram
console.log('📱 Navigating to Instagram...');
await page.goto('https://www.instagram.com/', { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForTimeout(3000);
// Check if we need to login
const loginFormPresent = await page.locator('input[name="username"]').isVisible().catch(() => false);
const loginButtonPresent = await page.locator('button:has-text("Log in"), button:has-text("Log In")').isVisible().catch(() => false);
if ((loginFormPresent || loginButtonPresent) && !cookiesLoaded) {
console.log('📱 Logging in to Instagram...');
// If we see the initial login button, click it first
if (loginButtonPresent && !loginFormPresent) {
await page.click('button:has-text("Log in"), button:has-text("Log In")');
await page.waitForTimeout(2000);
}
// Enter credentials
await page.fill('input[name="username"]', CONFIG.INSTAGRAM_USERNAME);
await page.waitForTimeout(1000);
await page.fill('input[name="password"]', CONFIG.INSTAGRAM_PASSWORD);
await page.waitForTimeout(1000);
// Submit login form - try multiple selectors
const loginButton = await page.locator('button[type="submit"], button:has-text("Log in"), button:has-text("Log In"), div[role="button"]:has-text("Log in")').first();
await loginButton.click();
console.log('⏳ Waiting for login to complete...');
await page.waitForTimeout(8000);
// Handle potential popups after login
const notNowButtons = ['text="Not Now"', 'text="Not now"', 'button:has-text("Not Now")', 'button:has-text("Not now")'];
for (const selector of notNowButtons) {
await page.click(selector).catch(() => {});
await page.waitForTimeout(1000);
}
// Save cookies after successful login
await saveCookies(context);
console.log('✅ Login successful, cookies saved');
}
// Wait for page to fully load
await page.waitForTimeout(3000);
// Instagram mobile web has different UI - we need to navigate to create page directly
console.log('📸 Navigating to create post page...');
// Method 1: Try direct navigation to create page
await page.goto('https://www.instagram.com/create/select/', { waitUntil: 'domcontentloaded' }).catch(async () => {
// Method 2: Try clicking the plus icon or create button
console.log('Trying alternative navigation methods...');
// Look for various possible selectors for the new post button
const newPostSelectors = [
'svg[aria-label="New post"]',
'svg[aria-label="New Post"]',
'a[href="/create/select/"]',
'a[href*="create"]',
'[aria-label="Create"]',
'svg[aria-label="Create"]',
// Plus icon variants
'svg path[d*="M12.003"]', // Common plus icon path
'div[role="button"]:has(svg)',
// Bottom navigation bar icons
'nav a[href*="create"]',
'nav svg[aria-label*="Create"]',
'nav svg[aria-label*="New"]'
];
let clicked = false;
for (const selector of newPostSelectors) {
try {
const element = await page.locator(selector).first();
if (await element.isVisible({ timeout: 1000 })) {
await element.click();
clicked = true;
console.log(`✅ Clicked create button using: ${selector}`);
break;
}
} catch (e) {
// Continue trying other selectors
}
}
if (!clicked) {
// Last resort: Try to find the file input directly
console.log('Could not find create button, looking for file input...');
}
});
await page.waitForTimeout(3000);
// Handle file upload
console.log('📤 Uploading image...');
// Look for file input - Instagram often hides it
let fileInputFound = false;
const fileInputSelectors = [
'input[type="file"][accept*="image"]',
'input[type="file"]',
'input[accept="image/jpeg,image/png,image/heic,image/heif,video/mp4,video/quicktime"]'
];
for (const selector of fileInputSelectors) {
const fileInput = await page.locator(selector).first();
if (await fileInput.count() > 0) {
await fileInput.setInputFiles(imagePath);
fileInputFound = true;
console.log('✅ Image selected');
break;
}
}
if (!fileInputFound) {
// If no file input found, try clicking "Select from computer" or similar button first
const selectButtons = [
'button:has-text("Select from computer")',
'button:has-text("Select From Computer")',
'text="Select from computer"',
'button:has-text("Choose file")',
'button:has-text("Upload")'
];
for (const selector of selectButtons) {
await page.click(selector).catch(() => {});
}
await page.waitForTimeout(2000);
// Try file input again
const fileInput = await page.locator('input[type="file"]').first();
await fileInput.setInputFiles(imagePath);
}
await page.waitForTimeout(5000);
// Click through the flow - Next buttons
console.log('📝 Processing image...');
// Look for Next/OK buttons
const nextSelectors = [
'button:has-text("Next")',
'button:has-text("OK")',
'div[role="button"]:has-text("Next")',
'button[type="button"]:has-text("Next")'
];
// First Next (after image selection)
for (const selector of nextSelectors) {
const nextBtn = await page.locator(selector).first();
if (await nextBtn.isVisible({ timeout: 1000 }).catch(() => false)) {
await nextBtn.click();
console.log('Clicked Next (crop/adjust)');
break;
}
}
await page.waitForTimeout(3000);
// Second Next (after filters/editing)
for (const selector of nextSelectors) {
const nextBtn = await page.locator(selector).first();
if (await nextBtn.isVisible({ timeout: 1000 }).catch(() => false)) {
await nextBtn.click();
console.log('Clicked Next (filters)');
break;
}
}
await page.waitForTimeout(3000);
// Add caption
console.log('✍️ Adding caption...');
const captionSelectors = [
'textarea[aria-label*="caption" i]',
'textarea[aria-label*="write" i]',
'textarea[placeholder*="caption" i]',
'textarea[placeholder*="Write" i]',
'textarea:not([aria-label*="Title"])',
'div[contenteditable="true"][role="textbox"]'
];
let captionAdded = false;
for (const selector of captionSelectors) {
try {
const captionField = await page.locator(selector).first();
if (await captionField.isVisible({ timeout: 1000 })) {
await captionField.click();
await captionField.fill(caption);
captionAdded = true;
console.log('✅ Caption added');
break;
}
} catch (e) {
// Continue trying
}
}
if (!captionAdded) {
console.log('⚠️ Could not add caption, continuing...');
}
await page.waitForTimeout(2000);
// Share/Post the image
console.log('🚀 Sharing post...');
const shareSelectors = [
'button:has-text("Share")',
'button:has-text("Post")',
'div[role="button"]:has-text("Share")',
'button[type="button"]:has-text("Share")'
];
for (const selector of shareSelectors) {
const shareBtn = await page.locator(selector).first();
if (await shareBtn.isVisible({ timeout: 1000 }).catch(() => false)) {
await shareBtn.click();
console.log('✅ Clicked Share button');
break;
}
}
// Wait for post to complete
await page.waitForTimeout(8000);
// Save cookies for next time
await saveCookies(context);
console.log('✅ Posted to Instagram successfully!');
return { success: true };
} catch (error) {
console.error('❌ Error posting to Instagram:', error.message);
// Take a screenshot for debugging
try {
const screenshotPath = `/root/Projects/dear-bubbe-admin/logs/instagram-error-${Date.now()}.png`;
await page.screenshot({ path: screenshotPath, fullPage: true });
console.log(`📸 Error screenshot saved: ${screenshotPath}`);
} catch (e) {
// Ignore screenshot errors
}
return { success: false, error: error.message };
} finally {
await browser.close();
}
}
// Main posting function
async function postAnonymousToInstagram() {
console.log('📸 Instagram Anonymous Poster');
console.log('================================');
// Load state
const state = await loadState();
// Reset daily counter
const today = new Date().toDateString();
if (today !== state.lastResetDate) {
state.dailyPosts = 0;
state.lastResetDate = today;
}
// Check limits
if (state.dailyPosts >= CONFIG.POSTS_PER_DAY) {
console.log(`📊 Daily limit reached (${state.dailyPosts}/${CONFIG.POSTS_PER_DAY})`);
return;
}
// Check interval
if (state.lastPostTime) {
const hoursSince = (Date.now() - new Date(state.lastPostTime).getTime()) / (1000 * 60 * 60);
if (hoursSince < CONFIG.MIN_INTERVAL_HOURS) {
console.log(`⏱️ Wait ${(CONFIG.MIN_INTERVAL_HOURS - hoursSince).toFixed(1)} more hours`);
return;
}
}
// Get messages
const messages = await getUserMessages();
const unposted = messages.filter(msg => !state.postedMessages.includes(msg));
if (unposted.length === 0) {
console.log('❌ No new messages');
return;
}
// Select message
const selected = unposted[Math.floor(Math.random() * unposted.length)];
console.log(`📝 Selected: "${selected.substring(0, 50)}..."`);
// Generate image
const timestamp = Date.now();
const imagePath = `/root/Projects/dear-bubbe-admin/public/instagram-${timestamp}.png`;
await generateInstagramImage(selected, imagePath);
console.log(`🎨 Image created: ${imagePath}`);
// Generate caption
const caption = generateCaption(selected);
console.log(`📱 Caption: ${caption.substring(0, 100)}...`);
// Post to Instagram
const result = await postToInstagram(imagePath, caption);
if (result.success) {
// Update state
state.postedMessages.push(selected);
state.lastPostTime = new Date().toISOString();
state.totalPosts++;
state.dailyPosts++;
if (state.postedMessages.length > 100) {
state.postedMessages = state.postedMessages.slice(-100);
}
await saveState(state);
console.log(`📊 Posted ${state.dailyPosts}/${CONFIG.POSTS_PER_DAY} today`);
console.log(`📈 Total Instagram posts: ${state.totalPosts}`);
console.log(`🖼️ Image: http://45.61.58.125:7902/instagram-${timestamp}.png`);
}
}
// Continuous posting
async function continuousPost() {
console.log('🚀 Starting Instagram Continuous Poster');
console.log(`📸 ${CONFIG.POSTS_PER_DAY} posts per day`);
console.log(`⏱️ Every ${CONFIG.MIN_INTERVAL_HOURS} hours`);
console.log('=' .repeat(40));
// Post immediately
await postAnonymousToInstagram();
// Check every 2 hours
setInterval(async () => {
await postAnonymousToInstagram();
}, 2 * 60 * 60 * 1000);
}
// Run
if (require.main === module) {
const args = process.argv.slice(2);
if (args[0] === 'continuous') {
continuousPost().catch(console.error);
} else {
postAnonymousToInstagram().catch(console.error);
}
}
module.exports = { postAnonymousToInstagram };