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
JavaScript
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;