Skip to content

Commit

Permalink
Merge branch 'master' into clipstudioreader
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeZeDev committed Nov 26, 2024
2 parents 73bcb07 + 208da85 commit 5e2b6d9
Show file tree
Hide file tree
Showing 134 changed files with 1,282 additions and 1,101 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/pull-request-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ jobs:
if: steps.packages.outcome == 'success' && (success() || failure())
env:
DISPLAY: ':99'
run: npm run test:websites -- MangaDex
run: npm run test:websites -- MangaDex
- name: Upload Test Results
if: failure()
uses: actions/upload-artifact@v4
with:
name: Test Results (${{ matrix.os }})
path: ./**/screenshot_*.png
3 changes: 2 additions & 1 deletion app/electron/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"noEmit": true
"noEmit": true,
"types": [ "chrome" ]
},
"include": [
"src/**/*.ts",
Expand Down
3 changes: 2 additions & 1 deletion app/nw/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"module": "ESNext",
"moduleResolution": "bundler",
"skipLibCheck": true,
"noEmit": true
"noEmit": true,
"types": [ "chrome", "nw.js" ]
},
"include": [
"src/**/*.ts",
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
"docs"
],
"devDependencies": {
"@stylistic/eslint-plugin": "^2.10.1",
"@stylistic/eslint-plugin": "^2.11.0",
"@types/chrome": "^0.0.283",
"@types/jsdom": "^21.1.7",
"@types/nw.js": "^0.92.0",
"eslint": "^9.15.0",
"eslint-plugin-tsdoc": "^0.3.0",
"jsdom": "^25.0.1",
"puppeteer-core": "^23.8.0",
"puppeteer-core": "^23.9.0",
"tslib": "^2.8.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.14.0",
"typescript": "^5.7.2",
"typescript-eslint": "^8.15.0",
"vite": "^5.4.11",
"vitest": "^2.1.5",
"vitest-mock-extended": "^2.0.2"
Expand Down
73 changes: 73 additions & 0 deletions test/AutomationEvasions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* Further Reading:
* - https://github.com/paulirish/headless-cat-n-mouse/blob/master/apply-evasions.js
* - https://deviceandbrowserinfo.com/learning_zone/articles/detecting-headless-chrome-puppeteer-2024
* - https://www.reddit.com/r/webscraping/comments/1evht3i/help_in_bypassing_cdp_detection/
* - https://github.com/kaliiiiiiiiii/brotector
* - https://github.com/rebrowser/rebrowser-patches?tab=readme-ov-file
*/

import { type Browser, type Target, TargetType, type Page } from 'puppeteer-core';

export type Evasion = (page: Page) => Promise<void>;

const conceal = `
function conceal(obj, key, intercept) {
function toString() {
return 'function ' + this.name + '() { [native code] }';
}
Object.defineProperty(toString, 'toString', {
value: toString,
writable: false,
enumerable: false,
});
Object.defineProperty(toString, 'prototype', {
value: undefined,
writable: false,
enumerable: false,
});
obj[key] = new Proxy(obj[key], {
get(target, key, receiver) {
return key === 'toString' ? toString : Reflect.get(target, key);
},
apply: intercept,
});
}
`;

/*
function EvadeWebDriverDetection(page: Page) {
//await page.evaluateOnNewDocument(`delete navigator.__proto__.webdriver`);
await page.evaluateOnNewDocument(`navigator.__proto__.webdriver = false`);
}
*/

/**
* When dumping an object with the console functions, the properties of the object will be evaluated.
* This evaluation only happens when the Developer Tools are opened, or a client is connected via CDP.
* A proxy object could be used to detect access to getters of the object when it is dumped.
*/
export async function EvadeChromeDevToolProtocolDetection(page: Page) {
await page.evaluateOnNewDocument(`
${conceal}
conceal(console, 'log', () => {});
conceal(console, 'warn', () => {});
conceal(console, 'error', () => {});
conceal(console, 'debug', () => {});
`);
}

/**
* Avoid automation detection for Blink-based browsers when used with Puppeteer.
* This will only hide changes applied by Puppeteer to access the capabilities of the underlying Blink browser (e.g., Electron or NW.js).
*/
export function SetupBlinkEvasions(browser: Browser, ...evasions: Evasion[]) {
// TODO: This doesn't work yet due to injection being too slow
// => https://github.com/puppeteer/puppeteer/issues/3667
browser.on('targetcreated', async (target: Target) => {
if(target.type() === TargetType.PAGE) {
const page = await target.page();
await Promise.all(evasions.map(setupEvasion => setupEvasion(page)));
}
});
}
17 changes: 14 additions & 3 deletions test/PuppeteerFixture.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as puppeteer from 'puppeteer-core';
import { AppURL, AppSelector } from './PuppeteerGlobal';
import { AppURL } from './PuppeteerGlobal';
import type { Evasion } from './AutomationEvasions';

export class PuppeteerFixture {

Expand All @@ -14,13 +15,23 @@ export class PuppeteerFixture {
return PuppeteerFixture.#page;
}

protected async OpenPage(url: string): Promise<puppeteer.Page> {
public async Screenshot(page: puppeteer.Page) {
await page.screenshot({
type: 'png',
fullPage: true,
captureBeyondViewport: true,
path: `./screenshot_${Date.now().toString(36)}.png`,
});
}

protected async OpenPage(url: string, ...evasions: Evasion[]): Promise<puppeteer.Page> {
const page = await (await this.GetBrowser()).newPage();
await Promise.all(evasions.map(setupEvasion => setupEvasion(page)));
await page.goto(url);
return page;
}

protected EvaluateHandle: typeof puppeteer.Page.prototype.evaluateHandle = async (pageFunction, ...args) => {
return (await PuppeteerFixture.#page).evaluateHandle(pageFunction, ...args);
return (await PuppeteerFixture.#page)!.evaluateHandle(pageFunction, ...args);
}
}
10 changes: 10 additions & 0 deletions web/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ export default tseslint.config({
'no-multi-spaces': 'error',
'no-throw-literal': 'error',
'tsdoc/syntax': 'warn',
/**
* Use restricted properties as workaround to address a flaw in the typescript system of global declarations.
* Globally declared modules (such as @types/node::Buffer) should be declared for e.g., tests but must not be declared for the web-application (browsers do not have `Buffer`).
* Unfortunately, once included these are available everywhere in the workspace without exception.
* See also: https://github.com/microsoft/TypeScript/issues/50424
*/
'no-restricted-properties': [ 'error', {
"object": "Buffer",
"property": "from"
} ],
'@typescript-eslint/naming-convention': [ 'error', // See: https://typescript-eslint.io/rules/naming-convention/#options
/*
{
Expand Down
10 changes: 5 additions & 5 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"protobufjs": "^7.4.0"
},
"devDependencies": {
"@fluentui/svg-icons": "^1.1.265",
"@fluentui/svg-icons": "^1.1.266",
"@fluentui/web-components": "^2.6.1",
"@microsoft/fast-element": "^1.14.0",
"@svelte-put/dragscroll": "^4.0.0",
Expand All @@ -23,14 +23,14 @@
"@vitejs/plugin-react": "^4.3.3",
"@vitejs/plugin-vue": "^5.2.0",
"@vscode/codicons": "^0.0.36",
"carbon-components-svelte": "^0.85.4",
"carbon-components-svelte": "^0.86.1",
"carbon-icons-svelte": "^12.13.0",
"fuse.js": "^7.0.0",
"lit": "^3.2.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"svelte": "^5.2.1",
"svelte-check": "^4.0.8",
"svelte": "^5.2.7",
"svelte-check": "^4.1.0",
"svelte-virtuallists": "^1.4.0",
"vue": "^3.5.13",
"vue-tsc": "^2.1.10"
Expand All @@ -42,7 +42,7 @@
"check:rules": "node ./scripts/coding-rules.mjs",
"check:svelte": "svelte-check --tsconfig=tsconfig.json --compiler-warnings a11y-click-events-have-key-events:ignore",
"check:vue": "vue-tsc --skipLibCheck --noEmit",
"check": "npm run check:ts && npm run check:lint && npm run check:svelte && npm run check:vue && npm run check:rules",
"check": "npm run check:ts && npm run check:lint && npm run check:svelte && npm run check:rules",
"build": "vite build",
"test": "vitest run",
"serve:dev": "vite --port=3000 --strictPort",
Expand Down
94 changes: 94 additions & 0 deletions web/src/AntiBotDetectionBypass_e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* Check the bot-detection bypass capabilities of HakuNeko's underlying Blink-based browser engine (NW.js, Electron)
*/

import { describe, it, expect } from 'vitest';
import type { Page } from 'puppeteer-core';
import { PuppeteerFixture } from '../../test/PuppeteerFixture';
import {
type Evasion,
EvadeChromeDevToolProtocolDetection,
} from '../../test/AutomationEvasions';

class TestFixture extends PuppeteerFixture {

private page: Page;

public async SetupPage(url: string, ...evasions: Evasion[]): Promise<TestFixture> {
await this.ClosePage();
this.page = await this.OpenPage(url, ...evasions);
//console.log('WebDriver:', await this.page.evaluate(() => window.navigator.webdriver));
//console.log('User-Agent:', await this.page.evaluate(() => window.navigator.userAgent));
//console.log('Console:', await this.page.evaluate(() => console.debug.toString()));
return this;
}

public async ClosePage(): Promise<void> {
return this.page?.close();
}

public async AssertElementText(selector: string, expected: string): Promise<void> {
try {
await this.page.waitForSelector(selector, { timeout: 7500 });
const actual = await this.page.$eval(selector, (element: HTMLElement) => element.innerText);
expect(actual).toBe(expected);
} catch(error) {
await this.Screenshot(this.page);
throw error;
}
}
}

describe('BrowserScan', { timeout: 15_000 }, () => {

it('Should pass Bot Detection Test', async () => {
const fixture = await new TestFixture().SetupPage('https://www.browserscan.net/bot-detection', EvadeChromeDevToolProtocolDetection);
try {
await fixture.AssertElementText(`div._5h5tql svg use[*|href="#status_success"]`, undefined);
} finally {
fixture.ClosePage();
}
});
});

describe('CloudFlare', { timeout: 15_000 }, () => {

it('Should bypass JavaScript Challenge', async () => {
const fixture = await new TestFixture().SetupPage('https://cloudflare.bot-check.ovh/automatic');
try {
await fixture.AssertElementText('#hash', '801BE7B2C3621A7DE738CF77BD4EF264');
} finally {
fixture.ClosePage();
}
});

it('Should bypass Turnstile Non-Interactive Challenge', { todo: true }, async () => {
const fixture = await new TestFixture().SetupPage('https://cloudflare.bot-check.ovh/turnstile-automatic');
try {
await fixture.AssertElementText('#hash', 'A9B6FA3DD8842CD8E2D6070784D92434');
} finally {
fixture.ClosePage();
}
});

it('Should bypass Turnstile Invisible Challenge', { todo: true }, async () => {
const fixture = await new TestFixture().SetupPage('https://cloudflare.bot-check.ovh/turnstile-invisible');
try {
await fixture.AssertElementText('#hash', 'A9B6FA3DD8842CD8E2D6070784D92434');
} finally {
fixture.ClosePage();
}
});
});

describe('Vercel', { timeout: 15_000 }, () => {

it('Should bypass Attack Challenge Mode', { todo: true }, async () => {
const fixture = await new TestFixture().SetupPage('https://vercel.bot-check.ovh', EvadeChromeDevToolProtocolDetection);
try {
await fixture.AssertElementText('#hash', 'FDF049D4B2312945BB7AA2158F041278');
} finally {
fixture.ClosePage();
}
});
});
53 changes: 0 additions & 53 deletions web/src/cloudflare_e2e.ts

This file was deleted.

19 changes: 19 additions & 0 deletions web/src/engine/BufferEncoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function FromHexString(hexString: string): Uint8Array {
return Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
}

export function GetBytesUTF8(text: string): Uint8Array {
return new TextEncoder().encode(text);
};

export function Uint8ToHexString(array: Uint8Array): string {
return array.reduce((result, x) => result + x.toString(16).padStart(2, '0'), '');
}

export function BufferToHexString(buffer: ArrayBuffer): string {
return Uint8ToHexString(new Uint8Array(buffer));
}

export function GetBytesB64(b64string: string): Uint8Array {
return Uint8Array.from(window.atob(b64string), c => c.charCodeAt(0));
}
Loading

0 comments on commit 5e2b6d9

Please sign in to comment.