build-in-public-bot
Version:
AI-powered CLI bot for automating build-in-public tweets with code screenshots
404 lines • 19.2 kB
JavaScript
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
;