if !item := Chrome.FindInstances() {
MsgBox, Не найден Chrome`, запущенный в debug mode
ExitApp
}
ChromeInst := {"base": Chrome, "DebugPort": item.port}
WinActivate, % "ahk_pid" item.PID
pages := ChromeInst.GetPageList()
found := ""
for i, page in pages
continue
until RegExMatch(page.url, "delfi.lv") && found := true
if !found {
MsgBox, Страница с http://delfi.lv не найдена
ExitApp
}
pageInst := new Chrome.Page( page.webSocketDebuggerUrl )
pageInst.Call("Page.bringToFront")
pageInst.WaitForLoad()
a := a_tickcount
RootNode := PageInst.Call("DOM.getDocument").root
OuterHTML := PageInst.Call("DOM.getOuterHTML", {"nodeId": RootNode.nodeId}).OuterHTML
msgbox % a_tickcount - a
MsgBox, % OuterHTML
ExitApp
class Chrome
{
static DebugPort := 9222
/*
Escape a string in a manner suitable for command line parameters
*/
CliEscape(Param)
{
return """" RegExReplace(Param, "(\\*)""", "$1$1\""") """"
}
FindInstances()
{
static Needle := "--remote-debugging-port=(\d+)"
Out := {}
for k, PID in EnumProcessesByName("chrome.exe")
cmd := GetCommandLine(PID).cmd
until found := RegExMatch(cmd, Needle, Match)
Return found ? {port: Match1, PID: PID} : ""
}
/*
ProfilePath - Path to the user profile directory to use. Will use the standard if left blank.
URLs - The page or array of pages for Chrome to load when it opens
Flags - Additional flags for chrome when launching
ChromePath - Path to chrome.exe, will detect from start menu when left blank
DebugPort - What port should Chrome's remote debugging server run on
*/
__New(ProfilePath:="", URLs:="about:blank", Flags:="", ChromePath:="", DebugPort:="")
{
; Verify ProfilePath
if (ProfilePath != "" && !InStr(FileExist(ProfilePath), "D"))
throw Exception("The given ProfilePath does not exist")
this.ProfilePath := ProfilePath
; Verify ChromePath
if (ChromePath == "")
FileGetShortcut, %A_StartMenuCommon%\Programs\Google Chrome.lnk, ChromePath
if (ChromePath == "")
RegRead, ChromePath, HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe
if !FileExist(ChromePath)
throw Exception("Chrome could not be found")
this.ChromePath := ChromePath
; Verify DebugPort
if (DebugPort != "")
{
if DebugPort is not integer
throw Exception("DebugPort must be a positive integer")
else if (DebugPort <= 0)
throw Exception("DebugPort must be a positive integer")
this.DebugPort := DebugPort
}
; Escape the URL(s)
for Index, URL in IsObject(URLs) ? URLs : [URLs]
URLString .= " " this.CliEscape(URL)
Run, % this.CliEscape(ChromePath)
. " --remote-debugging-port=" this.DebugPort
. (ProfilePath ? " --user-data-dir=" this.CliEscape(ProfilePath) : "")
. (Flags ? " " Flags : "")
. URLString
,,, OutputVarPID
this.PID := OutputVarPID
}
/*
End Chrome by terminating the process.
*/
Kill()
{
Process, Close, % this.PID
}
/*
Queries chrome for a list of pages that expose a debug interface.
In addition to standard tabs, these include pages such as extension
configuration pages.
*/
GetPageList()
{
http := ComObjCreate("WinHttp.WinHttpRequest.5.1")
http.open("GET", "http://127.0.0.1:" this.DebugPort "/json")
http.send()
return JSON.Parse(http.responseText)
}
GetActivePage(fnCallback := "") {
Return new this.Page( this.GetPageList()[1, "webSocketDebuggerUrl"], fnCallback )
}
/*
Returns a connection to the debug interface of a page that matches the
provided criteria. When multiple pages match the criteria, they appear
ordered by how recently the pages were opened.
Key - The key from the page list to search for, such as "url" or "title"
Value - The value to search for in the provided key
MatchMode - What kind of search to use, such as "exact", "contains", "startswith", or "regex"
Index - If multiple pages match the given criteria, which one of them to return
fnCallback - A function to be called whenever message is received from the page
*/
GetPageBy(Key, Value, MatchMode:="exact", Index:=1, fnCallback:="")
{
Count := 0
for n, PageData in this.GetPageList()
{
if (((MatchMode = "exact" && PageData[Key] = Value) ; Case insensitive
|| (MatchMode = "contains" && InStr(PageData[Key], Value))
|| (MatchMode = "startswith" && InStr(PageData[Key], Value) == 1)
|| (MatchMode = "regex" && PageData[Key] ~= Value))
&& ++Count == Index)
return new this.Page(PageData.webSocketDebuggerUrl, fnCallback)
}
}
/*
Shorthand for GetPageBy("url", Value, "startswith")
*/
GetPageByURL(Value, MatchMode:="startswith", Index:=1, fnCallback:="")
{
return this.GetPageBy("url", Value, MatchMode, Index, fnCallback)
}
/*
Shorthand for GetPageBy("title", Value, "startswith")
*/
GetPageByTitle(Value, MatchMode:="startswith", Index:=1, fnCallback:="")
{
return this.GetPageBy("title", Value, MatchMode, Index, fnCallback)
}
/*
Shorthand for GetPageBy("type", Type, "exact")
The default type to search for is "page", which is the visible area of
a normal Chrome tab.
*/
GetPage(Index:=1, Type:="page", fnCallback:="")
{
return this.GetPageBy("type", Type, "exact", Index, fnCallback)
}
/*
Connects to the debug interface of a page given its WebSocket URL.
*/
class Page
{
Connected := False
ID := 0
Responses := []
/*
wsurl - The desired page's WebSocket URL
fnCallback - A function to be called whenever message is received
*/
__New(wsurl, fnCallback:="")
{
this.fnCallback := fnCallback
this.BoundKeepAlive := this.Call.Bind(this, "Browser.getVersion",, False)
; TODO: Throw exception on invalid objects
if IsObject(wsurl)
wsurl := wsurl.webSocketDebuggerUrl
wsurl := StrReplace(wsurl, "localhost", "127.0.0.1")
this.ws := {"base": this.WebSocket, "_Event": this.Event, "Parent": this}
this.ws.__New(wsurl)
while !this.Connected
Sleep, 50
}
/*
Calls the specified endpoint and provides it with the given
parameters.
DomainAndMethod - The endpoint domain and method name for the
endpoint you would like to call. For example:
PageInst.Call("Browser.close")
PageInst.Call("Schema.getDomains")
Params - An associative array of parameters to be provided to the
endpoint. For example:
PageInst.Call("Page.printToPDF", {"scale": 0.5 ; Numeric Value
, "landscape": JSON.true ; Boolean Value
, "pageRanges: "1-5, 8, 11-13"}) ; String value
PageInst.Call("Page.navigate", {"url": "https://autohotkey.com/"})
WaitForResponse - Whether to block until a response is received from
Chrome, which is necessary to receive a return value, or whether
to continue on with the script without waiting for a response.
*/
Call(DomainAndMethod, Params:="", WaitForResponse:=True)
{
if !this.Connected
throw Exception("Not connected to tab")
; Use a temporary variable for ID in case more calls are made
; before we receive a response.
ID := this.ID += 1
this.ws.Send(JSON.Stringify({"id": ID
, "params": Params ? Params : {}
, "method": DomainAndMethod}))
if !WaitForResponse
return
; Wait for the response
this.responses[ID] := False
while !this.responses[ID]
Sleep, 50
; Get the response, check if it's an error
response := this.responses.Delete(ID)
if (response.error)
throw Exception("Chrome indicated error in response",, JSON.Stringify(response.error))
return response.result
}
/*
Run some JavaScript on the page. For example:
PageInst.Evaluate("alert(""I can't believe it's not IE!"");")
PageInst.Evaluate("document.getElementsByTagName('button')[0].click();")
*/
Evaluate(JS)
{
response := this.Call("Runtime.evaluate",
( LTrim Join
{
"expression": JS,
"objectGroup": "console",
"includeCommandLineAPI": JSON.true,
"silent": JSON.false,
"returnByValue": JSON.false,
"userGesture": JSON.true,
"awaitPromise": JSON.false
}
))
if (response.exceptionDetails)
throw Exception(response.result.description,, JSON.Stringify(response.exceptionDetails))
return response.result
}
/*
Waits for the page's readyState to match the DesiredState.
DesiredState - The state to wait for the page's ReadyState to match
Interval - How often it should check whether the state matches
*/
WaitForLoad(DesiredState:="complete", Interval:=100)
{
while this.Evaluate("document.readyState").value != DesiredState
Sleep, Interval
}
/*
Internal function triggered when the script receives a message on
the WebSocket connected to the page.
*/
Event(EventName, Event)
{
; If it was called from the WebSocket adjust the class context
if this.Parent
this := this.Parent
; TODO: Handle Error events
if (EventName == "Open")
{
this.Connected := True
BoundKeepAlive := this.BoundKeepAlive
SetTimer, %BoundKeepAlive%, 15000
}
else if (EventName == "Message")
{
data := JSON.Parse(Event.data)
; Run the callback routine
fnCallback := this.fnCallback
if (newData := %fnCallback%(data))
data := newData
if this.responses.HasKey(data.ID)
this.responses[data.ID] := data
}
else if (EventName == "Close")
{
this.Disconnect()
}
else if (EventName == "Error")
{
throw Exception("Websocket Error!")
}
}
/*
Disconnect from the page's debug interface, allowing the instance
to be garbage collected.
This method should always be called when you are finished with a
page or else your script will leak memory.
*/
Disconnect()
{
if !this.Connected
return
this.Connected := False
this.ws.Delete("Parent")
this.ws.Disconnect()
BoundKeepAlive := this.BoundKeepAlive
SetTimer, %BoundKeepAlive%, Delete
this.Delete("BoundKeepAlive")
}
class WebSocket
{
__New(WS_URL)
{
static wb
; Create an IE instance
Gui, +hWndhOld
Gui, New, +hWndhWnd
this.hWnd := hWnd
Gui, Add, ActiveX, vWB, Shell.Explorer
WB.Silent := true
Gui, %hOld%: Default
; Write an appropriate document
WB.Navigate("about:<!DOCTYPE html><meta http-equiv='X-UA-Compatible'"
. "content='IE=edge'><body></body>")
while (WB.ReadyState < 4)
sleep, 50
this.document := WB.document
; Add our handlers to the JavaScript namespace
this.document.parentWindow.ahk_savews := this._SaveWS.Bind(this)
this.document.parentWindow.ahk_event := this._Event.Bind(this)
this.document.parentWindow.ahk_ws_url := WS_URL
; Add some JavaScript to the page to open a socket
Script := this.document.createElement("script")
Script.text := "ws = new WebSocket(ahk_ws_url);`n"
. "ws.onopen = function(event){ ahk_event('Open', event); };`n"
. "ws.onclose = function(event){ ahk_event('Close', event); };`n"
. "ws.onerror = function(event){ ahk_event('Error', event); };`n"
. "ws.onmessage = function(event){ ahk_event('Message', event); };"
this.document.body.appendChild(Script)
}
; Called by the JS in response to WS events
_Event(EventName, Event)
{
this["On" EventName](Event)
}
; Sends data through the WebSocket
Send(Data)
{
this.document.parentWindow.ws.send(Data)
}
; Closes the WebSocket connection
Close(Code:=1000, Reason:="")
{
this.document.parentWindow.ws.close(Code, Reason)
}
; Closes and deletes the WebSocket, removing
; references so the class can be garbage collected
Disconnect()
{
if this.hWnd
{
this.Close()
Gui, % this.hWnd ": Destroy"
this.hWnd := False
}
}
}
}
}
EnumProcessesByName(procName) {
if !DllCall("Wtsapi32\WTSEnumerateProcesses", Ptr, 0, UInt, 0, UInt, 1, PtrP, pProcessInfo, PtrP, count)
throw Exception("WTSEnumerateProcesses failed. A_LastError: " . A_LastError)
addr := pProcessInfo, PIDs := []
Loop % count {
if StrGet( NumGet(addr + 8) ) = procName
PID := NumGet(addr + 4, "UInt"), PIDs.Push(PID)
addr += A_PtrSize = 4 ? 16 : 24
}
DllCall("Wtsapi32\WTSFreeMemory", Ptr, pProcessInfo)
Return PIDs
}
GetCommandLine(PID, SetDebugPrivilege := false, GetImagePath := false) {
static SetDebug := 0, PROCESS_QUERY_INFORMATION := 0x400, PROCESS_VM_READ := 0x10, STATUS_SUCCESS := 0
if (SetDebugPrivilege && !SetDebug) {
if !res := SeDebugPrivilege()
SetDebug := 1
else {
MsgBox, 4, Ошибка SeDebugPrivilege(), Не удалось установить привилегии.`nОшибка %res%`nПродолжить?
IfMsgBox, No
Return
}
}
hProc := DllCall("OpenProcess", UInt, PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, Int, 0, UInt, PID, Ptr)
(A_Is64bitOS && DllCall("IsWow64Process", Ptr, hProc, UIntP, IsWow64))
if (!A_Is64bitOS || IsWow64)
PtrSize := 4, PtrType := "UInt", pPtr := "UIntP", offsetCMD := 0x40
else
PtrSize := 8, PtrType := "Int64", pPtr := "Int64P", offsetCMD := 0x70
hModule := DllCall("GetModuleHandle", "str", "Ntdll", Ptr)
if (A_PtrSize < PtrSize) { ; скрипт 32, целевой процесс 64
if !QueryInformationProcess := DllCall("GetProcAddress", Ptr, hModule, AStr, "NtWow64QueryInformationProcess64", Ptr)
failed := "NtWow64QueryInformationProcess64"
if !ReadProcessMemory := DllCall("GetProcAddress", Ptr, hModule, AStr, "NtWow64ReadVirtualMemory64", Ptr)
failed := "NtWow64ReadVirtualMemory64"
info := 0, szPBI := 48, offsetPEB := 8
}
else {
if !QueryInformationProcess := DllCall("GetProcAddress", Ptr, hModule, AStr, "NtQueryInformationProcess", Ptr)
failed := "NtQueryInformationProcess"
ReadProcessMemory := "ReadProcessMemory"
if (A_PtrSize > PtrSize) ; скрипт 64, целевой процесс 32
info := 26, szPBI := 8, offsetPEB := 0
else ; скрипт и целевой процесс одной битности
info := 0, szPBI := PtrSize * 6, offsetPEB := PtrSize
}
if failed {
DllCall("CloseHandle", Ptr, hProc)
MsgBox, Не удалось получить указатель на функцию %failed%
Return
}
VarSetCapacity(PBI, 48, 0)
if DllCall(QueryInformationProcess, Ptr, hProc, UInt, info, Ptr, &PBI, UInt, szPBI, UIntP, bytes) != STATUS_SUCCESS {
DllCall("CloseHandle", Ptr, hProc)
Return
}
pPEB := NumGet(&PBI + offsetPEB, PtrType)
DllCall(ReadProcessMemory, Ptr, hProc, PtrType, pPEB + PtrSize * 4, pPtr, pRUPP, PtrType, PtrSize, UIntP, bytes)
DllCall(ReadProcessMemory, Ptr, hProc, PtrType, pRUPP + offsetCMD, UShortP, szCMD, PtrType, 2, UIntP, bytes)
DllCall(ReadProcessMemory, Ptr, hProc, PtrType, pRUPP + offsetCMD + PtrSize, pPtr, pCMD, PtrType, PtrSize, UIntP, bytes)
VarSetCapacity(buff, szCMD, 0)
DllCall(ReadProcessMemory, Ptr, hProc, PtrType, pCMD, Ptr, &buff, PtrType, szCMD, UIntP, bytes)
obj := { cmd: StrGet(&buff, "UTF-16") }
if (GetImagePath && obj.cmd) {
DllCall(ReadProcessMemory, Ptr, hProc, PtrType, pRUPP + offsetCMD - PtrSize*2, UShortP, szPATH, PtrType, 2, UIntP, bytes)
DllCall(ReadProcessMemory, Ptr, hProc, PtrType, pRUPP + offsetCMD - PtrSize, pPtr, pPATH, PtrType, PtrSize, UIntP, bytes)
VarSetCapacity(buff, szPATH, 0)
DllCall(ReadProcessMemory, Ptr, hProc, PtrType, pPATH, Ptr, &buff, PtrType, szPATH, UIntP, bytes)
obj.path := StrGet(&buff, "UTF-16") . (IsWow64 ? " *32" : "")
}
DllCall("CloseHandle", Ptr, hProc)
Return obj
}
SeDebugPrivilege() {
static PROCESS_QUERY_INFORMATION := 0x400, TOKEN_ADJUST_PRIVILEGES := 0x20, SE_PRIVILEGE_ENABLED := 0x2
hProc := DllCall("OpenProcess", UInt, PROCESS_QUERY_INFORMATION, Int, false, UInt, DllCall("GetCurrentProcessId"), Ptr)
DllCall("Advapi32\OpenProcessToken", Ptr, hProc, UInt, TOKEN_ADJUST_PRIVILEGES, PtrP, token)
DllCall("Advapi32\LookupPrivilegeValue", Ptr, 0, Str, "SeDebugPrivilege", Int64P, luid)
VarSetCapacity(TOKEN_PRIVILEGES, 16, 0)
NumPut(1, TOKEN_PRIVILEGES, "UInt")
NumPut(luid, TOKEN_PRIVILEGES, 4, "Int64")
NumPut(SE_PRIVILEGE_ENABLED, TOKEN_PRIVILEGES, 12, "UInt")
DllCall("Advapi32\AdjustTokenPrivileges", Ptr, token, Int, false, Ptr, &TOKEN_PRIVILEGES, UInt, 0, Ptr, 0, Ptr, 0)
res := A_LastError
DllCall("CloseHandle", Ptr, token)
DllCall("CloseHandle", Ptr, hProc)
Return res ; в случае удачи 0
}
class JSON
{
static JS := JSON._GetJScriptObject(), true := {}, false := {}, null := {}
Parse(sJson, js := false) {
if jsObj := this.VerifyJson(sJson)
Return js ? jsObj : this._CreateObject(jsObj)
}
Stringify(obj, js := false, indent := "") {
if js
Return this.JS.JSON.stringify(obj, "", indent)
else {
sObj := this._ObjToString(obj)
Return this.JS.eval("JSON.stringify(" . sObj . ",'','" . indent . "')")
}
}
GetKey(sJson, key, indent := "") {
if !this.VerifyJson(sJson)
Return
try Return this.JS.eval("JSON.stringify((" . sJson . ")" . (SubStr(key, 1, 1) = "[" ? "" : ".") . key . ",'','" . indent . "')")
catch
MsgBox, Bad key:`n`n%key%
}
SetKey(sJson, key, value, indent := "") {
if !this.VerifyJson(sJson)
Return
if !this.VerifyJson(value, true) {
MsgBox, % "Bad value: " . value . "`n"
. "Must be a valid JSON string." . "`n"
. "Enclose literal strings in quotes '' or """".`n"
. "As an empty string pass '' or """""
Return
}
try {
res := this.JS.eval( "var obj = (" . sJson . ");"
. "obj" . (SubStr(key, 1, 1) = "[" ? "" : ".") . key . "=" . value . ";"
. "JSON.stringify(obj,'','" . indent . "')" )
this.JS.eval("obj = ''")
Return res
}
catch
MsgBox, Bad key:`n`n%key%
}
RemoveKey(sJson, key, indent := "") {
if !this.VerifyJson(sJson)
Return
sign := SubStr(key, 1, 1) = "[" ? "" : "."
try {
if !RegExMatch(key, "(.*)\[(\d+)]$", match)
res := this.JS.eval("var obj = (" . sJson . "); delete obj" . sign . key . "; JSON.stringify(obj,'','" . indent . "')")
else
res := this.JS.eval( "var obj = (" . sJson . ");"
. "obj" . (match1 != "" ? sign . match1 : "") . ".splice(" . match2 . ", 1);"
. "JSON.stringify(obj,'','" . indent . "')" )
this.JS.eval("obj = ''")
Return res
}
catch
MsgBox, Bad key:`n`n%key%
}
Enum(sJson, key := "", indent := "") {
if !this.VerifyJson(sJson)
Return
conc := key ? (SubStr(key, 1, 1) = "[" ? "" : ".") . key : ""
try {
jsObj := this.JS.eval("(" sJson ")" . conc)
res := jsObj.IsArray()
if (res = "")
Return
obj := {}
if (res = -1) {
Loop % jsObj.length
obj[A_Index - 1] := this.JS.eval("JSON.stringify((" sJson ")" . conc . "[" . (A_Index - 1) . "],'','" . indent . "')")
}
else if (res = 0) {
keys := jsObj.GetKeys()
Loop % keys.length
k := keys[A_Index - 1], obj[k] := this.JS.eval("JSON.stringify((" sJson ")" . conc . "['" . k . "'],'','" . indent . "')")
}
Return obj
}
catch
MsgBox, Bad key:`n`n%key%
}
VerifyJson(sJson, silent := false) {
try jsObj := this.JS.eval("(" sJson ")")
catch {
if !silent
MsgBox, Bad JSON string:`n`n%sJson%
Return
}
Return IsObject(jsObj) ? jsObj : true
}
_ObjToString(obj) {
if IsObject( obj ) {
for k, v in ["true", "false", "null"]
if (obj = this[v])
Return v
isArray := true
for key in obj {
if IsObject(key)
throw Exception("Invalid key")
if !( key = A_Index || isArray := false )
break
}
for k, v in obj
str .= ( A_Index = 1 ? "" : "," ) . ( isArray ? "" : """" . k . """:" ) . this._ObjToString(v)
Return isArray ? "[" str "]" : "{" str "}"
}
else if !(obj*1 = "" || RegExMatch(obj, "\s"))
Return obj
for k, v in [["\", "\\"], [A_Tab, "\t"], ["""", "\"""], ["/", "\/"], ["`n", "\n"], ["`r", "\r"], [Chr(12), "\f"], [Chr(08), "\b"]]
obj := StrReplace( obj, v[1], v[2] )
Return """" obj """"
}
_GetJScriptObject() {
static doc
doc := ComObjCreate("htmlfile")
doc.write("<meta http-equiv=""X-UA-Compatible"" content=""IE=9"">")
JS := doc.parentWindow
JSON._AddMethods(JS)
Return JS
}
_AddMethods(ByRef JS) {
JScript =
(
Object.prototype.GetKeys = function () {
var keys = []
for (var k in this)
if (this.hasOwnProperty(k))
keys.push(k)
return keys
}
Object.prototype.IsArray = function () {
var toStandardString = {}.toString
return toStandardString.call(this) == '[object Array]'
}
)
JS.eval(JScript)
}
_CreateObject(jsObj) {
res := jsObj.IsArray()
if (res = "")
Return jsObj
else if (res = -1) {
obj := []
Loop % jsObj.length
obj[A_Index] := this._CreateObject(jsObj[A_Index - 1])
}
else if (res = 0) {
obj := {}
keys := jsObj.GetKeys()
Loop % keys.length
k := keys[A_Index - 1], obj[k] := this._CreateObject(jsObj[k])
}
Return obj
}
}
На момент запуска скрипта уже должен быть запущенным Chrome в debug mode, страница http://delfi.lv должна быть уже загружена.