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
JavaScript
"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 = `
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:
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'
]
)
page = await browser.get('https://twitter.com/login')
await asyncio.sleep(3)
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')
await page.send_keys('\n')
await asyncio.sleep(2)
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)
await asyncio.sleep(5)
await browser.get('https://twitter.com/compose/tweet')
await asyncio.sleep(3)
tweet_box = await page.select('[data-testid="tweetTextarea_0"]')
if tweet_box:
for char in tweet_text:
await tweet_box.send_keys(char)
await asyncio.sleep(random.uniform(0.05, 0.15))
await asyncio.sleep(2)
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)
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