├── Chrome.ahk ├── Examples ├── EventCallbacks.ahk ├── ExportPDF.ahk ├── InjectJS.ahk ├── Pastebin.ahk ├── 打开百度搜索内容并获取结果的示例.ahk └── 监听事件.ahk ├── LICENSE └── README.md /Chrome.ahk: -------------------------------------------------------------------------------- 1 | ; Chrome.ahk-plus v1.4.0 2 | ; https://github.com/telppa/Chrome.ahk-plus 3 | 4 | ; 基于 GeekDude 2023.03.21 Release 版修改,与 GeekDude 版相比有以下增强。 5 | ; 大幅简化元素及框架的操作。 6 | ; 支持谷歌 Chrome 与微软 Edge 。 7 | ; 报错可直接定位到用户代码,而不是库代码。 8 | ; 为所有可能造成死循环的地方添加了默认30秒的超时参数。 9 | ; 简化了 Chrome 用户配置目录的创建。 10 | ; 修复了 Chrome 打开缓慢而报错的问题。 11 | ; 修复了找不到开始菜单中的 Chrome 快捷方式而报错的问题。 12 | 13 | ; page.Call() 支持的参数与命令行支持的参数 14 | ; https://chromedevtools.github.io/devtools-protocol/tot/Browser/ 15 | ; https://peter.sh/experiments/chromium-command-line-switches/ 16 | 17 | ; 注意事项: 18 | ; 相同的 ProfilePath ,无法指定不同的 DebugPort ,会被 Chrome 自动修改为相同的 DebugPort 。 19 | ; 不要在 page.Evaluate() 前加 Critical ,这会导致 Evaluate() 返回不了值,而你很难发现它出错了。 20 | ; 为了与 GeekDude 版保持兼容性,本增强版从 1.3.5 开始调换了所有 Timeout 参数的位置,故可能与本增强版的旧版不兼容。 21 | 22 | ; 以后的人要想同步更新这个库,强烈建议使用 BCompare 之类的比较程序,比较着 GeekDude Release 版本进行更新。 23 | ; 不要尝试用未 Release 版本(即代码中有 “#Include JSON.ahk” “#Include WebSocket.ahk” 字样)进行更新。 24 | ; 因为这样会有太多坑,你要么搞不定,要么会浪费很多无谓的时间!!!!!! 25 | 26 | class Chrome 27 | { 28 | static Version := "1.4.0" 29 | 30 | static DebugPort := 9222 31 | 32 | /* 33 | Escape a string in a manner suitable for command line parameters 34 | */ 35 | CliEscape(Param) 36 | { 37 | return """" RegExReplace(Param, "(\\*)""", "$1$1\""") """" 38 | } 39 | 40 | /* 41 | Finds instances of chrome in debug mode and the ports they're running 42 | on. If no instances are found, returns a false value. If one or more 43 | instances are found, returns an associative array where the keys are 44 | the ports, and the values are the full command line texts used to start 45 | the processes. 46 | 47 | One example of how this may be used would be to open chrome on a 48 | different port if an instance of chrome is already open on the port 49 | you wanted to used. 50 | 51 | ``` 52 | ; If the wanted port is taken, use the largest taken port plus one 53 | DebugPort := 9222 54 | if (Chromes := Chrome.FindInstances()).HasKey(DebugPort) 55 | DebugPort := Chromes.MaxIndex() + 1 56 | ChromeInst := new Chrome(ProfilePath,,,, DebugPort) 57 | ``` 58 | 59 | Another use would be to scan for running instances and attach to one 60 | instead of starting a new instance. 61 | 62 | ``` 63 | if (Chromes := Chrome.FindInstances()) 64 | ChromeInst := {"base": Chrome, "DebugPort": Chromes.MinIndex()} 65 | else 66 | ChromeInst := new Chrome(ProfilePath) 67 | ``` 68 | */ 69 | FindInstances() 70 | { 71 | static Needle := "--remote-debugging-port=(\d+)" 72 | 73 | for k, v in ["chrome.exe", "msedge.exe"] 74 | { 75 | Out := {} 76 | for Item in ComObjGet("winmgmts:") 77 | .ExecQuery("SELECT CommandLine FROM Win32_Process" 78 | . " WHERE Name = '" v "'") 79 | ; https://learn.microsoft.com/zh-cn/windows/win32/cimwin32prov/win32-process 80 | if RegExMatch(Item.CommandLine, Needle, Match) 81 | Out[Match1] := Item.CommandLine 82 | 83 | if (Out.MaxIndex()) 84 | break 85 | } 86 | return Out.MaxIndex() ? Out : False 87 | } 88 | 89 | /* 90 | ProfilePath - Path to the user profile directory to use. Will use the standard if left blank. 91 | URLs - The page or array of pages for Chrome to load when it opens 92 | Flags - Additional flags for chrome when launching 93 | ChromePath - Path to chrome.exe, will detect from start menu when left blank 94 | DebugPort - What port should Chrome's remote debugging server run on 95 | */ 96 | __New(ProfilePath:="ChromeProfile", URLs:="about:blank", Flags:="", ChromePath:="", DebugPort:="") 97 | { 98 | if (ProfilePath == "") 99 | throw Exception("Need a profile directory", -1) 100 | ; Verify ProfilePath 101 | if (!InStr(FileExist(ProfilePath), "D")) 102 | { 103 | FileCreateDir, %ProfilePath% 104 | if (ErrorLevel = 1) 105 | throw Exception("Failed to create the profile directory", -1) 106 | } 107 | cc := DllCall("GetFullPathName", "str", ProfilePath, "uint", 0, "ptr", 0, "ptr", 0, "uint") 108 | VarSetCapacity(buf, cc*(A_IsUnicode?2:1)) 109 | DllCall("GetFullPathName", "str", ProfilePath, "uint", cc, "str", buf, "ptr", 0, "uint") 110 | this.ProfilePath := ProfilePath := buf 111 | 112 | ; Try to find chrome or msedge path 113 | if (ChromePath == "") 114 | { 115 | ; Try to find chrome path 116 | if !FileExist(ChromePath) 117 | ; By using winmgmts to get the path of a shortcut file we fix an edge case where the path is retreived incorrectly 118 | ; if using the ahk executable with a different architecture than the OS (using 32bit AHK on a 64bit OS for example) 119 | try ChromePath := ComObjGet("winmgmts:").ExecQuery("Select * from Win32_ShortcutFile where Name=""" StrReplace(A_StartMenuCommon "\Programs\Google Chrome.lnk", "\", "\\") """").ItemIndex(0).Target 120 | 121 | if !FileExist(ChromePath) 122 | RegRead, ChromePath, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe 123 | 124 | ; Try to find msedge path 125 | if !FileExist(ChromePath) 126 | try ChromePath := ComObjGet("winmgmts:").ExecQuery("Select * from Win32_ShortcutFile where Name=""" StrReplace(A_StartMenuCommon "\Programs\Microsoft Edge.lnk", "\", "\\") """").ItemIndex(0).Target 127 | 128 | if !FileExist(ChromePath) 129 | RegRead ChromePath, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\msedge.exe 130 | } 131 | 132 | ; Verify ChromePath 133 | if !FileExist(ChromePath) 134 | throw Exception("Chrome and Edge could not be found", -1) 135 | this.ChromePath := ChromePath 136 | 137 | ; Verify DebugPort 138 | if (DebugPort != "") 139 | { 140 | if DebugPort is not integer 141 | throw Exception("DebugPort must be a positive integer", -1) 142 | else if (DebugPort <= 0) 143 | throw Exception("DebugPort must be a positive integer", -1) 144 | this.DebugPort := DebugPort 145 | } 146 | 147 | ; Escape the URL(s) 148 | URLString := "" 149 | for Index, URL in IsObject(URLs) ? URLs : [URLs] 150 | URLString .= " " this.CliEscape(URL) 151 | 152 | Run, % this.CliEscape(ChromePath) 153 | . " --remote-debugging-port=" this.DebugPort 154 | . " --remote-allow-origins=*" 155 | . (ProfilePath ? " --user-data-dir=" this.CliEscape(ProfilePath) : "") 156 | . (Flags ? " " Flags : "") 157 | . URLString 158 | ,,, OutputVarPID 159 | this.PID := OutputVarPID 160 | } 161 | 162 | /* 163 | End Chrome by terminating the process. 164 | */ 165 | Kill() 166 | { 167 | Process, Close, % this.PID 168 | } 169 | 170 | /* 171 | Queries chrome for a list of pages that expose a debug interface. 172 | In addition to standard tabs, these include pages such as extension 173 | configuration pages. 174 | */ 175 | GetPageList(Timeout:=30) 176 | { 177 | http := ComObjCreate("WinHttp.WinHttpRequest.5.1") 178 | StartTime := A_TickCount 179 | loop 180 | { 181 | ; It is easy to fail here because "new chrome()" takes a long time to execute. 182 | ; Therefore, it will be tried again and again within 30 seconds until it succeeds or timeout. 183 | ; 极端情况可能出现因为 page.Call("Browser.close",, false) 不等待返回值的关闭了浏览器 184 | ; 然后又极快的使用 FindInstances() 附着在了正在关闭的 chrome 进程上导致超时 185 | ; 实际案例就是在聚合翻译器的重启功能上 186 | if (A_TickCount-StartTime > Timeout*1000) 187 | throw Exception("Get page list timeout") 188 | else 189 | try 190 | { 191 | http.Open("GET", "http://127.0.0.1:" this.DebugPort "/json", true) 192 | http.Send() 193 | http.WaitForResponse(-1) 194 | if (http.Status = 200) 195 | break 196 | } 197 | 198 | Sleep 50 199 | } 200 | return this.JSON.Load(http.responseText) 201 | } 202 | 203 | /* 204 | Returns a connection to the debug interface of a page that matches the 205 | provided criteria. When multiple pages match the criteria, they appear 206 | ordered by how recently the pages were opened. 207 | 208 | Key - The key from the page list to search for, such as "url" or "title" 209 | Value - The value to search for in the provided key 210 | MatchMode - What kind of search to use, such as "exact", "contains", "startswith", or "regex" 211 | Index - If multiple pages match the given criteria, which one of them to return 212 | FnCallback - A function to be called whenever message is received from the page 213 | Timeout - Maximum number of seconds to wait for the page connection 214 | */ 215 | GetPageBy(Key, Value, MatchMode:="exact", Index:=1, FnCallback:="", FnClose:="", Timeout:=30) 216 | { 217 | try 218 | { 219 | Count := 0 220 | for n, PageData in this.GetPageList() 221 | { 222 | if (((MatchMode = "exact" && PageData[Key] = Value) ; Case insensitive 223 | || (MatchMode = "contains" && InStr(PageData[Key], Value)) 224 | || (MatchMode = "startswith" && InStr(PageData[Key], Value) == 1) 225 | || (MatchMode = "regex" && PageData[Key] ~= Value)) 226 | && ++Count == Index) 227 | return new this.Page(PageData.webSocketDebuggerUrl, FnCallback, FnClose, Timeout) 228 | } 229 | } 230 | catch e 231 | throw Exception(e.Message, -1) 232 | } 233 | 234 | /* 235 | Shorthand for GetPageBy("url", Value, "startswith") 236 | */ 237 | GetPageByURL(Value, MatchMode:="startswith", Index:=1, FnCallback:="", FnClose:="", Timeout:=30) 238 | { 239 | try 240 | return this.GetPageBy("url", Value, MatchMode, Index, FnCallback, FnClose, Timeout) 241 | catch e 242 | throw Exception(e.Message, -1) 243 | } 244 | 245 | /* 246 | Shorthand for GetPageBy("title", Value, "startswith") 247 | */ 248 | GetPageByTitle(Value, MatchMode:="startswith", Index:=1, FnCallback:="", FnClose:="", Timeout:=30) 249 | { 250 | try 251 | return this.GetPageBy("title", Value, MatchMode, Index, FnCallback, FnClose, Timeout) 252 | catch e 253 | throw Exception(e.Message, -1) 254 | } 255 | 256 | /* 257 | Shorthand for GetPageBy("type", Type, "exact") 258 | 259 | The default type to search for is "page", which is the visible area of 260 | a normal Chrome tab. 261 | */ 262 | GetPage(Index:=1, Type:="page", FnCallback:="", FnClose:="", Timeout:=30) 263 | { 264 | try 265 | return this.GetPageBy("type", Type, "exact", Index, FnCallback, FnClose, Timeout) 266 | catch e 267 | throw Exception(e.Message, -1) 268 | } 269 | 270 | /* 271 | Connects to the debug interface of a page given its WebSocket URL. 272 | */ 273 | class Page 274 | { 275 | Connected := False 276 | Id := 0 277 | Responses := [] 278 | TargetId := "" 279 | Root := "" 280 | NodeId := "" 281 | 282 | /* 283 | WsUrl - The desired page's WebSocket URL 284 | FnCallback - A function to be called whenever message is received 285 | FnClose - A function to be called whenever the page connection is lost 286 | Timeout - Maximum number of seconds to wait for the page connection 287 | */ 288 | __New(WsUrl, FnCallback:="", FnClose:="", Timeout:=30) 289 | { 290 | this.FnCallback := FnCallback 291 | this.FnClose := FnClose 292 | ; Here is no waiting for a response so no need to add a timeout 293 | ; The method has a hide param in the first, so we need pass this in first 294 | this.BoundKeepAlive := this.Call.Bind(this, "Browser.getVersion",, False) 295 | 296 | ; TODO: Throw exception on invalid objects 297 | if IsObject(WsUrl) 298 | WsUrl := WsUrl.webSocketDebuggerUrl 299 | 300 | ; MUST PASS AN ADDRESS INSTEAD OF A VALUE 301 | ; or it will create circular references like the following 302 | ; this.ws.base.parent.ws.base.parent.ws.base.parent 303 | ; circular references will cause the Element.__Get.this.Clone() to fail 304 | ; There is no need to increase the reference count for ParentAddress 305 | ; because its lifetime is with the class Page 306 | ; Pass this.Event to cover the WebSocket's internal event dispatcher 307 | ws := {"base": this.WebSocket, "_Event": this.Event, "ParentAddress": &this} 308 | this.ws := new ws(WsUrl) 309 | 310 | ; The timeout here is perhaps duplicated with the previous line 311 | StartTime := A_TickCount 312 | while !this.Connected 313 | { 314 | if (A_TickCount-StartTime > Timeout*1000) 315 | throw Exception("Page connection timeout") 316 | else 317 | Sleep 50 318 | } 319 | 320 | ; Target Domain need 321 | RegExMatch(WsUrl, "page/(.+)", TargetId) 322 | this.TargetId := TargetId1 323 | 324 | ; DOM Domain need 325 | this.UpdateRoot() 326 | } 327 | 328 | /* 329 | Calls the specified endpoint and provides it with the given 330 | parameters. 331 | 332 | DomainAndMethod - The endpoint domain and method name for the 333 | endpoint you would like to call. For example: 334 | PageInst.Call("Browser.close") 335 | PageInst.Call("Schema.getDomains") 336 | 337 | Params - An associative array of parameters to be provided to the 338 | endpoint. For example: 339 | PageInst.Call("Page.printToPDF", {"scale": 0.5 ; Numeric Value 340 | , "landscape": Chrome.JSON.True() ; Boolean Value 341 | , "pageRanges: "1-5, 8, 11-13"}) ; String Value 342 | PageInst.Call("Page.navigate", {"url": "https://autohotkey.com/"}) 343 | 344 | WaitForResponse - Whether to block until a response is received from 345 | Chrome, which is necessary to receive a return value, or whether 346 | to continue on with the script without waiting for a response. 347 | 348 | Timeout - Maximum number of seconds to wait for a response. 349 | */ 350 | Call(DomainAndMethod, Params:="", WaitForResponse:=True, Timeout:=30) 351 | { 352 | if !this.Connected 353 | throw Exception("Not connected to tab", -1) 354 | 355 | ; Avoid external calls to DOM.getDocument that destroys the internal variable this.Root 356 | if (DomainAndMethod="DOM.getDocument" and IsObject(this.Root)) 357 | return this.Root 358 | 359 | if (DomainAndMethod = "DOM.getRoot") 360 | DomainAndMethod := "DOM.getDocument" 361 | 362 | ; Use a temporary variable for Id in case more calls are made 363 | ; before we receive a response. 364 | Id := this.Id += 1 365 | ; this.responses[Id] must be created before this.ws.Send() 366 | ; or we will get response timeout if we receive a reply very soon. 367 | if (WaitForResponse) 368 | this.responses[Id] := false 369 | 370 | this.ws.Send(Chrome.JSON.Dump({"id": Id 371 | , "params": Params ? Params : {} 372 | , "method": DomainAndMethod})) 373 | 374 | if !WaitForResponse 375 | return 376 | 377 | ; Wait for the response 378 | StartTime := A_TickCount 379 | while !this.responses[Id] 380 | { 381 | if (A_TickCount-StartTime > Timeout*1000) 382 | throw Exception(DomainAndMethod " response timeout", -1) 383 | else 384 | Sleep 10 385 | } 386 | 387 | ; Get the response, check if it's an error 388 | response := this.responses.Delete(Id) 389 | if (response.error) 390 | throw Exception("Chrome indicated error in response", -1, Chrome.JSON.Dump(response.error)) 391 | 392 | return response.result 393 | } 394 | 395 | /* 396 | Run some JavaScript on the page. For example: 397 | 398 | PageInst.Evaluate("alert(""I can't believe it's not IE!"");") 399 | PageInst.Evaluate("document.getElementsByTagName('button')[0].click();") 400 | */ 401 | Evaluate(JS, Timeout:=30) 402 | { 403 | try 404 | { 405 | ; You can see the parameters of Runtime.evaluate in the protocol monitor 406 | ; after pressing Enter in the chrome devtools - console. 407 | ; Missing parameter “uniqueContextId”. 408 | response := this.Call("Runtime.evaluate", 409 | (LTrim Join 410 | { 411 | "allowUnsafeEvalBlockedByCSP": Chrome.JSON.False, 412 | "awaitPromise": Chrome.JSON.False, 413 | "expression": JS, 414 | "generatePreview": Chrome.JSON.True, 415 | "includeCommandLineAPI": Chrome.JSON.True, 416 | "objectGroup": "console", 417 | "replMode": Chrome.JSON.True, 418 | "returnByValue": Chrome.JSON.False, 419 | "silent": Chrome.JSON.False, 420 | "userGesture": Chrome.JSON.True 421 | } 422 | ), , Timeout) 423 | 424 | if (response.exceptionDetails) 425 | throw Exception(response.result.description 426 | , -1 427 | , Chrome.JSON.Dump({"Code": JS, "exceptionDetails": response.exceptionDetails})) 428 | 429 | return response.result 430 | } 431 | catch e 432 | throw Exception(e.Message, -1) 433 | } 434 | 435 | /* 436 | Waits for the page's readyState to match the DesiredState. 437 | 438 | DesiredState - The state to wait for the page's ReadyState to match 439 | Interval - How often it should check whether the state matches 440 | Timeout - Maximum number of seconds to wait for the page's ReadyState to match 441 | */ 442 | WaitForLoad(DesiredState:="complete", Interval:=100, Timeout:=30) 443 | { 444 | try 445 | { 446 | StartTime := A_TickCount 447 | while this.Evaluate("document.readyState").value != DesiredState 448 | { 449 | if (A_TickCount-StartTime > Timeout*1000) 450 | throw Exception("Wait for page " DesiredState " timeout", -1) 451 | else 452 | Sleep Interval 453 | } 454 | } 455 | catch e 456 | throw Exception(e.Message, -1) 457 | } 458 | 459 | /* 460 | Internal function triggered when the script receives a message on 461 | the WebSocket connected to the page. 462 | */ 463 | Event(EventName, Event) 464 | { 465 | ; If it was called from the WebSocket adjust the class context 466 | if this.ParentAddress 467 | this := Object(this.ParentAddress) 468 | 469 | if (EventName == "Error") 470 | { 471 | throw Exception("Error: " Event.code, -1) 472 | } 473 | else if (EventName == "Open") 474 | { 475 | this.Connected := True 476 | BoundKeepAlive := this.BoundKeepAlive 477 | SetTimer %BoundKeepAlive%, 15000 478 | } 479 | else if (EventName == "Message") 480 | { 481 | data := Chrome.JSON.Load(Event.data) 482 | 483 | ; It's a response for the request of Page.Call() 484 | if (data.Id && this.responses.HasKey(data.Id)) 485 | this.responses[data.Id] := data 486 | 487 | ; It's CDP events 488 | if (data.method) 489 | { 490 | ; Run the callback routine 491 | FnCallback := this.FnCallback 492 | if (newData := %fnCallback%(data)) 493 | data := newData 494 | 495 | ; Auto update root DOM node when page has been totally updated 496 | if (data.method == "DOM.documentUpdated") 497 | this.UpdateRoot() 498 | } 499 | } 500 | else if (EventName == "Close") 501 | { 502 | this.Disconnect() 503 | 504 | FnClose := this.FnClose 505 | %FnClose%(this) 506 | } 507 | } 508 | 509 | /* 510 | Close the page and disconnect from the page's debug interface, 511 | allowing the instance to be garbage collected. 512 | 513 | This method fire Page.Disconnect() automatically, so you don't 514 | need to call Page.Disconnect() manually. 515 | */ 516 | Close() 517 | { 518 | this.Call("Page.close") 519 | } 520 | 521 | /* 522 | Disconnect from the page's debug interface, allowing the instance 523 | to be garbage collected. 524 | 525 | This method should always be called when you are finished with a 526 | page or else your script will leak memory. 527 | 528 | Page.Call("Browser.close") or Page.Call("Page.close") or manually 529 | closing the page will automatically fire this method. 530 | 531 | When this method is automatically fired, DO NOT call it again 532 | or you will miss the event FnClose and FnCallback. 533 | */ 534 | Disconnect() 535 | { 536 | if !this.Connected 537 | return 538 | 539 | this.Connected := False 540 | this.ws := "" 541 | 542 | BoundKeepAlive := this.BoundKeepAlive 543 | SetTimer %BoundKeepAlive%, Delete 544 | this.Delete("BoundKeepAlive") 545 | } 546 | 547 | ; https://www.dezlearn.com/nested-iframes-example/ 548 | SwitchToMainPage() 549 | { 550 | return this.NodeId := this.Root.root.nodeId 551 | } 552 | SwitchToFrame(Index*) 553 | { 554 | try 555 | { 556 | FrameTree := this.Call("Page.getFrameTree").frameTree 557 | 558 | loop % Index.Length() 559 | { 560 | i := Index[A_Index] 561 | 562 | if (A_Index = Index.Length()) 563 | FrameId := FrameTree.childFrames[i].frame.id 564 | else 565 | FrameTree := FrameTree.childFrames[i] 566 | } 567 | 568 | if (FrameId) 569 | return this.NodeId := this._FrameIdToNodeId(FrameId) 570 | } 571 | catch e 572 | throw Exception(e.Message, -1, e.Extra) 573 | } 574 | SwitchToFrameByURL(URL, MatchMode:="startswith") 575 | { 576 | return this.NodeId := this._SwitchToFrameBy("URL", URL, MatchMode) 577 | } 578 | SwitchToFrameByName(Name, MatchMode:="startswith") 579 | { 580 | return this.NodeId := this._SwitchToFrameBy("Name", Name, MatchMode) 581 | } 582 | _SwitchToFrameBy(Key, Value, MatchMode) 583 | { 584 | try 585 | { 586 | FrameTree := this.Call("Page.getFrameTree").frameTree 587 | FrameId := this._FindFrameBy(Key, Value, FrameTree, MatchMode) 588 | 589 | if (FrameId) 590 | return this._FrameIdToNodeId(FrameId) 591 | } 592 | catch e 593 | throw Exception(e.Message, -2, e.Extra) 594 | } 595 | _FindFrameBy(Key, Value, FrameTree, MatchMode) 596 | { 597 | for i, v in FrameTree.childFrames 598 | { 599 | if (Key = "URL") 600 | str := v.frame.url v.frame.urlFragment 601 | else if (Key = "Name") 602 | str := v.frame.name 603 | else 604 | return 605 | 606 | if (str = "") 607 | continue 608 | 609 | if ( (MatchMode = "exact" && str = Value) ; Case insensitive 610 | or (MatchMode = "contains" && InStr(str, Value)) 611 | or (MatchMode = "startswith" && InStr(str, Value) == 1) 612 | or (MatchMode = "regex" && str ~= Value) ) 613 | return v.frame.id 614 | } 615 | 616 | for i, v in FrameTree.childFrames 617 | { 618 | if (v.HasKey("childFrames")) 619 | ret := this._FindFrameBy(Key, Value, v, MatchMode) 620 | if (ret != "") 621 | return ret 622 | } 623 | } 624 | ; https://github.com/Xeo786/Rufaydium-Webdriver/blob/main/CDP.ahk 625 | _FrameIdToNodeId(FrameId) 626 | { 627 | ; 看起来 DOM.getFrameOwner 好像就直接将 FrameId 转为 NodeId 了 628 | ; 实际上必须进行下面4步转换才能得到正确的 NodeId 629 | ; 这个结论是试出来的,具体原理未知 630 | nodeId := this.Call("DOM.getFrameOwner", {"frameId": FrameId}).nodeId 631 | backendNodeId := this.Call("DOM.describeNode", {"nodeId": nodeId}).node.contentDocument.backendNodeId 632 | contentDocObject := this.Call("DOM.resolveNode", {"backendNodeId": backendNodeId}).object.objectId 633 | return this.Call("DOM.requestNode", {"objectId": contentDocObject}).nodeId 634 | } 635 | 636 | UpdateRoot() 637 | { 638 | static _i := 0 639 | 640 | try 641 | { 642 | i := _i += 1 643 | ; DOM.getDocument 是全局生效的,每次调用后都会破坏之前已经找到的页面元素 644 | ; 因此 Page.Call() 中做了特殊处理,可防止外部调用 DOM.getDocument 645 | ; 所以必须使用一个内部别名 DOM.getRoot 来调用 DOM.getDocument 646 | ; DOM.getRoot is an internal alias of DOM.getDocument 647 | Root := this.Call("DOM.getRoot") 648 | ; 页面自动刷新时 DOM.documentUpdated 事件会激活至少2次 649 | ; 每次 DOM.documentUpdated 事件又会自动调用 UpdateRoot() 650 | ; 此时就很容易出现数个 Page.Call("DOM.getRoot") 都在等待返回值 651 | ; 当返回值出现后,后调用的返回值将先写入 Page.NodeId 652 | ; 而后先调用的返回值又再次写入 Page.NodeId 653 | ; 这就会造成错误,因为先调用的返回值已经过时了 654 | ; 所以这里将对返回值的调用顺序进行验证,只保留最后一次调用的 655 | ; Keep only the return value of the last call to Page.Call("DOM.getRoot") 656 | if (i >= _i) 657 | { 658 | this.Root := Root 659 | this.NodeId := this.Root.root.nodeId 660 | } 661 | } 662 | catch e 663 | throw Exception(e.Message, -1, e.Extra) 664 | } 665 | 666 | QuerySelector(Selector) 667 | { 668 | try NodeId := this.Call("DOM.querySelector", {"nodeId": this.NodeId, "selector": Selector}).nodeId 669 | return (!NodeId) ? "" : new this.Element(NodeId, this) 670 | } 671 | QuerySelectorAll(Selector) 672 | { 673 | try NodeId := this.Call("DOM.querySelectorAll", {"nodeId": this.NodeId, "selector": Selector}).nodeIds 674 | return (!NodeId) ? "" : new this.Element(NodeId, this) 675 | } 676 | GetElementById(Id) 677 | { 678 | return this.querySelector("[id='" Id "']") 679 | } 680 | GetElementsbyClassName(Class) 681 | { 682 | return this.querySelectorAll("[class='" Class "']") 683 | } 684 | GetElementsbyName(Name) 685 | { 686 | return this.querySelectorAll("[name='" Name "']") 687 | } 688 | GetElementsbyTagName(TagName) 689 | { 690 | return this.querySelectorAll(TagName) 691 | } 692 | ; Not yet realized 693 | GetElementsbyXpath(Xpath) 694 | { 695 | return 696 | } 697 | 698 | Url[] 699 | { 700 | get 701 | { 702 | try 703 | { 704 | loop 3 705 | if (url := this.Evaluate("window.location.href;", Timeout := 2).value) 706 | return url 707 | } 708 | catch e 709 | throw Exception(e.Message, -1, e.Extra) 710 | } 711 | 712 | set 713 | { 714 | try 715 | this.Call("Page.navigate", {"url": value}) 716 | catch e 717 | throw Exception(e.Message, -1, e.Extra) 718 | } 719 | } 720 | 721 | class Element 722 | { 723 | __New(NodeId, Parent) 724 | { 725 | ObjRawSet(this, "NodeId", NodeId) 726 | ObjRawSet(this, "Parent", Parent) 727 | } 728 | 729 | __Get(Key) 730 | { 731 | ; The user wants to get a value like element[1] and NodeId is NodeIds like [1,2,3] 732 | if Key is digit 733 | { 734 | if (Key!="" and IsObject(this.NodeId)) 735 | { 736 | if (Key = 0) 737 | throw Exception("Array index start at 1 instead of 0", -1) 738 | 739 | ThisClone := ObjClone(this) 740 | ThisClone.NodeId := this.NodeId[Key] 741 | return ThisClone 742 | } 743 | } 744 | ; The user wants to get a value like element.textContent 745 | else 746 | { 747 | try 748 | { 749 | str := this._GetProp(Key).result.value 750 | 751 | if (SubStr(str, 1, 1)="{" and SubStr(str, 0, 1)="}") 752 | obj := Chrome.JSON.Load(str) 753 | 754 | return IsObject(obj) ? obj : str 755 | } 756 | catch e 757 | throw Exception(e.Message, -1, e.Extra) 758 | } 759 | } 760 | 761 | __Set(Key, Value) 762 | { 763 | try 764 | return this._SetProp(Key, Value) 765 | catch e 766 | throw Exception(e.Message, -1, e.Extra) 767 | } 768 | 769 | __Call(Name, Params*) 770 | { 771 | ; The user wants to call a method which not in this class 772 | if (!IsFunc(ObjGetBase(this)[Name])) 773 | { 774 | try 775 | { 776 | str := this._CallMethod(Name, Params*).result.value 777 | 778 | if (SubStr(str, 1, 1)="{" and SubStr(str, 0, 1)="}") 779 | obj := Chrome.JSON.Load(str) 780 | 781 | return IsObject(obj) ? obj : str 782 | } 783 | catch e 784 | throw Exception(e.Message, -1, e.Extra) 785 | } 786 | } 787 | 788 | __Delete() 789 | { 790 | ; MsgBox 元素释放了 791 | } 792 | 793 | _GetProp(Key) 794 | { 795 | JS = 796 | (LTrim 797 | function() { 798 | let result = this.%Key% 799 | if (typeof result === 'object' && result !== null) { 800 | return JSON.stringify(result) 801 | } else { return result }} 802 | ) 803 | 804 | return this._CallFunctionOn(JS) 805 | } 806 | 807 | _SetProp(Key, Value) 808 | { 809 | ; Escape ` to \` 810 | Value := StrReplace(Value, "``", "\``") 811 | ; Escape $ to \$ 812 | Value := StrReplace(Value, "$", "\$") 813 | 814 | JS = 815 | (LTrim 816 | function() { 817 | let template = ``%Value%``; 818 | this.%Key% = template } 819 | ) 820 | 821 | return this._CallFunctionOn(JS) 822 | } 823 | 824 | _CallMethod(Name, Params*) 825 | { 826 | for Key, Value in Params 827 | { 828 | ; Escape ` to \` 829 | Value := StrReplace(Value, "``", "\``") 830 | ; Escape $ to \$ 831 | Value := StrReplace(Value, "$", "\$") 832 | ; Build a string like 833 | ; let param_1 = `123`; 834 | ; let param_2 = `test`; 835 | StrParams1 .= Format("let param_{} = ``{}``;`n", A_Index, Value) 836 | ; Build a string like 837 | ; param_1,param_2,param_3 838 | StrParams2 .= (A_Index = 1) ? "param_1" : Format(",param_{}", A_Index) 839 | } 840 | 841 | JS = 842 | (LTrim 843 | function() { 844 | %StrParams1% 845 | let result = this.%Name%(%StrParams2%) 846 | if (typeof result === 'object' && result !== null) { 847 | return JSON.stringify(result) 848 | } else { return result }} 849 | ) 850 | 851 | return this._CallFunctionOn(JS) 852 | } 853 | 854 | _CallFunctionOn(JS) 855 | { 856 | objectId := this.Parent.Call("DOM.resolveNode", {"nodeId": this.NodeId}).object.objectId 857 | return this.Parent.Call("Runtime.callFunctionOn", {"objectId": objectId, "functionDeclaration": JS}) 858 | } 859 | 860 | ; Return the number of elements which found by QuerySelectorAll() 861 | Count() 862 | { 863 | return this.NodeId.Length() 864 | } 865 | 866 | ; Return a screenshot of the element (base64 encoded), 867 | ; you can save it as an image file by using the ImagePut library. 868 | ; https://github.com/iseahound/ImagePut 869 | Screenshot() 870 | { 871 | try 872 | { 873 | JS = 874 | (LTrim 875 | function() { 876 | const e = this.getBoundingClientRect(), 877 | t = this.ownerDocument.documentElement.getBoundingClientRect(); 878 | return JSON.stringify({ 879 | x: e.left - t.left, 880 | y: e.top - t.top, 881 | width: e.width, 882 | height: e.height, 883 | scale: 1})} 884 | ) 885 | 886 | params := { "captureBeyondViewport": Chrome.JSON.True 887 | , "clip": Chrome.JSON.Load(this._CallFunctionOn(JS).result.value) 888 | , "format": "png" 889 | , "fromSurface": Chrome.JSON.True 890 | , "quality": 100} 891 | 892 | return this.Parent.Call("Page.captureScreenshot", params).data 893 | } 894 | catch e 895 | throw Exception(e.Message, -1, e.Extra) 896 | } 897 | } 898 | 899 | class WebSocket { 900 | 901 | ; The primary HINTERNET handle to the websocket connection 902 | ; This field should not be set externally. 903 | ptr := "" 904 | 905 | ; Whether the websocket is operating in Synchronous or Asynchronous mode. 906 | ; This field should not be set externally. 907 | async := "" 908 | 909 | ; The readiness state of the websocket. 910 | ; This field should not be set externally. 911 | readyState := 0 912 | 913 | ; The URL this websocket is connected to 914 | ; This field should not be set externally. 915 | url := "" 916 | 917 | ; Internal array of HINTERNET handles 918 | HINTERNETs := [] 919 | 920 | ; Internal buffer used to receive incoming data 921 | cache := "" ; Access ONLY by ObjGetAddress 922 | cacheSize := 8192 923 | 924 | ; Internal buffer used to hold data fragments for multi-packet messages 925 | recData := "" 926 | recDataSize := 0 927 | 928 | ; Define in winerror.h 929 | ERROR_INVALID_OPERATION := 4317 930 | 931 | ; Aborted connection Event 932 | EVENT_ABORTED := { status: 1006 ; WEB_SOCKET_ABORTED_CLOSE_STATUS 933 | , reason: "The connection was closed without sending or receiving a close frame." } 934 | 935 | _LastError(Err := -1) 936 | { 937 | static module := DllCall("GetModuleHandle", "Str", "winhttp", "Ptr") 938 | Err := Err < 0 ? A_LastError : Err 939 | hMem := "" 940 | DllCall("Kernel32.dll\FormatMessage" 941 | , "Int", 0x1100 ; [in] DWORD dwFlags 942 | , "Ptr", module ; [in, optional] LPCVOID lpSource 943 | , "Int", Err ; [in] DWORD dwMessageId 944 | , "Int", 0 ; [in] DWORD dwLanguageId 945 | , "Ptr*", hMem ; [out] LPTSTR lpBuffer 946 | , "Int", 0 ; [in] DWORD nSize 947 | , "Ptr", 0 ; [in, optional] va_list *Arguments 948 | , "UInt") ; DWORD 949 | return StrGet(hMem), DllCall("Kernel32.dll\LocalFree", "Ptr", hMem, "Ptr") 950 | } 951 | 952 | ; Internal function used to load the mcode event filter 953 | _StatusSyncCallback() 954 | { 955 | if this.pCode 956 | return this.pCode 957 | b64 := (A_PtrSize == 4) 958 | ? "i1QkDIPsDIH6AAAIAHQIgfoAAAAEdTWLTCQUiwGJBCSLRCQQiUQkBItEJByJRCQIM8CB+gAACAAPlMBQjUQkBFD/cQyLQQj/cQT/0IPEDMIUAA==" 959 | : "SIPsSEyL0kGB+AAACAB0CUGB+AAAAAR1MEiLAotSGEyJTCQwRTPJQYH4AAAIAEiJTCQoSYtKCEyNRCQgQQ+UwUiJRCQgQf9SEEiDxEjD" 960 | if !DllCall("crypt32\CryptStringToBinary", "Str", b64, "UInt", 0, "UInt", 1, "Ptr", 0, "UInt*", s := 0, "Ptr", 0, "Ptr", 0) 961 | throw Exception("failed to parse b64 to binary") 962 | ObjSetCapacity(this, "code", s) 963 | this.pCode := ObjGetAddress(this, "code") 964 | if !DllCall("crypt32\CryptStringToBinary", "Str", b64, "UInt", 0, "UInt", 1, "Ptr", this.pCode, "UInt*", s, "Ptr", 0, "Ptr", 0) && 965 | throw Exception("failed to convert b64 to binary") 966 | if !DllCall("VirtualProtect", "Ptr", this.pCode, "UInt", s, "UInt", 0x40, "UInt*", 0) 967 | throw Exception("failed to mark memory as executable") 968 | return this.pCode 969 | /* c++ source 970 | struct __CONTEXT { 971 | void *obj; 972 | HWND hwnd; 973 | decltype(&SendMessageW) pSendMessage; 974 | UINT msg; 975 | }; 976 | void __stdcall WinhttpStatusCallback( 977 | void *hInternet, 978 | DWORD_PTR dwContext, 979 | DWORD dwInternetStatus, 980 | void *lpvStatusInformation, 981 | DWORD dwStatusInformationLength) { 982 | if (dwInternetStatus == 0x80000 || dwInternetStatus == 0x4000000) { 983 | __CONTEXT *context = (__CONTEXT *)dwContext; 984 | void *param[3] = { context->obj,hInternet,lpvStatusInformation }; 985 | context->pSendMessage(context->hwnd, context->msg, (WPARAM)param, dwInternetStatus == 0x80000); 986 | } 987 | } 988 | */ 989 | } 990 | 991 | ; Internal event dispatcher for compatibility with the legacy interface 992 | _Event(name, event) 993 | { 994 | this["On" name](event) 995 | } 996 | 997 | ; Reconnect 998 | reconnect() 999 | { 1000 | this.connect() 1001 | } 1002 | 1003 | pRecData[] { 1004 | get { 1005 | return ObjGetAddress(this, "recData") 1006 | } 1007 | } 1008 | 1009 | __New(url, events := 0, async := true, headers := "") 1010 | { 1011 | this.url := url 1012 | 1013 | this.HINTERNETs := [] 1014 | 1015 | ; Force async to boolean 1016 | this.async := async := !!async 1017 | 1018 | ; Initialize the Cache 1019 | ObjSetCapacity(this, "cache", this.cacheSize) 1020 | this.pCache := ObjGetAddress(this, "cache") 1021 | 1022 | ; Initialize the RecData 1023 | ; this.pRecData := ObjGetAddress(this, "recData") 1024 | 1025 | ; script's built-in window for message targeting 1026 | this.hWnd := A_ScriptHwnd 1027 | 1028 | ; Parse the url 1029 | if !RegExMatch(url, "Oi)^((?wss?)://)?((?[^:]+):(?.+)@)?(?[^/:]+)(:(?\d+))?(?/.*)?$", m) 1030 | throw Exception("Invalid websocket url") 1031 | this.m := m 1032 | 1033 | ; Open a new HTTP API instance 1034 | if !(hSession := DllCall("Winhttp\WinHttpOpen" 1035 | , "Ptr", 0 ; [in, optional] LPCWSTR pszAgentW 1036 | , "UInt", 0 ; [in] DWORD dwAccessType 1037 | , "Ptr", 0 ; [in] LPCWSTR pszProxyW 1038 | , "Ptr", 0 ; [in] LPCWSTR pszProxyBypassW 1039 | , "UInt", async * 0x10000000 ; [in] DWORD dwFlags 1040 | , "Ptr")) ; HINTERNET 1041 | throw Exception("WinHttpOpen failed: " this._LastError()) 1042 | this.HINTERNETs.Push(hSession) 1043 | 1044 | ; Connect the HTTP API to the remote host 1045 | port := m.PORT ? (m.PORT + 0) : (m.SCHEME = "ws") ? 80 : 443 1046 | if !(this.hConnect := DllCall("Winhttp\WinHttpConnect" 1047 | , "Ptr", hSession ; [in] HINTERNET hSession 1048 | , "WStr", m.HOST ; [in] LPCWSTR pswzServerName 1049 | , "UShort", port ; [in] INTERNET_PORT nServerPort 1050 | , "UInt", 0 ; [in] DWORD dwReserved 1051 | , "Ptr")) ; HINTERNET 1052 | throw Exception("WinHttpConnect failed: " this._LastError()) 1053 | this.HINTERNETs.Push(this.hConnect) 1054 | 1055 | ; Translate headers from array to string 1056 | if IsObject(headers) 1057 | { 1058 | s := "" 1059 | for k, v in headers 1060 | s .= "`r`n" k ": " v 1061 | headers := LTrim(s, "`r`n") 1062 | } 1063 | this.headers := headers 1064 | 1065 | ; Set any event handlers from events parameter 1066 | for k, v in IsObject(events) ? events : [] 1067 | if (k ~= "i)^(data|message|close|error|open)$") 1068 | this["on" k] := v 1069 | 1070 | ; Set up a handler for messages from the StatusSyncCallback mcode 1071 | this.wm_ahkmsg := DllCall("RegisterWindowMessage", "Str", "AHK_WEBSOCKET_STATUSCHANGE_" &this, "UInt") 1072 | ; .Bind({}) make parameter "this" = {} 1073 | OnMessage(this.wm_ahkmsg, this.WEBSOCKET_STATUSCHANGE.Bind({})) ; TODO: Proper binding 1074 | 1075 | ; Connect on start 1076 | this.connect() 1077 | } 1078 | 1079 | connect() { 1080 | ; Collect pointer to SendMessageW routine for the StatusSyncCallback mcode 1081 | static pSendMessageW := DllCall("GetProcAddress", "Ptr", DllCall("GetModuleHandle", "Str", "User32", "Ptr"), "AStr", "SendMessageW", "Ptr") 1082 | 1083 | ; If the HTTP connection is closed, we cannot request a websocket 1084 | if !this.HINTERNETs.Length() 1085 | throw Exception("The connection is closed") 1086 | 1087 | ; Shutdown any existing websocket connection 1088 | this.shutdown() 1089 | 1090 | ; Free any HINTERNET handles from previous websocket connections 1091 | while (this.HINTERNETs.Length() > 2) 1092 | DllCall("Winhttp\WinHttpCloseHandle", "Ptr", this.HINTERNETs.Pop()) 1093 | 1094 | ; Open an HTTP Request for the target path 1095 | dwFlags := (this.m.SCHEME = "wss") ? 0x800000 : 0 1096 | if !(hRequest := DllCall("Winhttp\WinHttpOpenRequest" 1097 | , "Ptr", this.hConnect ; [in] HINTERNET hConnect, 1098 | , "WStr", "GET" ; [in] LPCWSTR pwszVerb, 1099 | , "WStr", this.m.PATH ; [in] LPCWSTR pwszObjectName, 1100 | , "Ptr", 0 ; [in] LPCWSTR pwszVersion, 1101 | , "Ptr", 0 ; [in] LPCWSTR pwszReferrer, 1102 | , "Ptr", 0 ; [in] LPCWSTR *ppwszAcceptTypes, 1103 | , "UInt", dwFlags ; [in] DWORD dwFlags 1104 | , "Ptr")) ; HINTERNET 1105 | throw Exception("WinHttpOpenRequest failed: " this._LastError()) 1106 | this.HINTERNETs.Push(hRequest) 1107 | 1108 | if this.headers 1109 | { 1110 | if ! DllCall("Winhttp\WinHttpAddRequestHeaders" 1111 | , "Ptr", hRequest ; [in] HINTERNET hRequest, 1112 | , "WStr", this.headers ; [in] LPCWSTR lpszHeaders, 1113 | , "UInt", -1 ; [in] DWORD dwHeadersLength, 1114 | , "UInt", 0x20000000 ; [in] DWORD dwModifiers 1115 | , "Int") ; BOOL 1116 | throw Exception("WinHttpAddRequestHeaders failed: " this._LastError()) 1117 | } 1118 | 1119 | ; Make the HTTP Request 1120 | status := "00000" 1121 | if (!DllCall("Winhttp\WinHttpSetOption", "Ptr", hRequest, "UInt", 114, "Ptr", 0, "UInt", 0, "Int") 1122 | || !DllCall("Winhttp\WinHttpSendRequest", "Ptr", hRequest, "Ptr", 0, "UInt", 0, "Ptr", 0, "UInt", 0, "UInt", 0, "UPtr", 0, "Int") 1123 | || !DllCall("Winhttp\WinHttpReceiveResponse", "Ptr", hRequest, "Ptr", 0) 1124 | || !DllCall("Winhttp\WinHttpQueryHeaders", "Ptr", hRequest, "UInt", 19, "Ptr", 0, "WStr", status, "UInt*", 10, "Ptr", 0, "Int") 1125 | || status != "101") 1126 | throw Exception("Invalid status: " status) 1127 | 1128 | ; Upgrade the HTTP Request to a Websocket connection 1129 | if !(this.ptr := DllCall("Winhttp\WinHttpWebSocketCompleteUpgrade", "Ptr", hRequest, "Ptr", 0)) 1130 | throw Exception("WinHttpWebSocketCompleteUpgrade failed: " this._LastError()) 1131 | 1132 | ; Close the HTTP Request, save the Websocket connection 1133 | DllCall("Winhttp\WinHttpCloseHandle", "Ptr", this.HINTERNETs.Pop()) 1134 | this.HINTERNETs.Push(this.ptr) 1135 | this.readyState := 1 1136 | 1137 | ; Configure asynchronous callbacks 1138 | if (this.async) 1139 | { 1140 | ; Populate context struct for the mcode to reference 1141 | ObjSetCapacity(this, "__context", 4 * A_PtrSize) 1142 | pCtx := ObjGetAddress(this, "__context") 1143 | NumPut(&this , pCtx + A_PtrSize * 0, "Ptr") 1144 | NumPut(this.hWnd , pCtx + A_PtrSize * 1, "Ptr") 1145 | NumPut(pSendMessageW , pCtx + A_PtrSize * 2, "Ptr") 1146 | NumPut(this.wm_ahkmsg, pCtx + A_PtrSize * 3, "UInt") 1147 | 1148 | if !DllCall("Winhttp\WinHttpSetOption" 1149 | , "Ptr", this.ptr ; [in] HINTERNET hInternet 1150 | , "UInt", 45 ; [in] DWORD dwOption 1151 | , "Ptr*", pCtx ; [in] LPVOID lpBuffer 1152 | , "UInt", A_PtrSize ; [in] DWORD dwBufferLength 1153 | , "Int") ; BOOL 1154 | throw Exception("WinHttpSetOption failed: " this._LastError()) 1155 | 1156 | StatusCallback := this._StatusSyncCallback() 1157 | if (-1 == DllCall("Winhttp\WinHttpSetStatusCallback" 1158 | , "Ptr", this.ptr ; [in] HINTERNET hInternet, 1159 | , "Ptr", StatusCallback ; [in] WINHTTP_STATUS_CALLBACK lpfnInternetCallback, 1160 | , "UInt", 0x80000 ; [in] DWORD dwNotificationFlags, 1161 | , "UPtr", 0 ; [in] DWORD_PTR dwReserved 1162 | , "Ptr")) ; WINHTTP_STATUS_CALLBACK 1163 | throw Exception("WinHttpSetStatusCallback failed: " this._LastError()) 1164 | 1165 | ; Make the initial request for data to receive an asynchronous response for 1166 | if (ret := DllCall("Winhttp\WinHttpWebSocketReceive" 1167 | , "Ptr", this.ptr ; [in] HINTERNET hWebSocket, 1168 | , "Ptr", this.pCache ; [out] PVOID pvBuffer, 1169 | , "UInt", this.cacheSize ; [in] DWORD dwBufferLength, 1170 | , "UInt*", 0 ; [out] DWORD *pdwBytesRead, 1171 | , "UInt*", 0 ; [out] WINHTTP_WEB_SOCKET_BUFFER_TYPE *peBufferType 1172 | , "UInt")) ; DWORD 1173 | throw Exception("WinHttpWebSocketReceive failed: " ret) 1174 | } 1175 | 1176 | ; Fire the open event 1177 | this._Event("Open", {timestamp:A_Now A_Msec, url: this.url}) 1178 | } 1179 | 1180 | WEBSOCKET_STATUSCHANGE(wp, lp, msg, hwnd) { 1181 | ; Buffer events 1182 | Critical 1183 | 1184 | ; Grab `this` from the provided context struct 1185 | this := Object(NumGet(wp + A_PtrSize * 0, "Ptr")) 1186 | 1187 | if !lp { 1188 | this.readyState := 3 1189 | return 1190 | } 1191 | 1192 | ; Don't process data when the websocket isn't ready 1193 | if (this.readyState != 1) 1194 | return 1195 | 1196 | ; Grab the rest of the context data 1197 | hInternet := NumGet(wp + A_PtrSize * 1, "Ptr") 1198 | lpvStatusInformation := NumGet(wp + A_PtrSize * 2, "Ptr") 1199 | dwBytesTransferred := NumGet(lpvStatusInformation + 0, "UInt") 1200 | eBufferType := NumGet(lpvStatusInformation + 4, "UInt") 1201 | 1202 | ; Mark the current size of the received data buffer for use as an offset 1203 | ; for the start of any newly provided data 1204 | offset := this.recDataSize 1205 | 1206 | if (eBufferType > 3) 1207 | { 1208 | closeStatus := this.QueryCloseStatus() 1209 | this.shutdown() 1210 | ; We need to return as soon as possible. 1211 | ; If we don't use a SetTimer and call a ws request in Close event, it will cause a deadlock. 1212 | BoundFunc := this._Event.Bind(this, "Close", {reason: closeStatus.reason, status: closeStatus.status}) 1213 | SetTimer %BoundFunc%, -1 1214 | return 1215 | } 1216 | 1217 | try { 1218 | if (eBufferType == 0) ; BINARY 1219 | { 1220 | if offset ; Continued from a fragment 1221 | { 1222 | VarSetCapacity(data, offset + dwBytesTransferred) 1223 | 1224 | ; Copy data from the fragment buffer 1225 | DllCall("RtlMoveMemory" 1226 | , "Ptr", &data 1227 | , "Ptr", this.pRecData 1228 | , "UInt", this.recDataSize) 1229 | 1230 | ; Copy data from the new data cache 1231 | DllCall("RtlMoveMemory" 1232 | , "Ptr", &data + offset 1233 | , "Ptr", this.pCache 1234 | , "UInt", dwBytesTransferred) 1235 | 1236 | ; Clear fragment buffer 1237 | this.recDataSize := 0 1238 | 1239 | ; We need to return as soon as possible. 1240 | ; If we don't use a SetTimer and call a ws request in Data event, it will cause a deadlock. 1241 | BoundFunc := this._Event.Bind(this, "Data", {data: &data, size: offset + dwBytesTransferred}) 1242 | SetTimer %BoundFunc%, -1 1243 | } 1244 | else ; No prior fragment 1245 | { 1246 | ; Copy data from the new data cache 1247 | VarSetCapacity(data, dwBytesTransferred) 1248 | 1249 | DllCall("RtlMoveMemory" 1250 | , "Ptr", &data 1251 | , "Ptr", this.pCache 1252 | , "UInt", dwBytesTransferred) 1253 | 1254 | ; We need to return as soon as possible. 1255 | ; If we don't use a SetTimer and call a ws request in Data event, it will cause a deadlock. 1256 | BoundFunc := this._Event.Bind(this, "Data", {data: &data, size: dwBytesTransferred}) 1257 | SetTimer %BoundFunc%, -1 1258 | } 1259 | } 1260 | else if (eBufferType == 2) ; UTF8 1261 | { 1262 | if offset ; Continued from a fragment 1263 | { 1264 | this.recDataSize += dwBytesTransferred 1265 | ObjSetCapacity(this, "recData", this.recDataSize) 1266 | 1267 | DllCall("RtlMoveMemory" 1268 | , "Ptr", this.pRecData + offset 1269 | , "Ptr", this.pCache 1270 | , "UInt", dwBytesTransferred) 1271 | 1272 | msg := StrGet(this.pRecData, "utf-8") 1273 | this.recDataSize := 0 1274 | } 1275 | else ; No prior fragment 1276 | msg := StrGet(this.pCache, dwBytesTransferred, "utf-8") 1277 | 1278 | ; We need to return as soon as possible. 1279 | ; If we don't use a SetTimer and call a ws request in Message event, it will cause a deadlock. 1280 | BoundFunc := this._Event.Bind(this, "Message", {data: msg}) 1281 | SetTimer %BoundFunc%, -1 1282 | } 1283 | else if (eBufferType == 1 || eBufferType == 3) ; BINARY_FRAGMENT, UTF8_FRAGMENT 1284 | { 1285 | ; Add the fragment to the received data buffer 1286 | this.recDataSize += dwBytesTransferred 1287 | ObjSetCapacity(this, "recData", this.recDataSize) 1288 | DllCall("RtlMoveMemory" 1289 | , "Ptr", this.pRecData + offset 1290 | , "Ptr", this.pCache 1291 | , "UInt", dwBytesTransferred) 1292 | } 1293 | } 1294 | finally 1295 | { 1296 | askForMoreData := this.askForMoreData.Bind(this, hInternet) 1297 | SetTimer %askForMoreData%, -1 1298 | } 1299 | } 1300 | 1301 | askForMoreData(hInternet) 1302 | { 1303 | ; Original implementation used a while loop here, but in my experience 1304 | ; that causes lost messages 1305 | ret := DllCall("Winhttp\WinHttpWebSocketReceive" 1306 | , "Ptr", hInternet ; [in] HINTERNET hWebSocket, 1307 | , "Ptr", this.pCache ; [out] PVOID pvBuffer, 1308 | , "UInt", this.cacheSize ; [in] DWORD dwBufferLength, 1309 | , "UInt*", 0 ; [out] DWORD *pdwBytesRead, 1310 | , "UInt*", 0 ; [out] *peBufferType 1311 | , "UInt") ; DWORD 1312 | if (ret && ret != this.ERROR_INVALID_OPERATION) 1313 | this._Error({code: ret}) 1314 | } 1315 | 1316 | __Delete() 1317 | { 1318 | this.shutdown() 1319 | ; Free all active HINTERNETs 1320 | while (this.HINTERNETs.Length()) 1321 | DllCall("Winhttp\WinHttpCloseHandle", "Ptr", this.HINTERNETs.Pop()) 1322 | } 1323 | 1324 | ; Default error handler 1325 | _Error(err) 1326 | { 1327 | if (err.code != 12030) { 1328 | this._Event("Error", {code: ret}) 1329 | return 1330 | } 1331 | if (this.readyState == 3) 1332 | return 1333 | this.readyState := 3 1334 | try this._Event("Close", this.EVENT_ABORTED) 1335 | } 1336 | 1337 | queryCloseStatus() { 1338 | usStatus := 0 1339 | VarSetCapacity(vReason, 123, 0) 1340 | if (!DllCall("Winhttp\WinHttpWebSocketQueryCloseStatus" 1341 | , "Ptr", this.ptr ; [in] HINTERNET hWebSocket, 1342 | , "UShort*", usStatus ; [out] USHORT *pusStatus, 1343 | , "Ptr", &vReason ; [out] PVOID pvReason, 1344 | , "UInt", 123 ; [in] DWORD dwReasonLength, 1345 | , "UInt*", len ; [out] DWORD *pdwReasonLengthConsumed 1346 | , "UInt")) ; DWORD 1347 | return { status: usStatus, reason: StrGet(&vReason, len, "utf-8") } 1348 | else if (this.readyState > 1) 1349 | return this.EVENT_ABORTED 1350 | } 1351 | 1352 | ; eBufferType BINARY_MESSAGE = 0, BINARY_FRAGMENT = 1, UTF8_MESSAGE = 2, UTF8_FRAGMENT = 3 1353 | sendRaw(eBufferType, pvBuffer, dwBufferLength) { 1354 | if (this.readyState != 1) 1355 | throw Exception("websocket is disconnected") 1356 | if (ret := DllCall("Winhttp\WinHttpWebSocketSend" 1357 | , "Ptr", this.ptr ; [in] HINTERNET hWebSocket 1358 | , "UInt", eBufferType ; [in] WINHTTP_WEB_SOCKET_BUFFER_TYPE eBufferType 1359 | , "Ptr", pvBuffer ; [in] PVOID pvBuffer 1360 | , "UInt", dwBufferLength ; [in] DWORD dwBufferLength 1361 | , "UInt")) ; DWORD 1362 | this._Error({code: ret}) 1363 | } 1364 | 1365 | ; sends a utf-8 string to the server 1366 | send(str) 1367 | { 1368 | if (size := StrPut(str, "utf-8") - 1) 1369 | { 1370 | VarSetCapacity(buf, size, 0) 1371 | StrPut(str, &buf, "utf-8") 1372 | this.sendRaw(2, &buf, size) 1373 | } 1374 | else 1375 | this.sendRaw(2, 0, 0) 1376 | } 1377 | 1378 | receive() 1379 | { 1380 | if (this.async) 1381 | throw Exception("Used only in synchronous mode") 1382 | if (this.readyState != 1) 1383 | throw Exception("websocket is disconnected") 1384 | 1385 | rec := {data: "", size: 0, ptr: 0} 1386 | 1387 | offset := 0 1388 | while (!ret := DllCall("Winhttp\WinHttpWebSocketReceive" 1389 | , "Ptr", this.ptr ; [in] HINTERNET hWebSocket 1390 | , "Ptr", this.pCache ; [out] PVOID pvBuffer 1391 | , "UInt", this.cacheSize ; [in] DWORD dwBufferLength 1392 | , "UInt*", dwBytesRead := 0 ; [out] DWORD *pdwBytesRead 1393 | , "UInt*", eBufferType := 0 ; [out] WINHTTP_WEB_SOCKET_BUFFER_TYPE *peBufferType 1394 | , "UInt")) ; DWORD 1395 | { 1396 | switch eBufferType 1397 | { 1398 | case 0: 1399 | if offset 1400 | { 1401 | rec.size += dwBytesRead 1402 | ObjSetCapacity(rec, "data", rec.size) 1403 | ptr := ObjGetAddress(rec, "data") 1404 | DllCall("RtlMoveMemory", "Ptr", ptr + offset, "Ptr", this.pCache, "UInt", dwBytesRead) 1405 | } 1406 | else 1407 | { 1408 | rec.Size := dwBytesRead 1409 | ObjSetCapacity(rec, "data", rec.size) 1410 | ptr := ObjGetAddress(rec, "data") 1411 | DllCall("RtlMoveMemory", "Ptr", ptr, "Ptr", this.pCache, "UInt", dwBytesRead) 1412 | } 1413 | return rec 1414 | case 1, 3: 1415 | rec.size += dwBytesRead 1416 | ObjSetCapacity(rec, "data", rec.size) 1417 | ptr := ObjGetAddress(rec, "data") 1418 | DllCall("RtlMoveMemory", "Ptr", rec + offset, "Ptr", this.pCache, "UInt", dwBytesRead) 1419 | offset += dwBytesRead 1420 | case 2: 1421 | if (offset) { 1422 | rec.size += dwBytesRead 1423 | ObjSetCapacity(rec, "data", rec.size) 1424 | ptr := ObjGetAddress(rec, "data") 1425 | DllCall("RtlMoveMemory", "Ptr", ptr + offset, "Ptr", this.pCache, "UInt", dwBytesRead) 1426 | return StrGet(ptr, "utf-8") 1427 | } 1428 | return StrGet(this.pCache, dwBytesRead, "utf-8") 1429 | default: 1430 | rea := this.queryCloseStatus() 1431 | this.shutdown() 1432 | try this._Event("Close", {status: rea.status, reason: rea.reason}) 1433 | return 1434 | } 1435 | } 1436 | if (ret && ret != this.ERROR_INVALID_OPERATION) 1437 | this._Error({code: ret}) 1438 | } 1439 | 1440 | ; sends a close frame to the server to close the send channel, but leaves the receive channel open. 1441 | shutdown() { 1442 | if (this.readyState != 1) 1443 | return 1444 | this.readyState := 2 1445 | DllCall("Winhttp\WinHttpWebSocketShutdown", "Ptr", this.ptr, "UShort", 1000, "Ptr", 0, "UInt", 0) 1446 | this.readyState := 3 1447 | } 1448 | } 1449 | } 1450 | 1451 | Jxon_Load(p*) 1452 | { 1453 | return this.JSON.Load(p*) 1454 | } 1455 | 1456 | Jxon_Dump(p*) 1457 | { 1458 | return this.JSON.Dump(p*) 1459 | } 1460 | 1461 | Jxon_True() 1462 | { 1463 | return this.JSON.True() 1464 | } 1465 | 1466 | Jxon_False() 1467 | { 1468 | return this.JSON.False() 1469 | } 1470 | 1471 | Jxon_Null() 1472 | { 1473 | return this.JSON.Null() 1474 | } 1475 | 1476 | /* 1477 | cJson.ahk 0.6.0-git-built 1478 | Copyright (c) 2021 Philip Taylor (known also as GeekDude, G33kDude) 1479 | https://github.com/G33kDude/cJson.ahk 1480 | 1481 | MIT License 1482 | 1483 | Permission is hereby granted, free of charge, to any person obtaining a copy 1484 | of this software and associated documentation files (the "Software"), to deal 1485 | in the Software without restriction, including without limitation the rights 1486 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 1487 | copies of the Software, and to permit persons to whom the Software is 1488 | furnished to do so, subject to the following conditions: 1489 | 1490 | The above copyright notice and this permission notice shall be included in all 1491 | copies or substantial portions of the Software. 1492 | 1493 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1494 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1495 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1496 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1497 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1498 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1499 | SOFTWARE. 1500 | */ 1501 | class JSON 1502 | { 1503 | static version := "0.6.0-git-built" 1504 | 1505 | BoolsAsInts[] 1506 | { 1507 | get 1508 | { 1509 | this._init() 1510 | return NumGet(this.lib.bBoolsAsInts, "Int") 1511 | } 1512 | 1513 | set 1514 | { 1515 | this._init() 1516 | NumPut(value, this.lib.bBoolsAsInts, "Int") 1517 | return value 1518 | } 1519 | } 1520 | 1521 | NullsAsStrings[] 1522 | { 1523 | get 1524 | { 1525 | this._init() 1526 | return NumGet(this.lib.bNullsAsStrings, "Int") 1527 | } 1528 | 1529 | set 1530 | { 1531 | this._init() 1532 | NumPut(value, this.lib.bNullsAsStrings, "Int") 1533 | return value 1534 | } 1535 | } 1536 | 1537 | EmptyObjectsAsArrays[] 1538 | { 1539 | get 1540 | { 1541 | this._init() 1542 | return NumGet(this.lib.bEmptyObjectsAsArrays, "Int") 1543 | } 1544 | 1545 | set 1546 | { 1547 | this._init() 1548 | NumPut(value, this.lib.bEmptyObjectsAsArrays, "Int") 1549 | return value 1550 | } 1551 | } 1552 | 1553 | EscapeUnicode[] 1554 | { 1555 | get 1556 | { 1557 | this._init() 1558 | return NumGet(this.lib.bEscapeUnicode, "Int") 1559 | } 1560 | 1561 | set 1562 | { 1563 | this._init() 1564 | NumPut(value, this.lib.bEscapeUnicode, "Int") 1565 | return value 1566 | } 1567 | } 1568 | 1569 | _init() 1570 | { 1571 | if (this.lib) 1572 | return 1573 | this.lib := this._LoadLib() 1574 | 1575 | ; Populate globals 1576 | NumPut(&this.True, this.lib.objTrue, "UPtr") 1577 | NumPut(&this.False, this.lib.objFalse, "UPtr") 1578 | NumPut(&this.Null, this.lib.objNull, "UPtr") 1579 | 1580 | this.fnGetObj := Func("Object") 1581 | NumPut(&this.fnGetObj, this.lib.fnGetObj, "UPtr") 1582 | 1583 | this.fnCastString := Func("Format").Bind("{}") 1584 | NumPut(&this.fnCastString, this.lib.fnCastString, "UPtr") 1585 | } 1586 | 1587 | _LoadLib32Bit() { 1588 | static CodeBase64 := "" 1589 | . "3bocAQADAAFwATBXVlMAg+wgixV8DAAAAIt0JDCLXCQANIt8JDiLRCQAPIsKOQ4PhIpBAJSF2w+EqgAci0gDunQADLlfAAiJAHQkGMH+H2aJAFAc" 1590 | . "jVAgxwAiAABVAMdABG4AQmsADAhuAG8ADAwIdwBuAAwQXwBPIQAMFGIAagAMGGUAAGMAiRNmiUgAHo1EJBiJfCQACIlcJASJBCQhAFIc6OIZAWeN" 1591 | . "UCACiRO7IgBnZokAGIPEIDHAW14AX8NmkItUJEAgD7bAifkBLvCJAlQAN9ro7w0AAIkGI422Ad+DBxAFXPDHRCQEARIDYIFZgjOieoAzgwcBhhuQ" 1592 | . "AgABBI8AjUwkBIPkCPC6FIAF/3H8VYSJ5YCUUYHsqIGCAEEEizlmiRCJPEWUgHSBFoB0AQOLBwAPtwiNUfdmgyD6Fw+Hy4GU7P8Af/8Po9EPgkMi" 1593 | . "AgAqWAK+AQiNdkAAD7cLidgGFZ4BABUPo9aNWwJzAOaJB2aD+VsPyISkBYAJjh0AG4AHMG4PhBiAB4AEdA8IhbADACVQAmaDAHgCcokXD4XokQDk" 1594 | . "jVAEgAcEdYEHStiCBwaABwZlgQfIQYAHg8AIgD2CbIkgBw+E/gYAK0WUdr8AEsBXOEAxwYJEMeuAAt3YMcDpl0ERhLQmAkN2AIkfwB4Aew+FYP//" 1595 | . "/4MAwAJmD+/AjU1CmEIziQehJMAKDwARRbyLEIlMJFAYjU28wFwgAkZERCQcAh1MJBTAAhB5ghlEJMIZwAEDT8dn/wBSGItdoIsHg3DsJOmPAR7H" 1596 | . "H0FFOggPhfzBdsACiQchgC+JPCSJwA7oUAD+//+FwA+F4AeBYIAFwBWLRbCJHElDB6IKC2Z3IkBaD8yCjgFRwWFIAsATRWIMhmyABcIaLA+FawuB" 1597 | . "CEMgkMIcD7cQjWpKAAr5AAp7AAeADH0ID4REQQKF0g+EAjuDBCJ1Wo1FqEkFJ7P9ASd1R8uGLzOAUYAhcjDFF8whhwwBAwhz54kH6wvdANjrB93Y" 1598 | . "jXQmCACQuMAF/41l8IJZgKxdjWH8w0AjQC10Dw+OGwCKg0TpMIADCXfaQEG+EUGuZokwT6gYZoNA+y0PhBMHoAJFgpAhMtnox0WEwQMhIAMwD4TY" 1599 | . "oEONUwLPABEId4SLdZQAi1YIi04MiVUQiIlNjOgha02MEAqJB7jAKwD3ZQCIAcoPv8uJywDB+x8ByBHag4DA0InDiUWIABEAg9L/iVWMiVgQCIlQ" 1600 | . "DMIPjXPQQGaD/gl2vKANLogPhOCgDYPj34ABSEUPhYKMXZSiLWbAgzsUD4SSYEPgLkuAEWAWcIABMdtgASuAdQuNSAIPt6CEIA+JyI1KIAr5CYgP" 1601 | . "h9cgQYl9iMAHBDHJ5IDqMI0MiQSJxkACD7/SjQwiSmAG/o164AX/CQB24ot9iIk3hRDJD4RNgCEx0rgDgSHjBo0EgIPCAUABwDnKdfTgGNuhQhnd" 1602 | . "QAiEIKGQIBYQ3vHdWKBMlA+3FYAm+OAU5sJz+AUPIIW+/P//IAPcSFYIQAQAaU6AEpDgE2ZYD4U+QAEkd2Ehcy5r4gEid2zhAR7iASJ3c1XhAQ7i" 1603 | . "AQjgAQgief7RgEmDwAooeUlgToAN2rsjeRirPYAP0SAGAUcd4A8iQBOgAcArjXACArrhBIk3iXMIZoCJE4sXD7cKIgQEhMQgLIPABOsfgwQ9oLT+" 1604 | . "icaJ2gAEW8F+YgSvQR2hJm+BC1oCAmKDXHXUD7dKBSQE02JJ+S8PhG4RYAWD6VyAARkPhwI/4AUPt8n/JI3OCCC24llhcYN3ICbhPWA4fQ+FG0Ic" 1605 | . "A3y/7gnjk4BHQBkGoQ2kEuAGkM4Pg2fgBunqgADbZCqin9riAWcqyuIBYio94gG64AGioUIkYCiF1kvAPqAiuKMNA6Fh4YkMQwiAmSGSBDHAg1Ds" 1606 | . "BOmIwAa54dGDEMIEicbCIhfp5qvgM6ehu6uhqKWhqKSfByqiv6GioUWgx0WIY+EEQKKJRYQBaGeQ2MNHpGBiXQ+E7yEBQZEm3EShhJAu+SOkvvsB" 1607 | . "4WOQMfaLTYhmGIl15EJCc0XNzMwAzIPuAffhicgAweoDjTySAf8AKfiDwDBmiUQAdbyJyInRg/iICXfZMAWLfZARVkaNYQGwBYtFhKAK6AY8cB7J" 1608 | . "CXcgD6PL+A+CTQAHcx6gOvFN0wGEdjjhHvosdTcwAb6DAA66VdJRATsRBDawAE0wUOB0DjABc7PCA10QD4Xv+nICi12ERrmxGzIjZokI4yLXs8AB" 1609 | . "UB2hhOAV0y5DYAEp0C7pvZMBvjEDoYDB4AFmiTPpC6AcQAGJggSheEIBC+n2ISdNc0a7QRjBRumOMA3fGGsIuhAPQAIT3VugCIsH6VmBDkqQITCJ" 1610 | . "DzHJ0SHAMASNAllwRvsJdh2NWUK/gAAFD4a+UYBZEp/CAIcwcAiNWakAweMEjUoGiQ8IZolYUQMGZoldYoiUAw+GaAID1ANzJdkD8jAdD7egAkwL" 1611 | . "gKnB4QSNWghwOFaJcR3CBwgmBBopBCNVKQSvLQQKKgQKJgTMK7GAJQQCKQRsKASDwmoMdDHO0BC7kRTEMli1QQG5QAG5cD8aNKSRAqvRGZkCj0EB" 1612 | . "XEwBekEBu8FmSQFlMQXxMzkFUFEuAPAxyYlI/IPCIgIxHwYxwHABxfhjglkccReNQgAeUEwHDOlrwAygRNnoD7cMWAKwcbF2iRfZ4EyJ0PBxwQDp" 1613 | . "2yAE3SLYcCKLdZCQL4tLAAwPr0MID6/OgAHBifD3YwgwJigByrjDWANAB1MMVOlPUQdNIW27ESOJiAffaTBXGd1ZYCNV0XBTMBb6sGu4wAG6QQJo" 1614 | . "FJKD6zDwAgFi0qEg30WIoHcAaInAB9753EEIhgNkdMjQ6b4QBt7JwGYCDDS5L+wQQeEusEDxixBACCnBMksPt02giAHZ6UunAP2iWY2iAK+iACId" 1615 | . "yenlyQBCleEwWcnpSkkBBpMxUPR7hECgCOn/QADw2ejpy2AAQ7V4tQUAADAxMjM0NTY3ADg5QUJDREVGQTEBSB4AACAwAPgAHQAA8R8AAND9cACo" 1616 | . "tAA/AD8APwA/ADEAalgwBamQJMBwGj0AlK18AdN/AjoAvvwBf/QACmpwAFwQMGYAYQB4bABz0tGh0XHRQdFfxABWkgF1AGXyEHLYIlDQ12CNVNDX" 1617 | . "dCSqaIBgOAIqA1DOFFAB1mRwAMHMEGRiDORj42IAHCT/UBSLA4PU7BigAkBgAURiAaADVSDTOPAATPIAdLIBSPukAx1pGJVpMQWdacLWYQYwGA+3" 1618 | . "BuBo0I4JdAJXUAADdBGDxFADstf2uot+DIt2CAUQ2vpBL4CD0gCDUPoAdtgw2TyDuQgVEOZkwmlxoBKFwHQCvCHniTCJeATr6rFw3kb1dwjQd+QF" 1619 | . "1iQwkJBXjZDdANr/dwGz2Yn+U4nTgewCjAETP4t2BIlNKLSLUBDasJDifahAiXWkiE2jgHGFIgzASw+2BfENiEUKrHHtdKCfgH2sAQAZyYPhIIPB" 1620 | . "WwCLM4B9qACNRgACiQNmiQ4PhSYSAAyQA47qkpawMRD/i3AM8AIAdXyB0AA5eBgPjoDQBIWRBOCwBYsLjUGQAwq4k+sBYAyJRdjBAPgfiUXci0W0" 1621 | . "U3LuwXNF2EFz96HJEzFShWaJCnBIwEe5OofxEhAIEfIID4TU0VagUATGRawQAboBGwUQ9wLwEQyD+AEPwoTy0PgGD4TxaeGn5IQiEZz4AqChgQtg" 1622 | . "CUJRgQsDixWQsFdmNrsAD28FeA0AAIkAUBiNUB4PEQAg8w9+BYgAgGYPANZAEIkTul8AAQAsiVAci0W0iQBcJASJRCQIiQA0JOhNCgAAiwgDuSIA" 1623 | . "dIPHAYkAwoPAAokDZokACotNsDl5EA8Ijn0CAKb+weYEAANxDInCjUgCAIB9owCJC4nIAGbHAiwAD4V1QAQAAIB9rAASQwD///+LVbA5egAYD4/R" 1624 | . "/v//OWB6HA+PyABXCHGLAEYIiQQk6DgMBQJ0OgAcjVACiRMEZokAGQyD+AEPBIUJAEWNdCYAkIkFNTQkATjopQkBSAKwAKM7eBAPjToCAwWWcAyF" 1625 | . "2w+EIQGmiwPpWoEetCYBgCsAO1AYD4Q/AAUAAMZFrAC5RnuACIAThfj9AFJFgLSDAAGAfagAWoJuABCF0g+O3oAnAQAvMf+LcAzp/VMAEoQiZpCB" 1626 | . "LlKCLroKdAAIuYGjxwAiAAJPgaYMjVAQx0AABGIAagDHQAgQZQBjAAFmSA7pCt0BIbaBIIsGOwVCgIB3D4QKBoBKBYp4ggU+gAWLFXyABSA50A+E" 1627 | . "2IETMjmwMA+EZgCngThzgQ8EE76BOI1KIMcCACIAVQDHQgRuhABrAAMIbgBvAAOQDHcAbgADEF+ARUjHQhQCIUIYASFmIIlyHIkLgipmiQBKHolF" 1628 | . "2MH4H4iJRdxIYo1F2EBiBOn8xylFtIsAi4BNtIPAAYkBwWk5BQ2JTECKQA1DDOgcAgjACk20iwGJRWaswAqCUYQXgFHCDgJkiQHBW+k2xhVBM4tx" 1629 | . "gQcDvmzBTwEBQHHHCcAudQDBmwhmiXCS/MGcSgYEnY+LB2AikAEYdEq+QLIAiWDBjVAEvwCsQDUwIIt1pInQQVt5AsAxyYX2ficABAGOCInQv4CK" 1630 | . "AIPBAQCDwgJmiTg5zhh17L5BBMEKMIsDBQCrAYCxGdKJC4MA4iCDwl1miRAAjWX0W15fXY0QZ/hfw0GHxwGDxAAPQUnHRCQAZMAOuUDKIwcCSYCO" 1631 | . "AIo5AKEcjqEIoUA/QcaLEY1iQsBPD4XNwAbDxSsu/ICcATbAxZzBxXkcyA+Ov8GbRghADcNZnIMBAFyCWoUc6YzACqCNdgA5eIDRd4OtbYzR8YBi" 1632 | . "gVf+AAwAv5hw+///ZuVmgXkhBQYlAQXIYTZFsCFnjMarwBGDNuKkM3RjZ5njDQq4IS25gyxGAo1G0gZBM04EgWR3IATACBiLVaTiZEACiMz6AUEC" 1633 | . "pIl9nDHSjUBIAYsDic8EN4kQwYPCAQBCZscBQAkAOfp176Ehi6B9nIkDuKExZoEkhhAgFuISGA+MnCAJsjtgHYyT4SVuHQVBGDgD6bSgBONK6RRC" 1634 | . "AhyNQuAUABGgSASFwMgPiWgAgekvgAXhdzyFeKJ7QTcpPeMIjUVAyPIPEA65QFQAFMdF4ZUAQDKhIAEBAQtNyI1NuIsQiMdFzOECx0XcwwAK5MMA" 1635 | . "4GAEAPIPEYRN0AA3GI1N2GA3IiBCA0QkHAIJTCT6FGABEOEE4ACgfeIAQBkD4gBDSwQk/1IYiwBVwIPsJA+3ArJmoBeEE+AiITMJAAdRQCuLE7lA" 1636 | . "NADjFYkI1onHIlsGi0XAQA+3BAiDwYEGdeDnifiJE+ArAgPhpI1CVIXGt0BUA+k6wScD4zPBZVYBiVQkBIAPtlWjiRQkYVFQ2uh9+AAnk8cEkAUB" 1637 | . "P2ohRHAMMcDrgAmQOcIPhBKhCgLBoH/B4QQ5RA7YCHTqgX4hLWkhBearpHUSoAePoEEKfaFLAeS6gwAChdJ/cYEhjn2kixCNSsA3AAONVDoDhf9+" 1638 | . "QgniCjnQdfdhQAgZYwnpRSJPAJSNDAAMKcriCKAEZoM8QuAAdfaLdSF2wh5hHryPaQBIpBfgWeIQlGK6ZwW84ESgLIgQ4BKBCk1ipOAKVAgB4yuj" 1639 | . "D/khoSEB6e335yVFpACDwgOJEYXAeXjK6dXgAkAHZjjnVF2/QZ0iFeQnQ6rhI+AJH0AOoQQDhdt0d2DgdUABbL5lY6IgxwQghYKix0BA+HQAcgCB" 1640 | . "b3JqBoKidsEo7uZf4UO43eESA0AGJQhBf3BRgCAEqgqRf/YCBAgCBDUBBFatAjsjTgQVEzfwJuma0zEWdfVIABVF3CBfYIMGEIl0iFAgDhqRMh6w" 1641 | . "gwYiJo/6EAVs6ZbAAzAOAVEJMWW5ElshTIr2IAUD6YmDEAJhGInQD4Uz0QKqeEgIBYVJspEBTnQEm/IDASVHIQQHe4Q1wB2Z8CPpv0EGUX3pJRMC" 1642 | . "IIsA6XD58D5VVwBWU4PsRItEJMBYi1QkXIvAj9AZGfAeji/QNGEBMf+JQNWJ+otAGIABBMlyAkAMcA3rJfMpAAF0OVAgWsvBFaADYUwEVCQQcS/t" 1643 | . "IAE5EDN+ANqLBCQxycZEBCQPQEJMJEKLSCAIhcl5BwEBAfci2UA1ELsUgR3NzATMzPVQyInfg+sAAffmweoDjQQAkgHAKcGNQTAAidFmiURcGoVg" 1644 | . "0nXfgHzQBCALEJB0ErgtsStf/rIBgcMAXFwaZjlF4GQOaQSSAG7QP0tmO0RaTVABU1ABUUDpEAtgAIs8JIk4g8RENrhxR+FOw3AwEAsPtwJdYDwI" 1645 | . "D7cOZjkQyw+FHzADMcBmoIXJdStm0CjFyw7MhRPgAUAEMcATBPWQMaACD4TkYBpQLQ+3AAxGD7dcRQBmwDnZdLbpzlAB8hcgMduB7JzhE4QkIrAC" 1646 | . "WpwkjuEArCQitGEAUASL4EOJ0wSBwdAAgIPTAIMg+wAPhwvBTse+hXETuXEThcB4a3ALAIn4g+4B9+GJAvhxExySidcB2yAp2IPAMLARdGYJcRON" 1647 | . "VHAA7Q+EkpmhBU0AsFFyHInLkAAEicbRUYkDD7dCIv7hEeqJTdAIM4FWxNEJmw25Ahswdla4EGdmZmZABOkB9wLvwAf4H8H6AikCwpAbjQRGKfiJ" 1648 | . "CtfwB0zxB9eD6wKDIhrBGmaNVFxm0gDJEAmFbhGxhCRROrAIEyBBckvCAqJLev4AkHXzi7TSAYkGGwkB8gtEJEiNRCRAW3IJwABUImlwOEDSJFQQ" 1649 | . "JEyLELEnQI1MRCQw8mdMJFTgAUTVxGVYdABgdABcRGgPagsPag9qGNBpi1wkOCgPtwMSarqgHoXtYHQ/i1UAs2n2Vol200AX8WkD8CTwAgZq5jyJ" 1650 | . "VQAO1heSDtQRixBuuEIEcQ8wJQOUg5Ap8aEUAokQ6UfRJZD0PQoEgDIcAAYYi2wkkiBAZoSNQUkKvnHMAI1ZAokaZokxCA+3CCAEdHQPtkIdAQ2I" 1651 | . "XCQB8AlmQIP5Ig+PPuF9gyD5Bw+OdJAAjVkA+GaD+xoPh6gBUAQPt9v/JJ2cF7AQ8gjBBUCRQwq/XAHCBQRmiTmJGrsBkQZmiVkCD7dIKwAdwgqk" 1652 | . "sQIsgQgajSBDAokCuDMCA4MsxAR3HTEC8DEPCr4p8QS/ckUFMUELeQK86670XXAC0tN8B2Z0B1TrhncCoHICu3ECvhpu8gEZ8ALxBHEC6cpbAUN2" 1653 | . "cAJ0fL0E4cVZsQTpN3fTcAJUtQliGbwJ6Q93AtATXA+FdneQERADItUHQQDaB93pdX+DRVBbz9EA8wPgTuoB8FylJAwFIhZDBDFxA0kEgByJy4nO" 1654 | . "g+MAD2bB7ggPtpsCiMAZg+YPD7a2A5EAgB0CictmwekADGbB6wQPt8lxgAIPtrmhAbQCEAIDI0EGwUiJ+4uQcb77/InzUAAxALAB0BBxBqAQcASN" 1655 | . "WQhAB1EB4QAGROkd9QuNWYEwIiEID4ZOoVaD+R8PLIZEkADAD23gHnMCgIkyZokL6e731AJ9kCQIjV8BiV3OAMAhkPKgAYPD4QD6IKaL8ABSAevo" 1656 | . "dxIKdxYx8AAE6ZcH0fAAAekWh/AABBRkoQJZ4GYSsACD+14Ph7L+/4D/6Wn///+QBgA=" 1657 | static Code := false 1658 | if ((A_PtrSize * 8) != 32) { 1659 | Throw Exception("_LoadLib32Bit does not support " (A_PtrSize * 8) " bit AHK, please run using 32 bit AHK", -1) 1660 | } 1661 | ; MCL standalone loader https://github.com/G33kDude/MCLib.ahk 1662 | ; Copyright (c) 2021 G33kDude, CloakerSmoker (CC-BY-4.0) 1663 | ; https://creativecommons.org/licenses/by/4.0/ 1664 | if (!Code) { 1665 | CompressedSize := VarSetCapacity(DecompressionBuffer, 5678, 0) 1666 | if !DllCall("Crypt32\CryptStringToBinary", "Str", CodeBase64, "UInt", 0, "UInt", 1, "Ptr", &DecompressionBuffer, "UInt*", CompressedSize, "Ptr", 0, "Ptr", 0, "UInt") 1667 | throw Exception("Failed to convert MCLib b64 to binary", -1) 1668 | if !(pCode := DllCall("GlobalAlloc", "UInt", 0, "Ptr", 8216, "Ptr")) 1669 | throw Exception("Failed to reserve MCLib memory", -1) 1670 | DecompressedSize := 0 1671 | if (DllCall("ntdll\RtlDecompressBuffer", "UShort", 0x102, "Ptr", pCode, "UInt", 8216, "Ptr", &DecompressionBuffer, "UInt", CompressedSize, "UInt*", DecompressedSize, "UInt")) 1672 | throw Exception("Error calling RtlDecompressBuffer", -1, Format("0x{:08x}", r)) 1673 | for k, Offset in [24, 509, 598, 1479, 1671, 1803, 1828, 1892, 2290, 2321, 2342, 3228, 3232, 3236, 3240, 3244, 3248, 3252, 3256, 3260, 3264, 3268, 3272, 3276, 3280, 3284, 3288, 3292, 3296, 3300, 3304, 3308, 3312, 3316, 3320, 3324, 3328, 3332, 3336, 3340, 3344, 3348, 3352, 3356, 3360, 3364, 3368, 3372, 3376, 3380, 3384, 3388, 3392, 3396, 3400, 3404, 3408, 3412, 3416, 3420, 3424, 3428, 3432, 3436, 3847, 4091, 4099, 4116, 4508, 4520, 4532, 5455, 6153, 7138, 7453, 7503, 7916, 7926, 7953, 7960] { 1674 | Old := NumGet(pCode + 0, Offset, "Ptr") 1675 | NumPut(Old + pCode, pCode + 0, Offset, "Ptr") 1676 | } 1677 | OldProtect := 0 1678 | if !DllCall("VirtualProtect", "Ptr", pCode, "Ptr", 8216, "UInt", 0x40, "UInt*", OldProtect, "UInt") 1679 | Throw Exception("Failed to mark MCLib memory as executable", -1) 1680 | Exports := {} 1681 | for ExportName, ExportOffset in {"bBoolsAsInts": 0, "bEmptyObjectsAsArrays": 4, "bEscapeUnicode": 8, "bNullsAsStrings": 12, "dumps": 16, "fnCastString": 288, "fnGetObj": 292, "loads": 296, "objFalse": 3192, "objNull": 3196, "objTrue": 3200} { 1682 | Exports[ExportName] := pCode + ExportOffset 1683 | } 1684 | Code := Exports 1685 | } 1686 | return Code 1687 | } 1688 | _LoadLib64Bit() { 1689 | static CodeBase64 := "" 1690 | . "NLocAQAbAA34DTxTSIMA7EBIiwXkDAAAAEiLAEiJ00ggOQEPhIUANEiFENIPhJwBEIsCQQS5XwEQiUwkOEgAuiIAVQBuAGsIAEiNARyJEEi6IG4A" 1691 | . "bwB3ABNIiQBQCEi6XwBPAIhiAGoBDRC6dAA3AGaJUBxIjVAgQMdAGGUAYwAXEwBIidpmRIlIHsjo/BkBXwNBAFQACBiNUAIAHAAZEDHAAEiDxEBb" 1692 | . "w4tEACRwRQ+2yYlEgCQg6K8OAAAFGAgPH4ABv0GDABAsMdIDTAFHTAAUYOiKpoAqTAAdYDHAABAaAYMYkAEAHJcAQVUAQVRVV1ZTSIEk7MgBB7sU" 1693 | . "gV9EiQIagYyJzUjHQggBARNIiwEPtxBmQIP6IA+HzYAHSQC4/9n///7//wD/SQ+j0A+CMQICgWZIApAPtxFoSInIAxSgARQAD0gAjUkCc+ZIiUUC" 1694 | . "AIALWw+EzQUAMAAPjhMAGYAHbg8MhDyAB4AEdA+FtgIDA4pmg3gCckjAiVUAD4XXgOAACVIEAAkEdQMJxIMEBimABAZlgwSxgQSDwIAIgD3a/f//" 1695 | . "QFxARQAPhCkHADS6wYAUAEjHQwhBggA2YBMxwOmIwALEU0iEiU3BHnsPhWAAMwGAEAJIjVQkcGYAD+/ARTHJSIsMDcsAOYETRTHASQK8BT0PKUQk" 1696 | . "UEjIjbwkwVdIxwBeBEmgSIlUJDBBEFBBArYoQGoAB0ACBwACOAECBcABIMEi/1AwSIsAdCR4SItFAOlijQAEDx9EwSNCSToYD4XeAQ3BI4naSCSJ" 1697 | . "6cEF6E/AHoXAiA+FwwIblCSYAVgAidhIifHodAsngQRAPUNpdyUAXtQPzIKjQD/CFQ+3gJpAExlCZoZ+wVlDGiwPhXFBAw8fhMKDQxzGEQ8MhosA" 1698 | . "B0ACfQ+EV4FDAiJ1R0iJ+oAkhOjAgFqFwHU4yB2ID4c/w4TUciHPHaSHHgQHc+jBGLhAAxD/SIHEAZ5bXl9AXUFcQV3DAAotIHQPD44zgIeD6oIw" 1699 | . "gAMJd9ZBvIGlB0FxwSigOCNIi1UACEgPv+AK+C0PhAJE4ALyDxAVTgoFID64Ij2D+DAPhAIP4AKNSM9mg/kACHeRSItLCJAYSIPC4BtgB40UiYEA" 1700 | . "aFDQSIlLCIUJAESNSNBmQYP5CAl22OAHLg+EUREgSIPg34ABRQ+FBurCI8MHZoM7FA8UhCQBWbdkEP0EAAWARMmAASt1D0iNoEoCD7dCYAVNAC1G" 1701 | . "yuEKwAoPhwmCTMIgAkUx22biI4PoADBDjQybSYnSQcECmESNHEjgBv4RBAZ24EyADUWF24gPhBNAGUGD+2CVAv+AEvMPfgVxCQAAAESJ2jHA0QDq" 1702 | . "Zg9vyIPAAQBmD3LxAmYP/gLBAAHwATnQdecAZg9+wmYPcNgC5QAB2A+vwkGDAOMBdAWNBIABIsBBXPIPKmAAEEuACEWEyQ+EIgALAPIPXsjyDxFL" 1703 | . "MAgPtwNAGYAcMwYBoikFD4XC/P//QPIPWVMIMYAGEXBTCOlA4FHkaOA0ZlgPhSqBZeR3YSNzF2tDAuJ3bEMCBEMC4ndzrUMC8UBJQAIIQAIIRHoC" 1704 | . "3kECg8AKgD0HyvpGenhgUEG4RXoBD0QxwMBBA+m1oAUPRB9AAlQPhaKgAUypwJFBuGEETIAwTMA8FQEFTGBVQYNVIg+EBjkAOGAMBEyNFYMRgB/r" 1705 | . "HZBAt/5JiXDBSYnIoQQkVyAFL2uhIAHER0ALSUALBIdcXHXNAAUhHqEE2iMhL4gPhOQgC4PqXIABEBkPhxOBE7fSSQBjFJJMAdL/4oQPH+Gi1A+D" 1706 | . "ZiAqweNwOH0PheyhASAOHEG5IDsCHoIbC0iJsHMI6dSAA+YGVoAB4aARdMfpuGEGJC7Eo3alQwIpLpJDAiIuRAJ/M4MNIKbY+MYrgq2LBarngBe6" 1707 | . "ISdmIddD4D3U6VXABblh1knAJQAkIGaJSP5MYAbp1dOBOc+mlPnGpr/hcYakwEG9zczMzEepn6Z3iaYCr5JTRYAK0FN2SgzDMl3RF10PhLaQBdIa" 1708 | . "ZreCFAFU6BIQCcFThgD7//9FMdKJ+US6E7NElCS4QSKNDpTSXPQs8B+JyEkPAK/FSMHoI0SNAAyARQHJRSnIAEGDwDBmRYkEAlLQAYnBSInQSACD" 1709 | . "6gFBg/gJdwjOSJijWEmNFEKs6OWBJehYTONYCrAHmWFyZi71VvdydyQxAoWycuq0WV0PhdiwL4zp58It+WosdeBBF3iDxwF0AcEqQwNgVvdL4A8D" 1710 | . "B+3UGevfMBnwRXAzuKElZokDY08BtP9QwR14QAbwAeDBNXeyJ1MnGAJWEAKTAYABi2wNlvJj+AE2EQQ0T0FmuQIfk0/pASAPYSG+ARARAPJIDypD" 1711 | . "CChmiTPwQ0NiV+m8nZADSdAqMCWBKTHSETghIDVIBI1RMFD6CSB2HY1Rv4AABQ9EhuswYY1Rn8IAhyK+8ByNUamwOAbBDOIEchGUAwZEjVnBcVz7" 1712 | . "CQ+GlSAD4ABWv+EAEASO4wCf4wCHUnoxBFQKQQQITAQIVUgESEsEW0sENUcECrVMBApIBPtQEUgEDUsExvBAOEIEg8AM0QOyNS55sBNSF003YIAB" 1713 | . "QbuiDZgBRIlYowFFoQFWvME7pwFgowEqoAG61lzpBHQGEYABvqBzhwFqcIMB+MAZv5CyhwF4RYMB34ABTInIYDRQAPxFMe1JjUACJWAxKTNL6SjA" 1714 | . "DEwPYK9DCEG7oVpSTBtyTIBE6Q6QAYFuESG/8xEhwnSJO4MhViFAdSZzxv5QAhIljQyJ0HKRA2micgHJoADJ8QNTbCrCybBrwfIPWHsEN314v+kx" 1715 | . "0Qd0YxJEgFMP5LdAEn3pCsEBICyAgBRCAsGECRAaScfAc0KKxCym9xJwUAYABumi2RADQb0vqhZo8xECv+FPi0MIRInKECnC6ddQOEQB2rTpH3QA" 1716 | . "0vA8cQCFcABRQB/J6XyFAP2FAK9hgQBRyekdcAAViIQyfuAH6XpQByKO6TXzkACACgVY0ADCAP++DwADDwAKADAxMjM0NQA2Nzg5QUJDRARFRjEB" 1717 | . "BBAAANwQDwAArDAAlREA9AB8cABUtAA/AD8APwArPwAxAPzgzmDwDuz1tT8ARXwBkn8COgB5/AEqKvQAEXAA9SFJAGFgAGwAcwC13RXdX0QAVpIB" 1718 | . "dQBlWBHw3j90ALJsOeWS04hxbVNPKdFsy0gwZajy3VQkEFRMicZibUyNhGlCATHSwGxURW1xAP8MUCgg4cECYEiJdHdBAOACYHSL0APwc0FwcNWz" 1719 | . "X9mxAGi0A3B1datynYEAMOUFMKmBxg+3oH8Q+Al0SVAAA3QLAzC3IQtbXsOQSIuEdgjhb4BIAfCgbhggdOOBxXEKTI1EJCRYIAfomRBIhcAYdMpI" 1720 | . "AOYAATDrwJFwdkiLTtNfEJDYBECQkEFXQVY45fgRMnu0JMByALwk0IFwAEQPKYQk4IEAMIukJGARh6Dmi1EEIERA7VxJic1NmInHRJDn4HuFLkFS" 1721 | . "YA+2NRbw4DRRsDilugACAABBgP4BGQDJg+Egg8FbSACLA0mJwEiDwAACgHwkXABIiQADZkGJCA+FjQAFAABIhdIPjgCjAQAAZkQPbwAFE/7//2YP" 1722 | . "bgQ1IwAOMfbzD34EPREAEkiJ90jBAOcFSQN9GEiFEPYPhSUAvkCE7QAPhaQDAABFhAD2dX9JOXUwDwiOvQQBntsPhCwCBwAIixNIjUICMEG7IgAA" 1723 | . "DAByRIkAGkiLRxBNifgASInaSI2MJKBFAhiEAgfoCQoCMkEWuAEuACsCATxIjVAIArk6AiYTZokIKQBzdBQAEwQADrogAQEniVACDx9AAACLRxiD" 1724 | . "+AEPhAIEAJiD+AYPhFshAgQFD4RyAEmD+FACD4TRhU14AQSLsANBul8AIAJG+QApAB5EDxEAZg/WgHgQZg9+cBgAMIMAUgBDUBzobwkDGA65AmSA" 1725 | . "SAFGRIkISAiDxgGAeyAPj9xjgJGCrA+E6oCIAjPQQQEWiwtBuw0ABL4TAG4AHVEEgBsZSIkC0AJncQIxyUWFIOR+KWaQAAlBuAEAMwCDwQFIg8IC" 1726 | . "AgATAEE5zHXnHrkBCgN+AUGA6UiNSAACGdJIiQuD4gAgg8JdZokQDxAotCTAABMPKLwCJIE6RA8ohCTgEcENgcT4gAFbXl8AXUFcQV1BXkFAX8Nm" 1727 | . "Dx9EQAY7IFEwD4TmQEJFMRj2uXvBCsAshdT9QP//QYMHAcQyFkuAh4ADAoGIj93BBosAF41KAo1CA0JQjVQiA8EuD8MTiQDBg8ABOcJ19whBiQ8B" 1728 | . "EOlo//9o/w8fQ0L3AJdBQrheLANBwU8APMA4AQKRR2PAagCRD4U2gFECko8GwwAbQAI4f1pIi8xPEEBiwGnoJ8NhRGBOuoGFQD+EfoUFQq4floRA" 1729 | . "BYR12kB26LJAotzpUsAGwz3BI5bCI8BzEnTDcxBIQHZPAGIIAGoAA4VIiQjHIEAIZQBjwaFIDGGDe1AO6T5AJcRPi0APSDsN1vmAFYSiwMGTOw2p" 1730 | . "AgPjApcEBawAA0g5wQ+EojPCAwBIOQCnV4WGAvABBb8iAFUAbiwAa4Eigis4gCIgSEC/bgBvAHcABkjAiXgISL9fBCZAA0QQv8Erx0AYAiaJ3Hgc" 1731 | . "AUGBKwDBHkI7RNSJRNbpQ8AVDx+A4SFhQDUPiFP8gBLhD9YBARGLA0WNRCQBhDHS4WdIicFBQk2Ug8LgT8DhTwlE4DmE57rjTwNmiRFCNJKTYAhJ" 1732 | . "O0A0jB0hAXFANA+PsyGEwDSDLuhKgCEu1eFRHwABRkJQAUGJBwI99gU9TZtjBwA9BkARogiOWCABs0BHwFxHEEUYIAoxQF1lJITnwVGLByBNoQmE" 1733 | . "jjgABkAVgAvp/fvkOwRBuqFtSI1BBkGeu0Fu4BjBTWGPWQSBIGyJBABTYhDJYAYjGVM3ogfiJAMa1gAnCBqvBuXkNq3jJulmAAbkQMEC4gvCBQPp" 1734 | . "XqAa5AjgQyWEB3KAB+nS5hODByMBA4Ea+egXw2sB6RKz5glBueEXSY1AiAZBuuIXRYlI4hcMRYmAnsFwVvr//wTppOUJ8g8QB0gAjVQkYGYP78kA" 1735 | . "RTHJSIsNMesRoBGNhCThEEUxwAxBu6AMpqxIiwFIgIlUJDBIjZTiAQgPKYwCBUjHhCTGsCJnYQVUJCggFEAC1pBEAsAnnOYEqIQCRAZjgGnAAkQk" 1736 | . "QGIHAAE4EYIDRCQgIQPyDxEIhCSIAAH/UDBIAItUJGgPtwJmYIXAD4S/4BiiJ7+5whITuWBD4K/kd0ngoaSJwSKhQYnAA0QBB0oEIK7BoQd14EFy" 1737 | . "RbCJCOl5oAjhV0FBWVBED7bNoy6JoA/otJj4ICVYAgSD3MIAEiBMi0EYuAET6xQB4l6NSAFIOcIPJISEYYKJyOBgSMEA4QVJOUQI8HQ24mCjQhTp" 1738 | . "gAiCpOmzV2AK4gZBMcsBDA5AMUEfoIGA1eEMAFJAuGaDeJD+AHXygKHp1yCBAGaQg8IDQYkXaQBQeV2CT0ygAoJPjfqKYYqQw1pCWYBI4MHBDpNh" 1739 | . "uuBKuWzgAEG4oQCpIIQIx2CDdWB+SIHgYQAzQAbpc2EM40eLEgdAsyABobHQdfkNAQVdUArELDHS6OObUTbGBsXiDHMsdFRCcRJ1wQa7ZcYGdABy" 1740 | . "h+EfIndwAFgG6QWxTYXwAm2xWIsV2fUAEEq5AgOJgDtQCoMCSBgI6dzQBfAtBOnTZ4MAIwhtSugQwYphMKydYQK+8iThD1ENuVtRF8Q99/IDBemK" 1741 | . "FgKCAdUyayDCAQA1a35QA+AACALpUIEAiwfp0AEAGpBBV0FWQVUAQVRVV1ZTSIMA7EhIi2kgSYkAzEmJ0kiF7Q8MjlDRHyAkEEyLcQFgKnkYRTHb" 1742 | . "SL4UzcwDAEjgJAjrJKFxH005XCSwSuUhKgSDw6BSxyBMOd0YD4QPAATzAd5+24HRFjHSRTH/ZpA0ADhIhcl5CUj3CNlBv5INjVwkNhhBuRTiUXFX" 1743 | . "yEWJAM1Ig+sCQYPpgAFI9+ZIweoAbgAEkkgBwEgpwQCNQTBIidFmiQJDIXh10k1jyUUAhP90F0WNTf4EuC3xUWPJZkKJCERMENIASItcJAAISo0M" 1744 | . "S2ZBOdACD4VbkE64UTVwKQgPtxRzXUE7VAJQ/g+FP7ABZiAF5whJiTjiMEiDxEgDf4LgMk8QQQ+3CgEwABFmOcoPhQYHRQXzATEEI2aFyXQSt+kQ" 1745 | . "hfXRo8DrquP2PCACD4TKUAGABJMIEYAATAL+MAV0u+lasYABkAoAUho4AE1BHLoT0Qm2GJAlTCQoAEyLCUmJ00iJQONNhcl4e/ANTCCJyEyJyfgT" 1746 | . "SYkC0SMUTInQg8EwgVASDFNJg+oBchSkSJggAkNNQSamkRx/gKoQCfEYcEfCCbAEs0cIEA+3SP7QC3XlMQzASUNHoBI4W17DI/IPghxBujAyCmdm" 1747 | . "IwMA8whI9+5RAMH4AD9IwfoCSCnCAWEJQY0EQkQpyMHQCWaJREv+EUdxH0FACc2D6AK6URy5QaIcmGaJFERVCoXiWlAXQYsQEhzzOnFG/sJ1RlBe" 1748 | . "oDdjCfMTYC4REAlQSoSh0BpIiwJBIrrVq0mJEVAUEA8UtwGCWIISo7Yd6QDj//9MjRU28WHxR2aD+CKwZlHBgyD4Bw+OjJAAjVAA+GaD+hoPh6gB" 1749 | . "EAUPt9JJYxSS4EwB0v/i8A5wGPCNwUACSYsBvlwQBdCNIdIGBGaJMAEHiXih8hUPt0ECRVybQgMiIsAESYsR8VNJifIBw7+JAqAoABbwNCECkv8y" 1750 | . "GgG6cQW7clOfU6EFcgVYAmAmkHIC18V5AmZ/AoJmLvah8QKWp/MC8wpu/grpTzKsc/J88AJ0e7YCdKO5AiOr865xAlN2AmJ/AvuSqGFRFVwPhV8A" 1751 | . "ExEDIWdWC0EAWwvpycExEk2/q7ID85yEsCuPhQzuiQ8HAVwrBJAeSI0VSe8RcdzDg+Mgu740GkCJw2bB6wTTADxZ0gDoDBAB8C7AQwEc0hpAABQC" 1752 | . "AgadAwZjBQ4IMQXADOMFcAbpJxF3CY1QgTAiIQ+GgmQBRoP4Hw+GsStVQQ5sUh5aUB4ZAB7ppvJxb/YYQYugAwGACvOALFEphWrgAQAu4AChIGXw" 1753 | . "cRDTAOvwdJTwECRj9xnwAATpn/euAWHpFo/wAIISZoACjVDgkZAJXg+HUUjpaxABAflG" 1754 | static Code := false 1755 | if ((A_PtrSize * 8) != 64) { 1756 | Throw Exception("_LoadLib64Bit does not support " (A_PtrSize * 8) " bit AHK, please run using 64 bit AHK", -1) 1757 | } 1758 | ; MCL standalone loader https://github.com/G33kDude/MCLib.ahk 1759 | ; Copyright (c) 2021 G33kDude, CloakerSmoker (CC-BY-4.0) 1760 | ; https://creativecommons.org/licenses/by/4.0/ 1761 | if (!Code) { 1762 | CompressedSize := VarSetCapacity(DecompressionBuffer, 5343, 0) 1763 | if !DllCall("Crypt32\CryptStringToBinary", "Str", CodeBase64, "UInt", 0, "UInt", 1, "Ptr", &DecompressionBuffer, "UInt*", CompressedSize, "Ptr", 0, "Ptr", 0, "UInt") 1764 | throw Exception("Failed to convert MCLib b64 to binary", -1) 1765 | if !(pCode := DllCall("GlobalAlloc", "UInt", 0, "Ptr", 7984, "Ptr")) 1766 | throw Exception("Failed to reserve MCLib memory", -1) 1767 | DecompressedSize := 0 1768 | if (DllCall("ntdll\RtlDecompressBuffer", "UShort", 0x102, "Ptr", pCode, "UInt", 7984, "Ptr", &DecompressionBuffer, "UInt", CompressedSize, "UInt*", DecompressedSize, "UInt")) 1769 | throw Exception("Error calling RtlDecompressBuffer", -1, Format("0x{:08x}", r)) 1770 | OldProtect := 0 1771 | if !DllCall("VirtualProtect", "Ptr", pCode, "Ptr", 7984, "UInt", 0x40, "UInt*", OldProtect, "UInt") 1772 | Throw Exception("Failed to mark MCLib memory as executable", -1) 1773 | Exports := {} 1774 | for ExportName, ExportOffset in {"bBoolsAsInts": 0, "bEmptyObjectsAsArrays": 16, "bEscapeUnicode": 32, "bNullsAsStrings": 48, "dumps": 64, "fnCastString": 304, "fnGetObj": 320, "loads": 336, "objFalse": 3360, "objNull": 3376, "objTrue": 3392} { 1775 | Exports[ExportName] := pCode + ExportOffset 1776 | } 1777 | Code := Exports 1778 | } 1779 | return Code 1780 | } 1781 | _LoadLib() { 1782 | return A_PtrSize = 4 ? this._LoadLib32Bit() : this._LoadLib64Bit() 1783 | } 1784 | 1785 | Dump(obj, pretty := 0) 1786 | { 1787 | this._init() 1788 | if (!IsObject(obj)) 1789 | throw Exception("Input must be object", -1) 1790 | size := 0 1791 | DllCall(this.lib.dumps, "Ptr", &obj, "Ptr", 0, "Int*", size 1792 | , "Int", !!pretty, "Int", 0, "CDecl Ptr") 1793 | VarSetCapacity(buf, size*2+2, 0) 1794 | DllCall(this.lib.dumps, "Ptr", &obj, "Ptr*", &buf, "Int*", size 1795 | , "Int", !!pretty, "Int", 0, "CDecl Ptr") 1796 | return StrGet(&buf, size, "UTF-16") 1797 | } 1798 | 1799 | Load(ByRef json) 1800 | { 1801 | this._init() 1802 | 1803 | _json := " " json ; Prefix with a space to provide room for BSTR prefixes 1804 | VarSetCapacity(pJson, A_PtrSize) 1805 | NumPut(&_json, &pJson, 0, "Ptr") 1806 | 1807 | VarSetCapacity(pResult, 24) 1808 | 1809 | if (r := DllCall(this.lib.loads, "Ptr", &pJson, "Ptr", &pResult , "CDecl Int")) || ErrorLevel 1810 | { 1811 | throw Exception("Failed to parse JSON (" r "," ErrorLevel ")", -1 1812 | , Format("Unexpected character at position {}: '{}'" 1813 | , (NumGet(pJson)-&_json)//2, Chr(NumGet(NumGet(pJson), "short")))) 1814 | } 1815 | 1816 | result := ComObject(0x400C, &pResult)[] 1817 | if (IsObject(result)) 1818 | ObjRelease(&result) 1819 | return result 1820 | } 1821 | 1822 | True[] 1823 | { 1824 | get 1825 | { 1826 | static _ := {"value": true, "name": "true"} 1827 | return _ 1828 | } 1829 | } 1830 | 1831 | False[] 1832 | { 1833 | get 1834 | { 1835 | static _ := {"value": false, "name": "false"} 1836 | return _ 1837 | } 1838 | } 1839 | 1840 | Null[] 1841 | { 1842 | get 1843 | { 1844 | static _ := {"value": "", "name": "null"} 1845 | return _ 1846 | } 1847 | } 1848 | } 1849 | 1850 | } -------------------------------------------------------------------------------- /Examples/EventCallbacks.ahk: -------------------------------------------------------------------------------- 1 | #NoEnv 2 | SetBatchLines, -1 3 | 4 | #Include ../Chrome.ahk 5 | 6 | TestPages := 3 7 | 8 | 9 | ; --- Define a data URL for the test page --- 10 | 11 | ; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs 12 | DataURL = 13 | ( Comments 14 | 15 | 16 | 17 | ; Use {} to allow text insertion using Format() later 18 | Test Page {} 19 | 20 | 21 | 22 | 23 | 24 | ) 25 | 26 | 27 | ; --- Define some JavaScript to be injected into each page --- 28 | 29 | JS = 30 | ( Comments 31 | ; Using a self-invoking anonymous function for scope management 32 | ; https://blog.mgechev.com/2012/08/29/self-invoking-functions-in-javascript-or-immediately-invoked-function-expression/ 33 | (function(){ 34 | var clickCount = 0; 35 | 36 | ; Whenever the button tag with class someclass is clicked 37 | document.querySelector("button.someclass").onclick = function() { 38 | clickCount++; 39 | 40 | ; Prefix the message with AHK: so it can be 41 | ; filtered out in the AHK-based callback function 42 | console.log("AHK:" + clickCount); 43 | }; 44 | })(); 45 | ) 46 | 47 | 48 | ; --- Create a new Chrome instance --- 49 | 50 | ; Define an array of pages to open 51 | DataURLs := [] 52 | Loop, %TestPages% 53 | { 54 | File := Format("{}\{}.html", A_Temp, A_Index) 55 | FileDelete, % File 56 | FileAppend, % Format(DataURL, A_Index), % File 57 | DataURLs.Push(File) 58 | } 59 | 60 | ; Open Chrome with those pages 61 | ChromeInst := new Chrome("ChromeProfile", DataURLs) 62 | 63 | 64 | ; --- Connect to the pages --- 65 | PageInstances := [] 66 | Loop, %TestPages% 67 | { 68 | ; 不加延时容易报错 69 | Sleep, 2000 70 | ; Bind the page number to the function for extra information in the callback 71 | BoundCallback := Func("Callback").Bind(A_Index) 72 | 73 | ; Get an instance of the page, passing in the callback function 74 | if !(PageInst := ChromeInst.GetPageByTitle(A_Index, "contains",, BoundCallback)) 75 | { 76 | MsgBox, Could not retrieve page %A_Index%! 77 | ChromeInst.Kill() 78 | ExitApp 79 | } 80 | PageInstances.Push(PageInst) 81 | 82 | ; Enable console events and inject the JS payload 83 | PageInst.WaitForLoad() 84 | PageInst.Call("Console.enable") 85 | PageInst.Evaluate(JS) 86 | } 87 | 88 | MsgBox, Running... Click OK to exit 89 | 90 | 91 | ; --- Close the Chrome instance --- 92 | 93 | try 94 | PageInstances[1].Call("Browser.close") ; Fails when running headless 95 | catch 96 | ChromeInst.Kill() 97 | for Index, PageInst in PageInstances 98 | PageInst.Disconnect() 99 | 100 | ExitApp 101 | return 102 | 103 | 104 | Callback(PageNum, Event) 105 | { 106 | ; Filter for console messages starting with "AHK:" 107 | if (Event.Method == "Console.messageAdded" 108 | && InStr(Event.params.message.text, "AHK:") == 1) 109 | { 110 | ; Strip out the leading AHK: 111 | Text := SubStr(Event.params.message.text, 5) 112 | 113 | ToolTip, Clicked %Text% times on page %PageNum% 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Examples/ExportPDF.ahk: -------------------------------------------------------------------------------- 1 | #NoEnv 2 | SetBatchLines, -1 3 | 4 | #Include ../Chrome.ahk 5 | 6 | 7 | ; --- Create a new headless Chrome instance --- 8 | 9 | ChromeInst := new Chrome("ChromeProfile", "https://example.com", "--headless") 10 | 11 | 12 | ; --- Connect to the page --- 13 | 14 | if !(PageInst := ChromeInst.GetPage()) 15 | { 16 | MsgBox, Could not retrieve page! 17 | ChromeInst.Kill() 18 | } 19 | else 20 | { 21 | PageInst.WaitForLoad() 22 | 23 | 24 | ; --- Export a PDF of the page --- 25 | 26 | ; Get the PDF in Base64 27 | Base64PDF := PageInst.Call("Page.printToPDF").data 28 | 29 | ; Convert to a normal binary PDF 30 | Size := Base64_Decode(BinaryPDF, Base64PDF) 31 | 32 | ; Write the binary PDF to a file 33 | FileName := "Exported_" A_TickCount ".pdf" 34 | FileOpen(FileName, "w").RawWrite(BinaryPDF, Size) 35 | 36 | ; Open the file 37 | Run, %FileName% 38 | 39 | 40 | ; --- Close the Chrome instance --- 41 | 42 | try 43 | PageInst.Call("Browser.close") ; Fails when running headless 44 | catch 45 | ChromeInst.Kill() 46 | PageInst.Disconnect() 47 | } 48 | 49 | ExitApp 50 | return 51 | 52 | 53 | Base64_Encode(ByRef Out, ByRef In, InLen) 54 | { 55 | DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", &In 56 | , "UInt", InLen, "UInt", 0x40000001, "Ptr", 0, "UInt*", OutLen) 57 | VarSetCapacity(Out, OutLen * (1+A_IsUnicode)) 58 | DllCall("Crypt32.dll\CryptBinaryToString", "Ptr", &In 59 | , "UInt", InLen, "UInt", 0x40000001, "Str", Out, "UInt*", OutLen) 60 | return OutLen 61 | } 62 | 63 | Base64_Decode(ByRef Out, ByRef In) 64 | { 65 | DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", &In, "UInt", StrLen(In) 66 | , "UInt", 0x1, "Ptr", 0, "UInt*", OutLen, "Ptr", 0, "Ptr", 0) 67 | VarSetCapacity(Out, OutLen) 68 | DllCall("Crypt32.dll\CryptStringToBinary", "Ptr", &In, "UInt", StrLen(In) 69 | , "UInt", 0x1, "Str", Out, "UInt*", OutLen, "Ptr", 0, "Ptr", 0) 70 | return OutLen 71 | } -------------------------------------------------------------------------------- /Examples/InjectJS.ahk: -------------------------------------------------------------------------------- 1 | #NoEnv 2 | SetBatchLines, -1 3 | 4 | #Include ../Chrome.ahk 5 | 6 | 7 | ; --- Create a new Chrome instance --- 8 | 9 | ChromeInst := new Chrome("ChromeProfile", "https://autohotkey.com/") 10 | 11 | 12 | ; --- Connect to the page --- 13 | 14 | if !(PageInst := ChromeInst.GetPage()) 15 | { 16 | MsgBox, Could not retrieve page! 17 | ChromeInst.Kill() 18 | } 19 | else 20 | { 21 | ; --- Perform JavaScript injection --- 22 | 23 | Loop 24 | { 25 | InputBox, JS,, 26 | ( LTrim 27 | Enter some JavaScript to be run on the page, or leave blank to exit. For example: 28 | 29 | alert('hi'); 30 | window.location = "https://p.ahkscript.org/"; 31 | ) 32 | 33 | if (JS == "" || ErrorLevel) 34 | break 35 | 36 | try 37 | Result := PageInst.Evaluate(JS) 38 | catch e 39 | { 40 | MsgBox, % "Exception encountered in " e.What ":`n`n" 41 | . e.Message "`n`n" 42 | . "Specifically:`n`n" 43 | . Chrome.Jxon_Dump(Chrome.Jxon_Load(e.Extra), "`t") 44 | 45 | continue 46 | } 47 | 48 | MsgBox, % "Result:`n" Chrome.Jxon_Dump(Result, "`t") 49 | } 50 | 51 | 52 | ; --- Close the Chrome instance --- 53 | 54 | try 55 | PageInst.Call("Browser.close") ; Fails when running headless 56 | catch 57 | ChromeInst.Kill() 58 | PageInst.Disconnect() 59 | } 60 | 61 | ExitApp 62 | return 63 | -------------------------------------------------------------------------------- /Examples/Pastebin.ahk: -------------------------------------------------------------------------------- 1 | #NoEnv 2 | SetBatchLines, -1 3 | 4 | #Include ../Chrome.ahk 5 | 6 | 7 | ; --- Create a new Chrome instance --- 8 | 9 | ; Instead of providing a URL here, let's try 10 | ; navigating later for demonstration purposes 11 | ChromeInst := new Chrome("ChromeProfile") 12 | 13 | 14 | ; --- Connect to the page --- 15 | 16 | if !(PageInst := ChromeInst.GetPage()) 17 | { 18 | MsgBox, Could not retrieve page! 19 | ChromeInst.Kill() 20 | } 21 | else 22 | { 23 | ; --- Navigate to the desired URL --- 24 | 25 | PageInst.Call("Page.navigate", {"url": "https://p.ahkscript.org/"}) 26 | PageInst.WaitForLoad() 27 | 28 | 29 | ; --- Manipulate the page using the DOM endpoint --- 30 | ; One of the ways you can interact with elements on the page is by using 31 | ; the Chrome debugger API's DOM (Document Object Model) input. While this 32 | ; works, it can be more difficult than injecting JavaScript to perform 33 | ; the same manipulations. 34 | ; 35 | ; However, one benefit this method has over JavaScript injection is that 36 | ; you would not need to escape values before passing them to the endpoint. 37 | ; If you were using JavaScript injection and your value had a stray 38 | ; non-escaped end-quote in it, the text could break out of its string and 39 | ; be the cause of a JavaScript injection vulnerability (or just buggy code). 40 | 41 | ; Find the root node 42 | RootNode := PageInst.Call("DOM.getDocument").root 43 | 44 | ; Find and change the name element 45 | NameNode := PageInst.Call("DOM.querySelector", {"nodeId": RootNode.nodeId, "selector": "input[name=name]"}) 46 | PageInst.Call("DOM.setAttributeValue", {"nodeId": NameNode.NodeId, "name": "value", "value": "ChromeBot"}) 47 | 48 | ; Find and change the description element 49 | DescNode := PageInst.Call("DOM.querySelector", {"nodeId": RootNode.nodeId, "selector": "input[name=desc]"}) 50 | PageInst.Call("DOM.setAttributeValue", {"nodeId": DescNode.NodeId, "name": "value", "value": "Pasted with ChromeBot"}) 51 | 52 | 53 | ; --- Manipulate the page using JavaScript injection --- 54 | ; Whatever you pass to PageInst.Evaluate will act exactly like you were 55 | ; inputting it to the page's developer tools JavaScript console. 56 | 57 | PageInst.Evaluate("editor.setValue('test');") 58 | PageInst.Evaluate("document.querySelector('input[type=submit]').click();") 59 | PageInst.WaitForLoad() 60 | MsgBox, % "A new paste has been created at " 61 | . PageInst.Evaluate("window.location.href").value 62 | 63 | 64 | ; --- Close the Chrome instance --- 65 | 66 | try 67 | PageInst.Call("Browser.close") ; Fails when running headless 68 | catch 69 | ChromeInst.Kill() 70 | PageInst.Disconnect() 71 | } 72 | 73 | ExitApp 74 | return 75 | -------------------------------------------------------------------------------- /Examples/打开百度搜索内容并获取结果的示例.ahk: -------------------------------------------------------------------------------- 1 | ; ---------------------------------------------------------------------------------------- 2 | ; 第1个参数是用户配置文件目录,也就是 User_Data 目录,或 "First Run" 文件所在位置。 3 | ; 当然,也可以像本例中一样,随便设置一个位置,这样就会用新的临时身份来打开 Chrome 。 4 | ; 第2个参数是初始打开的网址们,不填打开空白页。 5 | ; 第3个参数是自定义命令行参数,多个参数之间加空格。 6 | ; 无头模式: "--headless" 7 | ; 浏览器标识: "--user-agent=""标识内容""" 8 | ; 既设置无头模式又改标识: "--headless --user-agent=""标识内容""" 9 | ; 更多其它设置,自己去搜 Chrome 命令行参数。 10 | ; 第4个参数是 Chrome.exe 位置,不填会尝试在开始菜单和注册表里找。 11 | ChromeInst := new Chrome("User_Data") 12 | 13 | 标签 := ChromeInst.GetPage() ; 连接当前标签 14 | 标签.Url := "https://www.baidu.com/" ; 打开百度 15 | 标签.WaitForLoad() ; 等待网页加载完成 16 | 17 | ; 对于使用 querySelector 或 querySelectorAll 等方法获取的元素 18 | ; 支持元素在 JS 中的全部属性与方法(注意:大小写必须与 JS 中的保持一致) 19 | ; 获取元素虽然支持 .getElementById() 等方法,但建议只用 .querySelector() 20 | ; 因为通过浏览器 -> 审查元素(开发者工具) -> Elements -> Copy -> Copy JS path 21 | ; 可以快速得到 .querySelector() 中需要填的内容 22 | 标签.querySelector("#hotsearch-refresh-btn").click() ; 调用元素的 js 方法,点击换一换按钮 23 | 24 | 元素集 := 标签.querySelectorAll("li.hotsearch-item") ; 获取元素集 25 | MsgBox % 元素集[1].textContent ; 读属性 26 | MsgBox % 元素集[2].InnerHTML ; 读属性但失败,因为大小写错误 27 | 元素集[3].innerHTML := "写属性就是这么简单" ; 写属性 28 | MsgBox 注意看,第3条热搜被改了 29 | 30 | 标签.getElementById("kw").value := "我爱ahk 我爱KMCounter" ; 在搜索框中输入文字 31 | 标签.getElementById("su").click() ; 点击搜索按钮 32 | 标签.WaitForLoad() ; 等待网页加载完成 33 | 34 | while (!标签.getElementById("2")) ; 等待元素出现 35 | Sleep 1000 36 | MsgBox % 标签.getElementById("2").innerText ; 打印第二条搜索结果 37 | 38 | 标签.Evaluate("alert('看到了吧!\nahk 操控 Chrome 也是非常简单的!')") ; 执行 JS 代码让 Chrome 弹一个提示框 39 | 40 | ; 这里用到的不是 JS ,而是 Chrome API 。 41 | ; https://chromedevtools.github.io/devtools-protocol/tot/Browser/ 42 | MsgBox % 标签.Call("Browser.getVersion").userAgent ; 获取浏览器 userAgent 43 | 44 | 标签.Close() ; 关闭标签 45 | 46 | ExitApp 47 | 48 | #Include ../Chrome.ahk 49 | -------------------------------------------------------------------------------- /Examples/监听事件.ahk: -------------------------------------------------------------------------------- 1 | ChromeInst := new Chrome("User_Data") 2 | 3 | 标签 := ChromeInst.GetPage( , , "Callback") ; 连接当前标签并为标签发生的事件设置回调函数 4 | 5 | 标签.Call("Network.enable") ; 开启 Network 事件监听 6 | 标签.Url := "https://www.baidu.com/" ; 打开百度 7 | 标签.WaitForLoad() ; 等待网页加载完成 8 | 标签.Call("Network.disable") ; 获取到目标内容后,及时关闭 Network 事件监听 9 | 10 | Sleep 10000 11 | 12 | 标签.Call("Browser.close") ; 关闭浏览器(所有页面和标签) 13 | ExitApp 14 | 15 | Callback(Event) 16 | { 17 | if (Event.Method == "Network.requestWillBeSent") 18 | { 19 | ; 这里会得到很多 requestId ,这是因为除网页本身还有各种资源(如图片)请求加载 20 | ToolTip % Event.params.requestId 21 | } 22 | } 23 | 24 | #Include ../Chrome.ahk -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 GeekDude 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chrome.ahk-plus 2 | 3 | Automate Google Chrome and MS Edge using native AutoHotkey. 4 | 使用纯 AutoHotkey 操控 Chrome 和 Edge 。 5 | 6 | 7 | ## What has been enhanced compared to Chrome.ahk 8 | 9 | * Significantly simplifies the manipulation of elements and frames. 10 | * `Google Chrome` and `Microsoft Edge` are supported. 11 | * Error reports directed to user code instead of library code. 12 | * Added 30-seconds timeout for all funtions that could cause a dead loop. 13 | * Simplified creation of ProfilePath. 14 | * Fixed an issue that Chrome to report error due to slow opening. 15 | * Fixed an issue that Chrome to report error due to shortcuts were not found. 16 | * 基于 GeekDude 2023.03.21 Release 版修改,与 GeekDude 版相比有以下增强。 17 | * 大幅简化元素及框架的操作。 18 | * 支持`谷歌 Chrome`、`微软 Edge`、`百分浏览器`。 19 | * 报错可直接定位到用户代码,而不是库代码。 20 | * 为所有可能造成死循环的地方添加了默认30秒的超时参数。 21 | * 简化了 Chrome 用户配置目录的创建。 22 | * 修复了 Chrome 打开缓慢而报错的问题。 23 | * 修复了找不到开始菜单中的 Chrome 快捷方式而报错的问题。 24 | 25 | 26 | ## How to use 27 | 28 | **You can find more sample code showing how to use this library in the Examples folder.** 29 | **Examples 目录下有更多示例。** 30 | 31 | 32 | ## Basic Demo 33 | 34 | ```AutoHotkey 35 | #Include Chrome.ahk 36 | 37 | ; Create an instance of the Chrome class using 38 | ; the folder ChromeProfile to store the user profile 39 | ChromeInst := new Chrome("ChromeProfile") 40 | 41 | ; Connect to the newly opened tab and navigate to another website 42 | PageInst := ChromeInst.GetPage() 43 | PageInst.Url := "https://autohotkey.com/" 44 | PageInst.WaitForLoad() 45 | 46 | ; Support for get/set all JS propertys of element 47 | MsgBox % PageInst.querySelector("#MainTitle").outerHTML 48 | PageInst.querySelector("#MainTitle").innerHTML := "Set property is also very easy" 49 | 50 | ; Support for call all JS methods of element 51 | rect := PageInst.querySelector("#MainTitle").getBoundingClientRect() 52 | MsgBox % rect.x "`n" rect.y 53 | 54 | ; Return a screenshot of the element (base64 encoded), 55 | base64 := PageInst.querySelector("#MainTitle").Screenshot() 56 | 57 | ; You can show or save it as an image file by using the ImagePut library. 58 | ; https://github.com/iseahound/ImagePut 59 | ; ImageShow(base64) 60 | 61 | ; Execute some JavaScript 62 | PageInst.Evaluate("alert('Hello World!');") 63 | 64 | ; Close the page 65 | PageInst.Close() 66 | 67 | ExitApp 68 | return 69 | ``` 70 | 71 | 72 | ## Switching Between Frame 73 | 74 | ![alt text](https://i.ibb.co/PW2P9ZG/Rufaydium-Frames-Example.png) 75 | 76 | Example for TAB/Page 1 77 | 78 | ```AutoHotkey 79 | PageInst.SwitchToFrame(1) ; switching to frame A 80 | PageInst.GetElementById(someid) ; this will get element from frame A 81 | PageInst.SwitchToFrame(2) ; switching to frame B 82 | PageInst.GetElementById(someid) ; this will get element from frame B 83 | PageInst.SwitchToFrame(2, 1) ; switching to frame BA 84 | PageInst.GetElementById(someid) ; this will get element from frame BA 85 | PageInst.SwitchToFrame(2) ; switch back to Frame B 86 | PageInst.SwitchToMainPage() ; switch back to Main Page / Main frame 87 | ``` 88 | 89 | Example for TAB/Page 1 90 | 91 | ```AutoHotkey 92 | PageInst.SwitchToFrame(1) ; switching to frame A 93 | PageInst.GetElementById(someid) ; this will get element from frame A 94 | PageInst.SwitchToFrameByName("B") ; switching to frame B by name 95 | PageInst.GetElementById(someid) ; this will get element from frame B 96 | PageInst.SwitchToFrameByURL(urlOfBA) ; switching to frame BA by url 97 | PageInst.GetElementById(someid) ; this will get element from frame BA 98 | PageInst.SwitchToFrame(2) ; switch back to Frame B 99 | PageInst.SwitchToMainPage() ; switch back to Main Page / Main frame 100 | ``` 101 | 102 | Example for TAB/Page 2 103 | 104 | ```AutoHotkey 105 | PageInst.SwitchToFrame(1) ; switching to frame X 106 | PageInst.SwitchToFrame(2) ; switching to frame Y 107 | PageInst.SwitchToFrame(3) ; switching to frame Z 108 | PageInst.SwitchToMainPage() ; switch back to Main Page / Main frame 109 | ``` 110 | --------------------------------------------------------------------------------