UNPKG

build-in-public-bot

Version:

AI-powered CLI bot for automating build-in-public tweets with code screenshots

187 lines (170 loc) 6.89 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TwitterNodriverService = void 0; const errors_1 = require("../utils/errors"); const logger_1 = require("../utils/logger"); const child_process_1 = require("child_process"); const util_1 = require("util"); const path_1 = __importDefault(require("path")); const promises_1 = __importDefault(require("fs/promises")); const execAsync = (0, util_1.promisify)(child_process_1.exec); class TwitterNodriverService { pythonScriptPath; constructor() { this.pythonScriptPath = path_1.default.join(__dirname, '../../scripts/twitter_nodriver.py'); } async checkDependencies() { try { await execAsync('python3 --version'); const { stderr } = await execAsync('python3 -c "import nodriver"'); if (stderr) { logger_1.logger.warn('Installing nodriver...'); await execAsync('pip3 install nodriver'); } } catch (error) { throw new errors_1.TwitterError('Python 3 and nodriver are required. Install with: pip3 install nodriver', error); } } async createPythonScript() { const scriptContent = `#!/usr/bin/env python3 import asyncio import nodriver as uc import sys import json import random import time async def post_tweet(username, password, tweet_text): """Post a tweet using nodriver for undetected automation""" browser = None try: # Start browser with optimized settings browser = await uc.start( browser_args=[ '--disable-blink-features=AutomationControlled', '--disable-features=IsolateOrigins,site-per-process', '--no-first-run', '--no-default-browser-check', '--disable-popup-blocking', '--disable-extensions', '--disable-dev-shm-usage' ] ) # Navigate to Twitter page = await browser.get('https://twitter.com/login') # Wait for page load await asyncio.sleep(3) # Login process # Find username input username_input = await page.select('input[autocomplete="username"]') if username_input: await username_input.send_keys(username) await asyncio.sleep(1) await page.send_keys('\t') # Tab to next button await page.send_keys('\n') # Press enter await asyncio.sleep(2) # Handle password password_input = await page.select('input[type="password"]') if password_input: await password_input.send_keys(password) await asyncio.sleep(1) await page.send_keys('\n') await asyncio.sleep(3) # Wait for home page await asyncio.sleep(5) # Navigate to compose await browser.get('https://twitter.com/compose/tweet') await asyncio.sleep(3) # Find tweet textarea tweet_box = await page.select('[data-testid="tweetTextarea_0"]') if tweet_box: # Type with human-like delays for char in tweet_text: await tweet_box.send_keys(char) await asyncio.sleep(random.uniform(0.05, 0.15)) await asyncio.sleep(2) # Find and click tweet button tweet_button = await page.select('[data-testid="tweetButton"]') if not tweet_button: tweet_button = await page.select('[data-testid="tweetButtonInline"]') if tweet_button: await tweet_button.click() await asyncio.sleep(3) # Extract tweet URL current_url = page.url if '/status/' in current_url: tweet_id = current_url.split('/status/')[-1].split('?')[0] result = { 'success': True, 'tweet_id': tweet_id, 'url': current_url } else: result = { 'success': True, 'tweet_id': str(int(time.time())), 'url': f'https://twitter.com/{username}/status/{int(time.time())}' } print(json.dumps(result)) else: raise Exception("Could not find tweet button") else: raise Exception("Could not find tweet textarea") except Exception as e: result = { 'success': False, 'error': str(e) } print(json.dumps(result)) finally: if browser: await browser.stop() if __name__ == '__main__': if len(sys.argv) != 4: print(json.dumps({'success': False, 'error': 'Invalid arguments'})) sys.exit(1) username = sys.argv[1] password = sys.argv[2] tweet_text = sys.argv[3] uc.loop().run_until_complete(post_tweet(username, password, tweet_text)) `; const scriptsDir = path_1.default.dirname(this.pythonScriptPath); await promises_1.default.mkdir(scriptsDir, { recursive: true }); await promises_1.default.writeFile(this.pythonScriptPath, scriptContent); await promises_1.default.chmod(this.pythonScriptPath, '755'); } async postTweet(username, password, text) { try { await this.checkDependencies(); await this.createPythonScript(); logger_1.logger.info('Posting tweet using nodriver...'); const { stdout, stderr } = await execAsync(`python3 "${this.pythonScriptPath}" "${username}" "${password}" "${text}"`, { timeout: 60000, }); if (stderr) { logger_1.logger.warn(`Python stderr: ${stderr}`); } const result = JSON.parse(stdout); if (result.success) { return { id: result.tweet_id, url: result.url }; } else { throw new errors_1.TwitterError(`Failed to post tweet: ${result.error}`); } } catch (error) { if (error.code === 'ETIMEDOUT') { throw new errors_1.TwitterError('Tweet posting timed out'); } throw new errors_1.TwitterError(`Failed to post tweet via nodriver: ${error.message || error}`); } } } exports.TwitterNodriverService = TwitterNodriverService; //# sourceMappingURL=twitter-nodriver.js.map