arn-browser 0.1.6 → 0.1.8
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/bin/cli.js +43 -0
- package/bin/install.js +420 -0
- package/package.json +8 -3
- package/src/index.d.ts +16 -6
- package/src/index.js +7 -4
- package/src/utility/{multilogin_token_manager.js → mlx_token.js} +32 -43
- package/src/utility/{launchBrowser.d.ts → playwright/pwLaunch.d.ts} +15 -7
- package/src/utility/{launchBrowser.js → playwright/pwLaunch.js} +61 -30
- package/src/{all_routes/routeWithSuperagent.d.ts → utility/playwright/routes/pwRoute.d.ts} +4 -4
- package/src/{all_routes/routeWithSuperagent.js → utility/playwright/routes/pwRoute.js} +2 -2
- package/src/utility/proxy-utility/proxy-chain.js +4 -3
- package/src/utility/proxy-utility/proxy-helper.js +1 -1
- package/src/utility/puppeteer/ppLaunch.d.ts +199 -0
- package/src/utility/puppeteer/ppLaunch.js +723 -0
- package/src/utility/puppeteer/routes/ppRoute.d.ts +64 -0
- package/src/utility/puppeteer/routes/ppRoute.js +326 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/HumanCursor.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/bezier.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/index.d.ts +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/index.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/randomizer.js +0 -0
- /package/src/{human-cursor → utility/playwright/human-cursor}/tweening.js +0 -0
- /package/src/utility/{playwright-helper.d.ts → playwright/playwright-helper.d.ts} +0 -0
- /package/src/utility/{playwright-helper.js → playwright/playwright-helper.js} +0 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Browser, Page } from "puppeteer-core";
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// ROUTING & CACHE TYPES
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Options for the ppRoute function.
|
|
9
|
+
*/
|
|
10
|
+
export interface PpRouteOptions {
|
|
11
|
+
/** Puppeteer Page */
|
|
12
|
+
page?: Page | null;
|
|
13
|
+
|
|
14
|
+
/** Log successful requests to console */
|
|
15
|
+
successLogs?: boolean;
|
|
16
|
+
|
|
17
|
+
/** Log failed requests to console */
|
|
18
|
+
errorLogs?: boolean;
|
|
19
|
+
|
|
20
|
+
/** Block Ad requests (Ghostery engine) */
|
|
21
|
+
blockAds?: boolean;
|
|
22
|
+
|
|
23
|
+
/** Block Image requests */
|
|
24
|
+
blockImage?: boolean;
|
|
25
|
+
|
|
26
|
+
/** Intercept requests using Superagent (bypassing browser stack for intercepted resources) */
|
|
27
|
+
useGot?: boolean;
|
|
28
|
+
|
|
29
|
+
/** Use full URL for cache key (otherwise origin+pathname) */
|
|
30
|
+
useFullUrl?: boolean;
|
|
31
|
+
|
|
32
|
+
/** Enable caching for requests */
|
|
33
|
+
useCache?: boolean;
|
|
34
|
+
|
|
35
|
+
/** Data object for Doublelist message interception (POST logic) */
|
|
36
|
+
m4w_send_on_post?: Record<string, any> | null;
|
|
37
|
+
|
|
38
|
+
/** Data object for Doublelist message interception (Message logic) */
|
|
39
|
+
m4w_send_on_message?: Record<string, any> | null;
|
|
40
|
+
|
|
41
|
+
/** * Array of URL substrings. If a URL contains any of these strings,
|
|
42
|
+
* it will NOT be blocked, even if blockImage is true.
|
|
43
|
+
* Default includes: ["cdn-cgi/challenge-platform"]
|
|
44
|
+
*/
|
|
45
|
+
allowImagePatterns?: string[];
|
|
46
|
+
|
|
47
|
+
/** * Array of URL substrings. If a URL contains any of these strings,
|
|
48
|
+
* the custom Superagent/Got fetch will be skipped, and the default
|
|
49
|
+
* Playwright network stack will be used instead.
|
|
50
|
+
*/
|
|
51
|
+
skipGotPatterns?: string[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sets up request interception, caching, and ad-blocking on a Puppeteer page.
|
|
56
|
+
*/
|
|
57
|
+
export function ppRoute(options: PpRouteOptions): Promise<void>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Starts logging cache statistics to the console at a set interval.
|
|
61
|
+
* @param log_cache Optional NodeCache instance (uses global by default)
|
|
62
|
+
* @param interval Interval in seconds (default: 10)
|
|
63
|
+
*/
|
|
64
|
+
export function ppCacheLogs(log_cache?: any, interval?: number): void;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// Import required libraries
|
|
2
|
+
import superagent from "superagent";
|
|
3
|
+
import { FiltersEngine, Request } from "@ghostery/adblocker";
|
|
4
|
+
import fetch from "node-fetch";
|
|
5
|
+
import NodeCache from "node-cache";
|
|
6
|
+
|
|
7
|
+
let AdBlockEngine;
|
|
8
|
+
|
|
9
|
+
// Create a NodeCache instance for caching responses
|
|
10
|
+
// This helps reduce bandwidth usage and speed up repeated requests
|
|
11
|
+
const globalCache = new NodeCache({
|
|
12
|
+
stdTTL: 3600, // Standard Time To Live in seconds for every generated cache element
|
|
13
|
+
checkperiod: 600, // The period in seconds for the automatic delete check interval
|
|
14
|
+
useClones: false, // Don't use clones for performance reasons
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function to start cache logging
|
|
19
|
+
* This outputs cache statistics (hits, misses, memory usage) to the console at a set interval.
|
|
20
|
+
* @param {Object} log_cache - The cache instance to monitor
|
|
21
|
+
* @param {number} interval - The interval in seconds between log outputs
|
|
22
|
+
*/
|
|
23
|
+
export function ppCacheLogs(log_cache = globalCache, interval = 10) {
|
|
24
|
+
if (interval > 0) {
|
|
25
|
+
setInterval(() => {
|
|
26
|
+
const stats = log_cache.stats;
|
|
27
|
+
const ksizeMB = (stats.ksize / (1024 * 1024)).toFixed(2);
|
|
28
|
+
const vsizeMB = (stats.vsize / (1024 * 1024)).toFixed(2);
|
|
29
|
+
const totalRequests = stats.hits + stats.misses;
|
|
30
|
+
const hitPercentage = totalRequests > 0 ? ((stats.hits / totalRequests) * 100).toFixed(2) : "0.00";
|
|
31
|
+
const missPercentage = totalRequests > 0 ? ((stats.misses / totalRequests) * 100).toFixed(2) : "0.00";
|
|
32
|
+
|
|
33
|
+
console.warn(
|
|
34
|
+
`Cache Stats: Success: ${stats.hits} (${hitPercentage}%), Misses: ${stats.misses} (${missPercentage}%), Keys: ${stats.keys}, Key: ${ksizeMB} MB, Value: ${vsizeMB} MB`
|
|
35
|
+
);
|
|
36
|
+
}, interval * 1000);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Function to fetch resources using Superagent library with optional caching.
|
|
42
|
+
* This mimics the browser's request but handles it in Node.js to allow caching or header manipulation.
|
|
43
|
+
* @param {boolean} useCache - Whether to use caching
|
|
44
|
+
* @param {string} url - The URL to fetch
|
|
45
|
+
* @param {Object} requestHeaders - Request headers from the original request
|
|
46
|
+
* @param {string} method - HTTP method (GET, POST, etc.)
|
|
47
|
+
* @param {boolean} useFullUrl - Whether to use the full URL as cache key or just origin+path
|
|
48
|
+
* @param {boolean} successLogs - Whether to log successful requests
|
|
49
|
+
* @param {boolean} errorLogs - Whether to log error requests
|
|
50
|
+
* @returns {Promise<Object>} - The response object containing status, headers, and body
|
|
51
|
+
*/
|
|
52
|
+
async function fetchWithClient(useCache, url, requestHeaders, method, useFullUrl, successLogs, errorLogs) {
|
|
53
|
+
// Determine the cache key based on configuration
|
|
54
|
+
let mainUrl = new URL(url).origin + new URL(url).pathname;
|
|
55
|
+
if (useFullUrl) {
|
|
56
|
+
mainUrl = url;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if the response is cached
|
|
60
|
+
if (useCache) {
|
|
61
|
+
const cachedResponse = globalCache.get(mainUrl);
|
|
62
|
+
if (cachedResponse) {
|
|
63
|
+
if (successLogs) console.log(`Serving from globalCache: ${mainUrl}`);
|
|
64
|
+
return cachedResponse;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Fetch the resource using superagent
|
|
70
|
+
// buffer(true) ensures we get the raw binary data (essential for images/fonts)
|
|
71
|
+
const response = await superagent(method, url).set(requestHeaders).buffer(true);
|
|
72
|
+
|
|
73
|
+
// Determine the correct body type (Buffer for binary, text for others)
|
|
74
|
+
const responseBody = response.body instanceof Buffer ? response.body : response.text;
|
|
75
|
+
|
|
76
|
+
// Save to cache
|
|
77
|
+
globalCache.set(mainUrl, {
|
|
78
|
+
status: response.status,
|
|
79
|
+
headers: response.headers,
|
|
80
|
+
body: responseBody,
|
|
81
|
+
});
|
|
82
|
+
if (successLogs) console.log(`Success (cached): ${mainUrl}`);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
status: response.status,
|
|
86
|
+
headers: response.headers,
|
|
87
|
+
body: responseBody,
|
|
88
|
+
};
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (errorLogs) console.error(`Failed to fetch: ${url}`, error);
|
|
91
|
+
// We return undefined on error, which signals the route handler to fall back to normal request
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Main function to set up routing, ad blocking, and request interception in Playwright.
|
|
97
|
+
* @param {Object} options - Configuration options
|
|
98
|
+
* @param {Object} options.context - Playwright context (optional, one is required)
|
|
99
|
+
* @param {Object} options.page - Playwright page (optional, one is required)
|
|
100
|
+
* @param {boolean} options.successLogs - Enable logging for successful fetches
|
|
101
|
+
* @param {boolean} options.errorLogs - Enable logging for failed fetches
|
|
102
|
+
* @param {boolean} options.blockImage - Enable global image blocking
|
|
103
|
+
* @param {boolean} options.blockAds - Enable Ghostery ad blocking
|
|
104
|
+
* @param {boolean} options.useGot - Enable custom fetching via Superagent (bypassing browser network stack for intercepted types)
|
|
105
|
+
* @param {boolean} options.useFullUrl - Use full URL for cache keys
|
|
106
|
+
* @param {boolean} options.useCache - Enable caching
|
|
107
|
+
* @param {Object} options.m4w_send_on_post - Custom handler data for Doublelist posts
|
|
108
|
+
* @param {Object} options.m4w_send_on_message - Custom handler data for Doublelist messages
|
|
109
|
+
* @param {Array<string>} options.allowImagePatterns - Array of strings/patterns. If a URL contains any of these, it will NOT be blocked even if blockImage is true.
|
|
110
|
+
* @param {Array<string>} options.skipGotPatterns - Array of strings/patterns. If a URL contains any of these, it will skip the custom Superagent fetch.
|
|
111
|
+
*/
|
|
112
|
+
export async function ppRoute({
|
|
113
|
+
page = null,
|
|
114
|
+
successLogs = false,
|
|
115
|
+
errorLogs = false,
|
|
116
|
+
blockAds = true,
|
|
117
|
+
blockImage = true,
|
|
118
|
+
useGot = true,
|
|
119
|
+
useFullUrl = true,
|
|
120
|
+
useCache = true,
|
|
121
|
+
m4w_send_on_post = null,
|
|
122
|
+
m4w_send_on_message = null,
|
|
123
|
+
allowImagePatterns = [], // Default empty, merged inside
|
|
124
|
+
skipGotPatterns = [], // Default empty, merged inside
|
|
125
|
+
}) {
|
|
126
|
+
// Validation: Ensure we have a page
|
|
127
|
+
if (!page) {
|
|
128
|
+
throw new Error("A Puppeteer page must be provided.");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- SETUP: Merge Defaults for allowImagePatterns ---
|
|
132
|
+
// Always allow Cloudflare challenge platform images
|
|
133
|
+
const defaultAllowedPatterns = ["cdn-cgi/challenge-platform"];
|
|
134
|
+
const finalImagePatterns = [...defaultAllowedPatterns, ...allowImagePatterns];
|
|
135
|
+
|
|
136
|
+
// --- SETUP: Merge Defaults for skipGotPatterns ---
|
|
137
|
+
// Always skip custom fetch for Cloudflare challenges (let browser handle it)
|
|
138
|
+
const defaultSkipPatterns = [];
|
|
139
|
+
const finalSkipPatterns = [...defaultSkipPatterns, ...skipGotPatterns];
|
|
140
|
+
|
|
141
|
+
// Initialize ad blocking AdBlockEngine if enabled and not already loaded
|
|
142
|
+
if (blockAds && !AdBlockEngine) {
|
|
143
|
+
// console.log("Initializing AdBlockEngine..............................");
|
|
144
|
+
AdBlockEngine = await FiltersEngine.fromPrebuiltAdsAndTracking(fetch);
|
|
145
|
+
if (!AdBlockEngine) {
|
|
146
|
+
throw new Error("Failed to initialize AdBlockEngine.");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Define resource types to intercept for custom fetching (useGot)
|
|
151
|
+
const interceptedResourceTypes = ["stylesheet", "script", "font"];
|
|
152
|
+
|
|
153
|
+
// If images are NOT blocked, we generally want to intercept/cache them too.
|
|
154
|
+
if (!blockImage) {
|
|
155
|
+
interceptedResourceTypes.push("image");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Enable request interception in Puppeteer
|
|
159
|
+
await page.setRequestInterception(true);
|
|
160
|
+
|
|
161
|
+
// Set up the global route interception
|
|
162
|
+
page.on("request", async (request) => {
|
|
163
|
+
// Puppeteer best practice: do not handle request if already handled
|
|
164
|
+
if (request.isInterceptResolutionHandled()) return;
|
|
165
|
+
|
|
166
|
+
const url = request.url();
|
|
167
|
+
const method = request.method();
|
|
168
|
+
const resourceType = request.resourceType();
|
|
169
|
+
|
|
170
|
+
// ============================================================
|
|
171
|
+
// Group 1: Image Blocking
|
|
172
|
+
// ============================================================
|
|
173
|
+
if (blockImage && resourceType === "image") {
|
|
174
|
+
// Check against the merged list (defaults + user input)
|
|
175
|
+
const isAllowed = finalImagePatterns.some((pattern) => url.includes(pattern));
|
|
176
|
+
|
|
177
|
+
if (!isAllowed) {
|
|
178
|
+
await request.abort();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ============================================================
|
|
184
|
+
// Group 2: Ad Blocking
|
|
185
|
+
// ============================================================
|
|
186
|
+
if (blockAds && AdBlockEngine) {
|
|
187
|
+
const adsBlockResult = AdBlockEngine.match(
|
|
188
|
+
Request.fromRawDetails({
|
|
189
|
+
url: url,
|
|
190
|
+
type: resourceType,
|
|
191
|
+
})
|
|
192
|
+
);
|
|
193
|
+
if (adsBlockResult.match) {
|
|
194
|
+
await request.abort();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ============================================================
|
|
200
|
+
// Group 3: Specific Script Blocking (Cookie Law)
|
|
201
|
+
// ============================================================
|
|
202
|
+
if (url.includes("cdn.cookielaw.org")) {
|
|
203
|
+
await request.abort();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ============================================================
|
|
208
|
+
// Group 4: m4w_send_on_message Handling (Doublelist API)
|
|
209
|
+
// ============================================================
|
|
210
|
+
if (m4w_send_on_message && url.includes("api.doublelist.com/api/messages") && method === "POST") {
|
|
211
|
+
const headers = request.headers();
|
|
212
|
+
const postData = request.postData();
|
|
213
|
+
const urlParams = new URLSearchParams(postData);
|
|
214
|
+
const messageJSON = decodeURIComponent(urlParams.get("messageJSON"));
|
|
215
|
+
const parsedMessageJSON = JSON.parse(messageJSON);
|
|
216
|
+
|
|
217
|
+
m4w_send_on_message.sender_id = parsedMessageJSON.sender_id;
|
|
218
|
+
const authorizationHeader = headers["authorization"];
|
|
219
|
+
m4w_send_on_message.token_value = authorizationHeader.replace("Bearer ", "");
|
|
220
|
+
|
|
221
|
+
console.log("posts_send_message Blocked (Data Extracted)");
|
|
222
|
+
await request.abort();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (m4w_send_on_message && url.includes("https://doublelist.com/posts_send_message/")) {
|
|
227
|
+
console.log("posts_send_message Blocked");
|
|
228
|
+
await request.abort();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ============================================================
|
|
233
|
+
// Group 5: m4w_send_on_post Handling (Doublelist API)
|
|
234
|
+
// ============================================================
|
|
235
|
+
if (m4w_send_on_post && url.includes("api.doublelist.com/api/messages") && method === "POST") {
|
|
236
|
+
if (
|
|
237
|
+
m4w_send_on_post.current_post_id &&
|
|
238
|
+
m4w_send_on_post.current_post_id !== m4w_send_on_post.last_post_id &&
|
|
239
|
+
m4w_send_on_post.current_send_to &&
|
|
240
|
+
m4w_send_on_post.current_send_to != m4w_send_on_post.last_send_to
|
|
241
|
+
) {
|
|
242
|
+
m4w_send_on_post.last_send_to = m4w_send_on_post.current_send_to;
|
|
243
|
+
m4w_send_on_post.last_post_id = m4w_send_on_post.current_post_id;
|
|
244
|
+
|
|
245
|
+
let postData = request.postData();
|
|
246
|
+
const urlParams = new URLSearchParams(postData);
|
|
247
|
+
const messageJSON = decodeURIComponent(urlParams.get("messageJSON"));
|
|
248
|
+
let parsedMessageJSON = JSON.parse(messageJSON);
|
|
249
|
+
|
|
250
|
+
parsedMessageJSON.chat_user = m4w_send_on_post.current_send_to;
|
|
251
|
+
parsedMessageJSON.post_id = m4w_send_on_post.current_post_id;
|
|
252
|
+
|
|
253
|
+
urlParams.set("messageJSON", JSON.stringify(parsedMessageJSON));
|
|
254
|
+
const final_postData = urlParams.toString();
|
|
255
|
+
|
|
256
|
+
await request.continue({
|
|
257
|
+
postData: final_postData,
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
} else {
|
|
261
|
+
console.log("send Message route abort (Duplicate or missing data)");
|
|
262
|
+
await request.abort();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (m4w_send_on_post && url.includes("https://doublelist.com/posts_send_message/")) {
|
|
268
|
+
console.log("posts_send_message Blocked");
|
|
269
|
+
await request.abort();
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (m4w_send_on_post && url.includes("doublelist.com/messages/")) {
|
|
274
|
+
console.log("doublelist.com/messages/ Redirected");
|
|
275
|
+
await request.respond({
|
|
276
|
+
status: 302,
|
|
277
|
+
headers: {
|
|
278
|
+
location: "https://httpbin.org/ip",
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ============================================================
|
|
285
|
+
// Group 6: Resource Interception (Custom Fetch/Cache)
|
|
286
|
+
// ============================================================
|
|
287
|
+
if (useGot && interceptedResourceTypes.includes(resourceType)) {
|
|
288
|
+
// Check against the merged list (defaults + user input)
|
|
289
|
+
const shouldSkipGot = finalSkipPatterns.some((pattern) => url.includes(pattern));
|
|
290
|
+
|
|
291
|
+
if (!shouldSkipGot) {
|
|
292
|
+
const requestHeaders = request.headers();
|
|
293
|
+
const requestMethod = request.method();
|
|
294
|
+
|
|
295
|
+
const response = await fetchWithClient(
|
|
296
|
+
useCache,
|
|
297
|
+
url,
|
|
298
|
+
requestHeaders,
|
|
299
|
+
requestMethod,
|
|
300
|
+
useFullUrl,
|
|
301
|
+
successLogs,
|
|
302
|
+
errorLogs
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
if (response) {
|
|
306
|
+
await request.respond({
|
|
307
|
+
status: response.status,
|
|
308
|
+
headers: response.headers,
|
|
309
|
+
body: response.body,
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
} else {
|
|
313
|
+
if (errorLogs) console.log("Continuing with normal request (fetchWithClient returned null):", url);
|
|
314
|
+
await request.continue();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ============================================================
|
|
321
|
+
// Default: Continue with normal request
|
|
322
|
+
// ============================================================
|
|
323
|
+
await request.continue();
|
|
324
|
+
return;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|