build-in-public-bot
Version:
AI-powered CLI bot for automating build-in-public tweets with code screenshots
247 lines • 10.4 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwitterPlaywrightService = void 0;
const errors_1 = require("../utils/errors");
const logger_1 = require("../utils/logger");
class TwitterPlaywrightService {
browser = null;
context = null;
async launchBrowser(_headless = false) {
let chromium;
try {
const playwright = await Promise.resolve(`${'playwright'}`).then(s => __importStar(require(s)));
chromium = playwright.chromium;
}
catch (error) {
throw new errors_1.TwitterError('Playwright is not installed. Run: npm install playwright');
}
this.browser = await chromium.launch({
headless: false,
args: [
'--disable-blink-features=AutomationControlled',
'--disable-features=IsolateOrigins,site-per-process',
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-web-security',
'--disable-features=site-per-process',
'--disable-site-isolation-trials',
'--disable-features=AudioServiceOutOfProcess',
'--disable-features=IsolateOrigins',
'--disable-features=site-per-process',
'--disable-blink-features',
'--disable-ipc-flooding-protection',
'--disable-features=ImprovedCookieControls,LazyFrameLoading,GlobalMediaControls,DestroyProfileOnBrowserClose',
'--disable-features=CrossSiteDocumentBlockingIfIsolating,CrossSiteDocumentBlockingAlways',
'--disable-features=StrictOriginIsolation',
'--flag-switches-begin',
'--disable-site-isolation-trials',
'--flag-switches-end'
]
});
this.context = await this.browser.newContext({
viewport: { width: 1920, height: 1080 },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
locale: 'en-US',
timezoneId: 'America/New_York',
deviceScaleFactor: 1,
hasTouch: false,
extraHTTPHeaders: {
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Connection': 'keep-alive',
'Upgrade-Insecure-Requests': '1',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Cache-Control': 'max-age=0'
}
});
await this.applyStealthPatches();
}
async applyStealthPatches() {
if (!this.context)
return;
await this.context.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
Object.defineProperty(navigator, 'plugins', {
get: () => {
const arr = [
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer' },
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai' },
{ name: 'Native Client', filename: 'internal-nacl-plugin' }
];
return Object.assign(arr, {
namedItem: (name) => arr.find(p => p.name === name),
refresh: () => { }
});
}
});
const originalQuery = window.navigator.permissions.query;
window.navigator.permissions.query = (parameters) => {
if (parameters.name === 'notifications') {
return Promise.resolve({ state: Notification.permission });
}
return originalQuery(parameters);
};
window.chrome = {
runtime: {},
loadTimes: () => ({}),
csi: () => ({})
};
const originalError = Error;
window.Error = new Proxy(originalError, {
construct(target, args) {
const error = new target(...args);
if (error.stack && error.stack.includes('CDP')) {
error.stack = error.stack.replace(/CDP/g, '');
}
return error;
}
});
});
}
async postTweet(username, password, text) {
let page = null;
try {
await this.launchBrowser();
if (!this.context) {
throw new errors_1.TwitterError('Browser context not initialized');
}
page = await this.context.newPage();
logger_1.logger.info('Navigating to Twitter...');
await page.goto('https://twitter.com/login', {
waitUntil: 'networkidle',
timeout: 30000
});
await page.waitForTimeout(2000 + Math.random() * 2000);
logger_1.logger.info('Logging in...');
const usernameInput = page.locator('input[autocomplete="username"]');
await usernameInput.waitFor({ state: 'visible', timeout: 10000 });
await usernameInput.click();
await this.humanType(page, username);
await page.keyboard.press('Enter');
await page.waitForTimeout(2000 + Math.random() * 1000);
const passwordInput = page.locator('input[type="password"]');
await passwordInput.waitFor({ state: 'visible', timeout: 10000 });
await passwordInput.click();
await this.humanType(page, password);
await page.keyboard.press('Enter');
await page.waitForTimeout(5000);
const homeIndicator = page.locator('[data-testid="primaryColumn"]');
try {
await homeIndicator.waitFor({ state: 'visible', timeout: 10000 });
}
catch {
throw new errors_1.TwitterError('Login failed - could not find home timeline');
}
logger_1.logger.info('Warming up session...');
await this.warmUpSession(page);
logger_1.logger.info('Composing tweet...');
await page.goto('https://twitter.com/compose/tweet', {
waitUntil: 'networkidle'
});
await page.waitForTimeout(2000);
const tweetBox = page.locator('[data-testid="tweetTextarea_0"]');
await tweetBox.waitFor({ state: 'visible', timeout: 10000 });
await tweetBox.click();
await this.humanType(page, text);
await page.waitForTimeout(2000 + Math.random() * 1000);
const tweetButton = page.locator('[data-testid="tweetButtonInline"]');
await tweetButton.click();
await page.waitForTimeout(3000);
const currentUrl = page.url();
let tweetId = Date.now().toString();
if (currentUrl.includes('/status/')) {
const match = currentUrl.match(/status\/(\d+)/);
if (match) {
tweetId = match[1];
}
}
return {
id: tweetId,
url: `https://twitter.com/${username}/status/${tweetId}`
};
}
catch (error) {
throw new errors_1.TwitterError('Failed to post tweet with Playwright', error);
}
finally {
if (page) {
await page.close();
}
await this.cleanup();
}
}
async humanType(page, text) {
for (const char of text) {
await page.keyboard.type(char);
await page.waitForTimeout(50 + Math.random() * 100);
if (Math.random() < 0.05) {
await page.waitForTimeout(300 + Math.random() * 500);
}
}
}
async warmUpSession(page) {
for (let i = 0; i < 3; i++) {
await page.evaluate(() => {
window.scrollBy(0, 200 + Math.random() * 300);
});
await page.waitForTimeout(1000 + Math.random() * 1000);
}
if (Math.random() < 0.1) {
await page.goto('https://twitter.com/notifications');
await page.waitForTimeout(2000);
await page.goto('https://twitter.com/home');
}
}
async cleanup() {
if (this.context) {
await this.context.close();
}
if (this.browser) {
await this.browser.close();
}
this.context = null;
this.browser = null;
}
}
exports.TwitterPlaywrightService = TwitterPlaywrightService;
//# sourceMappingURL=twitter-playwright.js.map
;