From b1c4ed915e6cab57efcbdf3db1bd7ccb009cc07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=E1=BA=BF=20H=C6=B0ng?= Date: Sat, 14 Feb 2026 14:36:20 +0700 Subject: [PATCH] feat: add ghostery adblocker & update deps Woah, two years :) --- bun.lockb | Bin 83340 -> 98676 bytes package.json | 15 ++-- src/index.ts | 188 +++++++++++++++++++++--------------------- src/proxy/reflect4.ts | 121 ++++++++++++++------------- 4 files changed, 166 insertions(+), 158 deletions(-) diff --git a/bun.lockb b/bun.lockb index 3390f68140eb4cf313819eeca78ba1ecdf0b6649..ee92b9ee04267d797365fb29d0a51e28d94dbda3 100644 GIT binary patch delta 29371 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ð$c8;v<5Z1V2P7mc~@ z${DusOpQR5Q+XHE{-ZAPnX9JHFbVqM4@6;K#2=YT${6CTnd$s{^QpMfx9MM6Mmc={ zG;YdS7^9=mC;gK$e%DZ;OM^IbW9nSz#I!+Czf1BXLr2xGl>@kHCNwN)+{DKGRa7;I zqye1G?~S(ledLw5S-HfNGZn+xKIMJv`N#Mzmv{an?{BskC)z#Cm-MfK?Ar<()m!g@f(f4*raWkxCLCrAmy_<2pE%As+vpOv~ zBQ;9yoEVwnoR*Q2lAM}Ov)@{Isu}mP9=)A`3=Y?Dk0-tOR9)$vGCHR>501X5KGR;! aqMxkPGNA8wquRx^9MmbT9lNxC_WuE9r`d`C delta 20785 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 = []; - logger.info(`Spawning ${config.proxy.count} proxies...`); - for (let i = 0; i < config.proxy.count; i++) { - logger.debug(`Spawning proxy ${i + 1}...`); - tasks.push((async () => { - const spawnId = `${i + 1}`; - while (true) { - try { - await reflect4.spawn(context, config.playwright.url, config.playwright.change_viewport, spawnId); - } catch (e) { - logger.error(`[${spawnId}] Error while running: ${e}`); - } - logger.warn(`[${spawnId}] Restarting in 3 seconds...`); - await new Promise(resolve => setTimeout(resolve, 3 * 1000)); - } - })()); - } - await Promise.all(tasks); - logger.info("All proxies spawned successfully."); -} - -process.on("SIGINT", async () => { - logger.info("Received SIGINT. Closing browser..."); - for (const context of browser.contexts()) { - await context.close(); - } - await browser.close(); - process.exit(); -}); +import { chromium, type Browser, type LaunchOptions } from "patchright"; +import { PlaywrightBlocker } from '@ghostery/adblocker-playwright'; +import fs from 'node:fs'; +import * as reflect4 from "./proxy/reflect4.js"; +import logger from "./logger.js"; +import Config from "./config.js"; +import { VERSION } from "./constants.js"; + +logger.info(`Castorsrm v${VERSION}`) +logger.warn("This software is provided by the author as is, without any warranty. Use at your own risk."); + +let config = new Config(); +if (fs.existsSync("config.json")) { + logger.info("Reading configuration from 'config.json'..."); + const text = await fs.promises.readFile("config.json", "utf-8"); + config = Config.fromJSON(text); +} else { + logger.info("No configuration file found. Using the default configuration."); + logger.info("Default configuration file 'config.json' created."); +} +// Write the new config file in case we updated something. +await fs.promises.writeFile("config.json", config.toJSON()); + +// Validate configuration +if (config.proxy.count < 1) { + logger.error("Proxy count must be greater than 0."); + process.exit(1); +} + +logger.level = process.env.LOG_LEVEL || config.logger.level; +logger.info(`Logger level set to '${logger.level}'`); + +let browser: Browser; +let launchOptions: LaunchOptions = { + headless: config.playwright.headless, +} + +logger.info("Launching browser..."); +logger.debug(`Launch options: ${JSON.stringify(launchOptions)}`); + +switch (config.playwright.browser) { + case "chromium": + logger.info("Using Chromium as the browser provider."); + logger.warn("Chromium is not supported by Twitch. Use at your own risk."); + browser = await chromium.launch({ ...launchOptions }); + break; + case "chrome": + logger.info("Using Google Chrome as the browser provider."); + browser = await chromium.launch({ ...launchOptions, channel: "chrome" }); + break; + case "cdp": + logger.info("Using Chrome DevTools Protocol (CDP) for browser connection."); + browser = await chromium.connectOverCDP(config.playwright.cdp); + break; + default: + logger.warn(`Unsupported browser channel: '${config.playwright.browser}'`); + logger.warn("Castorsrm will try to launch the browser anyway, but it may not work as expected."); + browser = await chromium.launch({ ...launchOptions, channel: config.playwright.browser }); + break; +} + +logger.info("Browser launched successfully, enabling adblocker..."); +let blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch); + +logger.info("Spawning proxies..."); +if (config.proxy.mode === "reflect4") { + const context = await browser.newContext(); + const tasks: Array> = []; + logger.info(`Spawning ${config.proxy.count} proxies...`); + for (let i = 0; i < config.proxy.count; i++) { + logger.debug(`Spawning proxy ${i + 1}...`); + tasks.push((async () => { + const spawnId = `${i + 1}`; + while (true) { + try { + await reflect4.spawn(context, blocker, config.playwright.url, config.playwright.change_viewport, spawnId); + } catch (e) { + logger.error(`[${spawnId}] Error while running: ${e}`); + } + logger.warn(`[${spawnId}] Restarting in 3 seconds...`); + await new Promise(resolve => setTimeout(resolve, 3 * 1000)); + } + })()); + } + await Promise.all(tasks); + logger.info("All proxies spawned successfully."); +} + +process.on("SIGINT", async () => { + logger.info("Received SIGINT. Closing browser..."); + for (const context of browser.contexts()) { + await context.close(); + } + await browser.close(); + process.exit(); +}); diff --git a/src/proxy/reflect4.ts b/src/proxy/reflect4.ts index 99e4edd..baec02b 100644 --- a/src/proxy/reflect4.ts +++ b/src/proxy/reflect4.ts @@ -1,59 +1,62 @@ -import { devices, type BrowserContext, type Locator } from "patchright"; -import logger from "../logger.js"; -import * as twitch from "../website/twitch.js"; -import * as constants from "../constants.js"; - -async function spawn(context: BrowserContext, targetUrl: string, changeViewport: boolean = false, spawnId: string = "unknown") { - const server = constants.REFLECT4_SERVERS[Math.floor(Math.random() * constants.REFLECT4_SERVERS.length)]; - logger.debug(`[${spawnId}] Using reflect4 server: ${server}`); - const page = await context.newPage(); - if (changeViewport) { - logger.debug(`[${spawnId}] Changing viewport size...`); - const deviceName = Object.keys(devices)[Math.floor(Math.random() * Object.keys(devices).length)]; - const device = devices[deviceName]; - logger.debug(`[${spawnId}] Using device: ${deviceName}`); - await page.setViewportSize(device.viewport); - } - try { - await page.goto(server); - } catch (e) { - logger.error(`[${spawnId}] Error while navigating to proxy website: ${e}`); - await page.close(); - throw e; - } - let targetInput: Locator | null = null; - const allInput = await page.locator("input").all(); - for (const input of allInput) { - const placeholder = await input.getAttribute("placeholder"); - if (placeholder?.includes("URL")) { - targetInput = input; - break; - } - } - if (!targetInput) { - logger.error(`[${spawnId}] Failed to find input field for URL input`); - await page.close(); - throw new Error(`Failed to find input field for URL input`); - } - await targetInput.fill(targetUrl); - await targetInput.press("Enter"); - logger.info(`[${spawnId}] Navigating to ${targetUrl}`); - await page.waitForTimeout(15000); // Wait for 15 second to let the page load - // Keep-alive the page open for 5 minutes then refresh - if (targetUrl.startsWith("https://www.twitch.tv/")) { - logger.info(`[${spawnId}] Twitch URL detected, using Twitch mode...`); - await twitch.keepAlive(page, spawnId); - } else { - logger.warn(`[${spawnId}] Unsupported URL: ${targetUrl}`); - logger.warn(`[${spawnId}] Will try to keep alive, but no guarantees`); - while (true) { - await page.waitForTimeout(5 * 60 * 1000); // 5 minutes - await page.reload(); - logger.debug(`[${spawnId}] Reloaded page`); - } - } - await page.close(); - logger.info(`[${spawnId}] Proxy with the server ${server} closed`); -} - -export { spawn }; +import { devices, type BrowserContext, type Locator } from "patchright"; +import logger from "../logger.js"; +import * as twitch from "../website/twitch.js"; +import * as constants from "../constants.js"; +import type { PlaywrightBlocker } from "@ghostery/adblocker-playwright"; + +async function spawn(context: BrowserContext, blocker: PlaywrightBlocker, targetUrl: string, changeViewport: boolean = false, spawnId: string = "unknown") { + const server = constants.REFLECT4_SERVERS[Math.floor(Math.random() * constants.REFLECT4_SERVERS.length)]; + logger.debug(`[${spawnId}] Using reflect4 server: ${server}`); + const page = await context.newPage(); + logger.debug(`[${spawnId}] New page created, enabling adblocker...`); + await blocker.enableBlockingInPage(page as any); // As any because technically patchright Page != playwright Page :D + if (changeViewport) { + logger.debug(`[${spawnId}] Changing viewport size...`); + const deviceName = Object.keys(devices)[Math.floor(Math.random() * Object.keys(devices).length)]; + const device = devices[deviceName]; + logger.debug(`[${spawnId}] Using device: ${deviceName}`); + await page.setViewportSize(device.viewport); + } + try { + await page.goto(server); + } catch (e) { + logger.error(`[${spawnId}] Error while navigating to proxy website: ${e}`); + await page.close(); + throw e; + } + let targetInput: Locator | null = null; + const allInput = await page.locator("input").all(); + for (const input of allInput) { + const placeholder = await input.getAttribute("placeholder"); + if (placeholder?.includes("URL")) { + targetInput = input; + break; + } + } + if (!targetInput) { + logger.error(`[${spawnId}] Failed to find input field for URL input`); + await page.close(); + throw new Error(`Failed to find input field for URL input`); + } + await targetInput.fill(targetUrl); + await targetInput.press("Enter"); + logger.info(`[${spawnId}] Navigating to ${targetUrl}`); + await page.waitForTimeout(15000); // Wait for 15 second to let the page load + // Keep-alive the page open for 5 minutes then refresh + if (targetUrl.startsWith("https://www.twitch.tv/")) { + logger.info(`[${spawnId}] Twitch URL detected, using Twitch mode...`); + await twitch.keepAlive(page, spawnId); + } else { + logger.warn(`[${spawnId}] Unsupported URL: ${targetUrl}`); + logger.warn(`[${spawnId}] Will try to keep alive, but no guarantees`); + while (true) { + await page.waitForTimeout(5 * 60 * 1000); // 5 minutes + await page.reload(); + logger.debug(`[${spawnId}] Reloaded page`); + } + } + await page.close(); + logger.info(`[${spawnId}] Proxy with the server ${server} closed`); +} + +export { spawn };