feat: add ghostery adblocker & update deps
Woah, two years :)
This commit is contained in:
15
package.json
15
package.json
@@ -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"
|
||||||
|
|||||||
188
src/index.ts
188
src/index.ts
@@ -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();
|
||||||
|
});
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user