@mazees/mark 5.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/LICENSE +29 -0
- package/README.md +209 -0
- package/bin/mark.js +27 -0
- package/out/renderer/assets/icon-DHo2CYph.svg +1 -0
- package/out/renderer/assets/index-G2fADWO-.js +4768 -0
- package/out/renderer/assets/index-f3mVse3c.css +1 -0
- package/out/renderer/assets/music-cover-CG4vi3kZ.png +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CJDn6rn8.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-400-normal-CqVvlrh5.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-BIdkeU1p.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-500-normal-DMPDjHtT.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-ClASKHrr.woff +0 -0
- package/out/renderer/assets/poppins-devanagari-600-normal-STEjXBNN.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-O-jipLrW.woff2 +0 -0
- package/out/renderer/assets/poppins-devanagari-700-normal-fHs-vx92.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-BOb3E3N0.woff +0 -0
- package/out/renderer/assets/poppins-latin-400-normal-cpxAROuN.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-C8OXljZJ.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-500-normal-DGXqpDMm.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-BJdTmd5m.woff +0 -0
- package/out/renderer/assets/poppins-latin-600-normal-zEkxB9Mr.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-BVuQR_eA.woff +0 -0
- package/out/renderer/assets/poppins-latin-700-normal-Qrb0O0WB.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-DaBSavcJ.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-400-normal-by3JarPu.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CK-6C4Hw.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-500-normal-CgAe2rWW.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-CAhIAdZj.woff2 +0 -0
- package/out/renderer/assets/poppins-latin-ext-600-normal-Df5ffKXP.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-DctTR6Tg.woff +0 -0
- package/out/renderer/assets/poppins-latin-ext-700-normal-cby-RkWa.woff2 +0 -0
- package/out/renderer/favicon.ico +0 -0
- package/out/renderer/icon-256.png +0 -0
- package/out/renderer/icon.svg +1 -0
- package/out/renderer/index.html +25 -0
- package/out/renderer/manifest.json +25 -0
- package/out/renderer/robot-simulator.html +1640 -0
- package/package.json +106 -0
- package/src/cli/index.js +332 -0
- package/src/cli/monitor.js +413 -0
- package/src/cli/theme.js +347 -0
- package/src/launcher/MarkTray.cs +227 -0
- package/src/main/browser-agent.js +1132 -0
- package/src/main/git-service.js +90 -0
- package/src/main/google/google-calendar.js +69 -0
- package/src/main/google/google-drive.js +263 -0
- package/src/main/google/google-gmail.js +131 -0
- package/src/main/google/google-service.js +241 -0
- package/src/main/node-tools.js +23 -0
- package/src/main/pc-agent-scripts/mouse-locker.ps1 +76 -0
- package/src/main/pc-agent-scripts/ocr-region.ps1 +185 -0
- package/src/main/pc-agent-scripts/pc-daemon.ps1 +726 -0
- package/src/main/pc-agent-scripts/pc-overlay.ps1 +253 -0
- package/src/main/pc-agent-scripts/pick-folder.ps1 +33 -0
- package/src/main/pc-agent-scripts/read-ui.ps1 +181 -0
- package/src/main/pc-agent-scripts/take-screenshot.ps1 +24 -0
- package/src/main/pc-agent-scripts/win-action.ps1 +380 -0
- package/src/main/plugins/plugin-loader.js +306 -0
- package/src/main/skills/skill-manager.js +265 -0
- package/src/main/syntax-validator.js +455 -0
- package/src/main/task-daemon.js +117 -0
- package/src/main/telegram/telegram-service.js +835 -0
- package/src/main/tools/browser-tools.js +516 -0
- package/src/main/tools/file-tools.js +1126 -0
- package/src/main/tools/git-tools.js +79 -0
- package/src/main/tools/google-tools.js +566 -0
- package/src/main/tools/index.js +36 -0
- package/src/main/tools/media-tools.js +42 -0
- package/src/main/tools/system-tools.js +295 -0
- package/src/main/tools/task-tools.js +73 -0
- package/src/main/tools/telegram-tools.js +101 -0
- package/src/renderer/index.html +24 -0
- package/src/renderer/public/favicon.ico +0 -0
- package/src/renderer/public/icon-256.png +0 -0
- package/src/renderer/public/icon.svg +1 -0
- package/src/renderer/public/manifest.json +25 -0
- package/src/renderer/public/robot-simulator.html +1640 -0
- package/src/renderer/src/App.jsx +304 -0
- package/src/renderer/src/api/ai/awareness.js +294 -0
- package/src/renderer/src/api/ai/chatSummarizer.js +79 -0
- package/src/renderer/src/api/ai/contextCompactor.js +129 -0
- package/src/renderer/src/api/ai/contextManager.js +1130 -0
- package/src/renderer/src/api/ai/core.js +167 -0
- package/src/renderer/src/api/ai/index.js +4 -0
- package/src/renderer/src/api/ai/memoryGroomer.js +101 -0
- package/src/renderer/src/api/ai/persona.js +115 -0
- package/src/renderer/src/api/ai/planning.js +317 -0
- package/src/renderer/src/api/ai/relationship.js +177 -0
- package/src/renderer/src/api/ai/skillSynthesizer.js +168 -0
- package/src/renderer/src/api/ai/tools.js +167 -0
- package/src/renderer/src/api/ai/utils.js +359 -0
- package/src/renderer/src/api/db.js +1013 -0
- package/src/renderer/src/api/oramaStore.js +592 -0
- package/src/renderer/src/api/ragPipeline.js +87 -0
- package/src/renderer/src/api/subagent/subagentExecutor.js +780 -0
- package/src/renderer/src/api/subagent/subagentPrompt.js +44 -0
- package/src/renderer/src/api/subagent/subagentStore.js +173 -0
- package/src/renderer/src/api/taskExecutor.js +18 -0
- package/src/renderer/src/api/taskStore.js +315 -0
- package/src/renderer/src/api/tools/core-tools.js +707 -0
- package/src/renderer/src/api/tools/index.js +151 -0
- package/src/renderer/src/api/turnPairMigrator.js +200 -0
- package/src/renderer/src/api/vectorMemory.js +136 -0
- package/src/renderer/src/api/wakeWord.js +101 -0
- package/src/renderer/src/api/web-bridge.js +885 -0
- package/src/renderer/src/api/webSpeech.js +178 -0
- package/src/renderer/src/api/workspaceRag.js +76 -0
- package/src/renderer/src/assets/electron.svg +10 -0
- package/src/renderer/src/assets/icon.svg +1 -0
- package/src/renderer/src/assets/main.css +589 -0
- package/src/renderer/src/assets/music-cover.png +0 -0
- package/src/renderer/src/assets/wavy-lines.svg +25 -0
- package/src/renderer/src/components/Chat/ApprovalBubble.jsx +127 -0
- package/src/renderer/src/components/Chat/CodeBlock.jsx +83 -0
- package/src/renderer/src/components/Chat/DurableTaskBubble.jsx +455 -0
- package/src/renderer/src/components/Chat/FileDiffModal.jsx +328 -0
- package/src/renderer/src/components/Chat/GeminiWidgets.jsx +82 -0
- package/src/renderer/src/components/Chat/MemoryFooterBubble.jsx +34 -0
- package/src/renderer/src/components/Chat/MessageBubble.jsx +459 -0
- package/src/renderer/src/components/Chat/PluginExecutionBubble.jsx +41 -0
- package/src/renderer/src/components/Chat/ThinkingBubble.jsx +316 -0
- package/src/renderer/src/components/Chat/YoutubeSearchBubble.jsx +43 -0
- package/src/renderer/src/components/Chat/YoutubeSummaryBubble.jsx +40 -0
- package/src/renderer/src/components/Chat/index.js +11 -0
- package/src/renderer/src/components/ChatList.jsx +434 -0
- package/src/renderer/src/components/GlobalCameraManager.jsx +67 -0
- package/src/renderer/src/components/Versions.jsx +15 -0
- package/src/renderer/src/components/YoutubeMusicPlayer.jsx +375 -0
- package/src/renderer/src/components/camera/CameraPreview.jsx +236 -0
- package/src/renderer/src/components/config/AiEngineSection.jsx +363 -0
- package/src/renderer/src/components/config/CameraSection.jsx +100 -0
- package/src/renderer/src/components/config/PermissionsSection.jsx +130 -0
- package/src/renderer/src/components/config/ShortcutSection.jsx +102 -0
- package/src/renderer/src/components/config/VoiceSection.jsx +110 -0
- package/src/renderer/src/components/config/index.js +5 -0
- package/src/renderer/src/components/core/Avatar.jsx +2197 -0
- package/src/renderer/src/components/core/ConfirmModal.jsx +80 -0
- package/src/renderer/src/components/core/DraggableHoloCard.jsx +125 -0
- package/src/renderer/src/components/core/FloatingMenu.jsx +272 -0
- package/src/renderer/src/components/core/HoloCard.jsx +64 -0
- package/src/renderer/src/components/core/InputBar.jsx +1346 -0
- package/src/renderer/src/components/core/ProcessPanel.jsx +216 -0
- package/src/renderer/src/components/core/ResponseArea.jsx +149 -0
- package/src/renderer/src/components/core/StatusIndicator.jsx +59 -0
- package/src/renderer/src/components/core/ThoughtNeuralFlow.jsx +168 -0
- package/src/renderer/src/components/core/ToolClustersDeck.jsx +210 -0
- package/src/renderer/src/components/core/native-skills.js +43 -0
- package/src/renderer/src/components/neural-core/EmotionalCortexLobe.jsx +359 -0
- package/src/renderer/src/components/neural-core/NodeDissectionModal.jsx +57 -0
- package/src/renderer/src/components/neural-core/ProceduralMatrixLobe.jsx +193 -0
- package/src/renderer/src/components/neural-core/ProceduralSkillModal.jsx +53 -0
- package/src/renderer/src/components/neural-core/ResetAiModal.jsx +198 -0
- package/src/renderer/src/components/neural-core/SynapticMemoryLobe.jsx +204 -0
- package/src/renderer/src/components/neural-core/traitMeta.js +102 -0
- package/src/renderer/src/components/subagent/AgentChatFeed.jsx +603 -0
- package/src/renderer/src/components/subagent/AgentInspector.jsx +224 -0
- package/src/renderer/src/components/subagent/AgentSidebar.jsx +216 -0
- package/src/renderer/src/components/subagent/SubagentIntercom.jsx +572 -0
- package/src/renderer/src/components/subagent/SubagentTopologyMap.jsx +423 -0
- package/src/renderer/src/contexts/ApprovalContext.jsx +425 -0
- package/src/renderer/src/contexts/ChatContext.jsx +61 -0
- package/src/renderer/src/contexts/YoutubeMusicContext.jsx +333 -0
- package/src/renderer/src/data/faqData.js +202 -0
- package/src/renderer/src/hooks/agent/executeAgentTool.js +895 -0
- package/src/renderer/src/hooks/agent/index.js +4 -0
- package/src/renderer/src/hooks/agent/useMarkMusic.js +75 -0
- package/src/renderer/src/hooks/agent/useMarkPlan.js +2739 -0
- package/src/renderer/src/hooks/agent/useMarkState.js +173 -0
- package/src/renderer/src/hooks/agent/useMarkYoutube.js +82 -0
- package/src/renderer/src/hooks/agent/useRelationalGrowth.js +201 -0
- package/src/renderer/src/hooks/telegram/useTelegramBot.js +63 -0
- package/src/renderer/src/hooks/useAwareness.js +289 -0
- package/src/renderer/src/hooks/useChatArchiver.js +52 -0
- package/src/renderer/src/hooks/useConfirm.jsx +68 -0
- package/src/renderer/src/hooks/useMarkAgent.js +454 -0
- package/src/renderer/src/hooks/useMemoryGroomer.js +152 -0
- package/src/renderer/src/hooks/useVAD.js +511 -0
- package/src/renderer/src/hooks/useWebSpeechWakeWord.js +122 -0
- package/src/renderer/src/main.jsx +12 -0
- package/src/renderer/src/pages/ChatStudio.jsx +922 -0
- package/src/renderer/src/pages/Configuration.jsx +360 -0
- package/src/renderer/src/pages/GoogleWorkspace.jsx +293 -0
- package/src/renderer/src/pages/Guidebook.jsx +917 -0
- package/src/renderer/src/pages/Knowledge.jsx +241 -0
- package/src/renderer/src/pages/MarkHome.jsx +491 -0
- package/src/renderer/src/pages/NeuralCore.jsx +786 -0
- package/src/renderer/src/pages/Plugins.jsx +630 -0
- package/src/renderer/src/pages/SkillEditor.jsx +380 -0
- package/src/renderer/src/pages/Skills.jsx +219 -0
- package/src/renderer/src/pages/Subagents.jsx +342 -0
- package/src/renderer/src/pages/TelegramBot.jsx +343 -0
- package/src/renderer/src/utils/chatContentParser.js +203 -0
- package/src/renderer/src/utils/toolClusters.js +201 -0
- package/src/server/bin/sha3_wasm_bg.wasm +0 -0
- package/src/server/config-manager.js +57 -0
- package/src/server/index.js +195 -0
- package/src/server/launcher.js +87 -0
- package/src/server/memory/db-store.js +758 -0
- package/src/server/memory/orama-store.js +177 -0
- package/src/server/memory/vector-engine.js +109 -0
- package/src/server/routes/ai.routes.js +280 -0
- package/src/server/routes/awareness.routes.js +62 -0
- package/src/server/routes/chat.routes.js +61 -0
- package/src/server/routes/config.routes.js +41 -0
- package/src/server/routes/index.js +35 -0
- package/src/server/routes/integrations.routes.js +116 -0
- package/src/server/routes/memory.routes.js +256 -0
- package/src/server/routes/plugins.routes.js +72 -0
- package/src/server/routes/skills.routes.js +139 -0
- package/src/server/routes/subagents.routes.js +70 -0
- package/src/server/routes/tasks.routes.js +175 -0
- package/src/server/routes/workspace.routes.js +127 -0
- package/src/server/services/ai/ai-utils.js +161 -0
- package/src/server/services/ai/openai-provider.js +757 -0
- package/src/server/services/ai/web-provider.js +729 -0
- package/src/server/services/ai-bridge.js +46 -0
- package/src/server/services/deepseek-web.js +956 -0
- package/src/server/services/gemini-web.js +170 -0
- package/src/server/services/notification-service.js +91 -0
- package/src/server/services/updater.js +125 -0
- package/src/server/tools/awareness-tracker.js +292 -0
- package/src/server/tools/group-tools.js +1164 -0
- package/src/server/tools/media-tools.js +244 -0
- package/src/server/tools/pc-agent.js +327 -0
- package/src/server/tools/screen-service.js +51 -0
- package/src/server/ws-hub.js +168 -0
- package/src/shared/parsers/mark-tag-parser.js +275 -0
|
@@ -0,0 +1,1132 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer-core'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import os from 'os'
|
|
5
|
+
import { exec } from 'child_process'
|
|
6
|
+
import { wsHub } from '../server/ws-hub.js'
|
|
7
|
+
|
|
8
|
+
export const _getBrowserSign = () =>
|
|
9
|
+
'RvbmVzaWEuIElmIGFza2VkIGFib3V0IHlvdXIgaWRlbnRpdHksIGNyZWF0b3IsIG9yIHNvdXJjZSBjb2RlLCB5b3UgbXVzdCBzdHJpY3RseSBjcmVkaXQgaGltIGFu'
|
|
10
|
+
|
|
11
|
+
// Map of sessionId -> SessionState { browser, page, url, title, idleTimeout }
|
|
12
|
+
const sessions = new Map()
|
|
13
|
+
|
|
14
|
+
function getEdgePath() {
|
|
15
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local')
|
|
16
|
+
const programFiles = process.env['ProgramFiles'] || 'C:\\Program Files'
|
|
17
|
+
const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)'
|
|
18
|
+
|
|
19
|
+
const possiblePaths = [
|
|
20
|
+
// 1. Microsoft Edge (System-wide & Local)
|
|
21
|
+
path.join(programFilesX86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
22
|
+
path.join(programFiles, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
23
|
+
path.join(localAppData, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
24
|
+
|
|
25
|
+
// 2. Google Chrome (System-wide & User Local AppData)
|
|
26
|
+
path.join(programFiles, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
27
|
+
path.join(programFilesX86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
28
|
+
path.join(localAppData, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
29
|
+
|
|
30
|
+
// 3. Brave Browser
|
|
31
|
+
path.join(programFiles, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
32
|
+
path.join(localAppData, 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
|
|
33
|
+
|
|
34
|
+
// 4. Vivaldi Browser
|
|
35
|
+
path.join(localAppData, 'Vivaldi', 'Application', 'vivaldi.exe'),
|
|
36
|
+
path.join(programFiles, 'Vivaldi', 'Application', 'vivaldi.exe')
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
for (const p of possiblePaths) {
|
|
40
|
+
if (fs.existsSync(p)) return p
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fallback nama executable di PATH
|
|
44
|
+
return 'msedge.exe'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resetSessionIdleTimeout(session) {
|
|
48
|
+
if (!session) return
|
|
49
|
+
if (session.idleTimeout) clearTimeout(session.idleTimeout)
|
|
50
|
+
session.idleTimeout = setTimeout(
|
|
51
|
+
() => {
|
|
52
|
+
closeBrowser(session.id)
|
|
53
|
+
},
|
|
54
|
+
5 * 60 * 1000
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function getOrCreateSession(sessionId = 'default', headless = false) {
|
|
59
|
+
let session = sessions.get(sessionId)
|
|
60
|
+
if (session && session.page && !session.page.isClosed()) {
|
|
61
|
+
resetSessionIdleTimeout(session)
|
|
62
|
+
return session
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const edgePath = getEdgePath()
|
|
66
|
+
const userDataDir = path.join(
|
|
67
|
+
os.homedir(),
|
|
68
|
+
'.config',
|
|
69
|
+
'mark-agent',
|
|
70
|
+
'browser-sessions',
|
|
71
|
+
sessionId
|
|
72
|
+
)
|
|
73
|
+
if (!fs.existsSync(userDataDir)) {
|
|
74
|
+
fs.mkdirSync(userDataDir, { recursive: true })
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const browser = await puppeteer.launch({
|
|
78
|
+
executablePath: edgePath,
|
|
79
|
+
headless: headless ? 'new' : false,
|
|
80
|
+
userDataDir,
|
|
81
|
+
ignoreDefaultArgs: ['--enable-automation'],
|
|
82
|
+
defaultViewport: null,
|
|
83
|
+
args: [
|
|
84
|
+
'--disable-blink-features=AutomationControlled',
|
|
85
|
+
'--disable-features=IsolateOrigins,site-per-process',
|
|
86
|
+
'--no-sandbox',
|
|
87
|
+
'--disable-setuid-sandbox',
|
|
88
|
+
'--disable-infobars',
|
|
89
|
+
'--no-first-run',
|
|
90
|
+
'--no-default-browser-check',
|
|
91
|
+
'--disable-background-timer-throttling',
|
|
92
|
+
'--disable-backgrounding-occluded-windows',
|
|
93
|
+
'--disable-renderer-backgrounding',
|
|
94
|
+
'--window-position=80,60',
|
|
95
|
+
'--window-size=1280,800',
|
|
96
|
+
'--lang=id-ID,id,en-US,en'
|
|
97
|
+
]
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
const pages = await browser.pages()
|
|
101
|
+
const page = pages.length > 0 ? pages[0] : await browser.newPage()
|
|
102
|
+
|
|
103
|
+
// Real Edge Windows 11 User-Agent & HTTP Headers
|
|
104
|
+
const userAgent =
|
|
105
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 Edg/133.0.0.0'
|
|
106
|
+
await page.setUserAgent(userAgent)
|
|
107
|
+
|
|
108
|
+
await page.setExtraHTTPHeaders({
|
|
109
|
+
'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
110
|
+
'Sec-CH-UA': '"Not(A:Brand";v="99", "Microsoft Edge";v="133", "Chromium";v="133"',
|
|
111
|
+
'Sec-CH-UA-Mobile': '?0',
|
|
112
|
+
'Sec-CH-UA-Platform': '"Windows"',
|
|
113
|
+
'Upgrade-Insecure-Requests': '1'
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
// Comprehensive Anti-Bot Detection Stealth Overrides
|
|
117
|
+
await page.evaluateOnNewDocument(() => {
|
|
118
|
+
const makeNative = (fn, name) => {
|
|
119
|
+
Object.defineProperty(fn, 'name', { value: name, configurable: true })
|
|
120
|
+
const toString = () => `function ${name}() { [native code] }`
|
|
121
|
+
Object.defineProperty(fn, 'toString', { value: toString, configurable: true })
|
|
122
|
+
return fn
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 1. Mock Screen & Window Coordinates (Anti-Bot Stealth)
|
|
126
|
+
Object.defineProperties(window, {
|
|
127
|
+
screenX: { get: () => 80, configurable: true },
|
|
128
|
+
screenY: { get: () => 60, configurable: true },
|
|
129
|
+
screenLeft: { get: () => 80, configurable: true },
|
|
130
|
+
screenTop: { get: () => 60, configurable: true },
|
|
131
|
+
outerWidth: { get: () => 1280, configurable: true },
|
|
132
|
+
outerHeight: { get: () => 800, configurable: true },
|
|
133
|
+
innerWidth: { get: () => 1280, configurable: true },
|
|
134
|
+
innerHeight: { get: () => 800, configurable: true }
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
Object.defineProperties(window.screen, {
|
|
138
|
+
availLeft: { get: () => 0, configurable: true },
|
|
139
|
+
availTop: { get: () => 0, configurable: true },
|
|
140
|
+
availWidth: { get: () => 1920, configurable: true },
|
|
141
|
+
availHeight: { get: () => 1040, configurable: true },
|
|
142
|
+
width: { get: () => 1920, configurable: true },
|
|
143
|
+
height: { get: () => 1080, configurable: true }
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// 2. WebDriver stealth: returns false with native getter signature
|
|
147
|
+
try {
|
|
148
|
+
delete Object.getPrototypeOf(navigator).webdriver
|
|
149
|
+
} catch (_) {}
|
|
150
|
+
const webdriverGetter = makeNative(() => false, 'get webdriver')
|
|
151
|
+
Object.defineProperty(Navigator.prototype, 'webdriver', {
|
|
152
|
+
get: webdriverGetter,
|
|
153
|
+
enumerable: true,
|
|
154
|
+
configurable: true
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// 3. Native Chrome runtime structure matching official Edge
|
|
158
|
+
const sendMessageFn = makeNative(function () {}, 'sendMessage')
|
|
159
|
+
const connectFn = makeNative(function () {}, 'connect')
|
|
160
|
+
window.chrome = {
|
|
161
|
+
app: {
|
|
162
|
+
isInstalled: false,
|
|
163
|
+
InstallState: {
|
|
164
|
+
DISABLED: 'disabled',
|
|
165
|
+
INSTALLED: 'installed',
|
|
166
|
+
NOT_INSTALLED: 'not_installed'
|
|
167
|
+
},
|
|
168
|
+
RunningState: { CANNOT_RUN: 'cannot_run', READY_TO_RUN: 'ready_to_run', RUNNING: 'running' }
|
|
169
|
+
},
|
|
170
|
+
runtime: {
|
|
171
|
+
OnInstalledReason: {},
|
|
172
|
+
OnRestartRequiredReason: {},
|
|
173
|
+
PlatformArch: {},
|
|
174
|
+
PlatformNaclArch: {},
|
|
175
|
+
PlatformOs: {},
|
|
176
|
+
RequestUpdateCheckStatus: {},
|
|
177
|
+
connect: connectFn,
|
|
178
|
+
sendMessage: sendMessageFn
|
|
179
|
+
},
|
|
180
|
+
loadTimes: makeNative(function () {
|
|
181
|
+
return {
|
|
182
|
+
requestTime: performance.now() / 1000,
|
|
183
|
+
startLoadTime: performance.now() / 1000,
|
|
184
|
+
commitLoadTime: performance.now() / 1000,
|
|
185
|
+
finishDocumentLoadTime: performance.now() / 1000,
|
|
186
|
+
finishLoadTime: performance.now() / 1000,
|
|
187
|
+
firstPaintTime: performance.now() / 1000,
|
|
188
|
+
firstPaintAfterLoadTime: 0,
|
|
189
|
+
navigationType: 'Other',
|
|
190
|
+
wasFetchedViaSpdy: false,
|
|
191
|
+
wasNpnNegotiated: false,
|
|
192
|
+
npnNegotiatedProtocol: '',
|
|
193
|
+
wasAlternateProtocolAvailable: false,
|
|
194
|
+
connectionInfo: 'http/1.1'
|
|
195
|
+
}
|
|
196
|
+
}, 'loadTimes'),
|
|
197
|
+
csi: makeNative(function () {
|
|
198
|
+
return {
|
|
199
|
+
startE: performance.now(),
|
|
200
|
+
onloadT: performance.now(),
|
|
201
|
+
pageT: performance.now(),
|
|
202
|
+
tran: 15
|
|
203
|
+
}
|
|
204
|
+
}, 'csi')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 4. PluginArray and MimeTypeArray Prototype emulation
|
|
208
|
+
const pluginData = [
|
|
209
|
+
{
|
|
210
|
+
name: 'PDF Viewer',
|
|
211
|
+
filename: 'internal-pdf-viewer',
|
|
212
|
+
description: 'Portable Document Format'
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'Chrome PDF Viewer',
|
|
216
|
+
filename: 'internal-pdf-viewer',
|
|
217
|
+
description: 'Portable Document Format'
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'Chromium PDF Viewer',
|
|
221
|
+
filename: 'internal-pdf-viewer',
|
|
222
|
+
description: 'Portable Document Format'
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'Microsoft Edge PDF Viewer',
|
|
226
|
+
filename: 'internal-pdf-viewer',
|
|
227
|
+
description: 'Portable Document Format'
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'WebKit built-in PDF',
|
|
231
|
+
filename: 'internal-pdf-viewer',
|
|
232
|
+
description: 'Portable Document Format'
|
|
233
|
+
}
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
const createPluginArray = (plugins) => {
|
|
237
|
+
const arr = Object.create(PluginArray.prototype)
|
|
238
|
+
plugins.forEach((p, idx) => {
|
|
239
|
+
const pluginObj = Object.create(Plugin.prototype)
|
|
240
|
+
Object.defineProperties(pluginObj, {
|
|
241
|
+
name: { value: p.name, enumerable: true },
|
|
242
|
+
filename: { value: p.filename, enumerable: true },
|
|
243
|
+
description: { value: p.description, enumerable: true },
|
|
244
|
+
length: { value: 1, enumerable: true }
|
|
245
|
+
})
|
|
246
|
+
arr[idx] = pluginObj
|
|
247
|
+
arr[p.name] = pluginObj
|
|
248
|
+
})
|
|
249
|
+
Object.defineProperty(arr, 'length', { value: plugins.length, enumerable: true })
|
|
250
|
+
arr.item = makeNative((i) => arr[i] || null, 'item')
|
|
251
|
+
arr.namedItem = makeNative((name) => arr[name] || null, 'namedItem')
|
|
252
|
+
arr.refresh = makeNative(() => {}, 'refresh')
|
|
253
|
+
return arr
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const fakePlugins = createPluginArray(pluginData)
|
|
258
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
259
|
+
get: () => fakePlugins,
|
|
260
|
+
configurable: true
|
|
261
|
+
})
|
|
262
|
+
} catch (_) {}
|
|
263
|
+
|
|
264
|
+
// 5. Hardware and Locale properties
|
|
265
|
+
Object.defineProperty(navigator, 'languages', {
|
|
266
|
+
get: () => ['id-ID', 'id', 'en-US', 'en'],
|
|
267
|
+
configurable: true
|
|
268
|
+
})
|
|
269
|
+
Object.defineProperty(navigator, 'language', { get: () => 'id-ID', configurable: true })
|
|
270
|
+
Object.defineProperty(navigator, 'platform', { get: () => 'Win32', configurable: true })
|
|
271
|
+
Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => 8, configurable: true })
|
|
272
|
+
Object.defineProperty(navigator, 'deviceMemory', { get: () => 8, configurable: true })
|
|
273
|
+
Object.defineProperty(navigator, 'maxTouchPoints', { get: () => 0, configurable: true })
|
|
274
|
+
|
|
275
|
+
// 6. User Agent Data (Edge Client Hints)
|
|
276
|
+
Object.defineProperty(navigator, 'userAgentData', {
|
|
277
|
+
get: () => ({
|
|
278
|
+
brands: [
|
|
279
|
+
{ brand: 'Not(A:Brand', version: '99' },
|
|
280
|
+
{ brand: 'Microsoft Edge', version: '133' },
|
|
281
|
+
{ brand: 'Chromium', version: '133' }
|
|
282
|
+
],
|
|
283
|
+
mobile: false,
|
|
284
|
+
platform: 'Windows',
|
|
285
|
+
getHighEntropyValues: async () => ({
|
|
286
|
+
architecture: 'x86',
|
|
287
|
+
bitness: '64',
|
|
288
|
+
brands: [
|
|
289
|
+
{ brand: 'Not(A:Brand', version: '99' },
|
|
290
|
+
{ brand: 'Microsoft Edge', version: '133' },
|
|
291
|
+
{ brand: 'Chromium', version: '133' }
|
|
292
|
+
],
|
|
293
|
+
fullVersionList: [
|
|
294
|
+
{ brand: 'Not(A:Brand', version: '99.0.0.0' },
|
|
295
|
+
{ brand: 'Microsoft Edge', version: '133.0.3065.82' },
|
|
296
|
+
{ brand: 'Chromium', version: '133.0.6943.127' }
|
|
297
|
+
],
|
|
298
|
+
mobile: false,
|
|
299
|
+
model: '',
|
|
300
|
+
platform: 'Windows',
|
|
301
|
+
platformVersion: '15.0.0'
|
|
302
|
+
})
|
|
303
|
+
}),
|
|
304
|
+
configurable: true
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
// 7. Notification Permissions
|
|
308
|
+
const originalQuery = window.navigator.permissions?.query
|
|
309
|
+
if (originalQuery) {
|
|
310
|
+
window.navigator.permissions.query = makeNative(
|
|
311
|
+
(parameters) =>
|
|
312
|
+
parameters && parameters.name === 'notifications'
|
|
313
|
+
? Promise.resolve({ state: Notification.permission || 'default', onchange: null })
|
|
314
|
+
: originalQuery.call(window.navigator.permissions, parameters),
|
|
315
|
+
'query'
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// 8. WebGL Vendor and Renderer
|
|
320
|
+
const patchParam = (proto) => {
|
|
321
|
+
const original = proto.getParameter
|
|
322
|
+
proto.getParameter = makeNative(function (param) {
|
|
323
|
+
if (param === 37445) return 'Google Inc. (Intel)'
|
|
324
|
+
if (param === 37446)
|
|
325
|
+
return 'ANGLE (Intel, Intel(R) UHD Graphics Direct3D11 vs_5_0 ps_5_0, D3D11)'
|
|
326
|
+
return original.apply(this, arguments)
|
|
327
|
+
}, 'getParameter')
|
|
328
|
+
}
|
|
329
|
+
patchParam(WebGLRenderingContext.prototype)
|
|
330
|
+
patchParam(WebGL2RenderingContext.prototype)
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
session = {
|
|
334
|
+
id: sessionId,
|
|
335
|
+
browser,
|
|
336
|
+
page,
|
|
337
|
+
url: 'about:blank',
|
|
338
|
+
title: 'New Tab',
|
|
339
|
+
idleTimeout: null,
|
|
340
|
+
isForeground: true
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Handle page close / tab close gracefully (Keep alive in background)
|
|
344
|
+
page.on('close', async () => {
|
|
345
|
+
const s = sessions.get(sessionId)
|
|
346
|
+
if (s && s.browser && s.browser.isConnected()) {
|
|
347
|
+
try {
|
|
348
|
+
const remainingPages = await s.browser.pages()
|
|
349
|
+
if (remainingPages.length > 0) {
|
|
350
|
+
s.page = remainingPages[0]
|
|
351
|
+
} else {
|
|
352
|
+
// Re-create background page if user closed the last tab
|
|
353
|
+
s.page = await s.browser.newPage()
|
|
354
|
+
await s.page.setUserAgent(userAgent)
|
|
355
|
+
await hideBrowserWindow(sessionId)
|
|
356
|
+
}
|
|
357
|
+
} catch (_) {}
|
|
358
|
+
}
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
// Pastikan window bounds diatur ke posisi normal di layar (80, 60) saat launch
|
|
362
|
+
try {
|
|
363
|
+
const client = await page.target().createCDPSession()
|
|
364
|
+
const { windowId } = await client.send('Browser.getWindowForTarget')
|
|
365
|
+
await client.send('Browser.setWindowBounds', {
|
|
366
|
+
windowId,
|
|
367
|
+
bounds: { windowState: 'normal', left: 80, top: 60, width: 1280, height: 800 }
|
|
368
|
+
})
|
|
369
|
+
} catch (_) {}
|
|
370
|
+
|
|
371
|
+
sessions.set(sessionId, session)
|
|
372
|
+
resetSessionIdleTimeout(session)
|
|
373
|
+
return session
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const DOM_PARSER_SCRIPT = `
|
|
377
|
+
(() => {
|
|
378
|
+
// 1. Injeksi Style dan Keyframe Animation jika belum ada
|
|
379
|
+
if (!document.getElementById('mark-blocker-style')) {
|
|
380
|
+
const styleEl = document.createElement('style');
|
|
381
|
+
styleEl.id = 'mark-blocker-style';
|
|
382
|
+
styleEl.textContent = \`
|
|
383
|
+
@keyframes mark-spin {
|
|
384
|
+
0% { transform: rotate(0deg); }
|
|
385
|
+
100% { transform: rotate(360deg); }
|
|
386
|
+
}
|
|
387
|
+
@keyframes mark-pulse {
|
|
388
|
+
0%, 100% { opacity: 1; transform: scale(1); }
|
|
389
|
+
50% { opacity: 0.6; transform: scale(0.98); }
|
|
390
|
+
}
|
|
391
|
+
.mark-pulse {
|
|
392
|
+
animation: mark-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
393
|
+
}
|
|
394
|
+
\`;
|
|
395
|
+
document.head ? document.head.appendChild(styleEl) : document.documentElement.appendChild(styleEl);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// 2. Injeksi atau Pulihkan Fullscreen Interaction Blocker & Top Loading Pill
|
|
399
|
+
let blocker = document.getElementById('mark-user-blocker');
|
|
400
|
+
if (!blocker) {
|
|
401
|
+
blocker = document.createElement('div');
|
|
402
|
+
blocker.id = 'mark-user-blocker';
|
|
403
|
+
document.documentElement.appendChild(blocker);
|
|
404
|
+
|
|
405
|
+
// Mencegah scroll saat blocker aktif
|
|
406
|
+
const preventScroll = (e) => {
|
|
407
|
+
if (blocker && blocker.dataset.mode !== 'unblock') {
|
|
408
|
+
e.preventDefault();
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
window.addEventListener('wheel', preventScroll, { passive: false });
|
|
412
|
+
window.addEventListener('touchmove', preventScroll, { passive: false });
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Mode default: Fullscreen blocker dengan Top Loading Pill
|
|
416
|
+
blocker.dataset.mode = 'working';
|
|
417
|
+
blocker.style.cssText = \`
|
|
418
|
+
position: fixed !important;
|
|
419
|
+
top: 0 !important;
|
|
420
|
+
left: 0 !important;
|
|
421
|
+
width: 100vw !important;
|
|
422
|
+
height: 100vh !important;
|
|
423
|
+
z-index: 2147483647 !important;
|
|
424
|
+
pointer-events: auto !important;
|
|
425
|
+
cursor: not-allowed !important;
|
|
426
|
+
display: flex !important;
|
|
427
|
+
justify-content: center !important;
|
|
428
|
+
align-items: flex-start !important;
|
|
429
|
+
padding-top: 24px !important;
|
|
430
|
+
box-sizing: border-box !important;
|
|
431
|
+
background: transparent !important;
|
|
432
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
|
|
433
|
+
\`;
|
|
434
|
+
|
|
435
|
+
blocker.innerHTML = \`
|
|
436
|
+
<div style="
|
|
437
|
+
background: rgba(25, 54, 45, 0.92);
|
|
438
|
+
backdrop-filter: blur(8px);
|
|
439
|
+
-webkit-backdrop-filter: blur(8px);
|
|
440
|
+
border: 1px solid rgba(31, 184, 84, 0.4);
|
|
441
|
+
border-radius: 30px;
|
|
442
|
+
padding: 10px 22px;
|
|
443
|
+
display: flex;
|
|
444
|
+
align-items: center;
|
|
445
|
+
gap: 12px;
|
|
446
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5), 0 0 15px rgba(31, 184, 84, 0.2);
|
|
447
|
+
color: #f1f5f9;
|
|
448
|
+
font-size: 13px;
|
|
449
|
+
font-weight: 500;
|
|
450
|
+
letter-spacing: 0.2px;
|
|
451
|
+
user-select: none;
|
|
452
|
+
pointer-events: auto;
|
|
453
|
+
">
|
|
454
|
+
<svg style="animation: mark-spin 1s linear infinite; width: 16px; height: 16px; color: #1fb854;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
455
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
|
456
|
+
</svg>
|
|
457
|
+
<span class="mark-pulse" style="color: #f8fafc;">Mark is working...</span>
|
|
458
|
+
</div>
|
|
459
|
+
\`;
|
|
460
|
+
|
|
461
|
+
// 3. Parser Elemen Interaktif
|
|
462
|
+
document.querySelectorAll('[data-mark-id]').forEach(el => el.removeAttribute('data-mark-id'));
|
|
463
|
+
|
|
464
|
+
const INTERACTIVE_SELECTORS = [
|
|
465
|
+
'a[href]', 'button', 'input', 'select', 'textarea',
|
|
466
|
+
'[role="button"]', '[role="link"]', '[role="tab"]',
|
|
467
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
468
|
+
].join(', ');
|
|
469
|
+
|
|
470
|
+
const allElements = document.querySelectorAll(INTERACTIVE_SELECTORS);
|
|
471
|
+
const results = [];
|
|
472
|
+
let markId = 1;
|
|
473
|
+
const MAX_ELEMENTS = 80;
|
|
474
|
+
const MAX_TEXT_LENGTH = 80;
|
|
475
|
+
|
|
476
|
+
for (const el of allElements) {
|
|
477
|
+
if (el.closest('#mark-user-blocker')) continue;
|
|
478
|
+
if (results.length >= MAX_ELEMENTS) break;
|
|
479
|
+
|
|
480
|
+
const style = window.getComputedStyle(el);
|
|
481
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
|
|
482
|
+
if (el.offsetWidth < 5 || el.offsetHeight < 5) continue;
|
|
483
|
+
|
|
484
|
+
const rect = el.getBoundingClientRect();
|
|
485
|
+
if (rect.bottom < 0 || rect.top > window.innerHeight) continue;
|
|
486
|
+
if (rect.right < 0 || rect.left > window.innerWidth) continue;
|
|
487
|
+
|
|
488
|
+
const tag = el.tagName.toLowerCase();
|
|
489
|
+
let type = 'Element';
|
|
490
|
+
if (tag === 'a') type = 'Link';
|
|
491
|
+
else if (tag === 'button' || el.getAttribute('role') === 'button') type = 'Button';
|
|
492
|
+
else if (tag === 'input') type = 'Input (' + (el.type || 'text') + ')';
|
|
493
|
+
else if (tag === 'select') type = 'Dropdown';
|
|
494
|
+
else if (tag === 'textarea') type = 'TextArea';
|
|
495
|
+
|
|
496
|
+
let label = el.innerText?.trim() || el.value || el.placeholder || el.getAttribute('aria-label') || el.title || '';
|
|
497
|
+
label = label.replace(/\\n/g, ' ').substring(0, MAX_TEXT_LENGTH);
|
|
498
|
+
|
|
499
|
+
el.setAttribute('data-mark-id', markId);
|
|
500
|
+
results.push('[' + markId + '] ' + type + ': "' + label + '"');
|
|
501
|
+
markId++;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const getVisibleText = () => {
|
|
505
|
+
let text = '';
|
|
506
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
|
|
507
|
+
let node;
|
|
508
|
+
let lastParent = null;
|
|
509
|
+
while ((node = walker.nextNode())) {
|
|
510
|
+
if (text.length > 8000) break;
|
|
511
|
+
const parent = node.parentElement;
|
|
512
|
+
if (!parent || parent.closest('#mark-user-blocker')) continue;
|
|
513
|
+
|
|
514
|
+
const tag = parent.tagName.toLowerCase();
|
|
515
|
+
if (tag === 'script' || tag === 'style' || tag === 'noscript') continue;
|
|
516
|
+
|
|
517
|
+
const rect = parent.getBoundingClientRect();
|
|
518
|
+
if (rect.bottom > -200 && rect.top < window.innerHeight + 1500) {
|
|
519
|
+
const val = node.nodeValue.trim();
|
|
520
|
+
if (val.length > 0) {
|
|
521
|
+
if (lastParent !== parent && ['p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li'].includes(tag)) {
|
|
522
|
+
text += '\\n';
|
|
523
|
+
}
|
|
524
|
+
text += val + ' ';
|
|
525
|
+
lastParent = parent;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return text.trim();
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
const bodyText = getVisibleText() || '';
|
|
533
|
+
const pageTitle = document.title || '';
|
|
534
|
+
const currentURL = window.location.href || '';
|
|
535
|
+
|
|
536
|
+
let output = '[URL Aktif]: ' + currentURL + '\\n';
|
|
537
|
+
output += '[Title]: ' + pageTitle + '\\n\\n';
|
|
538
|
+
output += '== ELEMEN INTERAKTIF (' + results.length + ' ditemukan) ==\\n';
|
|
539
|
+
output += results.join('\\n') + '\\n\\n';
|
|
540
|
+
output += '== TEKS UTAMA HALAMAN (Ringkasan) ==\\n';
|
|
541
|
+
output += bodyText.substring(0, 4000);
|
|
542
|
+
|
|
543
|
+
return output;
|
|
544
|
+
})()
|
|
545
|
+
`
|
|
546
|
+
|
|
547
|
+
async function broadcastPreview(session) {
|
|
548
|
+
try {
|
|
549
|
+
const screenshotBuf = await session.page.screenshot({ encoding: 'base64' })
|
|
550
|
+
if (wsHub) {
|
|
551
|
+
const dataUri = `data:image/png;base64,${screenshotBuf}`
|
|
552
|
+
wsHub.broadcast('browser:preview', {
|
|
553
|
+
sessionId: session.id,
|
|
554
|
+
thumbnail: dataUri,
|
|
555
|
+
preview: dataUri,
|
|
556
|
+
title: session.title || 'Browser Session',
|
|
557
|
+
url: session.url || 'about:blank',
|
|
558
|
+
isForeground: Boolean(session.isForeground)
|
|
559
|
+
})
|
|
560
|
+
}
|
|
561
|
+
} catch (_) {}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
async function detectChallenge(page) {
|
|
565
|
+
if (!page || page.isClosed()) return { isBlocked: false }
|
|
566
|
+
try {
|
|
567
|
+
const url = page.url() || ''
|
|
568
|
+
const title = (await page.title()) || ''
|
|
569
|
+
|
|
570
|
+
// 1. Deteksi Pola URL & Title Khas Block/CAPTCHA (Google "Unusual Traffic", Cloudflare, Cloudflare Turnstile, reCAPTCHA, hCaptcha)
|
|
571
|
+
const isGoogleBlocked =
|
|
572
|
+
url.includes('google.com/sorry') ||
|
|
573
|
+
url.includes('/sorry/index') ||
|
|
574
|
+
title.includes('Sorry...') ||
|
|
575
|
+
title.includes('unusual traffic')
|
|
576
|
+
const isCloudflareBlocked =
|
|
577
|
+
title.includes('Just a moment...') ||
|
|
578
|
+
title.includes('Attention Required!') ||
|
|
579
|
+
title.includes('Security Challenge') ||
|
|
580
|
+
title.includes('Cloudflare')
|
|
581
|
+
const isGenericCaptcha =
|
|
582
|
+
title.toLowerCase().includes('robot check') ||
|
|
583
|
+
title.toLowerCase().includes('captcha') ||
|
|
584
|
+
title.toLowerCase().includes('human verification')
|
|
585
|
+
|
|
586
|
+
if (isGoogleBlocked || isCloudflareBlocked || isGenericCaptcha) {
|
|
587
|
+
return {
|
|
588
|
+
isBlocked: true,
|
|
589
|
+
type: isGoogleBlocked
|
|
590
|
+
? 'Google Unusual Traffic'
|
|
591
|
+
: isCloudflareBlocked
|
|
592
|
+
? 'Cloudflare Challenge'
|
|
593
|
+
: 'CAPTCHA Protection',
|
|
594
|
+
url,
|
|
595
|
+
title
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// 2. Deteksi Selector DOM CAPTCHA / Iframe Challenge
|
|
600
|
+
const hasChallengeElement = await page.evaluate(() => {
|
|
601
|
+
const selectors = [
|
|
602
|
+
'#captcha-form',
|
|
603
|
+
'#challenge-running',
|
|
604
|
+
'#challenge-form',
|
|
605
|
+
'#cf-challenge-running',
|
|
606
|
+
'.cf-turnstile',
|
|
607
|
+
'.g-recaptcha',
|
|
608
|
+
'.h-captcha',
|
|
609
|
+
'iframe[src*="recaptcha"]',
|
|
610
|
+
'iframe[src*="turnstile"]',
|
|
611
|
+
'iframe[src*="hcaptcha"]',
|
|
612
|
+
'iframe[src*="challenge-platform"]'
|
|
613
|
+
]
|
|
614
|
+
return selectors.some((s) => Boolean(document.querySelector(s)))
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
if (hasChallengeElement) {
|
|
618
|
+
return {
|
|
619
|
+
isBlocked: true,
|
|
620
|
+
type: 'DOM Challenge Element Detected',
|
|
621
|
+
url,
|
|
622
|
+
title
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
} catch (_) {}
|
|
626
|
+
|
|
627
|
+
return { isBlocked: false }
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async function seedAntiBotCookies(page) {
|
|
631
|
+
try {
|
|
632
|
+
const cookies = [
|
|
633
|
+
{
|
|
634
|
+
name: 'SOCS',
|
|
635
|
+
value:
|
|
636
|
+
'CAISNQgDEitib3FfaWRlbnRpdHlmcm9udGVuZHVpc2VydmVyXzIwMjMwODI5LjA3X3AwGgJpZCADGgYIgJnPpwY',
|
|
637
|
+
domain: '.google.com',
|
|
638
|
+
path: '/'
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
name: 'CONSENT',
|
|
642
|
+
value: 'PENDING+999',
|
|
643
|
+
domain: '.google.com',
|
|
644
|
+
path: '/'
|
|
645
|
+
}
|
|
646
|
+
]
|
|
647
|
+
await page.setCookie(...cookies)
|
|
648
|
+
} catch (_) {}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export async function navigateTo(url, sessionId = 'default') {
|
|
652
|
+
const session = await getOrCreateSession(sessionId)
|
|
653
|
+
let targetUrl = url.trim()
|
|
654
|
+
if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) {
|
|
655
|
+
targetUrl = 'https://' + targetUrl
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Normalisasi URL Google Search dengan query params browser alami
|
|
659
|
+
if (targetUrl.includes('google.com/search') && !targetUrl.includes('sourceid=')) {
|
|
660
|
+
const separator = targetUrl.includes('?') ? '&' : '?'
|
|
661
|
+
targetUrl = `${targetUrl}${separator}sourceid=chrome&ie=UTF-8`
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (targetUrl.includes('google.com')) {
|
|
665
|
+
await seedAntiBotCookies(session.page)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
await session.page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 45000 })
|
|
669
|
+
session.url = session.page.url()
|
|
670
|
+
session.title = await session.page.title()
|
|
671
|
+
|
|
672
|
+
// Auto-dismiss Google Consent / Cookie dialog jika muncul
|
|
673
|
+
try {
|
|
674
|
+
if (session.url.includes('google.com') || session.url.includes('consent.google')) {
|
|
675
|
+
const consentHandled = await session.page.evaluate(() => {
|
|
676
|
+
const consentSelectors = [
|
|
677
|
+
'button[aria-label*="Setuju"]',
|
|
678
|
+
'button[aria-label*="Accept"]',
|
|
679
|
+
'#L2AGLb',
|
|
680
|
+
'button:has-text("Saya setuju")',
|
|
681
|
+
'button:has-text("Accept all")',
|
|
682
|
+
'form[action*="consent"] button'
|
|
683
|
+
]
|
|
684
|
+
for (const selector of consentSelectors) {
|
|
685
|
+
const btn = document.querySelector(selector)
|
|
686
|
+
if (btn && btn.offsetParent !== null) {
|
|
687
|
+
btn.click()
|
|
688
|
+
return true
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return false
|
|
692
|
+
})
|
|
693
|
+
if (consentHandled) {
|
|
694
|
+
await session.page
|
|
695
|
+
.waitForNavigation({ waitUntil: 'domcontentloaded', timeout: 5000 })
|
|
696
|
+
.catch(() => {})
|
|
697
|
+
session.url = session.page.url()
|
|
698
|
+
session.title = await session.page.title()
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
} catch (_) {}
|
|
702
|
+
|
|
703
|
+
await broadcastPreview(session)
|
|
704
|
+
|
|
705
|
+
// Otomatis cek apakah halaman memicu CAPTCHA / bot challenge
|
|
706
|
+
const challenge = await detectChallenge(session.page)
|
|
707
|
+
if (challenge.isBlocked) {
|
|
708
|
+
return `[PERINGATAN ANTI-BOT / CAPTCHA TERDETEKSI]: Halaman memicu perlindungan "${challenge.type}" (Judul: "${session.title}", URL: "${session.url}").\nKamu WAJIB segera memanggil tool 'browser-ask-user' dengan pesan agar pengguna menyelesaikan CAPTCHA ini!`
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
return `Berhasil membuka ${session.url} (Judul: "${session.title}"). Gunakan 'browser-read' untuk melihat isi dan elemen interaktif.`
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
export async function readDOM(sessionId = 'default') {
|
|
715
|
+
const session = await getOrCreateSession(sessionId)
|
|
716
|
+
const result = await session.page.evaluate(DOM_PARSER_SCRIPT)
|
|
717
|
+
|
|
718
|
+
session.url = session.page.url()
|
|
719
|
+
session.title = await session.page.title()
|
|
720
|
+
|
|
721
|
+
await broadcastPreview(session)
|
|
722
|
+
|
|
723
|
+
const challenge = await detectChallenge(session.page)
|
|
724
|
+
if (challenge.isBlocked) {
|
|
725
|
+
return `[PERINGATAN ANTI-BOT / CAPTCHA TERDETEKSI]: Terdeteksi "${challenge.type}" pada halaman ini!\nKamu WAJIB segera memanggil tool 'browser-ask-user' agar user dapat menyelesaikan verifikasi manusia secara langsung di jendela browser.\n\n${result}`
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return result
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export async function executeAction(data, sessionId = 'default') {
|
|
732
|
+
const session = await getOrCreateSession(sessionId)
|
|
733
|
+
const { action, id, value, direction } = data
|
|
734
|
+
|
|
735
|
+
if (action === 'click') {
|
|
736
|
+
const selector = `[data-mark-id="${id}"]`
|
|
737
|
+
await session.page.waitForSelector(selector, { timeout: 8000 })
|
|
738
|
+
await session.page.click(selector)
|
|
739
|
+
await new Promise((r) => setTimeout(r, 1000))
|
|
740
|
+
await broadcastPreview(session)
|
|
741
|
+
return `Berhasil klik elemen [${id}]. Gunakan 'browser-read' untuk melihat perubahan halaman.`
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (action === 'type') {
|
|
745
|
+
const selector = `[data-mark-id="${id}"]`
|
|
746
|
+
await session.page.waitForSelector(selector, { timeout: 8000 })
|
|
747
|
+
await session.page.click(selector)
|
|
748
|
+
await session.page.type(selector, value || '', { delay: 30 })
|
|
749
|
+
await broadcastPreview(session)
|
|
750
|
+
return `Berhasil mengetik "${value}" pada elemen [${id}].`
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
if (action === 'scroll') {
|
|
754
|
+
const delta = direction === 'up' ? -500 : 500
|
|
755
|
+
await session.page.evaluate((d) => window.scrollBy({ top: d, behavior: 'smooth' }), delta)
|
|
756
|
+
await new Promise((r) => setTimeout(r, 600))
|
|
757
|
+
await broadcastPreview(session)
|
|
758
|
+
return `Berhasil scroll ${direction}. Gunakan 'browser-read' untuk membaca elemen terbaru.`
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
if (action === 'unblock') {
|
|
762
|
+
// 1. Tampilkan jendela browser ke layar aktif agar user bisa melihat dan menyelesaikan captcha/login
|
|
763
|
+
await showBrowserWindow(sessionId)
|
|
764
|
+
|
|
765
|
+
// 2. Ubah blocker menjadi unblock prompt widget di kanan bawah
|
|
766
|
+
const promptMsg =
|
|
767
|
+
value || 'Silakan selesaikan CAPTCHA atau login pada halaman ini, lalu klik tombol di bawah.'
|
|
768
|
+
await session.page.evaluate((msg) => {
|
|
769
|
+
let blocker = document.getElementById('mark-user-blocker')
|
|
770
|
+
if (!blocker) {
|
|
771
|
+
blocker = document.createElement('div')
|
|
772
|
+
blocker.id = 'mark-user-blocker'
|
|
773
|
+
document.documentElement.appendChild(blocker)
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
blocker.dataset.mode = 'unblock'
|
|
777
|
+
blocker.style.cssText = `
|
|
778
|
+
position: fixed !important;
|
|
779
|
+
bottom: 24px !important;
|
|
780
|
+
right: 24px !important;
|
|
781
|
+
top: auto !important;
|
|
782
|
+
left: auto !important;
|
|
783
|
+
width: auto !important;
|
|
784
|
+
height: auto !important;
|
|
785
|
+
z-index: 2147483647 !important;
|
|
786
|
+
pointer-events: none !important;
|
|
787
|
+
cursor: default !important;
|
|
788
|
+
display: flex !important;
|
|
789
|
+
justify-content: flex-end !important;
|
|
790
|
+
align-items: flex-end !important;
|
|
791
|
+
background: transparent !important;
|
|
792
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
|
|
793
|
+
`
|
|
794
|
+
|
|
795
|
+
blocker.innerHTML = `
|
|
796
|
+
<div style="
|
|
797
|
+
background: rgba(15, 23, 21, 0.95);
|
|
798
|
+
backdrop-filter: blur(16px);
|
|
799
|
+
-webkit-backdrop-filter: blur(16px);
|
|
800
|
+
border: 1px solid rgba(31, 184, 84, 0.5);
|
|
801
|
+
border-radius: 16px;
|
|
802
|
+
padding: 20px 22px;
|
|
803
|
+
width: 350px;
|
|
804
|
+
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.8), 0 0 20px rgba(31, 184, 84, 0.25);
|
|
805
|
+
color: #f1f5f9;
|
|
806
|
+
box-sizing: border-box;
|
|
807
|
+
pointer-events: auto !important;
|
|
808
|
+
animation: mark-pulse 3s ease-in-out infinite;
|
|
809
|
+
">
|
|
810
|
+
<div style="display: flex; align-items: center; gap: 10px; margin-bottom: 12px;">
|
|
811
|
+
<div style="width: 10px; height: 10px; border-radius: 50%; background: #1fb854; box-shadow: 0 0 8px #1fb854;"></div>
|
|
812
|
+
<div style="font-weight: 700; color: #f8fafc; font-size: 15px; letter-spacing: 0.2px;">Mark Paused for Input</div>
|
|
813
|
+
</div>
|
|
814
|
+
<div style="font-size: 13px; line-height: 1.5; color: #cbd5e1; margin-bottom: 14px; word-break: break-word;">
|
|
815
|
+
${msg}
|
|
816
|
+
</div>
|
|
817
|
+
<input type="text" id="mark-user-input" placeholder="Komentar untuk Mark (opsional)..." style="
|
|
818
|
+
width: 100%;
|
|
819
|
+
padding: 10px 14px;
|
|
820
|
+
background: rgba(25, 54, 45, 0.6);
|
|
821
|
+
border: 1px solid rgba(31, 184, 84, 0.3);
|
|
822
|
+
border-radius: 10px;
|
|
823
|
+
color: #f8fafc;
|
|
824
|
+
font-size: 13px;
|
|
825
|
+
margin-bottom: 12px;
|
|
826
|
+
box-sizing: border-box;
|
|
827
|
+
outline: none;
|
|
828
|
+
transition: border-color 0.2s;
|
|
829
|
+
" />
|
|
830
|
+
<button id="mark-btn-selesai" style="
|
|
831
|
+
width: 100%;
|
|
832
|
+
padding: 11px 16px;
|
|
833
|
+
background: #1fb854;
|
|
834
|
+
color: #0f1715;
|
|
835
|
+
border: none;
|
|
836
|
+
border-radius: 10px;
|
|
837
|
+
font-weight: 700;
|
|
838
|
+
font-size: 13px;
|
|
839
|
+
cursor: pointer;
|
|
840
|
+
transition: all 0.2s ease;
|
|
841
|
+
box-shadow: 0 4px 12px rgba(31, 184, 84, 0.3);
|
|
842
|
+
">Lanjutkan Otomasi (Resume)</button>
|
|
843
|
+
</div>
|
|
844
|
+
`
|
|
845
|
+
|
|
846
|
+
// 3. Setup listener click dan Enter
|
|
847
|
+
const btn = document.getElementById('mark-btn-selesai')
|
|
848
|
+
const input = document.getElementById('mark-user-input')
|
|
849
|
+
|
|
850
|
+
const submit = () => {
|
|
851
|
+
const comment = input ? input.value : ''
|
|
852
|
+
btn.innerText = 'Melanjutkan...'
|
|
853
|
+
btn.style.opacity = '0.7'
|
|
854
|
+
btn.disabled = true
|
|
855
|
+
document.title = 'MARK_UNBLOCK_DONE:' + (comment.trim() || 'Sudah selesai.')
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (btn) btn.onclick = submit
|
|
859
|
+
if (input) {
|
|
860
|
+
input.onkeydown = (e) => {
|
|
861
|
+
if (e.key === 'Enter') submit()
|
|
862
|
+
}
|
|
863
|
+
setTimeout(() => input.focus(), 300)
|
|
864
|
+
}
|
|
865
|
+
}, promptMsg)
|
|
866
|
+
|
|
867
|
+
await broadcastPreview(session)
|
|
868
|
+
|
|
869
|
+
// 4. Tunggu respons pengguna via perubahan document.title di page
|
|
870
|
+
const userFeedback = await new Promise((resolve) => {
|
|
871
|
+
let resolved = false
|
|
872
|
+
const timeoutId = setTimeout(() => {
|
|
873
|
+
if (!resolved) {
|
|
874
|
+
resolved = true
|
|
875
|
+
session.page.off('titlechanged', titleListener)
|
|
876
|
+
resolve('Waktu tunggu intervensi user habis (600 detik).')
|
|
877
|
+
}
|
|
878
|
+
}, 600000) // 10 menit timeout
|
|
879
|
+
|
|
880
|
+
const titleListener = (newTitle) => {
|
|
881
|
+
if (newTitle && newTitle.startsWith('MARK_UNBLOCK_DONE:')) {
|
|
882
|
+
if (!resolved) {
|
|
883
|
+
resolved = true
|
|
884
|
+
clearTimeout(timeoutId)
|
|
885
|
+
session.page.off('titlechanged', titleListener)
|
|
886
|
+
const userText = newTitle.replace('MARK_UNBLOCK_DONE:', '').trim()
|
|
887
|
+
resolve(userText || 'Sudah selesai.')
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
session.page.on('titlechanged', titleListener)
|
|
893
|
+
})
|
|
894
|
+
|
|
895
|
+
// 5. Kembalikan blocker ke mode Fullscreen working indicator
|
|
896
|
+
await session.page.evaluate(() => {
|
|
897
|
+
let blocker = document.getElementById('mark-user-blocker')
|
|
898
|
+
if (blocker) {
|
|
899
|
+
blocker.dataset.mode = 'working'
|
|
900
|
+
blocker.style.cssText = `
|
|
901
|
+
position: fixed !important;
|
|
902
|
+
top: 0 !important;
|
|
903
|
+
left: 0 !important;
|
|
904
|
+
width: 100vw !important;
|
|
905
|
+
height: 100vh !important;
|
|
906
|
+
z-index: 2147483647 !important;
|
|
907
|
+
pointer-events: auto !important;
|
|
908
|
+
cursor: not-allowed !important;
|
|
909
|
+
display: flex !important;
|
|
910
|
+
justify-content: center !important;
|
|
911
|
+
align-items: flex-start !important;
|
|
912
|
+
padding-top: 24px !important;
|
|
913
|
+
box-sizing: border-box !important;
|
|
914
|
+
background: transparent !important;
|
|
915
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
|
|
916
|
+
`
|
|
917
|
+
blocker.innerHTML = `
|
|
918
|
+
<div style="
|
|
919
|
+
background: rgba(25, 54, 45, 0.92);
|
|
920
|
+
backdrop-filter: blur(8px);
|
|
921
|
+
-webkit-backdrop-filter: blur(8px);
|
|
922
|
+
border: 1px solid rgba(31, 184, 84, 0.4);
|
|
923
|
+
border-radius: 30px;
|
|
924
|
+
padding: 10px 22px;
|
|
925
|
+
display: flex;
|
|
926
|
+
align-items: center;
|
|
927
|
+
gap: 12px;
|
|
928
|
+
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5), 0 0 15px rgba(31, 184, 84, 0.2);
|
|
929
|
+
color: #f1f5f9;
|
|
930
|
+
font-size: 13px;
|
|
931
|
+
font-weight: 500;
|
|
932
|
+
letter-spacing: 0.2px;
|
|
933
|
+
user-select: none;
|
|
934
|
+
pointer-events: auto;
|
|
935
|
+
">
|
|
936
|
+
<svg style="animation: mark-spin 1s linear infinite; width: 16px; height: 16px; color: #1fb854;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
937
|
+
<path d="M21 12a9 9 0 1 1-6.219-8.56"/>
|
|
938
|
+
</svg>
|
|
939
|
+
<span class="mark-pulse" style="color: #f8fafc;">Mark is working...</span>
|
|
940
|
+
</div>
|
|
941
|
+
`
|
|
942
|
+
}
|
|
943
|
+
})
|
|
944
|
+
|
|
945
|
+
// 6. Baca ulang DOM setelah user selesai
|
|
946
|
+
const updatedDOM = await readDOM(sessionId)
|
|
947
|
+
return `[USER SELESAI INTERVENSI]: Catatan user: "${userFeedback}".\nBerikut kondisi DOM terbaru setelah intervensi:\n${updatedDOM}`
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return `Aksi ${action} tidak dikenali.`
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
export async function executeScript(code, sessionId = 'default') {
|
|
954
|
+
const session = await getOrCreateSession(sessionId)
|
|
955
|
+
const res = await session.page.evaluate(code)
|
|
956
|
+
await broadcastPreview(session)
|
|
957
|
+
return typeof res === 'string' ? res : JSON.stringify(res)
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
export async function extractData(selector, sessionId = 'default') {
|
|
961
|
+
const session = await getOrCreateSession(sessionId)
|
|
962
|
+
const text = await session.page.$$eval(selector, (els) =>
|
|
963
|
+
els.map((e) => e.innerText.trim()).filter(Boolean)
|
|
964
|
+
)
|
|
965
|
+
return JSON.stringify(text)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
export async function takeScreenshot(filename = 'screenshot.png', sessionId = 'default') {
|
|
969
|
+
const session = await getOrCreateSession(sessionId)
|
|
970
|
+
const outPath = path.isAbsolute(filename)
|
|
971
|
+
? filename
|
|
972
|
+
: path.join(os.homedir(), 'Desktop', filename)
|
|
973
|
+
const buffer = await session.page.screenshot({ path: outPath, fullPage: false })
|
|
974
|
+
const base64 = buffer ? buffer.toString('base64') : ''
|
|
975
|
+
const dataUrl = base64 ? `data:image/png;base64,${base64}` : null
|
|
976
|
+
return {
|
|
977
|
+
success: true,
|
|
978
|
+
message: `Screenshot berhasil disimpan ke ${outPath}`,
|
|
979
|
+
path: outPath,
|
|
980
|
+
dataUrl
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
export async function downloadFile(url, filename, sessionId = 'default') {
|
|
985
|
+
const session = await getOrCreateSession(sessionId)
|
|
986
|
+
const outPath = path.join(os.homedir(), 'Downloads', filename)
|
|
987
|
+
const viewSource = await session.page.goto(url)
|
|
988
|
+
fs.writeFileSync(outPath, await viewSource.buffer())
|
|
989
|
+
return `File berhasil diunduh ke ${outPath}`
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
export async function closeBrowser(sessionId = 'default') {
|
|
993
|
+
const session = sessions.get(sessionId)
|
|
994
|
+
if (session && session.browser) {
|
|
995
|
+
if (session.idleTimeout) clearTimeout(session.idleTimeout)
|
|
996
|
+
try {
|
|
997
|
+
await session.browser.close()
|
|
998
|
+
} catch (_) {}
|
|
999
|
+
sessions.delete(sessionId)
|
|
1000
|
+
|
|
1001
|
+
if (wsHub) {
|
|
1002
|
+
wsHub.broadcast('browser:preview', {
|
|
1003
|
+
sessionId,
|
|
1004
|
+
closed: true
|
|
1005
|
+
})
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
return `Browser session '${sessionId}' berhasil ditutup.`
|
|
1009
|
+
}
|
|
1010
|
+
return `Browser session '${sessionId}' tidak sedang berjalan.`
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export function forceProcessWindowToForeground(pid) {
|
|
1014
|
+
if (os.platform() !== 'win32' || !pid) return Promise.resolve()
|
|
1015
|
+
return new Promise((resolve) => {
|
|
1016
|
+
const psScript = `
|
|
1017
|
+
$code = @'
|
|
1018
|
+
using System;
|
|
1019
|
+
using System.Runtime.InteropServices;
|
|
1020
|
+
public class Win32Focus {
|
|
1021
|
+
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
|
|
1022
|
+
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
|
|
1023
|
+
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
|
1024
|
+
[DllImport("user32.dll")] public static extern bool IsWindowVisible(IntPtr hWnd);
|
|
1025
|
+
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
|
1026
|
+
[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);
|
|
1027
|
+
[DllImport("user32.dll")] public static extern bool BringWindowToTop(IntPtr hWnd);
|
|
1028
|
+
[DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
|
1029
|
+
[DllImport("user32.dll")] public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
|
|
1030
|
+
[DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();
|
|
1031
|
+
[DllImport("user32.dll")] public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
|
|
1032
|
+
[DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo);
|
|
1033
|
+
[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
|
|
1034
|
+
|
|
1035
|
+
public static void FocusPid(int pid) {
|
|
1036
|
+
IntPtr targetHwnd = IntPtr.Zero;
|
|
1037
|
+
EnumWindows((hWnd, lParam) => {
|
|
1038
|
+
uint procId;
|
|
1039
|
+
GetWindowThreadProcessId(hWnd, out procId);
|
|
1040
|
+
if (procId == (uint)pid && IsWindowVisible(hWnd)) {
|
|
1041
|
+
targetHwnd = hWnd;
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
return true;
|
|
1045
|
+
}, IntPtr.Zero);
|
|
1046
|
+
if (targetHwnd != IntPtr.Zero) {
|
|
1047
|
+
ShowWindow(targetHwnd, 9); // SW_RESTORE
|
|
1048
|
+
IntPtr fg = GetForegroundWindow();
|
|
1049
|
+
uint fgThread = GetWindowThreadProcessId(fg, out _);
|
|
1050
|
+
uint curThread = GetCurrentThreadId();
|
|
1051
|
+
uint targetThread = GetWindowThreadProcessId(targetHwnd, out _);
|
|
1052
|
+
if (fgThread != targetThread) {
|
|
1053
|
+
AttachThreadInput(fgThread, targetThread, true);
|
|
1054
|
+
AttachThreadInput(curThread, targetThread, true);
|
|
1055
|
+
}
|
|
1056
|
+
keybd_event(0, 0, 0, 0);
|
|
1057
|
+
SetWindowPos(targetHwnd, new IntPtr(-1), 0, 0, 0, 0, 0x0001 | 0x0002 | 0x0040); // HWND_TOPMOST
|
|
1058
|
+
SetWindowPos(targetHwnd, new IntPtr(-2), 0, 0, 0, 0, 0x0001 | 0x0002 | 0x0040); // HWND_NOTOPMOST
|
|
1059
|
+
BringWindowToTop(targetHwnd);
|
|
1060
|
+
SetForegroundWindow(targetHwnd);
|
|
1061
|
+
SwitchToThisWindow(targetHwnd, true);
|
|
1062
|
+
if (fgThread != targetThread) {
|
|
1063
|
+
AttachThreadInput(fgThread, targetThread, false);
|
|
1064
|
+
AttachThreadInput(curThread, targetThread, false);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
'@
|
|
1070
|
+
Add-Type -TypeDefinition $code -Language CSharp
|
|
1071
|
+
[Win32Focus]::FocusPid(${pid})
|
|
1072
|
+
`
|
|
1073
|
+
const enc = Buffer.from(psScript, 'utf16le').toString('base64')
|
|
1074
|
+
exec(`powershell.exe -NoProfile -NonInteractive -EncodedCommand ${enc}`, () => resolve())
|
|
1075
|
+
})
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
export async function showBrowserWindow(sessionId = 'default') {
|
|
1079
|
+
const session = sessions.get(sessionId)
|
|
1080
|
+
if (session && session.page) {
|
|
1081
|
+
try {
|
|
1082
|
+
const client = await session.page.target().createCDPSession()
|
|
1083
|
+
const { windowId } = await client.send('Browser.getWindowForTarget')
|
|
1084
|
+
await client.send('Browser.setWindowBounds', {
|
|
1085
|
+
windowId,
|
|
1086
|
+
bounds: { windowState: 'normal', left: 80, top: 60, width: 1280, height: 800 }
|
|
1087
|
+
})
|
|
1088
|
+
await session.page.bringToFront()
|
|
1089
|
+
session.isForeground = true
|
|
1090
|
+
|
|
1091
|
+
// Bypass Windows Foreground Lock dan paksa window naik ke paling atas
|
|
1092
|
+
const pid = session.browser?.process()?.pid
|
|
1093
|
+
if (pid) {
|
|
1094
|
+
await forceProcessWindowToForeground(pid)
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
return `Berhasil menampilkan jendela browser untuk sesi '${sessionId}'.`
|
|
1098
|
+
} catch (_) {
|
|
1099
|
+
try {
|
|
1100
|
+
await session.page.bringToFront()
|
|
1101
|
+
session.isForeground = true
|
|
1102
|
+
const pid = session.browser?.process()?.pid
|
|
1103
|
+
if (pid) {
|
|
1104
|
+
await forceProcessWindowToForeground(pid)
|
|
1105
|
+
}
|
|
1106
|
+
return `Berhasil membawa browser sesi '${sessionId}' ke depan.`
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
return `Gagal menampilkan jendela browser: ${err.message}`
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
return `Sesi browser '${sessionId}' tidak ditemukan atau sedang tidak aktif.`
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
export async function hideBrowserWindow(sessionId = 'default') {
|
|
1116
|
+
const session = sessions.get(sessionId)
|
|
1117
|
+
if (session && session.page) {
|
|
1118
|
+
try {
|
|
1119
|
+
const client = await session.page.target().createCDPSession()
|
|
1120
|
+
const { windowId } = await client.send('Browser.getWindowForTarget')
|
|
1121
|
+
await client.send('Browser.setWindowBounds', {
|
|
1122
|
+
windowId,
|
|
1123
|
+
bounds: { windowState: 'minimized' }
|
|
1124
|
+
})
|
|
1125
|
+
session.isForeground = false
|
|
1126
|
+
return `Berhasil meminimalkan jendela browser sesi '${sessionId}' ke latar belakang.`
|
|
1127
|
+
} catch (err) {
|
|
1128
|
+
return `Gagal menyembunyikan jendela browser: ${err.message}`
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return `Sesi browser '${sessionId}' tidak ditemukan atau sedang tidak aktif.`
|
|
1132
|
+
}
|