feat: add ghostery adblocker & update deps

Woah, two years :)
This commit is contained in:
2026-02-14 14:36:20 +07:00
parent bb9a31cc78
commit b1c4ed915e
4 changed files with 166 additions and 158 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -11,19 +11,20 @@
"build": "swc ./src -d dist --strip-leading-paths" "build": "swc ./src -d dist --strip-leading-paths"
}, },
"devDependencies": { "devDependencies": {
"@swc-node/register": "^1.10.10", "@swc-node/register": "^1.11.1",
"@swc/cli": "^0.6.0", "@swc/cli": "^0.6.0",
"@swc/core": "^1.11.20", "@swc/core": "^1.15.11",
"@types/bun": "latest" "@types/bun": "latest"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "^5.0.0" "typescript": "^5.8.3"
}, },
"dependencies": { "dependencies": {
"patchright": "^1.51.3", "@ghostery/adblocker-playwright": "^2.14.1",
"pino": "^9.6.0", "patchright": "^1.57.0",
"pino-pretty": "^13.0.0", "pino": "^9.14.0",
"playwright": "^1.51.1" "pino-pretty": "^13.1.3",
"playwright": "^1.58.2"
}, },
"trustedDependencies": [ "trustedDependencies": [
"@swc/core" "@swc/core"

View File

@@ -1,92 +1,96 @@
import { chromium, type Browser, type LaunchOptions } from "patchright"; import { chromium, type Browser, type LaunchOptions } from "patchright";
import fs from 'node:fs'; import { PlaywrightBlocker } from '@ghostery/adblocker-playwright';
import * as reflect4 from "./proxy/reflect4.js"; import fs from 'node:fs';
import logger from "./logger.js"; import * as reflect4 from "./proxy/reflect4.js";
import Config from "./config.js"; import logger from "./logger.js";
import { VERSION } from "./constants.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."); 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")) { let config = new Config();
logger.info("Reading configuration from 'config.json'..."); if (fs.existsSync("config.json")) {
const text = await fs.promises.readFile("config.json", "utf-8"); logger.info("Reading configuration from 'config.json'...");
config = Config.fromJSON(text); const text = await fs.promises.readFile("config.json", "utf-8");
} else { config = Config.fromJSON(text);
logger.info("No configuration file found. Using the default configuration."); } else {
logger.info("Default configuration file 'config.json' created."); 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()); // 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) { // Validate configuration
logger.error("Proxy count must be greater than 0."); if (config.proxy.count < 1) {
process.exit(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}'`); logger.level = process.env.LOG_LEVEL || config.logger.level;
logger.info(`Logger level set to '${logger.level}'`);
let browser: Browser;
let launchOptions: LaunchOptions = { let browser: Browser;
headless: config.playwright.headless, let launchOptions: LaunchOptions = {
} headless: config.playwright.headless,
}
logger.info("Launching browser...");
logger.debug(`Launch options: ${JSON.stringify(launchOptions)}`); logger.info("Launching browser...");
logger.debug(`Launch options: ${JSON.stringify(launchOptions)}`);
switch (config.playwright.browser) {
case "chromium": switch (config.playwright.browser) {
logger.info("Using Chromium as the browser provider."); case "chromium":
logger.warn("Chromium is not supported by Twitch. Use at your own risk."); logger.info("Using Chromium as the browser provider.");
browser = await chromium.launch({ ...launchOptions }); logger.warn("Chromium is not supported by Twitch. Use at your own risk.");
break; browser = await chromium.launch({ ...launchOptions });
case "chrome": break;
logger.info("Using Google Chrome as the browser provider."); case "chrome":
browser = await chromium.launch({ ...launchOptions, channel: "chrome" }); logger.info("Using Google Chrome as the browser provider.");
break; browser = await chromium.launch({ ...launchOptions, channel: "chrome" });
case "cdp": break;
logger.info("Using Chrome DevTools Protocol (CDP) for browser connection."); case "cdp":
browser = await chromium.connectOverCDP(config.playwright.cdp); logger.info("Using Chrome DevTools Protocol (CDP) for browser connection.");
break; browser = await chromium.connectOverCDP(config.playwright.cdp);
default: break;
logger.warn(`Unsupported browser channel: '${config.playwright.browser}'`); default:
logger.warn("Castorsrm will try to launch the browser anyway, but it may not work as expected."); logger.warn(`Unsupported browser channel: '${config.playwright.browser}'`);
browser = await chromium.launch({ ...launchOptions, channel: config.playwright.browser }); logger.warn("Castorsrm will try to launch the browser anyway, but it may not work as expected.");
break; browser = await chromium.launch({ ...launchOptions, channel: config.playwright.browser });
} break;
}
logger.info("Browser launched successfully, spawning proxies");
if (config.proxy.mode === "reflect4") { logger.info("Browser launched successfully, enabling adblocker...");
const context = await browser.newContext(); let blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch);
const tasks: Array<Promise<void>> = [];
logger.info(`Spawning ${config.proxy.count} proxies...`); logger.info("Spawning proxies...");
for (let i = 0; i < config.proxy.count; i++) { if (config.proxy.mode === "reflect4") {
logger.debug(`Spawning proxy ${i + 1}...`); const context = await browser.newContext();
tasks.push((async () => { const tasks: Array<Promise<void>> = [];
const spawnId = `${i + 1}`; logger.info(`Spawning ${config.proxy.count} proxies...`);
while (true) { for (let i = 0; i < config.proxy.count; i++) {
try { logger.debug(`Spawning proxy ${i + 1}...`);
await reflect4.spawn(context, config.playwright.url, config.playwright.change_viewport, spawnId); tasks.push((async () => {
} catch (e) { const spawnId = `${i + 1}`;
logger.error(`[${spawnId}] Error while running: ${e}`); while (true) {
} try {
logger.warn(`[${spawnId}] Restarting in 3 seconds...`); await reflect4.spawn(context, blocker, config.playwright.url, config.playwright.change_viewport, spawnId);
await new Promise(resolve => setTimeout(resolve, 3 * 1000)); } catch (e) {
} logger.error(`[${spawnId}] Error while running: ${e}`);
})()); }
} logger.warn(`[${spawnId}] Restarting in 3 seconds...`);
await Promise.all(tasks); await new Promise(resolve => setTimeout(resolve, 3 * 1000));
logger.info("All proxies spawned successfully."); }
} })());
}
process.on("SIGINT", async () => { await Promise.all(tasks);
logger.info("Received SIGINT. Closing browser..."); logger.info("All proxies spawned successfully.");
for (const context of browser.contexts()) { }
await context.close();
} process.on("SIGINT", async () => {
await browser.close(); logger.info("Received SIGINT. Closing browser...");
process.exit(); for (const context of browser.contexts()) {
}); await context.close();
}
await browser.close();
process.exit();
});

View File

@@ -1,59 +1,62 @@
import { devices, type BrowserContext, type Locator } from "patchright"; import { devices, type BrowserContext, type Locator } from "patchright";
import logger from "../logger.js"; import logger from "../logger.js";
import * as twitch from "../website/twitch.js"; import * as twitch from "../website/twitch.js";
import * as constants from "../constants.js"; import * as constants from "../constants.js";
import type { PlaywrightBlocker } from "@ghostery/adblocker-playwright";
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)]; async function spawn(context: BrowserContext, blocker: PlaywrightBlocker, targetUrl: string, changeViewport: boolean = false, spawnId: string = "unknown") {
logger.debug(`[${spawnId}] Using reflect4 server: ${server}`); const server = constants.REFLECT4_SERVERS[Math.floor(Math.random() * constants.REFLECT4_SERVERS.length)];
const page = await context.newPage(); logger.debug(`[${spawnId}] Using reflect4 server: ${server}`);
if (changeViewport) { const page = await context.newPage();
logger.debug(`[${spawnId}] Changing viewport size...`); logger.debug(`[${spawnId}] New page created, enabling adblocker...`);
const deviceName = Object.keys(devices)[Math.floor(Math.random() * Object.keys(devices).length)]; await blocker.enableBlockingInPage(page as any); // As any because technically patchright Page != playwright Page :D
const device = devices[deviceName]; if (changeViewport) {
logger.debug(`[${spawnId}] Using device: ${deviceName}`); logger.debug(`[${spawnId}] Changing viewport size...`);
await page.setViewportSize(device.viewport); const deviceName = Object.keys(devices)[Math.floor(Math.random() * Object.keys(devices).length)];
} const device = devices[deviceName];
try { logger.debug(`[${spawnId}] Using device: ${deviceName}`);
await page.goto(server); await page.setViewportSize(device.viewport);
} catch (e) { }
logger.error(`[${spawnId}] Error while navigating to proxy website: ${e}`); try {
await page.close(); await page.goto(server);
throw e; } catch (e) {
} logger.error(`[${spawnId}] Error while navigating to proxy website: ${e}`);
let targetInput: Locator | null = null; await page.close();
const allInput = await page.locator("input").all(); throw e;
for (const input of allInput) { }
const placeholder = await input.getAttribute("placeholder"); let targetInput: Locator | null = null;
if (placeholder?.includes("URL")) { const allInput = await page.locator("input").all();
targetInput = input; for (const input of allInput) {
break; const placeholder = await input.getAttribute("placeholder");
} if (placeholder?.includes("URL")) {
} targetInput = input;
if (!targetInput) { break;
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`); if (!targetInput) {
} logger.error(`[${spawnId}] Failed to find input field for URL input`);
await targetInput.fill(targetUrl); await page.close();
await targetInput.press("Enter"); throw new Error(`Failed to find input field for URL input`);
logger.info(`[${spawnId}] Navigating to ${targetUrl}`); }
await page.waitForTimeout(15000); // Wait for 15 second to let the page load await targetInput.fill(targetUrl);
// Keep-alive the page open for 5 minutes then refresh await targetInput.press("Enter");
if (targetUrl.startsWith("https://www.twitch.tv/")) { logger.info(`[${spawnId}] Navigating to ${targetUrl}`);
logger.info(`[${spawnId}] Twitch URL detected, using Twitch mode...`); await page.waitForTimeout(15000); // Wait for 15 second to let the page load
await twitch.keepAlive(page, spawnId); // Keep-alive the page open for 5 minutes then refresh
} else { if (targetUrl.startsWith("https://www.twitch.tv/")) {
logger.warn(`[${spawnId}] Unsupported URL: ${targetUrl}`); logger.info(`[${spawnId}] Twitch URL detected, using Twitch mode...`);
logger.warn(`[${spawnId}] Will try to keep alive, but no guarantees`); await twitch.keepAlive(page, spawnId);
while (true) { } else {
await page.waitForTimeout(5 * 60 * 1000); // 5 minutes logger.warn(`[${spawnId}] Unsupported URL: ${targetUrl}`);
await page.reload(); logger.warn(`[${spawnId}] Will try to keep alive, but no guarantees`);
logger.debug(`[${spawnId}] Reloaded page`); while (true) {
} await page.waitForTimeout(5 * 60 * 1000); // 5 minutes
} await page.reload();
await page.close(); logger.debug(`[${spawnId}] Reloaded page`);
logger.info(`[${spawnId}] Proxy with the server ${server} closed`); }
} }
await page.close();
export { spawn }; logger.info(`[${spawnId}] Proxy with the server ${server} closed`);
}
export { spawn };