UNPKG

insta-meme-bot-cli

Version:

Production-ready Instagram meme bot that scrapes memes from Pinterest and posts them automatically with CLI and programmatic API support

403 lines (338 loc) โ€ข 10.5 kB
#!/usr/bin/env node require('dotenv').config(); const InstagramMemeBot = require('../bot'); const PinterestScraper = require('../scrapers/pinterest'); const Utils = require('../utils'); /** * CLI interface for Instagram Meme Bot */ class CLI { constructor() { this.args = this.parseArgs(process.argv.slice(2)); this.bot = null; } /** * Parse command line arguments with robust handling * @param {string[]} args - Command line arguments * @returns {Object} Parsed arguments */ parseArgs(args) { const parsed = { username: null, password: null, memeType: 'funny', interval: '60m', once: false, loop: false, test: false, help: false, headless: true, logFile: 'bot.log', customQuery: null }; // Valid meme types with aliases const memeTypeMap = { 'funny': 'funny', 'desi': 'desi', 'dark': 'dark', 'trending': 'trending', 'viral': 'trending', // Map viral to trending 'relatable': 'relatable', 'new': 'trending', 'latest': 'trending', 'hot': 'trending' }; // Pre-parse meme type from positional arguments (more robust) let memeType = 'funny'; // Check flag-based meme type first const memeTypeIndex = args.findIndex(arg => arg === '-t' || arg === '--meme-type'); if (memeTypeIndex !== -1 && args[memeTypeIndex + 1]) { const typeArg = args[memeTypeIndex + 1].toLowerCase(); memeType = memeTypeMap[typeArg] || 'funny'; } else { // Check positional arguments for meme types for (const arg of args.slice(2)) { const normalizedArg = arg.toLowerCase(); if (memeTypeMap[normalizedArg]) { memeType = memeTypeMap[normalizedArg]; break; } } } parsed.memeType = memeType; for (let i = 0; i < args.length; i++) { const arg = args[i]; const nextArg = args[i + 1]; switch (arg) { case '--username': case '-u': if (nextArg && !nextArg.startsWith('-')) { parsed.username = nextArg; i++; } break; case '--password': case '-p': if (nextArg && !nextArg.startsWith('-')) { parsed.password = nextArg; i++; } break; case '--meme-type': case '-t': if (nextArg && !nextArg.startsWith('-')) { parsed.memeType = nextArg; i++; } break; case '--custom-query': case '-q': if (nextArg && !nextArg.startsWith('-')) { parsed.customQuery = nextArg; i++; } break; case '--interval': case '-i': if (nextArg && !nextArg.startsWith('-')) { parsed.interval = nextArg; i++; } break; case '--log-file': case '-l': if (nextArg && !nextArg.startsWith('-')) { parsed.logFile = nextArg; i++; } break; case '--once': parsed.once = true; break; case '--loop': parsed.loop = true; break; case '--test': parsed.test = true; break; case '--no-headless': parsed.headless = false; break; case '--help': case '-h': parsed.help = true; break; case '--version': case '-v': this.showVersion(); process.exit(0); break; default: if (arg.startsWith('-')) { console.error(`Unknown option: ${arg}`); this.showHelp(); process.exit(1); } } } return parsed; } /** * Show help information */ showHelp() { const categories = Object.keys(PinterestScraper.getMemeCategories()).join(', '); console.log(` Instagram Meme Bot - Automated meme posting from Pinterest to Instagram USAGE: instagram-meme-bot [OPTIONS] OPTIONS: -u, --username <username> Instagram username (or set INSTA_USERNAME env var) -p, --password <password> Instagram password (or set INSTA_PASSWORD env var) -t, --meme-type <type> Meme category: ${categories} (default: funny) -q, --custom-query <query> Custom Pinterest search query (overrides meme-type) -i, --interval <interval> Post interval: 30s, 45m, 2h, etc. (default: 60m) -l, --log-file <file> Log file path (default: bot.log) --once Post once and exit --loop Run continuously (default behavior) --test Test configuration without posting --no-headless Run browser in visible mode (for debugging) -h, --help Show this help message -v, --version Show version information EXAMPLES: # Post a funny meme once instagram-meme-bot --username myuser --password mypass --once # Run continuously with desi memes every 30 minutes instagram-meme-bot -t desi -i 30m --loop # Use custom search query instagram-meme-bot -q "programming memes" --once # Test configuration instagram-meme-bot --test # Run with environment variables export INSTA_USERNAME=myuser export INSTA_PASSWORD=mypass instagram-meme-bot --loop ENVIRONMENT VARIABLES: INSTA_USERNAME Instagram username INSTA_PASSWORD Instagram password PUPPETEER_EXECUTABLE_PATH Custom Chrome/Chromium path DISCLAIMER: This tool is for educational purposes only. Use responsibly and ensure compliance with Instagram's Terms of Service. The authors are not responsible for any account restrictions or bans. `); } /** * Show version information */ showVersion() { const packageJson = require('../../package.json'); console.log(`Instagram Meme Bot v${packageJson.version}`); } /** * Validate CLI arguments */ validateArgs() { const errors = []; if (!this.args.username) { errors.push('Instagram username is required (use --username or INSTA_USERNAME env var)'); } if (!this.args.password) { errors.push('Instagram password is required (use --password or INSTA_PASSWORD env var)'); } if (!this.args.customQuery) { const validTypes = Object.keys(PinterestScraper.getMemeCategories()); if (!validTypes.includes(this.args.memeType)) { errors.push(`Invalid meme type '${this.args.memeType}'. Valid types: ${validTypes.join(', ')}`); } } try { Utils.parseInterval(this.args.interval); } catch (error) { errors.push(`Invalid interval format '${this.args.interval}'. Use format like '30m', '2h', '45s'`); } if (errors.length > 0) { console.error('Configuration errors:'); errors.forEach(error => console.error(` โŒ ${error}`)); console.error('\nUse --help for usage information.'); process.exit(1); } } /** * Initialize the bot with CLI arguments */ initializeBot() { this.bot = new InstagramMemeBot({ username: this.args.username, password: this.args.password, memeType: this.args.memeType, customQuery: this.args.customQuery, interval: this.args.interval, logFile: this.args.logFile, headless: this.args.headless }); } /** * Handle graceful shutdown */ setupSignalHandlers() { const shutdown = (signal) => { console.log(`\n๐Ÿ›‘ Received ${signal}, shutting down gracefully...`); if (this.bot) { this.bot.stop(); } process.exit(0); }; process.on('SIGINT', () => shutdown('SIGINT')); process.on('SIGTERM', () => shutdown('SIGTERM')); } /** * Run the CLI application */ async run() { try { // Show help if requested if (this.args.help) { this.showHelp(); return; } // Validate arguments this.validateArgs(); // Initialize bot this.initializeBot(); // Setup signal handlers for graceful shutdown this.setupSignalHandlers(); // Run based on mode if (this.args.test) { await this.runTest(); } else if (this.args.once) { await this.runOnce(); } else { await this.runLoop(); } } catch (error) { console.error(`โŒ Fatal error: ${error.message}`); process.exit(1); } } /** * Run test mode */ async runTest() { console.log('๐Ÿงช Testing bot configuration...\n'); const results = await this.bot.test(); console.log('\n๐Ÿ“Š Test Results:'); console.log(` Pinterest Scraping: ${results.scraping ? 'โœ… PASS' : 'โŒ FAIL'}`); console.log(` Instagram Connection: ${results.instagram ? 'โœ… PASS' : 'โŒ FAIL'}`); if (results.errors.length > 0) { console.log('\nโŒ Errors:'); results.errors.forEach(error => console.log(` โ€ข ${error}`)); } const success = results.scraping && results.instagram; console.log(`\n๐Ÿงช Overall: ${success ? 'โœ… PASS' : 'โŒ FAIL'}`); if (success) { console.log('\nโœ… Configuration is valid! You can now run the bot with --once or --loop'); } else { console.log('\nโŒ Please fix the errors above before running the bot.'); process.exit(1); } } /** * Run once mode */ async runOnce() { console.log('๐Ÿš€ Posting single meme...\n'); try { await this.bot.postSingleMeme(); console.log('\nโœ… Meme posted successfully!'); } catch (error) { console.error(`\nโŒ Failed to post meme: ${error.message}`); process.exit(1); } } /** * Run loop mode */ async runLoop() { console.log('๐Ÿ”„ Starting continuous mode...\n'); try { await this.bot.start(); // Keep the process running console.log('โœ… Bot is running. Press Ctrl+C to stop.\n'); // Keep process alive await new Promise(() => {}); // This will run indefinitely until interrupted } catch (error) { console.error(`\nโŒ Bot failed to start: ${error.message}`); process.exit(1); } } } // Run CLI if this file is executed directly if (require.main === module) { const cli = new CLI(); cli.run().catch(error => { console.error(`โŒ Unexpected error: ${error.message}`); process.exit(1); }); } module.exports = CLI;