real-browser-mcp-server 1.3.4 → 1.4.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/README.md +96 -9
- package/dist/lib/cjs/index.d.ts +2 -0
- package/dist/lib/cjs/index.d.ts.map +1 -0
- package/dist/lib/cjs/index.js +386 -0
- package/dist/lib/cjs/index.js.map +1 -0
- package/dist/lib/cjs/module/pageController.d.ts +2 -0
- package/dist/lib/cjs/module/pageController.d.ts.map +1 -0
- package/{lib → dist/lib}/cjs/module/pageController.js +28 -29
- package/dist/lib/cjs/module/pageController.js.map +1 -0
- package/dist/lib/cjs/module/turnstile.d.ts +2 -0
- package/dist/lib/cjs/module/turnstile.d.ts.map +1 -0
- package/{lib → dist/lib}/cjs/module/turnstile.js +24 -12
- package/dist/lib/cjs/module/turnstile.js.map +1 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +118 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mcp/handlers/browser.d.ts +30 -0
- package/dist/src/mcp/handlers/browser.d.ts.map +1 -0
- package/dist/src/mcp/handlers/browser.js +232 -0
- package/dist/src/mcp/handlers/browser.js.map +1 -0
- package/dist/src/mcp/handlers/dom.d.ts +149 -0
- package/dist/src/mcp/handlers/dom.d.ts.map +1 -0
- package/dist/src/mcp/handlers/dom.js +577 -0
- package/dist/src/mcp/handlers/dom.js.map +1 -0
- package/dist/src/mcp/handlers/extract.d.ts +59 -0
- package/dist/src/mcp/handlers/extract.d.ts.map +1 -0
- package/dist/src/mcp/handlers/extract.js +455 -0
- package/dist/src/mcp/handlers/extract.js.map +1 -0
- package/dist/src/mcp/handlers/form-handlers.d.ts +9 -0
- package/dist/src/mcp/handlers/form-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/form-handlers.js +56 -0
- package/dist/src/mcp/handlers/form-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/helpers.d.ts +47 -0
- package/dist/src/mcp/handlers/helpers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/helpers.js +515 -0
- package/dist/src/mcp/handlers/helpers.js.map +1 -0
- package/dist/src/mcp/handlers/index.d.ts +6 -0
- package/dist/src/mcp/handlers/index.d.ts.map +1 -0
- package/dist/src/mcp/handlers/index.js +61 -0
- package/dist/src/mcp/handlers/index.js.map +1 -0
- package/dist/src/mcp/handlers/media-handlers.d.ts +10 -0
- package/dist/src/mcp/handlers/media-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/media-handlers.js +535 -0
- package/dist/src/mcp/handlers/media-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/network.d.ts +147 -0
- package/dist/src/mcp/handlers/network.d.ts.map +1 -0
- package/dist/src/mcp/handlers/network.js +1135 -0
- package/dist/src/mcp/handlers/network.js.map +1 -0
- package/dist/src/mcp/handlers/state.d.ts +34 -0
- package/dist/src/mcp/handlers/state.d.ts.map +1 -0
- package/dist/src/mcp/handlers/state.js +226 -0
- package/dist/src/mcp/handlers/state.js.map +1 -0
- package/dist/src/mcp/handlers/utility-handlers.d.ts +167 -0
- package/dist/src/mcp/handlers/utility-handlers.d.ts.map +1 -0
- package/dist/src/mcp/handlers/utility-handlers.js +280 -0
- package/dist/src/mcp/handlers/utility-handlers.js.map +1 -0
- package/dist/src/mcp/handlers/vision.d.ts +127 -0
- package/dist/src/mcp/handlers/vision.d.ts.map +1 -0
- package/dist/src/mcp/handlers/vision.js +549 -0
- package/dist/src/mcp/handlers/vision.js.map +1 -0
- package/dist/src/mcp/index.d.ts +3 -0
- package/dist/src/mcp/index.d.ts.map +1 -0
- package/dist/src/mcp/index.js +166 -0
- package/dist/src/mcp/index.js.map +1 -0
- package/dist/src/mcp/server.d.ts +2 -0
- package/dist/src/mcp/server.d.ts.map +1 -0
- package/dist/src/mcp/server.js +117 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/mcp/tools.d.ts +8 -0
- package/dist/src/mcp/tools.d.ts.map +1 -0
- package/{src → dist/src}/mcp/tools.js +12 -11
- package/dist/src/mcp/tools.js.map +1 -0
- package/dist/src/shared/cache-manager.d.ts +80 -0
- package/dist/src/shared/cache-manager.d.ts.map +1 -0
- package/dist/src/shared/cache-manager.js +221 -0
- package/dist/src/shared/cache-manager.js.map +1 -0
- package/dist/src/shared/tools.d.ts +2 -0
- package/dist/src/shared/tools.d.ts.map +1 -0
- package/dist/src/shared/tools.js +606 -0
- package/dist/src/shared/tools.js.map +1 -0
- package/dist/src/types.d.ts +376 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/test/cjs/test.d.ts +11 -0
- package/dist/test/cjs/test.d.ts.map +1 -0
- package/dist/test/cjs/test.js +289 -0
- package/dist/test/cjs/test.js.map +1 -0
- package/dist/test/mcp/smoke-test.d.ts +29 -0
- package/dist/test/mcp/smoke-test.d.ts.map +1 -0
- package/dist/test/mcp/smoke-test.js +132 -0
- package/dist/test/mcp/smoke-test.js.map +1 -0
- package/lib/esm/index.mjs +229 -184
- package/lib/esm/module/pageController.mjs +21 -18
- package/lib/esm/module/turnstile.mjs +7 -0
- package/package.json +25 -16
- package/typings.d.ts +7 -1
- package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
- package/.github/SETUP.md +0 -111
- package/.github/workflows/publish.yml +0 -135
- package/Dockerfile +0 -79
- package/lib/cjs/adblocker.bin +0 -0
- package/lib/cjs/index.js +0 -352
- package/src/ai/action-parser.js +0 -274
- package/src/ai/core.js +0 -378
- package/src/ai/element-finder.js +0 -466
- package/src/ai/index.js +0 -82
- package/src/ai/page-analyzer.js +0 -304
- package/src/ai/selector-healer.js +0 -236
- package/src/index.js +0 -121
- package/src/mcp/handlers.js +0 -5071
- package/src/mcp/index.js +0 -190
- package/src/mcp/server.js +0 -144
- package/src/shared/tools.js +0 -618
- package/test/cjs/test.js +0 -267
- package/test/esm/package.json +0 -13
- package/test/esm/test.js +0 -235
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.globalCache = exports.getHeadlessFromEnv = exports.notifyProgress = exports.setProgressCallback = exports.requireBrowser = exports.getState = exports.handlers = void 0;
|
|
4
|
+
exports.executeTool = executeTool;
|
|
5
|
+
exports.cleanup = cleanup;
|
|
6
|
+
// Handler aggregation hub
|
|
7
|
+
const browser_1 = require("./browser");
|
|
8
|
+
const dom_1 = require("./dom");
|
|
9
|
+
const network_1 = require("./network");
|
|
10
|
+
const vision_1 = require("./vision");
|
|
11
|
+
const extract_1 = require("./extract");
|
|
12
|
+
const helpers_1 = require("./helpers");
|
|
13
|
+
const utility_handlers_1 = require("./utility-handlers");
|
|
14
|
+
const media_handlers_1 = require("./media-handlers");
|
|
15
|
+
const form_handlers_1 = require("./form-handlers");
|
|
16
|
+
const state_1 = require("./state");
|
|
17
|
+
Object.defineProperty(exports, "setProgressCallback", { enumerable: true, get: function () { return state_1.setProgressCallback; } });
|
|
18
|
+
Object.defineProperty(exports, "notifyProgress", { enumerable: true, get: function () { return state_1.notifyProgress; } });
|
|
19
|
+
Object.defineProperty(exports, "getHeadlessFromEnv", { enumerable: true, get: function () { return state_1.getHeadlessFromEnv; } });
|
|
20
|
+
Object.defineProperty(exports, "getState", { enumerable: true, get: function () { return state_1.getState; } });
|
|
21
|
+
Object.defineProperty(exports, "requireBrowser", { enumerable: true, get: function () { return state_1.requireBrowser; } });
|
|
22
|
+
Object.defineProperty(exports, "globalCache", { enumerable: true, get: function () { return state_1.globalCache; } });
|
|
23
|
+
exports.handlers = {
|
|
24
|
+
...browser_1.browserHandlers,
|
|
25
|
+
...dom_1.domHandlers,
|
|
26
|
+
...network_1.networkHandlers,
|
|
27
|
+
...vision_1.visionHandlers,
|
|
28
|
+
...extract_1.extractHandlers,
|
|
29
|
+
...helpers_1.helpersHandlers,
|
|
30
|
+
...utility_handlers_1.utilityHandlers,
|
|
31
|
+
...media_handlers_1.mediaHandlers,
|
|
32
|
+
...form_handlers_1.formHandlers
|
|
33
|
+
};
|
|
34
|
+
async function executeTool(name, args = {}) {
|
|
35
|
+
if (exports.handlers[name]) {
|
|
36
|
+
try {
|
|
37
|
+
return await exports.handlers[name](args);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return { success: false, error: error.message };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { success: false, error: `Tool ${name} not implemented` };
|
|
44
|
+
}
|
|
45
|
+
async function cleanup() {
|
|
46
|
+
if (state_1.state.browserInstance) {
|
|
47
|
+
try {
|
|
48
|
+
await state_1.state.browserInstance.close();
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
if (typeof state_1.state.browserInstance.process === 'function') {
|
|
52
|
+
state_1.state.browserInstance.process()?.kill('SIGKILL');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
state_1.state.browserInstance = null;
|
|
56
|
+
state_1.state.pageInstance = null;
|
|
57
|
+
state_1.state.blockerInstance = null;
|
|
58
|
+
state_1.state.setupPageFn = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/mcp/handlers/index.ts"],"names":[],"mappings":";;;AAuBA,kCASC;AAED,0BAcC;AAhDD,0BAA0B;AAC1B,uCAA4C;AAC5C,+BAAoC;AACpC,uCAA4C;AAC5C,qCAA0C;AAC1C,uCAA4C;AAC5C,uCAA4C;AAC5C,yDAAqD;AACrD,qDAAiD;AACjD,mDAA+C;AAC/C,mCAAgI;AAwC7F,oGAxCnB,2BAAmB,OAwCmB;AAAE,+FAxCnB,sBAAc,OAwCmB;AAAE,mGAxCnB,0BAAkB,OAwCmB;AAAjF,yFAxCgE,gBAAQ,OAwChE;AAAE,+FAxCgE,sBAAc,OAwChE;AAA2D,4FAxCO,mBAAW,OAwCP;AAvC1F,QAAA,QAAQ,GAAQ;IAC3B,GAAG,yBAAe;IAClB,GAAG,iBAAW;IACd,GAAG,yBAAe;IAClB,GAAG,uBAAc;IACjB,GAAG,yBAAe;IAClB,GAAG,yBAAe;IAClB,GAAG,kCAAe;IAClB,GAAG,8BAAa;IAChB,GAAG,4BAAY;CAChB,CAAC;AAEK,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,OAAY,EAAE;IAC5D,IAAI,gBAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC;YACH,OAAO,MAAM,gBAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QAClD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,IAAI,kBAAkB,EAAE,CAAC;AACnE,CAAC;AAEM,KAAK,UAAU,OAAO;IAC3B,IAAI,aAAK,CAAC,eAAe,EAAE,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,aAAK,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QACtC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,OAAQ,aAAK,CAAC,eAAuB,CAAC,OAAO,KAAK,UAAU,EAAE,CAAC;gBAChE,aAAK,CAAC,eAAuB,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC;QACD,aAAK,CAAC,eAAe,GAAG,IAAI,CAAC;QAC7B,aAAK,CAAC,YAAY,GAAG,IAAI,CAAC;QAC1B,aAAK,CAAC,eAAe,GAAG,IAAI,CAAC;QAC7B,aAAK,CAAC,WAAW,GAAG,IAAI,CAAC;IAC3B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const mediaHandlers: {
|
|
2
|
+
stream_extractor(params?: any): Promise<{
|
|
3
|
+
success: boolean;
|
|
4
|
+
streams: any;
|
|
5
|
+
totalCount: number;
|
|
6
|
+
}>;
|
|
7
|
+
player_api_hook(params?: any): Promise<any>;
|
|
8
|
+
media_extractor(params?: any): Promise<any>;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=media-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-handlers.d.ts","sourceRoot":"","sources":["../../../../src/mcp/handlers/media-handlers.ts"],"names":[],"mappings":"AAsRA,eAAO,MAAM,aAAa;8BACO,GAAG;;;;;6BA8CJ,GAAG;6BAuCH,GAAG;CAuOlC,CAAC"}
|
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mediaHandlers = void 0;
|
|
4
|
+
// Media handlers — Stream extraction, player control, media tools
|
|
5
|
+
const state_1 = require("./state");
|
|
6
|
+
// ═══════════════════════════════════════════════════════════════
|
|
7
|
+
// Media Handlers — Stream extraction, player control, media tools
|
|
8
|
+
// ═══════════════════════════════════════════════════════════════
|
|
9
|
+
/**
|
|
10
|
+
* Shared helper: Extract streams from a page/frame context.
|
|
11
|
+
* Used by both stream_extractor and media_extractor to avoid duplication.
|
|
12
|
+
*/
|
|
13
|
+
async function extractStreamsFromContext(context, contextName = 'main') {
|
|
14
|
+
return await context.evaluate(() => {
|
|
15
|
+
const result = { video: [], audio: [], hls: [], dash: [], download: [], embedded: [] };
|
|
16
|
+
// 1. Direct video/audio elements
|
|
17
|
+
document.querySelectorAll('video source, video').forEach(el => {
|
|
18
|
+
const src = el.src || el.getAttribute('src') || el.currentSrc;
|
|
19
|
+
if (src && src.startsWith('http'))
|
|
20
|
+
result.video.push({ src, type: el.type || 'video' });
|
|
21
|
+
});
|
|
22
|
+
document.querySelectorAll('audio source, audio').forEach(el => {
|
|
23
|
+
const src = el.src || el.getAttribute('src');
|
|
24
|
+
if (src && src.startsWith('http'))
|
|
25
|
+
result.audio.push({ src, type: el.type || 'audio' });
|
|
26
|
+
});
|
|
27
|
+
// 2. Script content analysis for HLS/DASH/MP4
|
|
28
|
+
const scripts = [...document.querySelectorAll('script')].map(s => s.textContent).join('\n');
|
|
29
|
+
const html = document.documentElement.innerHTML;
|
|
30
|
+
const combined = scripts + html;
|
|
31
|
+
// HLS streams
|
|
32
|
+
const hlsMatches = combined.match(/https?:\/\/[^\s"'<>]+\.m3u8[^\s"'<>]*/gi) || [];
|
|
33
|
+
result.hls = [...new Set(hlsMatches)].map(src => ({ src: src.replace(/\\"/g, ''), type: 'hls' }));
|
|
34
|
+
// DASH streams
|
|
35
|
+
const dashMatches = combined.match(/https?:\/\/[^\s"'<>]+\.mpd[^\s"'<>]*/gi) || [];
|
|
36
|
+
result.dash = [...new Set(dashMatches)].map(src => ({ src: src.replace(/\\"/g, ''), type: 'dash' }));
|
|
37
|
+
// Direct MP4/video links
|
|
38
|
+
const mp4Matches = combined.match(/https?:\/\/[^\s"'<>]+\.(mp4|mkv|avi|webm)[^\s"'<>]*/gi) || [];
|
|
39
|
+
result.download = [...new Set(mp4Matches)].map(src => ({ src: src.replace(/\\"/g, ''), type: 'direct' }));
|
|
40
|
+
// 3. Data attributes and hidden sources
|
|
41
|
+
document.querySelectorAll('[data-src], [data-video], [data-url], [data-file]').forEach(el => {
|
|
42
|
+
const dataSrc = el.dataset.src || el.dataset.video || el.dataset.url || el.dataset.file;
|
|
43
|
+
if (dataSrc && dataSrc.startsWith('http')) {
|
|
44
|
+
result.video.push({ src: dataSrc, type: 'data-attribute' });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
// 4. Embedded player iframes (just URLs, not content)
|
|
48
|
+
document.querySelectorAll('iframe[src]').forEach(el => {
|
|
49
|
+
const src = el.src;
|
|
50
|
+
if (src && src.startsWith('http')) {
|
|
51
|
+
result.embedded.push({ src, type: 'iframe' });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// 5. JWPlayer / VideoJS / Plyr sources
|
|
55
|
+
if (window.jwplayer) {
|
|
56
|
+
try {
|
|
57
|
+
const jw = window.jwplayer();
|
|
58
|
+
const playlist = jw.getPlaylist?.() || [];
|
|
59
|
+
playlist.forEach((item) => {
|
|
60
|
+
if (item.file)
|
|
61
|
+
result.video.push({ src: item.file, type: 'jwplayer' });
|
|
62
|
+
(item.sources || []).forEach((s) => {
|
|
63
|
+
if (s.file)
|
|
64
|
+
result.video.push({ src: s.file, type: 'jwplayer-source' });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (e) { }
|
|
69
|
+
}
|
|
70
|
+
if (window.player && window.player.src) {
|
|
71
|
+
try {
|
|
72
|
+
const src = typeof window.player.src === 'function' ? window.player.src() : window.player.src;
|
|
73
|
+
if (src)
|
|
74
|
+
result.video.push({ src, type: 'player-api' });
|
|
75
|
+
}
|
|
76
|
+
catch (e) { }
|
|
77
|
+
}
|
|
78
|
+
// 6. Look for common patterns
|
|
79
|
+
const patterns = [
|
|
80
|
+
/file\s*:\s*["']([^"']+)["']/gi,
|
|
81
|
+
/source\s*:\s*["']([^"']+)["']/gi,
|
|
82
|
+
/src\s*:\s*["']([^"']+\.(?:m3u8|mp4|mkv))["']/gi,
|
|
83
|
+
/url\s*:\s*["']([^"']+\.(?:m3u8|mp4))["']/gi,
|
|
84
|
+
/video_url\s*=\s*["']([^"']+)["']/gi,
|
|
85
|
+
/sources\s*:\s*\[([^\]]+)\]/gi
|
|
86
|
+
];
|
|
87
|
+
patterns.forEach(pattern => {
|
|
88
|
+
let match;
|
|
89
|
+
while ((match = pattern.exec(combined)) !== null) {
|
|
90
|
+
const url = match[1];
|
|
91
|
+
if (url && url.startsWith('http') && (url.includes('.mp4') || url.includes('.m3u8'))) {
|
|
92
|
+
result.download.push({ src: url, type: 'pattern-match' });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return result;
|
|
97
|
+
}).catch(() => ({ video: [], audio: [], hls: [], dash: [], download: [], embedded: [] }));
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Shared helper: Detect video player in a page/frame context.
|
|
101
|
+
* Used by both player_api_hook and media_extractor.
|
|
102
|
+
*/
|
|
103
|
+
async function detectPlayerInContext(context, actionOrParams, contextName = 'main') {
|
|
104
|
+
const action = typeof actionOrParams === 'string' ? actionOrParams : (actionOrParams?.action || 'info');
|
|
105
|
+
const playerType = typeof actionOrParams === 'object' ? actionOrParams?.playerType : 'auto';
|
|
106
|
+
return await context.evaluate(({ playerType, action }) => {
|
|
107
|
+
const result = { detected: false, type: null, sources: [], info: {} };
|
|
108
|
+
// 1. JWPlayer detection
|
|
109
|
+
if (window.jwplayer) {
|
|
110
|
+
try {
|
|
111
|
+
const jw = window.jwplayer();
|
|
112
|
+
if (jw) {
|
|
113
|
+
result.detected = true;
|
|
114
|
+
result.type = 'jwplayer';
|
|
115
|
+
result.info = {
|
|
116
|
+
duration: jw.getDuration?.(),
|
|
117
|
+
currentTime: jw.getPosition?.(),
|
|
118
|
+
volume: jw.getVolume?.(),
|
|
119
|
+
state: jw.getState?.()
|
|
120
|
+
};
|
|
121
|
+
if (action === 'sources') {
|
|
122
|
+
const playlist = jw.getPlaylist?.() || [];
|
|
123
|
+
playlist.forEach((item) => {
|
|
124
|
+
if (item.file)
|
|
125
|
+
result.sources.push({ src: item.file, type: 'jwplayer' });
|
|
126
|
+
(item.sources || []).forEach((s) => {
|
|
127
|
+
if (s.file)
|
|
128
|
+
result.sources.push({ src: s.file, type: 'jwplayer-source', label: s.label });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (action === 'play')
|
|
133
|
+
jw.play?.();
|
|
134
|
+
if (action === 'pause')
|
|
135
|
+
jw.pause?.();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (e) { }
|
|
139
|
+
}
|
|
140
|
+
// 2. Video.js detection
|
|
141
|
+
if (window.videojs && !result.detected) {
|
|
142
|
+
try {
|
|
143
|
+
const players = document.querySelectorAll('.video-js');
|
|
144
|
+
if (players.length > 0) {
|
|
145
|
+
const player = window.videojs(players[0].id || players[0]);
|
|
146
|
+
result.detected = true;
|
|
147
|
+
result.type = 'videojs';
|
|
148
|
+
result.info = {
|
|
149
|
+
duration: player.duration?.(),
|
|
150
|
+
currentTime: player.currentTime?.(),
|
|
151
|
+
volume: player.volume?.()
|
|
152
|
+
};
|
|
153
|
+
if (action === 'sources') {
|
|
154
|
+
const src = player.currentSrc?.();
|
|
155
|
+
if (src)
|
|
156
|
+
result.sources.push({ src, type: 'videojs' });
|
|
157
|
+
}
|
|
158
|
+
if (action === 'play')
|
|
159
|
+
player.play?.();
|
|
160
|
+
if (action === 'pause')
|
|
161
|
+
player.pause?.();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch (e) { }
|
|
165
|
+
}
|
|
166
|
+
// 3. Plyr detection
|
|
167
|
+
if (window.Plyr && !result.detected) {
|
|
168
|
+
try {
|
|
169
|
+
const plyrElements = document.querySelectorAll('.plyr');
|
|
170
|
+
if (plyrElements.length > 0 && plyrElements[0].plyr) {
|
|
171
|
+
const player = plyrElements[0].plyr;
|
|
172
|
+
result.detected = true;
|
|
173
|
+
result.type = 'plyr';
|
|
174
|
+
result.info = {
|
|
175
|
+
duration: player.duration,
|
|
176
|
+
currentTime: player.currentTime,
|
|
177
|
+
volume: player.volume
|
|
178
|
+
};
|
|
179
|
+
if (action === 'sources') {
|
|
180
|
+
const src = player.source;
|
|
181
|
+
if (src)
|
|
182
|
+
result.sources.push({ src, type: 'plyr' });
|
|
183
|
+
}
|
|
184
|
+
if (action === 'play')
|
|
185
|
+
player.play?.();
|
|
186
|
+
if (action === 'pause')
|
|
187
|
+
player.pause?.();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (e) { }
|
|
191
|
+
}
|
|
192
|
+
// 4. Generic window.player
|
|
193
|
+
if ((window.player || window.videoPlayer) && !result.detected) {
|
|
194
|
+
try {
|
|
195
|
+
const player = window.player || window.videoPlayer;
|
|
196
|
+
result.detected = true;
|
|
197
|
+
result.type = 'generic';
|
|
198
|
+
result.info = {
|
|
199
|
+
duration: player.getDuration?.() || player.duration,
|
|
200
|
+
currentTime: player.getCurrentTime?.() || player.currentTime,
|
|
201
|
+
volume: player.getVolume?.() || player.volume
|
|
202
|
+
};
|
|
203
|
+
if (action === 'sources') {
|
|
204
|
+
const sources = player.getSources?.() || player.getPlaylist?.() || [];
|
|
205
|
+
sources.forEach((s) => {
|
|
206
|
+
if (s.file || s.src)
|
|
207
|
+
result.sources.push({ src: s.file || s.src, type: 'generic' });
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (e) { }
|
|
212
|
+
}
|
|
213
|
+
// 5. HTML5 Video fallback
|
|
214
|
+
if (!result.detected) {
|
|
215
|
+
const video = document.querySelector('video');
|
|
216
|
+
if (video) {
|
|
217
|
+
result.detected = true;
|
|
218
|
+
result.type = 'html5';
|
|
219
|
+
result.info = {
|
|
220
|
+
duration: video.duration, currentTime: video.currentTime,
|
|
221
|
+
volume: video.volume, paused: video.paused, src: video.src || video.currentSrc
|
|
222
|
+
};
|
|
223
|
+
if (action === 'sources') {
|
|
224
|
+
if (video.src)
|
|
225
|
+
result.sources.push({ src: video.src, type: 'html5' });
|
|
226
|
+
if (video.currentSrc && video.currentSrc !== video.src) {
|
|
227
|
+
result.sources.push({ src: video.currentSrc, type: 'html5-current' });
|
|
228
|
+
}
|
|
229
|
+
video.querySelectorAll('source').forEach(s => { if (s.src)
|
|
230
|
+
result.sources.push({ src: s.src, type: 'html5-source' }); });
|
|
231
|
+
}
|
|
232
|
+
if (action === 'play')
|
|
233
|
+
video.play();
|
|
234
|
+
if (action === 'pause')
|
|
235
|
+
video.pause();
|
|
236
|
+
if (action === 'seek' && typeof actionOrParams === 'object' && actionOrParams.seekTime)
|
|
237
|
+
video.currentTime = actionOrParams.seekTime;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// 6. Look for common obfuscated player variables
|
|
241
|
+
const commonPlayerVars = ['player', 'videoPlayer', 'mediaPlayer', 'vPlayer', 'hls', 'flv'];
|
|
242
|
+
for (const varName of commonPlayerVars) {
|
|
243
|
+
if (window[varName] && !result.detected) {
|
|
244
|
+
try {
|
|
245
|
+
const p = window[varName];
|
|
246
|
+
if (typeof p === 'object' && (p.play || p.getDuration || p.src)) {
|
|
247
|
+
result.detected = true;
|
|
248
|
+
result.type = `${varName}-object`;
|
|
249
|
+
result.info = { raw: true };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (e) { }
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}, { playerType, action }).catch(() => ({ detected: false }));
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Shared helper: Deduplicate streams by URL
|
|
260
|
+
*/
|
|
261
|
+
function deduplicateStreams(streams) {
|
|
262
|
+
Object.keys(streams).forEach(key => {
|
|
263
|
+
if (Array.isArray(streams[key])) {
|
|
264
|
+
const seen = new Set();
|
|
265
|
+
streams[key] = streams[key].filter(item => {
|
|
266
|
+
if (seen.has(item.src))
|
|
267
|
+
return false;
|
|
268
|
+
seen.add(item.src);
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
return streams;
|
|
274
|
+
}
|
|
275
|
+
exports.mediaHandlers = {
|
|
276
|
+
async stream_extractor(params = {}) {
|
|
277
|
+
const { page } = (0, state_1.requireBrowser)();
|
|
278
|
+
const { types = ['all'], quality = 'best', searchIframes = true, deep = true } = params;
|
|
279
|
+
(0, state_1.notifyProgress)('stream_extractor', 'started', 'Extracting streams (enhanced mode)...');
|
|
280
|
+
// Extract from main page
|
|
281
|
+
const mainStreams = await extractStreamsFromContext(page, 'main');
|
|
282
|
+
let allStreams = { ...mainStreams };
|
|
283
|
+
// Search in iframes if enabled
|
|
284
|
+
if (searchIframes) {
|
|
285
|
+
const frames = page.frames();
|
|
286
|
+
(0, state_1.notifyProgress)('stream_extractor', 'progress', `Searching ${frames.length} frames...`);
|
|
287
|
+
for (let i = 1; i < frames.length && i < 10; i++) {
|
|
288
|
+
try {
|
|
289
|
+
const frame = frames[i];
|
|
290
|
+
const frameUrl = frame.url();
|
|
291
|
+
if (frameUrl && frameUrl !== 'about:blank') {
|
|
292
|
+
const frameStreams = await extractStreamsFromContext(frame, `frame-${i}`);
|
|
293
|
+
Object.keys(frameStreams).forEach(key => {
|
|
294
|
+
if (Array.isArray(frameStreams[key])) {
|
|
295
|
+
frameStreams[key].forEach(stream => {
|
|
296
|
+
stream.source = `iframe: ${frameUrl}`;
|
|
297
|
+
});
|
|
298
|
+
allStreams[key] = [...(allStreams[key] || []), ...frameStreams[key]];
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (e) {
|
|
304
|
+
// Frame access error, skip
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Deduplicate
|
|
309
|
+
allStreams = deduplicateStreams(allStreams);
|
|
310
|
+
const totalStreams = Object.values(allStreams).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0);
|
|
311
|
+
(0, state_1.notifyProgress)('stream_extractor', 'completed', `Found ${totalStreams} streams (including iframes)`, { totalStreams });
|
|
312
|
+
return { success: true, streams: allStreams, totalCount: totalStreams };
|
|
313
|
+
},
|
|
314
|
+
async player_api_hook(params = {}) {
|
|
315
|
+
const { page } = (0, state_1.requireBrowser)();
|
|
316
|
+
const { playerType = 'auto', action = 'info', searchIframes = true } = params;
|
|
317
|
+
(0, state_1.notifyProgress)('player_api_hook', 'started', `Player ${action}: ${playerType}`);
|
|
318
|
+
// Try main page first
|
|
319
|
+
let playerInfo = await detectPlayerInContext(page, { playerType, action }, 'main');
|
|
320
|
+
// Search iframes if no player found and searchIframes is enabled
|
|
321
|
+
if (!playerInfo.detected && searchIframes) {
|
|
322
|
+
const frames = page.frames();
|
|
323
|
+
(0, state_1.notifyProgress)('player_api_hook', 'progress', `Searching ${frames.length} frames for player...`);
|
|
324
|
+
for (let i = 1; i < frames.length && i < 10; i++) {
|
|
325
|
+
try {
|
|
326
|
+
const frame = frames[i];
|
|
327
|
+
const frameUrl = frame.url();
|
|
328
|
+
if (frameUrl && frameUrl !== 'about:blank') {
|
|
329
|
+
const framePlayer = await detectPlayerInContext(frame, { playerType, action }, `frame-${i}`);
|
|
330
|
+
if (framePlayer.detected) {
|
|
331
|
+
playerInfo = { ...framePlayer, frameSource: frameUrl };
|
|
332
|
+
(0, state_1.notifyProgress)('player_api_hook', 'progress', `Found player in iframe: ${frameUrl}`);
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
// Frame access error, skip
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
(0, state_1.notifyProgress)('player_api_hook', 'completed', playerInfo.detected ? `Player detected: ${playerInfo.type}${playerInfo.frameSource ? ' (in iframe)' : ''}` : 'No player found', { detected: playerInfo.detected, type: playerInfo.type });
|
|
343
|
+
return { success: true, ...playerInfo };
|
|
344
|
+
},
|
|
345
|
+
async media_extractor(params = {}) {
|
|
346
|
+
const { page } = (0, state_1.requireBrowser)();
|
|
347
|
+
const { action = 'extract', types = ['all'], quality = 'best', searchIframes = true, deep = true, selector, index, playerAction = 'info', encodedData, decoderType = 'auto', aesKey, aesIV, urls, aiOptimize = true } = params;
|
|
348
|
+
(0, state_1.notifyProgress)('media_extractor', 'started', `Media extraction action: ${action}`);
|
|
349
|
+
// Helper: Auto-detect decoder type
|
|
350
|
+
const autoDetectDecoder = (data) => {
|
|
351
|
+
if (data.includes('%'))
|
|
352
|
+
return 'url';
|
|
353
|
+
if (/^[A-Za-z0-9+/=]+$/.test(data) && data.length % 4 === 0)
|
|
354
|
+
return 'base64';
|
|
355
|
+
return 'url';
|
|
356
|
+
};
|
|
357
|
+
switch (action) {
|
|
358
|
+
case 'extract': {
|
|
359
|
+
(0, state_1.notifyProgress)('media_extractor', 'progress', 'Extracting all media...');
|
|
360
|
+
// Get iframes
|
|
361
|
+
const frames = page.frames();
|
|
362
|
+
const iframes = frames.map((f, i) => ({ index: i, name: f.name(), url: f.url() }));
|
|
363
|
+
// Extract streams from main page
|
|
364
|
+
let allStreams = await extractStreamsFromContext(page, 'main');
|
|
365
|
+
// Search in iframes
|
|
366
|
+
if (searchIframes) {
|
|
367
|
+
for (let i = 1; i < frames.length && i < 10; i++) {
|
|
368
|
+
try {
|
|
369
|
+
const frame = frames[i];
|
|
370
|
+
const frameUrl = frame.url();
|
|
371
|
+
if (frameUrl && frameUrl !== 'about:blank') {
|
|
372
|
+
const frameStreams = await extractStreamsFromContext(frame, `frame-${i}`);
|
|
373
|
+
Object.keys(frameStreams).forEach(key => {
|
|
374
|
+
if (Array.isArray(frameStreams[key])) {
|
|
375
|
+
frameStreams[key].forEach(stream => { stream.source = `iframe: ${frameUrl}`; });
|
|
376
|
+
allStreams[key] = [...(allStreams[key] || []), ...frameStreams[key]];
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (e) { }
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Deduplicate
|
|
385
|
+
allStreams = deduplicateStreams(allStreams);
|
|
386
|
+
// Detect players
|
|
387
|
+
let playerInfo = await detectPlayerInContext(page, playerAction, 'main');
|
|
388
|
+
if (!playerInfo.detected && searchIframes) {
|
|
389
|
+
for (let i = 1; i < frames.length && i < 10; i++) {
|
|
390
|
+
try {
|
|
391
|
+
const frame = frames[i];
|
|
392
|
+
const frameUrl = frame.url();
|
|
393
|
+
if (frameUrl && frameUrl !== 'about:blank') {
|
|
394
|
+
const framePlayer = await detectPlayerInContext(frame, playerAction, `frame-${i}`);
|
|
395
|
+
if (framePlayer.detected) {
|
|
396
|
+
playerInfo = { ...framePlayer, frameSource: frameUrl };
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (e) { }
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const totalStreams = Object.values(allStreams).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0);
|
|
405
|
+
(0, state_1.notifyProgress)('media_extractor', 'completed', `Extracted ${totalStreams} streams, ${iframes.length} iframes, player: ${playerInfo.detected ? playerInfo.type : 'none'}`);
|
|
406
|
+
return {
|
|
407
|
+
success: true,
|
|
408
|
+
action: 'extract',
|
|
409
|
+
streams: allStreams,
|
|
410
|
+
iframes,
|
|
411
|
+
player: playerInfo,
|
|
412
|
+
totalStreams,
|
|
413
|
+
iframeCount: iframes.length
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
case 'list_iframes': {
|
|
417
|
+
const frames = page.frames();
|
|
418
|
+
const iframes = frames.map((f, i) => ({ index: i, name: f.name(), url: f.url() }));
|
|
419
|
+
(0, state_1.notifyProgress)('media_extractor', 'completed', `Found ${iframes.length} iframes`);
|
|
420
|
+
return { success: true, action: 'list_iframes', count: iframes.length, iframes };
|
|
421
|
+
}
|
|
422
|
+
case 'switch_iframe': {
|
|
423
|
+
const frames = page.frames();
|
|
424
|
+
const targetFrame = selector
|
|
425
|
+
? await page.$(selector).then(el => el?.contentFrame())
|
|
426
|
+
: frames[index];
|
|
427
|
+
if (targetFrame) {
|
|
428
|
+
(0, state_1.notifyProgress)('media_extractor', 'completed', `Switched to iframe: ${targetFrame.url()}`);
|
|
429
|
+
return { success: true, action: 'switch_iframe', switched: true, url: targetFrame.url(), frameIndex: index };
|
|
430
|
+
}
|
|
431
|
+
(0, state_1.notifyProgress)('media_extractor', 'error', 'Iframe not found');
|
|
432
|
+
return { success: false, error: 'Iframe not found' };
|
|
433
|
+
}
|
|
434
|
+
case 'player_control': {
|
|
435
|
+
const { playerType, seekTime, volume } = params;
|
|
436
|
+
(0, state_1.notifyProgress)('media_extractor', 'progress', `Player control: ${playerAction}`);
|
|
437
|
+
let result = await detectPlayerInContext(page, playerAction, 'main');
|
|
438
|
+
if (!result.detected && searchIframes) {
|
|
439
|
+
const frames = page.frames();
|
|
440
|
+
for (let i = 1; i < frames.length && i < 10; i++) {
|
|
441
|
+
try {
|
|
442
|
+
const frame = frames[i];
|
|
443
|
+
const frameUrl = frame.url();
|
|
444
|
+
if (frameUrl && frameUrl !== 'about:blank') {
|
|
445
|
+
const frameResult = await detectPlayerInContext(frame, playerAction, `frame-${i}`);
|
|
446
|
+
if (frameResult.detected) {
|
|
447
|
+
result = { ...frameResult, frameSource: frameUrl };
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch (e) { }
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
(0, state_1.notifyProgress)('media_extractor', 'completed', result.detected ? `Player ${playerAction} executed: ${result.type}` : 'No player found');
|
|
456
|
+
return { success: true, action: 'player_control', playerAction, ...result };
|
|
457
|
+
}
|
|
458
|
+
case 'decode_url': {
|
|
459
|
+
if (!encodedData) {
|
|
460
|
+
return { success: false, error: 'encodedData is required for decode_url action' };
|
|
461
|
+
}
|
|
462
|
+
const type = decoderType === 'auto' ? autoDetectDecoder(encodedData) : decoderType;
|
|
463
|
+
let decoded;
|
|
464
|
+
(0, state_1.notifyProgress)('media_extractor', 'progress', `Decoding with ${type}...`);
|
|
465
|
+
switch (type) {
|
|
466
|
+
case 'url':
|
|
467
|
+
decoded = state_1.decoders.urlDecode(encodedData);
|
|
468
|
+
break;
|
|
469
|
+
case 'base64':
|
|
470
|
+
decoded = state_1.decoders.base64Decode(encodedData);
|
|
471
|
+
break;
|
|
472
|
+
case 'aes':
|
|
473
|
+
if (!aesKey) {
|
|
474
|
+
return { success: false, error: 'aesKey is required for AES decryption' };
|
|
475
|
+
}
|
|
476
|
+
decoded = state_1.decoders.decryptAES(encodedData, aesKey, aesIV);
|
|
477
|
+
break;
|
|
478
|
+
default:
|
|
479
|
+
decoded = { success: false, error: 'Unknown decoder type' };
|
|
480
|
+
}
|
|
481
|
+
(0, state_1.notifyProgress)('media_extractor', 'completed', decoded.success ? 'Decoding successful' : 'Decoding failed');
|
|
482
|
+
return { action: 'decode_url', decoderType: type, ...decoded };
|
|
483
|
+
}
|
|
484
|
+
case 'batch_extract': {
|
|
485
|
+
if (!urls || !Array.isArray(urls) || urls.length === 0) {
|
|
486
|
+
return { success: false, error: 'urls array is required for batch_extract action' };
|
|
487
|
+
}
|
|
488
|
+
(0, state_1.notifyProgress)('media_extractor', 'progress', `Batch extracting from ${urls.length} URLs...`);
|
|
489
|
+
const results = [];
|
|
490
|
+
const errors = [];
|
|
491
|
+
for (let i = 0; i < urls.length; i++) {
|
|
492
|
+
const url = urls[i];
|
|
493
|
+
try {
|
|
494
|
+
(0, state_1.notifyProgress)('media_extractor', 'progress', `Processing ${i + 1}/${urls.length}: ${url}`);
|
|
495
|
+
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
|
|
496
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
497
|
+
const streams = await extractStreamsFromContext(page, 'main');
|
|
498
|
+
const dedupedStreams = deduplicateStreams(streams);
|
|
499
|
+
const totalCount = Object.values(dedupedStreams).reduce((sum, arr) => sum + (Array.isArray(arr) ? arr.length : 0), 0);
|
|
500
|
+
const meta = await page.evaluate(() => ({
|
|
501
|
+
title: document.title,
|
|
502
|
+
url: window.location.href
|
|
503
|
+
}));
|
|
504
|
+
results.push({
|
|
505
|
+
url,
|
|
506
|
+
success: true,
|
|
507
|
+
streams: dedupedStreams,
|
|
508
|
+
totalCount,
|
|
509
|
+
title: meta.title,
|
|
510
|
+
finalUrl: meta.url
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
catch (error) {
|
|
514
|
+
errors.push({ url, error: error.message });
|
|
515
|
+
results.push({ url, success: false, error: error.message });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
const successCount = results.filter(r => r.success).length;
|
|
519
|
+
(0, state_1.notifyProgress)('media_extractor', 'completed', `Batch extraction complete: ${successCount}/${urls.length} successful`);
|
|
520
|
+
return {
|
|
521
|
+
success: true,
|
|
522
|
+
action: 'batch_extract',
|
|
523
|
+
totalUrls: urls.length,
|
|
524
|
+
successful: successCount,
|
|
525
|
+
failed: urls.length - successCount,
|
|
526
|
+
results,
|
|
527
|
+
errors
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
default:
|
|
531
|
+
return { success: false, error: `Unknown action: ${action}. Supported: extract, list_iframes, switch_iframe, player_control, decode_url, batch_extract` };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
//# sourceMappingURL=media-handlers.js.map
|