@tocha688/browser 0.0.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/index.cjs +4 -4
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -4
- package/package.json +2 -2
- package/src/actions/auto/types.ts +63 -63
- package/src/const/browser.ts +214 -214
- package/src/core/launcher.ts +45 -45
- package/src/human/mouse.ts +308 -308
- package/src/index.ts +12 -12
- package/src/network/cache.ts +129 -129
- package/src/network/resource.ts +61 -61
- package/src/providers/ads_tool.ts +38 -38
- package/src/providers/adspower.ts +17 -7
- package/src/providers/base.ts +14 -2
- package/src/providers/local.ts +12 -3
- package/src/providers/ok.ts +8 -2
- package/src/types/index.ts +3 -0
package/src/network/cache.ts
CHANGED
|
@@ -1,130 +1,130 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 用于浏览器缓存
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { type Page } from "patchright-core";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { createHash } from "crypto";
|
|
9
|
-
import { BrowserCachePath } from "../core/config";
|
|
10
|
-
|
|
11
|
-
function getCachePath(url: string, cachePath: string) {
|
|
12
|
-
const hash = createHash("md5").update(url).digest("hex");
|
|
13
|
-
const cacheFile = path.join(cachePath, hash);
|
|
14
|
-
const metaFile = cacheFile + ".meta.json";
|
|
15
|
-
return { cacheFile, metaFile }
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/** 缓存浏览器到目录 ./tmp/browser_cache */
|
|
19
|
-
export async function routeAssetsCache(page: Page, cachePath: string) {
|
|
20
|
-
// 确保缓存目录存在
|
|
21
|
-
if (!fs.existsSync(cachePath)) {
|
|
22
|
-
fs.mkdirSync(cachePath, { recursive: true });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
console.log("启用浏览器缓存")
|
|
26
|
-
|
|
27
|
-
await page.route("**/*", async (route) => {
|
|
28
|
-
const req = route.request();
|
|
29
|
-
//如果不是get直接放行
|
|
30
|
-
if (req.method() !== "GET") {
|
|
31
|
-
return route.continue();
|
|
32
|
-
}
|
|
33
|
-
const type = req.resourceType()
|
|
34
|
-
if (["xhr", "fetch", "websocket", "document"].includes(type)) {
|
|
35
|
-
return route.continue();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 尝试从缓存读取 (使用 URL hash 作为文件名)
|
|
39
|
-
const url = req.url();
|
|
40
|
-
const { cacheFile, metaFile } = getCachePath(url, cachePath);
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
if (fs.existsSync(cacheFile) && fs.existsSync(metaFile)) {
|
|
44
|
-
const meta = JSON.parse(fs.readFileSync(metaFile, "utf-8"));
|
|
45
|
-
const body = fs.readFileSync(cacheFile);
|
|
46
|
-
// 检查缓存是否过期
|
|
47
|
-
if (Date.now() - meta.timestamp > meta.timeout) {
|
|
48
|
-
fs.unlinkSync(cacheFile);
|
|
49
|
-
fs.unlinkSync(metaFile);
|
|
50
|
-
return route.continue();
|
|
51
|
-
}
|
|
52
|
-
// console.log(`命中缓存 ${url}`, metaFile);
|
|
53
|
-
return route.fulfill({
|
|
54
|
-
status: 200,
|
|
55
|
-
headers: meta.headers,
|
|
56
|
-
body: body,
|
|
57
|
-
contentType: meta.contentType
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
} catch (e) {
|
|
61
|
-
// 缓存读取失败,继续请求
|
|
62
|
-
// console.log(`缓存 ${url} 读取失败`, e);
|
|
63
|
-
}
|
|
64
|
-
return route.continue();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// 监听响应事件,检查是否缓存到本地
|
|
68
|
-
await page.on("response", async (response) => {
|
|
69
|
-
const req = response.request();
|
|
70
|
-
if (req.method() !== "GET") {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const status = response.status()
|
|
74
|
-
if (![200, 201].includes(status)) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
const url = response.url();
|
|
78
|
-
const { cacheFile, metaFile } = getCachePath(url, cachePath);
|
|
79
|
-
if (fs.existsSync(cacheFile) && fs.existsSync(metaFile)) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
// 检查响应头是否允许缓存
|
|
83
|
-
const headers = response.headers();
|
|
84
|
-
const resType = headers["content-type"] || "";
|
|
85
|
-
if (resType.includes("text/plain") || url.includes(";")) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const cacheControl = headers["cache-control"] || "";
|
|
89
|
-
const pragma = headers["pragma"] || "";
|
|
90
|
-
// 如果明确禁止缓存,则不缓存
|
|
91
|
-
if (cacheControl.includes("no-store") || pragma.includes("no-cache")) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const type = req.resourceType()
|
|
95
|
-
if (!(pragma || cacheControl)) {
|
|
96
|
-
// 如果没有cache-control和pragma头,只缓存document类型的资源
|
|
97
|
-
if (["xhr", "fetch", "websocket", "document"].includes(type)) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
// 如果有参数也不缓存
|
|
101
|
-
const params = new URL(url).searchParams
|
|
102
|
-
if (params.toString().length > 0) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
// if (["document", "script"].includes(type) && params.size > 0) {
|
|
106
|
-
// return;
|
|
107
|
-
// }
|
|
108
|
-
// if (["script"].includes(type) && params.size > 38) {
|
|
109
|
-
// return;
|
|
110
|
-
// }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
// 保存到缓存
|
|
115
|
-
const buffer = await response.body();
|
|
116
|
-
fs.writeFileSync(cacheFile, buffer);
|
|
117
|
-
fs.writeFileSync(metaFile, JSON.stringify({
|
|
118
|
-
url: url,
|
|
119
|
-
headers: headers,
|
|
120
|
-
contentType: headers["content-type"],
|
|
121
|
-
timestamp: Date.now(),
|
|
122
|
-
// 从header中获取缓存时间,默认1分钟
|
|
123
|
-
timeout: parseInt(headers["cache-control"]?.match(/max-age=(\d+)/)?.[1] || "99999999") * 1000,
|
|
124
|
-
}));
|
|
125
|
-
// console.log(`缓存 ${url}`, resType);
|
|
126
|
-
} catch (e) {
|
|
127
|
-
// console.log(`缓存 ${url} 写入失败`, e);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* 用于浏览器缓存
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type Page } from "patchright-core";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import { BrowserCachePath } from "../core/config";
|
|
10
|
+
|
|
11
|
+
function getCachePath(url: string, cachePath: string) {
|
|
12
|
+
const hash = createHash("md5").update(url).digest("hex");
|
|
13
|
+
const cacheFile = path.join(cachePath, hash);
|
|
14
|
+
const metaFile = cacheFile + ".meta.json";
|
|
15
|
+
return { cacheFile, metaFile }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** 缓存浏览器到目录 ./tmp/browser_cache */
|
|
19
|
+
export async function routeAssetsCache(page: Page, cachePath: string) {
|
|
20
|
+
// 确保缓存目录存在
|
|
21
|
+
if (!fs.existsSync(cachePath)) {
|
|
22
|
+
fs.mkdirSync(cachePath, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log("启用浏览器缓存")
|
|
26
|
+
|
|
27
|
+
await page.route("**/*", async (route) => {
|
|
28
|
+
const req = route.request();
|
|
29
|
+
//如果不是get直接放行
|
|
30
|
+
if (req.method() !== "GET") {
|
|
31
|
+
return route.continue();
|
|
32
|
+
}
|
|
33
|
+
const type = req.resourceType()
|
|
34
|
+
if (["xhr", "fetch", "websocket", "document"].includes(type)) {
|
|
35
|
+
return route.continue();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 尝试从缓存读取 (使用 URL hash 作为文件名)
|
|
39
|
+
const url = req.url();
|
|
40
|
+
const { cacheFile, metaFile } = getCachePath(url, cachePath);
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
if (fs.existsSync(cacheFile) && fs.existsSync(metaFile)) {
|
|
44
|
+
const meta = JSON.parse(fs.readFileSync(metaFile, "utf-8"));
|
|
45
|
+
const body = fs.readFileSync(cacheFile);
|
|
46
|
+
// 检查缓存是否过期
|
|
47
|
+
if (Date.now() - meta.timestamp > meta.timeout) {
|
|
48
|
+
fs.unlinkSync(cacheFile);
|
|
49
|
+
fs.unlinkSync(metaFile);
|
|
50
|
+
return route.continue();
|
|
51
|
+
}
|
|
52
|
+
// console.log(`命中缓存 ${url}`, metaFile);
|
|
53
|
+
return route.fulfill({
|
|
54
|
+
status: 200,
|
|
55
|
+
headers: meta.headers,
|
|
56
|
+
body: body,
|
|
57
|
+
contentType: meta.contentType
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// 缓存读取失败,继续请求
|
|
62
|
+
// console.log(`缓存 ${url} 读取失败`, e);
|
|
63
|
+
}
|
|
64
|
+
return route.continue();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// 监听响应事件,检查是否缓存到本地
|
|
68
|
+
await page.on("response", async (response) => {
|
|
69
|
+
const req = response.request();
|
|
70
|
+
if (req.method() !== "GET") {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const status = response.status()
|
|
74
|
+
if (![200, 201].includes(status)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const url = response.url();
|
|
78
|
+
const { cacheFile, metaFile } = getCachePath(url, cachePath);
|
|
79
|
+
if (fs.existsSync(cacheFile) && fs.existsSync(metaFile)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// 检查响应头是否允许缓存
|
|
83
|
+
const headers = response.headers();
|
|
84
|
+
const resType = headers["content-type"] || "";
|
|
85
|
+
if (resType.includes("text/plain") || url.includes(";")) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const cacheControl = headers["cache-control"] || "";
|
|
89
|
+
const pragma = headers["pragma"] || "";
|
|
90
|
+
// 如果明确禁止缓存,则不缓存
|
|
91
|
+
if (cacheControl.includes("no-store") || pragma.includes("no-cache")) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const type = req.resourceType()
|
|
95
|
+
if (!(pragma || cacheControl)) {
|
|
96
|
+
// 如果没有cache-control和pragma头,只缓存document类型的资源
|
|
97
|
+
if (["xhr", "fetch", "websocket", "document"].includes(type)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// 如果有参数也不缓存
|
|
101
|
+
const params = new URL(url).searchParams
|
|
102
|
+
if (params.toString().length > 0) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// if (["document", "script"].includes(type) && params.size > 0) {
|
|
106
|
+
// return;
|
|
107
|
+
// }
|
|
108
|
+
// if (["script"].includes(type) && params.size > 38) {
|
|
109
|
+
// return;
|
|
110
|
+
// }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// 保存到缓存
|
|
115
|
+
const buffer = await response.body();
|
|
116
|
+
fs.writeFileSync(cacheFile, buffer);
|
|
117
|
+
fs.writeFileSync(metaFile, JSON.stringify({
|
|
118
|
+
url: url,
|
|
119
|
+
headers: headers,
|
|
120
|
+
contentType: headers["content-type"],
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
// 从header中获取缓存时间,默认1分钟
|
|
123
|
+
timeout: parseInt(headers["cache-control"]?.match(/max-age=(\d+)/)?.[1] || "99999999") * 1000,
|
|
124
|
+
}));
|
|
125
|
+
// console.log(`缓存 ${url}`, resType);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
// console.log(`缓存 ${url} 写入失败`, e);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
130
|
}
|
package/src/network/resource.ts
CHANGED
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import type { Page, Route, Request } from "patchright-core";
|
|
2
|
-
|
|
3
|
-
export async function fetchResource(route: Route, req: Request, page: Page) {
|
|
4
|
-
// 使用 Node.js 原生 fetch,不继承浏览器代理设置
|
|
5
|
-
return await fetch(req.url(), {
|
|
6
|
-
method: req.method(),
|
|
7
|
-
body: req.postData(),
|
|
8
|
-
headers: req.headers(),
|
|
9
|
-
})
|
|
10
|
-
.then(async (res) => {
|
|
11
|
-
return route.fulfill({
|
|
12
|
-
status: res.status,
|
|
13
|
-
headers: Object.fromEntries(res.headers.entries()),
|
|
14
|
-
body: Buffer.from(await res.arrayBuffer()),
|
|
15
|
-
});
|
|
16
|
-
}).catch(() => {
|
|
17
|
-
return route.abort();
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function routeAssetsLocal(page: Page) {
|
|
22
|
-
await page.route("**/*", async (route) => {
|
|
23
|
-
const req = route.request();
|
|
24
|
-
const url = req.url();
|
|
25
|
-
const type = req.resourceType();
|
|
26
|
-
//如果不是get直接放行
|
|
27
|
-
if (!["GET"].includes(req.method())) {
|
|
28
|
-
return await route.continue();
|
|
29
|
-
}
|
|
30
|
-
if (
|
|
31
|
-
["xhr", "fetch", "websocket", "document"].includes(type)
|
|
32
|
-
|| (["script"].includes(type) && url.includes("?"))
|
|
33
|
-
) {
|
|
34
|
-
//使用浏览器代理进行请求
|
|
35
|
-
return await route.continue();
|
|
36
|
-
}
|
|
37
|
-
//使用本地网络进行请求
|
|
38
|
-
return await fetchResource(route, req, page);
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export async function routeAssetsAbort(page: Page) {
|
|
43
|
-
await page.route("**/*", async (route) => {
|
|
44
|
-
const req = route.request();
|
|
45
|
-
//如果不是get直接放行
|
|
46
|
-
if (req.method() !== "GET") {
|
|
47
|
-
return route.continue();
|
|
48
|
-
}
|
|
49
|
-
const type = req.resourceType()
|
|
50
|
-
// if (["script"].includes(type)) {
|
|
51
|
-
// return await curlResource(route, req, page);
|
|
52
|
-
// }
|
|
53
|
-
if (
|
|
54
|
-
!["xhr", "fetch", "websocket", "document", "script"].includes(type)
|
|
55
|
-
) {
|
|
56
|
-
//使用fetch进行请求
|
|
57
|
-
return route.abort();
|
|
58
|
-
}
|
|
59
|
-
//其他请求继续
|
|
60
|
-
return route.continue();
|
|
61
|
-
});
|
|
1
|
+
import type { Page, Route, Request } from "patchright-core";
|
|
2
|
+
|
|
3
|
+
export async function fetchResource(route: Route, req: Request, page: Page) {
|
|
4
|
+
// 使用 Node.js 原生 fetch,不继承浏览器代理设置
|
|
5
|
+
return await fetch(req.url(), {
|
|
6
|
+
method: req.method(),
|
|
7
|
+
body: req.postData(),
|
|
8
|
+
headers: req.headers(),
|
|
9
|
+
})
|
|
10
|
+
.then(async (res) => {
|
|
11
|
+
return route.fulfill({
|
|
12
|
+
status: res.status,
|
|
13
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
14
|
+
body: Buffer.from(await res.arrayBuffer()),
|
|
15
|
+
});
|
|
16
|
+
}).catch(() => {
|
|
17
|
+
return route.abort();
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function routeAssetsLocal(page: Page) {
|
|
22
|
+
await page.route("**/*", async (route) => {
|
|
23
|
+
const req = route.request();
|
|
24
|
+
const url = req.url();
|
|
25
|
+
const type = req.resourceType();
|
|
26
|
+
//如果不是get直接放行
|
|
27
|
+
if (!["GET"].includes(req.method())) {
|
|
28
|
+
return await route.continue();
|
|
29
|
+
}
|
|
30
|
+
if (
|
|
31
|
+
["xhr", "fetch", "websocket", "document"].includes(type)
|
|
32
|
+
|| (["script"].includes(type) && url.includes("?"))
|
|
33
|
+
) {
|
|
34
|
+
//使用浏览器代理进行请求
|
|
35
|
+
return await route.continue();
|
|
36
|
+
}
|
|
37
|
+
//使用本地网络进行请求
|
|
38
|
+
return await fetchResource(route, req, page);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function routeAssetsAbort(page: Page) {
|
|
43
|
+
await page.route("**/*", async (route) => {
|
|
44
|
+
const req = route.request();
|
|
45
|
+
//如果不是get直接放行
|
|
46
|
+
if (req.method() !== "GET") {
|
|
47
|
+
return route.continue();
|
|
48
|
+
}
|
|
49
|
+
const type = req.resourceType()
|
|
50
|
+
// if (["script"].includes(type)) {
|
|
51
|
+
// return await curlResource(route, req, page);
|
|
52
|
+
// }
|
|
53
|
+
if (
|
|
54
|
+
!["xhr", "fetch", "websocket", "document", "script"].includes(type)
|
|
55
|
+
) {
|
|
56
|
+
//使用fetch进行请求
|
|
57
|
+
return route.abort();
|
|
58
|
+
}
|
|
59
|
+
//其他请求继续
|
|
60
|
+
return route.continue();
|
|
61
|
+
});
|
|
62
62
|
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
|
|
2
|
-
/** ads专用参数加密 */
|
|
3
|
-
export function adsEncodeBase64(e: string) {
|
|
4
|
-
let t, r
|
|
5
|
-
void 0 === t && (t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),
|
|
6
|
-
void 0 === r && (r = "hTy1bfRJz4nLPcBCO7WtmNIaGvVeul5Zo8kq32UxrYw_-0gsjp96SDFXQiEMKdHA");
|
|
7
|
-
var n = t
|
|
8
|
-
, a = r
|
|
9
|
-
, i = Buffer.from(e).toString('base64').split("")
|
|
10
|
-
, s = "";
|
|
11
|
-
return i.map((function (e) {
|
|
12
|
-
var t = n.indexOf(e);
|
|
13
|
-
return s += t < 0 ? e : a.substr(t, 1),
|
|
14
|
-
e
|
|
15
|
-
}
|
|
16
|
-
)),
|
|
17
|
-
s
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/** ads专用参数解密 */
|
|
22
|
-
export function adsDecodeBase64(e: string) {
|
|
23
|
-
let t, r;
|
|
24
|
-
if (void 0 === t && (t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),
|
|
25
|
-
void 0 === r && (r = "hTy1bfRJz4nLPcBCO7WtmNIaGvVeul5Zo8kq32UxrYw_-0gsjp96SDFXQiEMKdHA"),
|
|
26
|
-
!e)
|
|
27
|
-
return "";
|
|
28
|
-
var n = t
|
|
29
|
-
, a = r
|
|
30
|
-
, i = e.split("")
|
|
31
|
-
, s = "";
|
|
32
|
-
return i.map((function (e) {
|
|
33
|
-
var t = a.indexOf(e);
|
|
34
|
-
return s += t < 0 ? e : n.substr(t, 1),
|
|
35
|
-
e
|
|
36
|
-
}
|
|
37
|
-
)), Buffer.from(s, 'base64').toString()
|
|
38
|
-
}
|
|
1
|
+
|
|
2
|
+
/** ads专用参数加密 */
|
|
3
|
+
export function adsEncodeBase64(e: string) {
|
|
4
|
+
let t, r
|
|
5
|
+
void 0 === t && (t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),
|
|
6
|
+
void 0 === r && (r = "hTy1bfRJz4nLPcBCO7WtmNIaGvVeul5Zo8kq32UxrYw_-0gsjp96SDFXQiEMKdHA");
|
|
7
|
+
var n = t
|
|
8
|
+
, a = r
|
|
9
|
+
, i = Buffer.from(e).toString('base64').split("")
|
|
10
|
+
, s = "";
|
|
11
|
+
return i.map((function (e) {
|
|
12
|
+
var t = n.indexOf(e);
|
|
13
|
+
return s += t < 0 ? e : a.substr(t, 1),
|
|
14
|
+
e
|
|
15
|
+
}
|
|
16
|
+
)),
|
|
17
|
+
s
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/** ads专用参数解密 */
|
|
22
|
+
export function adsDecodeBase64(e: string) {
|
|
23
|
+
let t, r;
|
|
24
|
+
if (void 0 === t && (t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),
|
|
25
|
+
void 0 === r && (r = "hTy1bfRJz4nLPcBCO7WtmNIaGvVeul5Zo8kq32UxrYw_-0gsjp96SDFXQiEMKdHA"),
|
|
26
|
+
!e)
|
|
27
|
+
return "";
|
|
28
|
+
var n = t
|
|
29
|
+
, a = r
|
|
30
|
+
, i = e.split("")
|
|
31
|
+
, s = "";
|
|
32
|
+
return i.map((function (e) {
|
|
33
|
+
var t = a.indexOf(e);
|
|
34
|
+
return s += t < 0 ? e : n.substr(t, 1),
|
|
35
|
+
e
|
|
36
|
+
}
|
|
37
|
+
)), Buffer.from(s, 'base64').toString()
|
|
38
|
+
}
|
|
@@ -4,17 +4,19 @@ import { faker } from "@faker-js/faker";
|
|
|
4
4
|
import { adsEncodeBase64, adsDecodeBase64 } from "./ads_tool";
|
|
5
5
|
import { PcScreen, WindowsWebGL, Cpus, Memorys, rSysFonts } from "../const/browser";
|
|
6
6
|
import type { BrowserResult, BrowserStartOptions } from "../types";
|
|
7
|
-
import { checkAutoProxy, rArrItem, rInt, uuidV4, rAutoDigitInt } from "@tocha688/utils";
|
|
7
|
+
import { checkAutoProxy, rArrItem, rInt, uuidV4, rAutoDigitInt, type ProxyServerResult, startProxyServer, getIpInfo } from "@tocha688/utils";
|
|
8
8
|
import { launchBrowserBase } from "./base";
|
|
9
9
|
|
|
10
10
|
export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<BrowserResult> {
|
|
11
11
|
return launchBrowserBase(opts, {
|
|
12
12
|
prepare: async (userDataDir, devtoolsPort) => {
|
|
13
|
+
|
|
13
14
|
const webGLFP_path = path.join(userDataDir, "webgl.json")
|
|
14
15
|
const dynamicConfig_path = path.join(userDataDir, "dynamic.config")
|
|
15
16
|
const staticConfig_path = path.join(userDataDir, "static.config")
|
|
16
17
|
const params_path = path.join(userDataDir, "params.config")
|
|
17
18
|
|
|
19
|
+
|
|
18
20
|
let params: string;
|
|
19
21
|
let screenSize: string;
|
|
20
22
|
|
|
@@ -29,8 +31,10 @@ export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<Br
|
|
|
29
31
|
if (opts.proxy) {
|
|
30
32
|
ipinfo = await checkAutoProxy(opts.proxy);
|
|
31
33
|
proxyInfo = new URL(ipinfo.proxy)
|
|
34
|
+
} else {
|
|
35
|
+
ipinfo = await getIpInfo(ipinfo.proxy);
|
|
32
36
|
}
|
|
33
|
-
|
|
37
|
+
// console.log(ipinfo)
|
|
34
38
|
// webgl指纹
|
|
35
39
|
const config = {
|
|
36
40
|
// 指纹随机数种子
|
|
@@ -38,7 +42,7 @@ export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<Br
|
|
|
38
42
|
geop: ipinfo?.latitude ? ipinfo?.latitude + "," + ipinfo?.longitude : "",
|
|
39
43
|
// 加上随机字符串 MSBYH29R
|
|
40
44
|
device_name: "PC" + faker.string.alphanumeric(8).toUpperCase(),
|
|
41
|
-
timezone: ipinfo?.timezone || "America/Los_Angeles",
|
|
45
|
+
timezone: ipinfo?.timezone || ipinfo?.timeZone || "America/Los_Angeles",
|
|
42
46
|
proxy: {
|
|
43
47
|
protocol: proxyInfo?.protocol.replace(":", "") || "http",
|
|
44
48
|
host: proxyInfo?.hostname || "127.0.0.1",
|
|
@@ -47,10 +51,11 @@ export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<Br
|
|
|
47
51
|
password: proxyInfo?.password || "",
|
|
48
52
|
},
|
|
49
53
|
mac: faker.internet.mac(),
|
|
50
|
-
scanPorts: "
|
|
54
|
+
scanPorts: "",
|
|
51
55
|
screenSize: rArrItem(PcScreen),
|
|
52
56
|
langs: ipinfo?.languages ?? "en-US,en",
|
|
53
57
|
}
|
|
58
|
+
// console.log(config)
|
|
54
59
|
const webGL = rArrItem(WindowsWebGL);
|
|
55
60
|
const webGLFP = {
|
|
56
61
|
"UNMASKED_VENDOR_WEBGL": webGL.name,
|
|
@@ -116,7 +121,9 @@ export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<Br
|
|
|
116
121
|
}
|
|
117
122
|
],
|
|
118
123
|
"AudioFp": rAutoDigitInt(980, config.fp),
|
|
119
|
-
"WebGLMark": rAutoDigitInt("5001", config.fp).toString(),
|
|
124
|
+
// "WebGLMark": rAutoDigitInt("5001", config.fp).toString(),
|
|
125
|
+
// "WebGLMark": config.fp.toString(),
|
|
126
|
+
"WebGLMark": "5001",
|
|
120
127
|
"StartTime": Math.floor(Date.now() / 1000),
|
|
121
128
|
"GeolocationSetting": "ask",
|
|
122
129
|
"FlashPluginSetting": "block",
|
|
@@ -125,7 +132,8 @@ export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<Br
|
|
|
125
132
|
"DisableBackgroundMode": true,
|
|
126
133
|
"DisableWebRTC": true,
|
|
127
134
|
"HardwareConcurrency": rArrItem(Cpus),
|
|
128
|
-
"DeviceMemory": rArrItem(Memorys),
|
|
135
|
+
// "DeviceMemory": rArrItem(Memorys),
|
|
136
|
+
"DeviceMemory": rArrItem([4, 8]),
|
|
129
137
|
"LoadExtensionErrorBox": false,
|
|
130
138
|
"ClientRectFp": rAutoDigitInt("5001", config.fp).toString(),
|
|
131
139
|
"ForceProcessExit": true,
|
|
@@ -184,11 +192,12 @@ export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<Br
|
|
|
184
192
|
"DynamicConfig": dynamicConfig_path,
|
|
185
193
|
"StaticConfig": staticConfig_path
|
|
186
194
|
}));
|
|
187
|
-
|
|
195
|
+
|
|
188
196
|
fs.writeFileSync(params_path, params)
|
|
189
197
|
screenSize = staticConfig.ScreenSize;
|
|
190
198
|
}
|
|
191
199
|
|
|
200
|
+
|
|
192
201
|
const args = [
|
|
193
202
|
"--disable-background-networking",
|
|
194
203
|
"--disable-client-side-phishing-detection",
|
|
@@ -223,6 +232,7 @@ export async function AdsBrowserStartMain(opts: BrowserStartOptions): Promise<Br
|
|
|
223
232
|
"--flag-switches-begin",
|
|
224
233
|
"--flag-switches-end",
|
|
225
234
|
"--load-extension=",
|
|
235
|
+
// "--timezone=" + (ipinfo?.timezone ?? "America/Los_Angeles"),
|
|
226
236
|
"--no-sandbox"
|
|
227
237
|
];
|
|
228
238
|
|
package/src/providers/base.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { makeUniqueBrowserConfig } from "../core/launcher";
|
|
1
|
+
import { makeUniqueBrowserConfig, getBrowserWebSocketUrl } from "../core/launcher";
|
|
2
2
|
import portfinder from 'portfinder';
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import * as cl from "chrome-launcher";
|
|
5
|
-
import { tryCatch, tryLoop } from "@tocha688/utils";
|
|
5
|
+
import { getIpInfo, startProxyServer, tryCatch, tryLoop, type ProxyServerResult } from "@tocha688/utils";
|
|
6
6
|
import type { BrowserResult, BrowserStartOptions } from "../types";
|
|
7
|
+
import { chromium } from "patchright-core";
|
|
7
8
|
|
|
8
9
|
export interface BrowserLaunchConfig {
|
|
9
10
|
prepare: (userDataDir: string, devtoolsPort: number) => Promise<{
|
|
@@ -13,6 +14,9 @@ export interface BrowserLaunchConfig {
|
|
|
13
14
|
}>;
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
16
20
|
export async function launchBrowserBase(
|
|
17
21
|
opts: BrowserStartOptions,
|
|
18
22
|
config: BrowserLaunchConfig
|
|
@@ -46,17 +50,25 @@ export async function launchBrowserBase(
|
|
|
46
50
|
chromeFlags: args,
|
|
47
51
|
startingUrl: hookResult.startingUrl || "about:blank"
|
|
48
52
|
});
|
|
53
|
+
const wsUrl = await getBrowserWebSocketUrl(brw.port)
|
|
49
54
|
|
|
50
55
|
return {
|
|
51
56
|
userDataDir,
|
|
52
57
|
port: brw.port,
|
|
53
58
|
browser: brw,
|
|
59
|
+
wsUrl,
|
|
54
60
|
close: async () => {
|
|
55
61
|
await tryCatch(async () => await brw.kill());
|
|
56
62
|
if (hookResult?.cleanup) {
|
|
57
63
|
await tryCatch(hookResult.cleanup);
|
|
58
64
|
}
|
|
59
65
|
await tryLoop(async () => fs.rmSync(userDataDir, { recursive: true, force: true }));
|
|
66
|
+
},
|
|
67
|
+
async mainPage() {
|
|
68
|
+
const browser = await chromium.connectOverCDP(wsUrl)
|
|
69
|
+
const ctx = browser.contexts()?.[0] ?? await browser.newContext()
|
|
70
|
+
const page = ctx?.pages()?.[0] ?? await ctx.newPage()
|
|
71
|
+
return page;
|
|
60
72
|
}
|
|
61
73
|
};
|
|
62
74
|
} catch (e) {
|
package/src/providers/local.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { BrowserStartOptions, BrowserResult } from "../types";
|
|
2
2
|
import * as cl from "chrome-launcher";
|
|
3
|
+
import { getBrowserWebSocketUrl } from "../core/launcher";
|
|
4
|
+
import { chromium } from "patchright-core";
|
|
3
5
|
|
|
4
6
|
export async function startLocalBrowser(opts: BrowserStartOptions): Promise<BrowserResult> {
|
|
5
|
-
const { chromePath, headless = false,
|
|
7
|
+
const { chromePath, headless = false, userDataDir } = opts;
|
|
6
8
|
|
|
7
9
|
const chromeFlags = [
|
|
8
10
|
"--disable-blink-features=AutomationControlled",
|
|
@@ -14,18 +16,25 @@ export async function startLocalBrowser(opts: BrowserStartOptions): Promise<Brow
|
|
|
14
16
|
chromeFlags.push("--headless");
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
// Proxy handling would go here if needed, but usually handled by Playwright context or upstream
|
|
18
|
-
|
|
19
19
|
const browser = await cl.launch({
|
|
20
20
|
chromePath,
|
|
21
21
|
chromeFlags,
|
|
22
22
|
userDataDir,
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
+
const wsUrl = await getBrowserWebSocketUrl(browser.port);
|
|
26
|
+
|
|
25
27
|
return {
|
|
26
28
|
userDataDir: userDataDir || "",
|
|
27
29
|
port: browser.port,
|
|
28
30
|
browser: browser,
|
|
31
|
+
wsUrl,
|
|
29
32
|
close: async () => await browser.kill(),
|
|
33
|
+
async mainPage() {
|
|
34
|
+
const brw = await chromium.connectOverCDP(wsUrl)
|
|
35
|
+
const ctx = brw.contexts()?.[0] ?? await brw.newContext()
|
|
36
|
+
const page = ctx?.pages()?.[0] ?? await ctx.newPage()
|
|
37
|
+
return page;
|
|
38
|
+
}
|
|
30
39
|
};
|
|
31
40
|
}
|