UNPKG

build-in-public-bot

Version:

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

404 lines 19.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TwitterAuthService = void 0; const puppeteer_extra_1 = __importDefault(require("puppeteer-extra")); const puppeteer_extra_plugin_stealth_1 = __importDefault(require("puppeteer-extra-plugin-stealth")); const logger_1 = require("../utils/logger"); const errors_1 = require("../utils/errors"); const promises_1 = __importDefault(require("fs/promises")); const path_1 = __importDefault(require("path")); const stealth = (0, puppeteer_extra_plugin_stealth_1.default)(); stealth.enabledEvasions.delete('iframe.contentWindow'); stealth.enabledEvasions.delete('media.codecs'); puppeteer_extra_1.default.use(stealth); class TwitterAuthService { browser = null; page = null; async authenticate(username, password) { try { logger_1.logger.startSpinner('Launching browser for Twitter authentication...'); this.browser = await puppeteer_extra_1.default.launch({ headless: false, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--no-first-run', '--no-zygote', '--disable-gpu' ] }); this.page = await this.browser.newPage(); await this.page.setViewport({ width: 1280, height: 800 }); logger_1.logger.stopSpinner(true, 'Browser launched'); logger_1.logger.startSpinner('Navigating to Twitter login...'); await this.page.goto('https://twitter.com/i/flow/login', { waitUntil: 'networkidle2', timeout: 30000 }); logger_1.logger.stopSpinner(true, 'Loaded Twitter login page'); await this.page.waitForSelector('input[autocomplete="username"]', { timeout: 10000 }); logger_1.logger.info('Entering username...'); await this.page.type('input[autocomplete="username"]', username, { delay: 100 }); await this.page.keyboard.press('Enter'); await new Promise(resolve => setTimeout(resolve, 2000)); const phoneEmailInput = await this.page.$('input[data-testid="ocfEnterTextTextInput"]'); if (phoneEmailInput) { logger_1.logger.warn('Twitter is asking for phone/email verification'); throw new errors_1.TwitterError('Phone/email verification required. Please use an account that doesn\'t require this.'); } await this.page.waitForSelector('input[type="password"]', { timeout: 10000 }); logger_1.logger.info('Entering password...'); await this.page.type('input[type="password"]', password, { delay: 100 }); await this.page.keyboard.press('Enter'); logger_1.logger.startSpinner('Logging in...'); await this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 15000 }); const homeTimeline = await this.page.$('[data-testid="primaryColumn"]'); if (!homeTimeline) { throw new errors_1.TwitterError('Login failed. Please check your credentials.'); } logger_1.logger.stopSpinner(true, 'Login successful!'); const authData = await this.extractAuthData(username); await this.cleanup(); return authData; } catch (error) { await this.cleanup(); logger_1.logger.stopSpinner(false, 'Authentication failed'); if (error instanceof errors_1.TwitterError) { throw error; } throw new errors_1.TwitterError(`Authentication failed: ${error.message}`, error); } } async extractAuthData(username) { if (!this.page) { throw new errors_1.TwitterError('No active page'); } logger_1.logger.info('Extracting authentication data...'); const cookies = await this.page.cookies(); const localStorageData = await this.page.evaluate(() => { const items = {}; const storage = window.localStorage; for (let i = 0; i < storage.length; i++) { const key = storage.key(i); if (key) { items[key] = storage.getItem(key) || ''; } } return items; }); const sessionStorageData = await this.page.evaluate(() => { const items = {}; const storage = window.sessionStorage; for (let i = 0; i < storage.length; i++) { const key = storage.key(i); if (key) { items[key] = storage.getItem(key) || ''; } } return items; }); const authToken = cookies.find(c => c.name === 'auth_token')?.value; const ct0 = cookies.find(c => c.name === 'ct0')?.value; if (!authToken || !ct0) { throw new errors_1.TwitterError('Failed to extract authentication tokens'); } return { cookies, localStorage: localStorageData, sessionStorage: sessionStorageData, authToken, ct0, username }; } async cleanup() { if (this.page) { await this.page.close().catch(() => { }); } if (this.browser) { await this.browser.close().catch(() => { }); try { const pages = await this.browser.pages(); if (pages.length === 0) { const browserProcess = this.browser.process(); if (browserProcess && browserProcess.spawnargs) { const userDataDirArg = browserProcess.spawnargs.find(arg => arg.includes('puppeteer-profile-')); if (userDataDirArg) { const userDataDir = userDataDirArg.split('=')[1]; if (userDataDir && userDataDir.includes('puppeteer-profile-')) { const fs = require('fs').promises; await fs.rmdir(userDataDir, { recursive: true }).catch(() => { }); } } } } } catch (e) { } } this.page = null; this.browser = null; } async saveAuthData(authData, filePath) { const dir = path_1.default.dirname(filePath); await promises_1.default.mkdir(dir, { recursive: true }); const dataToSave = { ...authData, savedAt: new Date().toISOString() }; await promises_1.default.writeFile(filePath, JSON.stringify(dataToSave, null, 2), 'utf-8'); } async loadAuthData(filePath) { try { const data = await promises_1.default.readFile(filePath, 'utf-8'); return JSON.parse(data); } catch (error) { return null; } } async launchBrowser(headless = false) { if (!this.browser) { const tempDir = require('os').tmpdir(); const userDataDir = require('path').join(tempDir, `puppeteer-profile-${Date.now()}`); this.browser = await puppeteer_extra_1.default.launch({ headless: headless ? 'new' : false, userDataDir, args: [ '--incognito', '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-accelerated-2d-canvas', '--no-first-run', '--no-zygote', '--disable-gpu', '--disable-blink-features=AutomationControlled', '--disable-features=IsolateOrigins,site-per-process', '--flag-switches-begin', '--disable-site-isolation-trials', '--flag-switches-end', '--window-size=1920,1080', '--start-maximized', '--disable-infobars', '--exclude-switches=enable-automation', '--disable-extensions', '--disable-default-apps', '--disable-background-timer-throttling', '--disable-renderer-backgrounding', '--disable-features=TranslateUI', '--disable-ipc-flooding-protection', '--force-color-profile=srgb', '--disable-features=AudioServiceOutOfProcess,IsolateOrigins,site-per-process', '--enable-features=NetworkService,NetworkServiceInProcess', '--disable-features=VizDisplayCompositor', '--disable-features=ScriptStreaming', '--disable-features=UserAgentClientHint', '--disable-features=WebRtcHideLocalIpsWithMdns', '--window-position=0,0', '--ignore-certificate-errors', '--ignore-certificate-errors-skip-list', '--disable-plugins', '--disable-plugins-discovery', '--disable-extensions-except=', '--disable-extensions-file-access-check', '--disable-extensions-http-throttling', '--load-extension=', '--disable-component-extensions-with-background-pages', '--disable-web-security', '--allow-running-insecure-content', '--disable-features=PrivacySandboxSettings4', '--disable-features=PrivacySandboxAdsAPIsOverride', '--disable-features=PrivacySandboxSettings3', '--disable-features=InterestGroupStorage', '--disable-features=AdInterestGroupAPI', '--disable-features=Fledge', '--disable-features=Topics', '--disable-features=BrowsingTopics' ], defaultViewport: null, ignoreDefaultArgs: ['--enable-automation', '--enable-blink-features', '--enable-logging', '--load-extension', '--enable-extension'] }); const pages = await this.browser.pages(); for (const page of pages) { await this.applyAdvancedEvasion(page); } this.browser.on('targetcreated', async (target) => { if (target.type() === 'page') { const page = await target.page(); if (page) { await this.applyAdvancedEvasion(page); } } }); } return this.browser; } async applyAdvancedEvasion(page) { await page.evaluateOnNewDocument(() => { Object.defineProperty(navigator, 'webdriver', { get: () => undefined, configurable: true }); delete navigator.__proto__.webdriver; Object.defineProperty(navigator, 'plugins', { get: () => { const pluginData = [ { name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' }, { name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' }, { name: 'Native Client', filename: 'internal-nacl-plugin', description: '' } ]; const pluginArray = Object.create(PluginArray.prototype); pluginData.forEach((data, i) => { const plugin = Object.create(Plugin.prototype, { name: { value: data.name }, filename: { value: data.filename }, description: { value: data.description }, length: { value: 0 } }); pluginArray[i] = plugin; pluginArray[data.name] = plugin; }); Object.defineProperty(pluginArray, 'length', { value: pluginData.length }); return pluginArray; } }); Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); window.chrome = { runtime: { id: undefined, connect: undefined, sendMessage: undefined }, loadTimes: function () { return { requestTime: Date.now() / 1000, startLoadTime: Date.now() / 1000, commitLoadTime: Date.now() / 1000, finishDocumentLoadTime: Date.now() / 1000, finishLoadTime: Date.now() / 1000, firstPaintTime: Date.now() / 1000, firstPaintAfterLoadTime: 0, navigationType: 'Other', wasFetchedViaSpdy: false, wasNpnNegotiated: true, npnNegotiatedProtocol: 'h2', wasAlternateProtocolAvailable: false, connectionInfo: 'h2' }; }, csi: function () { return { onloadT: Date.now(), pageT: Date.now() - performance.timeOrigin, startE: performance.timeOrigin, tran: 15 }; }, app: { isInstalled: false, InstallState: { DISABLED: 'disabled', INSTALLED: 'installed', NOT_INSTALLED: 'not_installed' }, RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' } } }; const originalQuery = window.navigator.permissions.query; window.navigator.permissions.query = function (parameters) { if (parameters.name === 'notifications') { return Promise.resolve({ state: Notification.permission }); } return originalQuery.apply(navigator.permissions, [parameters]); }; const originalToDataURL = HTMLCanvasElement.prototype.toDataURL; HTMLCanvasElement.prototype.toDataURL = function (_type) { const context = this.getContext('2d'); if (context) { const imageData = context.getImageData(0, 0, this.width, this.height); for (let i = 0; i < imageData.data.length; i += 4) { imageData.data[i] = Math.min(255, Math.max(0, imageData.data[i] + (Math.random() * 2 - 1))); } context.putImageData(imageData, 0, 0); } return originalToDataURL.apply(this, arguments); }; const getParameter = WebGLRenderingContext.prototype.getParameter; WebGLRenderingContext.prototype.getParameter = function (parameter) { if (parameter === 37445) { return 'Intel Inc.'; } if (parameter === 37446) { return 'Intel Iris OpenGL Engine'; } return getParameter.apply(this, [parameter]); }; if ('getBattery' in navigator) { navigator.getBattery = () => Promise.reject(new Error('Battery API not supported')); } Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8 }); Object.defineProperty(navigator, 'deviceMemory', { get: () => 8 }); Object.defineProperty(screen, 'availWidth', { get: () => 1920 }); Object.defineProperty(screen, 'availHeight', { get: () => 1080 }); Object.defineProperty(screen, 'width', { get: () => 1920 }); Object.defineProperty(screen, 'height', { get: () => 1080 }); Object.defineProperty(screen, 'colorDepth', { get: () => 24 }); Object.defineProperty(screen, 'pixelDepth', { get: () => 24 }); const originalDateTimeFormat = Intl.DateTimeFormat; window.Intl.DateTimeFormat = function (...args) { if (args[1] && typeof args[1] === 'object') { args[1].timeZone = args[1].timeZone || 'America/New_York'; } return new originalDateTimeFormat(...args); }; const AudioContext = window.AudioContext || window.webkitAudioContext; if (AudioContext) { const originalCreateOscillator = AudioContext.prototype.createOscillator; AudioContext.prototype.createOscillator = function () { const oscillator = originalCreateOscillator.apply(this, arguments); const originalConnect = oscillator.connect; oscillator.connect = function () { return originalConnect.apply(this, arguments); }; return oscillator; }; } }); try { const client = await page.target().createCDPSession(); await client.send('Page.setBypassCSP', { enabled: true }); } catch (e) { } await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'); await page.setExtraHTTPHeaders({ '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,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Cache-Control': 'max-age=0', 'Sec-Ch-Ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', 'Sec-Ch-Ua-Mobile': '?0', 'Sec-Ch-Ua-Platform': '"macOS"', 'Sec-Fetch-Dest': 'document', 'Sec-Fetch-Mode': 'navigate', 'Sec-Fetch-Site': 'none', 'Sec-Fetch-User': '?1', 'Upgrade-Insecure-Requests': '1' }); } } exports.TwitterAuthService = TwitterAuthService; //# sourceMappingURL=twitter-auth.js.map