Basic Authentication with Chrome.ahk

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
rgoijre23
Posts: 20
Joined: 24 May 2021, 13:43

Basic Authentication with Chrome.ahk

23 Jun 2021, 14:21

[Mod edit: Topic title changed based on swagfag's suggestion.]

Hello,

I have made multiple posts in the past about projects I am working on. With the help from @gregster, I have been able to somewhat get a better understanding of AHK especially using Chrome.ahk. However, on this new project, I have ran into a minor issue. This script, I have to load up 7 sites on my network in Google Chrome, log into them automatically, and download a data file from each of them (The URL will automatically download the file I need). This is my code.

Code: Select all

#NoEnv
#Include ../Chrome.ahk

SetBatchLines, -1       ; Have script run at maximum speed.

URL1 := "https IPAddress /dataFile"  Broken Link for safety  
URL2 := "https IPAddress /dataFile"  Broken Link for safety
URL3 := "https IPAddress /dataFile"  Broken Link for safety
URL4 := "https IPAddress /dataFile"  Broken Link for safety
URL5 := "https IPAddress /dataFile"  Broken Link for safety 
URL6 := "https IPAddress /dataFile"  Broken Link for safety 
URL7 := "https IPAddress /dataFile"  Broken Link for safety

/*  Creating an instance of Chrome.
	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.
*/
global ChromeInst := new Chrome(,[URL1, URL2, URL3, URL4, URL5, URL6, URL7],"--ignore-certificate-errors",,9222)
For some reason, URLs 1-4 will load up directly to its site, but 5-7 goes to my Google Chrome file directory on my station. Here is a photo of the issue.
Capture.PNG
Capture.PNG (36.48 KiB) Viewed 1200 times
I don't really understand why this is happening, because any other URL will work. Let me know if anyone knows what this issue can be caused from! Thanks! :D (Also, if anyone knows how to log in automatically to each of the sites as well, that would be so helpful as well!)

EDIT: I made a mistake. I fixed it. However, like I said before, if anyone knows how to log in the sites automatically/simultaneously, I would appreciate it!
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Google Chrome Tab Instances Issue

24 Jun 2021, 13:09

Code: Select all

#Requires AutoHotkey v1.1.33.09

#NoEnv
#SingleInstance Force
SetBatchLines -1

; https://github.com/subwaymatch/cdp-modify-response-example
; https://github.com/subwaymatch/cdp-modify-response-example/blob/master/puppeteer-example.js#L91
; https://stackoverflow.com/questions/50834002/chrome-headless-browser-with-corporate-proxy-authetication-not-working
; https://www.autohotkey.com/boards/viewtopic.php?t=51259

FileCreateDir, ChromeUserProfileData
ChromeInst := new Chrome("ChromeUserProfileData")

; in ur code, use a boundfunc obj per page.
; also figure out how ure gonna associate/pass data around
fnEventHandler := Func("OnEvent")
PageInst := ChromeInst.GetPageBy("url", "about:blank", "exact", 1, fnEventHandler)

; Fetch.enable # https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-enable
;
; Enables issuing of requestPaused events. A request will be paused until client calls one of
; failRequest, fulfillRequest or continueRequest/continueWithAuth.
;
; PARAMETERS
;                        patterns array[ RequestPattern ]
;                      (optional) If specified, only requests matching any of these patterns will produce
;                                 fetchRequested event and will be paused until clients response. If not set,
;                                 all requests will be affected.
;              handleAuthRequests boolean
;                      (optional) If true, authRequired events will be issued and requests will be paused
;                                 expecting a call to continueWithAuth.
PageInst.Call("Fetch.enable", {handleAuthRequests: Chrome.Jxon_True()})

; Basic Authentication Test Page - https://jigsaw.w3.org/HTTP/
; user: "guest" // pass: "guest"
PageInst.Call("Page.navigate", {url: "https://jigsaw.w3.org/HTTP/Basic/"})

Esc::ExitApp

OnEvent(EventDataObj) {
	global PageInst

	switch EventDataObj.method
	{
	; Fetch.requestPaused # https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-requestPaused
	;
	; Issued when the domain is enabled and the request URL matches the specified filter. The request is paused
	; until the client responds with one of continueRequest, failRequest or fulfillRequest. The stage of the
	; request can be determined by presence of responseErrorReason and responseStatusCode -- the request is at
	; the response stage if either of these fields is present and in the request stage otherwise.
	;
	; PARAMETERS
	;                   requestId RequestId
	;                             Each request the page makes will have a unique id.
	;                     request Network.Request
	;                             The details of the request.
	;                     frameId Page.FrameId
	;                             The id of the frame that initiated the request.
	;                resourceType Network.ResourceType
	;                             How the requested resource will be used.
	;         responseErrorReason Network.ErrorReason
	;                  (optional) Response error if intercepted at response stage.
	;          responseStatusCode integer
	;                  (optional) Response code if intercepted at response stage.
	;             responseHeaders array[ HeaderEntry ]
	;                  (optional) Response headers if intercepted at the response stage.
	;                   networkId RequestId
	;                  (optional) If the intercepted request had a corresponding Network.requestWillBeSent event
	;                             fired for it, then this networkId will be the same as the requestId present in
	;                             the requestWillBeSent event.
	case "Fetch.requestPaused":
		; Fetch.continueRequest # https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueRequest
		;
		; Continues the request, optionally modifying some of its parameters.
		;
		; PARAMETERS
		;               requestId RequestId
		;               (options) An id the client received in requestPaused event.
		;                     url string
		;               (options) If set, the request url will be modified in a way that's not observable by page.
		;                  method string
		;               (options) If set, the request method is overridden.
		;                postData string
		;               (options) If set, overrides the post data in the request. (Encoded as a base64 string when
		;                         passed over JSON)
		;                 headers array[ HeaderEntry ]
		;               (options) If set, overrides the request headers.

		; we dont care about fiddling with the request so just forward it as is, without altering anything
		PageInst.Call("Fetch.continueRequest"
			, {requestId: EventDataObj.params.requestId} ; eg. "interception-job-13.0"
			, WaitForResponse := false) ; needs to be sent ASYNC, otherwise ure stuck in an infinite loop!!

	; Fetch.authRequired # https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#event-authRequired
	;
	; Issued when the domain is enabled with handleAuthRequests set to true. The request is paused until client responds
	; with continueWithAuth.
	;
	; PARAMETERS
	;                   requestId RequestId
	;                   (options) Each request the page makes will have a unique id.
	;                     request Network.Request
	;                   (options) The details of the request.
	;                     frameId Page.FrameId
	;                   (options) The id of the frame that initiated the request.
	;                resourceType Network.ResourceType
	;                   (options) How the requested resource will be used.
	;               authChallenge AuthChallenge
	;                   (options) Details of the Authorization Challenge encountered. If this is set, client should
	;                             respond with continueRequest that contains AuthChallengeResponse.
	case "Fetch.authRequired":
		; Fetch.AuthChallengeResponse # https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#type-AuthChallengeResponse
		;
		; Response to an AuthChallenge.
		; Type: object
		;
		; PROPERTIES
		;                response string
		;               (options) The decision on what to do in response to the authorization challenge. Default means
		;                         deferring to the default behavior of the net stack, which will likely either the Cancel
		;                         authentication or display a popup dialog box.
		;                         Allowed Values: "Default", "CancelAuth", "ProvideCredentials"
		;                username string
		;               (options) The username to provide, possibly empty. Should only be set if response is "ProvideCredentials".
		;                password string
		;               (options) The password to provide, possibly empty. Should only be set if response is "ProvideCredentials".
		AuthChallengeResponse := {response: "ProvideCredentials", username: "guest", password: "guest"}

		; Fetch.continueWithAuth # https://chromedevtools.github.io/devtools-protocol/tot/Fetch/#method-continueWithAuth
		;
		; Continues a request supplying authChallengeResponse following authRequired event.
		;
		; PARAMETERS
		;               requestId RequestId
		;               (options) An id the client received in authRequired event.
		;   authChallengeResponse AuthChallengeResponse
		;               (options) Response to with an authChallenge.
		PageInst.Call("Fetch.continueWithAuth", {requestId: EventDataObj.params.requestId
			, authChallengeResponse: AuthChallengeResponse}
			, WaitForResponse := false) ; same as before, ASYNC!!
	}
}

; Chrome.ahk v1.2
; Copyright GeekDude 2018
; https://github.com/G33kDude/Chrome.ahk

class Chrome
{
	static DebugPort := 9222

	/*
		Escape a string in a manner suitable for command line parameters
	*/
	CliEscape(Param)
	{
		return """" RegExReplace(Param, "(\\*)""", "$1$1\""") """"
	}

	/*
		Finds instances of chrome in debug mode and the ports they're running
		on. If no instances are found, returns a false value. If one or more
		instances are found, returns an associative array where the keys are
		the ports, and the values are the full command line texts used to start
		the processes.

		One example of how this may be used would be to open chrome on a
		different port if an instance of chrome is already open on the port
		you wanted to used.

		```
		; If the wanted port is taken, use the largest taken port plus one
		DebugPort := 9222
		if (Chromes := Chrome.FindInstances()).HasKey(DebugPort)
			DebugPort := Chromes.MaxIndex() + 1
		ChromeInst := new Chrome(ProfilePath,,,, DebugPort)
		```

		Another use would be to scan for running instances and attach to one
		instead of starting a new instance.

		```
		if (Chromes := Chrome.FindInstances())
			ChromeInst := {"base": Chrome, "DebugPort": Chromes.MinIndex()}
		else
			ChromeInst := new Chrome(ProfilePath)
		```
	*/
	FindInstances()
	{
		static Needle := "--remote-debugging-port=(\d+)"
		Out := {}
		for Item in ComObjGet("winmgmts:")
			.ExecQuery("SELECT CommandLine FROM Win32_Process"
			. " WHERE Name = 'chrome.exe'")
			if RegExMatch(Item.CommandLine, Needle, Match)
				Out[Match1] := Item.CommandLine
		return Out.MaxIndex() ? Out : False
	}

	/*
		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 Pahs\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 this.Jxon_Load(http.responseText)
	}

	/*
		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": Chrome.Jxon_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(Chrome.Jxon_Dump({"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",, Chrome.Jxon_Dump(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": Chrome.Jxon_True(),
				"silent": Chrome.Jxon_False(),
				"returnByValue": Chrome.Jxon_False(),
				"userGesture": Chrome.Jxon_True(),
				"awaitPromise": Chrome.Jxon_False()
			}
			))

			if (response.exceptionDetails)
				throw Exception(response.result.description,, Chrome.Jxon_Dump(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 := Chrome.Jxon_Load(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
				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
				}
			}
		}
	}

	Jxon_Load(ByRef src, args*)
	{
		static q := Chr(34)

		key := "", is_key := false
		stack := [ tree := [] ]
		is_arr := { (tree): 1 }
		next := q . "{[01234567890-tfn"
		pos := 0
		while ( (ch := SubStr(src, ++pos, 1)) != "" )
		{
			if InStr(" `t`n`r", ch)
				continue
			if !InStr(next, ch, true)
			{
				ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n"))
				col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))

				msg := Format("{}: line {} col {} (char {})"
				,   (next == "")      ? ["Extra data", ch := SubStr(src, pos)][1]
				: (next == "'")     ? "Unterminated string starting at"
				: (next == "\")     ? "Invalid \escape"
				: (next == ":")     ? "Expecting ':' delimiter"
				: (next == q)       ? "Expecting object key enclosed in double quotes"
				: (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
				: (next == ",}")    ? "Expecting ',' delimiter or object closing '}'"
				: (next == ",]")    ? "Expecting ',' delimiter or array closing ']'"
				: [ "Expecting JSON value(string, number, [true, false, null], object or array)"
				, ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
				, ln, col, pos)

				throw Exception(msg, -1, ch)
			}

			is_array := is_arr[obj := stack[1]]

			if i := InStr("{[", ch)
			{
				val := (proto := args[i]) ? new proto : {}
				is_array? ObjPush(obj, val) : obj[key] := val
				ObjInsertAt(stack, 1, val)

				is_arr[val] := !(is_key := ch == "{")
				next := q . (is_key ? "}" : "{[]0123456789-tfn")
			}

			else if InStr("}]", ch)
			{
				ObjRemoveAt(stack, 1)
				next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
			}

			else if InStr(",:", ch)
			{
				is_key := (!is_array && ch == ",")
				next := is_key ? q : q . "{[0123456789-tfn"
			}

			else ; string | number | true | false | null
			{
				if (ch == q) ; string
				{
					i := pos
					while i := InStr(src, q,, i+1)
					{
						val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
						static end := A_AhkVersion<"2" ? 0 : -1
						if (SubStr(val, end) != "\")
							break
					}
					if !i ? (pos--, next := "'") : 0
						continue

					pos := i ; update pos

					val := StrReplace(val,    "\/",  "/")
					, val := StrReplace(val, "\" . q,    q)
					, val := StrReplace(val,    "\b", "`b")
					, val := StrReplace(val,    "\f", "`f")
					, val := StrReplace(val,    "\n", "`n")
					, val := StrReplace(val,    "\r", "`r")
					, val := StrReplace(val,    "\t", "`t")

					i := 0
					while i := InStr(val, "\",, i+1)
					{
						if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
							continue 2

						; \uXXXX - JSON unicode escape sequence
						xxxx := Abs("0x" . SubStr(val, i+2, 4))
						if (A_IsUnicode || xxxx < 0x100)
							val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
					}

					if is_key
					{
						key := val, next := ":"
						continue
					}
				}

				else ; number | true | false | null
				{
					val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)

					; For numerical values, numerify integers and keep floats as is.
					; I'm not yet sure if I should numerify floats in v2.0-a ...
					static number := "number", integer := "integer"
					if val is %number%
					{
						if val is %integer%
							val += 0
					}
					; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo,
					; SOMETIMES return strings due to certain optimizations. Since it
					; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a
					else if (val == "true" || val == "false")
						val := %value% + 0
					; AHK_H has built-in null, can't do 'val := %value%' where value == "null"
					; as it would raise an exception in AHK_H(overriding built-in var)
					else if (val == "null")
						val := ""
					; any other values are invalid, continue to trigger error
					else if (pos--, next := "#")
						continue

					pos += i-1
				}

				is_array? ObjPush(obj, val) : obj[key] := val
				next := obj==tree ? "" : is_array ? ",]" : ",}"
			}
		}

		return tree[1]
	}

	Jxon_Dump(obj, indent:="", lvl:=1)
	{
		static q := Chr(34)

		if IsObject(obj)
		{
			static Type := Func("Type")
			if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "")
				throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj))

			prefix := SubStr(A_ThisFunc, 1, InStr(A_ThisFunc, ".",, 0))
			fn_t := prefix "Jxon_True",  obj_t := this ? %fn_t%(this) : %fn_t%()
			fn_f := prefix "Jxon_False", obj_f := this ? %fn_f%(this) : %fn_f%()

			if (&obj == &obj_t)
				return "true"
			else if (&obj == &obj_f)
				return "false"

			is_array := 0
			for k in obj
				is_array := k == A_Index
			until !is_array

			static integer := "integer"
			if indent is %integer%
			{
				if (indent < 0)
					throw Exception("Indent parameter must be a postive integer.", -1, indent)
				spaces := indent, indent := ""
				Loop % spaces
					indent .= " "
			}
			indt := ""
			Loop, % indent ? lvl : 0
				indt .= indent

			this_fn := this ? Func(A_ThisFunc).Bind(this) : A_ThisFunc
			lvl += 1, out := "" ; Make #Warn happy
			for k, v in obj
			{
				if IsObject(k) || (k == "")
					throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>")

				if !is_array
					out .= ( ObjGetCapacity([k], 1) ? %this_fn%(k) : q . k . q ) ;// key
				.  ( indent ? ": " : ":" ) ; token + padding
				out .= %this_fn%(v, indent, lvl) ; value
				.  ( indent ? ",`n" . indt : "," ) ; token + indent
			}

			if (out != "")
			{
				out := Trim(out, ",`n" . indent)
				if (indent != "")
					out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
			}

			return is_array ? "[" . out . "]" : "{" . out . "}"
		}

		; Number
		else if (ObjGetCapacity([obj], 1) == "")
			return obj

		; String (null -> not supported by AHK)
		if (obj != "")
		{
			obj := StrReplace(obj,  "\",    "\\")
			, obj := StrReplace(obj,  "/",    "\/")
			, obj := StrReplace(obj,    q, "\" . q)
			, obj := StrReplace(obj, "`b",    "\b")
			, obj := StrReplace(obj, "`f",    "\f")
			, obj := StrReplace(obj, "`n",    "\n")
			, obj := StrReplace(obj, "`r",    "\r")
			, obj := StrReplace(obj, "`t",    "\t")

			static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]"
			while RegExMatch(obj, needle, m)
				obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0])))
		}

		return q . obj . q
	}

	Jxon_True()
	{
		static obj := {}
		return obj
	}

	Jxon_False()
	{
		static obj := {}
		return obj
	}
}
rgoijre23
Posts: 20
Joined: 24 May 2021, 13:43

Re: Google Chrome Tab Instances Issue

29 Jun 2021, 19:18

swagfag wrote:
24 Jun 2021, 13:09

Code: Select all

#Requires AutoHotkey v1.1.33.09

#NoEnv
#SingleInstance Force
SetBatchLines -1

; https://github.com/subwaymatch/cdp-modify-response-example
; https://github.com/subwaymatch/cdp-modify-response-example/blob/master/puppeteer-example.js#L91
; https://stackoverflow.com/questions/50834002/chrome-headless-browser-with-corporate-proxy-authetication-not-working
; https://www.autohotkey.com/boards/viewtopic.php?t=51259

FileCreateDir, ChromeUserProfileData
ChromeInst := new Chrome("ChromeUserProfileData")

; in ur code, use a boundfunc obj per page.
; also figure out how ure gonna associate/pass data around
fnEventHandler := Func("OnEvent")
PageInst := ChromeInst.GetPageBy("url", "about:blank", "exact", 1, fnEventHandler)

; Fetch.enable # https chromedevtools.github.io /devtools-protocol/tot/Fetch/#method-enable  Broken Link for safety
;
; Enables issuing of requestPaused events. A request will be paused until client calls one of
; failRequest, fulfillRequest or continueRequest/continueWithAuth.
;
; PARAMETERS
;                        patterns array[ RequestPattern ]
;                      (optional) If specified, only requests matching any of these patterns will produce
;                                 fetchRequested event and will be paused until clients response. If not set,
;                                 all requests will be affected.
;              handleAuthRequests boolean
;                      (optional) If true, authRequired events will be issued and requests will be paused
;                                 expecting a call to continueWithAuth.
PageInst.Call("Fetch.enable", {handleAuthRequests: Chrome.Jxon_True()})

; Basic Authentication Test Page - https jigsaw.w3.org /HTTP/  Broken Link for safety
; user: "guest" // pass: "guest"
PageInst.Call("Page.navigate", {url: "https jigsaw.w3.org /HTTP/  Broken Link for safetyBasic/"})

Esc::ExitApp

OnEvent(EventDataObj) {
	global PageInst

	switch EventDataObj.method
	{
	; Fetch.requestPaused # https chromedevtools.github.io /devtools-protocol/tot/Fetch/#event-requestPaused  Broken Link for safety
	;
	; Issued when the domain is enabled and the request URL matches the specified filter. The request is paused
	; until the client responds with one of continueRequest, failRequest or fulfillRequest. The stage of the
	; request can be determined by presence of responseErrorReason and responseStatusCode -- the request is at
	; the response stage if either of these fields is present and in the request stage otherwise.
	;
	; PARAMETERS
	;                   requestId RequestId
	;                             Each request the page makes will have a unique id.
	;                     request Network.Request
	;                             The details of the request.
	;                     frameId Page.FrameId
	;                             The id of the frame that initiated the request.
	;                resourceType Network.ResourceType
	;                             How the requested resource will be used.
	;         responseErrorReason Network.ErrorReason
	;                  (optional) Response error if intercepted at response stage.
	;          responseStatusCode integer
	;                  (optional) Response code if intercepted at response stage.
	;             responseHeaders array[ HeaderEntry ]
	;                  (optional) Response headers if intercepted at the response stage.
	;                   networkId RequestId
	;                  (optional) If the intercepted request had a corresponding Network.requestWillBeSent event
	;                             fired for it, then this networkId will be the same as the requestId present in
	;                             the requestWillBeSent event.
	case "Fetch.requestPaused":
		; Fetch.continueRequest # https chromedevtools.github.io /devtools-protocol/tot/Fetch/#method-continueRequest  Broken Link for safety
		;
		; Continues the request, optionally modifying some of its parameters.
		;
		; PARAMETERS
		;               requestId RequestId
		;               (options) An id the client received in requestPaused event.
		;                     url string
		;               (options) If set, the request url will be modified in a way that's not observable by page.
		;                  method string
		;               (options) If set, the request method is overridden.
		;                postData string
		;               (options) If set, overrides the post data in the request. (Encoded as a base64 string when
		;                         passed over JSON)
		;                 headers array[ HeaderEntry ]
		;               (options) If set, overrides the request headers.

		; we dont care about fiddling with the request so just forward it as is, without altering anything
		PageInst.Call("Fetch.continueRequest"
			, {requestId: EventDataObj.params.requestId} ; eg. "interception-job-13.0"
			, WaitForResponse := false) ; needs to be sent ASYNC, otherwise ure stuck in an infinite loop!!

	; Fetch.authRequired # https chromedevtools.github.io /devtools-protocol/tot/Fetch/#event-authRequired  Broken Link for safety
	;
	; Issued when the domain is enabled with handleAuthRequests set to true. The request is paused until client responds
	; with continueWithAuth.
	;
	; PARAMETERS
	;                   requestId RequestId
	;                   (options) Each request the page makes will have a unique id.
	;                     request Network.Request
	;                   (options) The details of the request.
	;                     frameId Page.FrameId
	;                   (options) The id of the frame that initiated the request.
	;                resourceType Network.ResourceType
	;                   (options) How the requested resource will be used.
	;               authChallenge AuthChallenge
	;                   (options) Details of the Authorization Challenge encountered. If this is set, client should
	;                             respond with continueRequest that contains AuthChallengeResponse.
	case "Fetch.authRequired":
		; Fetch.AuthChallengeResponse # https chromedevtools.github.io /devtools-protocol/tot/Fetch/#type-AuthChallengeResponse  Broken Link for safety
		;
		; Response to an AuthChallenge.
		; Type: object
		;
		; PROPERTIES
		;                response string
		;               (options) The decision on what to do in response to the authorization challenge. Default means
		;                         deferring to the default behavior of the net stack, which will likely either the Cancel
		;                         authentication or display a popup dialog box.
		;                         Allowed Values: "Default", "CancelAuth", "ProvideCredentials"
		;                username string
		;               (options) The username to provide, possibly empty. Should only be set if response is "ProvideCredentials".
		;                password string
		;               (options) The password to provide, possibly empty. Should only be set if response is "ProvideCredentials".
		AuthChallengeResponse := {response: "ProvideCredentials", username: "guest", password: "guest"}

		; Fetch.continueWithAuth # https chromedevtools.github.io /devtools-protocol/tot/Fetch/#method-continueWithAuth  Broken Link for safety
		;
		; Continues a request supplying authChallengeResponse following authRequired event.
		;
		; PARAMETERS
		;               requestId RequestId
		;               (options) An id the client received in authRequired event.
		;   authChallengeResponse AuthChallengeResponse
		;               (options) Response to with an authChallenge.
		PageInst.Call("Fetch.continueWithAuth", {requestId: EventDataObj.params.requestId
			, authChallengeResponse: AuthChallengeResponse}
			, WaitForResponse := false) ; same as before, ASYNC!!
	}
}

; Chrome.ahk v1.2
; Copyright GeekDude 2018
; https://github.com/G33kDude/Chrome.ahk

class Chrome
{
	static DebugPort := 9222

	/*
		Escape a string in a manner suitable for command line parameters
	*/
	CliEscape(Param)
	{
		return """" RegExReplace(Param, "(\\*)""", "$1$1\""") """"
	}

	/*
		Finds instances of chrome in debug mode and the ports they're running
		on. If no instances are found, returns a false value. If one or more
		instances are found, returns an associative array where the keys are
		the ports, and the values are the full command line texts used to start
		the processes.

		One example of how this may be used would be to open chrome on a
		different port if an instance of chrome is already open on the port
		you wanted to used.

		```
		; If the wanted port is taken, use the largest taken port plus one
		DebugPort := 9222
		if (Chromes := Chrome.FindInstances()).HasKey(DebugPort)
			DebugPort := Chromes.MaxIndex() + 1
		ChromeInst := new Chrome(ProfilePath,,,, DebugPort)
		```

		Another use would be to scan for running instances and attach to one
		instead of starting a new instance.

		```
		if (Chromes := Chrome.FindInstances())
			ChromeInst := {"base": Chrome, "DebugPort": Chromes.MinIndex()}
		else
			ChromeInst := new Chrome(ProfilePath)
		```
	*/
	FindInstances()
	{
		static Needle := "--remote-debugging-port=(\d+)"
		Out := {}
		for Item in ComObjGet("winmgmts:")
			.ExecQuery("SELECT CommandLine FROM Win32_Process"
			. " WHERE Name = 'chrome.exe'")
			if RegExMatch(Item.CommandLine, Needle, Match)
				Out[Match1] := Item.CommandLine
		return Out.MaxIndex() ? Out : False
	}

	/*
		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 Pahs\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: "  Broken Link for safety this.DebugPort "/json")
		http.send()
		return this.Jxon_Load(http.responseText)
	}

	/*
		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": Chrome.Jxon_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(Chrome.Jxon_Dump({"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",, Chrome.Jxon_Dump(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": Chrome.Jxon_True(),
				"silent": Chrome.Jxon_False(),
				"returnByValue": Chrome.Jxon_False(),
				"userGesture": Chrome.Jxon_True(),
				"awaitPromise": Chrome.Jxon_False()
			}
			))

			if (response.exceptionDetails)
				throw Exception(response.result.description,, Chrome.Jxon_Dump(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 := Chrome.Jxon_Load(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
				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
				}
			}
		}
	}

	Jxon_Load(ByRef src, args*)
	{
		static q := Chr(34)

		key := "", is_key := false
		stack := [ tree := [] ]
		is_arr := { (tree): 1 }
		next := q . "{[01234567890-tfn"
		pos := 0
		while ( (ch := SubStr(src, ++pos, 1)) != "" )
		{
			if InStr(" `t`n`r", ch)
				continue
			if !InStr(next, ch, true)
			{
				ln := ObjLength(StrSplit(SubStr(src, 1, pos), "`n"))
				col := pos - InStr(src, "`n",, -(StrLen(src)-pos+1))

				msg := Format("{}: line {} col {} (char {})"
				,   (next == "")      ? ["Extra data", ch := SubStr(src, pos)][1]
				: (next == "'")     ? "Unterminated string starting at"
				: (next == "\")     ? "Invalid \escape"
				: (next == ":")     ? "Expecting ':' delimiter"
				: (next == q)       ? "Expecting object key enclosed in double quotes"
				: (next == q . "}") ? "Expecting object key enclosed in double quotes or object closing '}'"
				: (next == ",}")    ? "Expecting ',' delimiter or object closing '}'"
				: (next == ",]")    ? "Expecting ',' delimiter or array closing ']'"
				: [ "Expecting JSON value(string, number, [true, false, null], object or array)"
				, ch := SubStr(src, pos, (SubStr(src, pos)~="[\]\},\s]|$")-1) ][1]
				, ln, col, pos)

				throw Exception(msg, -1, ch)
			}

			is_array := is_arr[obj := stack[1]]

			if i := InStr("{[", ch)
			{
				val := (proto := args[i]) ? new proto : {}
				is_array? ObjPush(obj, val) : obj[key] := val
				ObjInsertAt(stack, 1, val)

				is_arr[val] := !(is_key := ch == "{")
				next := q . (is_key ? "}" : "{[]0123456789-tfn")
			}

			else if InStr("}]", ch)
			{
				ObjRemoveAt(stack, 1)
				next := stack[1]==tree ? "" : is_arr[stack[1]] ? ",]" : ",}"
			}

			else if InStr(",:", ch)
			{
				is_key := (!is_array && ch == ",")
				next := is_key ? q : q . "{[0123456789-tfn"
			}

			else ; string | number | true | false | null
			{
				if (ch == q) ; string
				{
					i := pos
					while i := InStr(src, q,, i+1)
					{
						val := StrReplace(SubStr(src, pos+1, i-pos-1), "\\", "\u005C")
						static end := A_AhkVersion<"2" ? 0 : -1
						if (SubStr(val, end) != "\")
							break
					}
					if !i ? (pos--, next := "'") : 0
						continue

					pos := i ; update pos

					val := StrReplace(val,    "\/",  "/")
					, val := StrReplace(val, "\" . q,    q)
					, val := StrReplace(val,    "\b", "`b")
					, val := StrReplace(val,    "\f", "`f")
					, val := StrReplace(val,    "\n", "`n")
					, val := StrReplace(val,    "\r", "`r")
					, val := StrReplace(val,    "\t", "`t")

					i := 0
					while i := InStr(val, "\",, i+1)
					{
						if (SubStr(val, i+1, 1) != "u") ? (pos -= StrLen(SubStr(val, i)), next := "\") : 0
							continue 2

						; \uXXXX - JSON unicode escape sequence
						xxxx := Abs("0x" . SubStr(val, i+2, 4))
						if (A_IsUnicode || xxxx < 0x100)
							val := SubStr(val, 1, i-1) . Chr(xxxx) . SubStr(val, i+6)
					}

					if is_key
					{
						key := val, next := ":"
						continue
					}
				}

				else ; number | true | false | null
				{
					val := SubStr(src, pos, i := RegExMatch(src, "[\]\},\s]|$",, pos)-pos)

					; For numerical values, numerify integers and keep floats as is.
					; I'm not yet sure if I should numerify floats in v2.0-a ...
					static number := "number", integer := "integer"
					if val is %number%
					{
						if val is %integer%
							val += 0
					}
					; in v1.1, true,false,A_PtrSize,A_IsUnicode,A_Index,A_EventInfo,
					; SOMETIMES return strings due to certain optimizations. Since it
					; is just 'SOMETIMES', numerify to be consistent w/ v2.0-a
					else if (val == "true" || val == "false")
						val := %value% + 0
					; AHK_H has built-in null, can't do 'val := %value%' where value == "null"
					; as it would raise an exception in AHK_H(overriding built-in var)
					else if (val == "null")
						val := ""
					; any other values are invalid, continue to trigger error
					else if (pos--, next := "#")
						continue

					pos += i-1
				}

				is_array? ObjPush(obj, val) : obj[key] := val
				next := obj==tree ? "" : is_array ? ",]" : ",}"
			}
		}

		return tree[1]
	}

	Jxon_Dump(obj, indent:="", lvl:=1)
	{
		static q := Chr(34)

		if IsObject(obj)
		{
			static Type := Func("Type")
			if Type ? (Type.Call(obj) != "Object") : (ObjGetCapacity(obj) == "")
				throw Exception("Object type not supported.", -1, Format("<Object at 0x{:p}>", &obj))

			prefix := SubStr(A_ThisFunc, 1, InStr(A_ThisFunc, ".",, 0))
			fn_t := prefix "Jxon_True",  obj_t := this ? %fn_t%(this) : %fn_t%()
			fn_f := prefix "Jxon_False", obj_f := this ? %fn_f%(this) : %fn_f%()

			if (&obj == &obj_t)
				return "true"
			else if (&obj == &obj_f)
				return "false"

			is_array := 0
			for k in obj
				is_array := k == A_Index
			until !is_array

			static integer := "integer"
			if indent is %integer%
			{
				if (indent < 0)
					throw Exception("Indent parameter must be a postive integer.", -1, indent)
				spaces := indent, indent := ""
				Loop % spaces
					indent .= " "
			}
			indt := ""
			Loop, % indent ? lvl : 0
				indt .= indent

			this_fn := this ? Func(A_ThisFunc).Bind(this) : A_ThisFunc
			lvl += 1, out := "" ; Make #Warn happy
			for k, v in obj
			{
				if IsObject(k) || (k == "")
					throw Exception("Invalid object key.", -1, k ? Format("<Object at 0x{:p}>", &obj) : "<blank>")

				if !is_array
					out .= ( ObjGetCapacity([k], 1) ? %this_fn%(k) : q . k . q ) ;// key
				.  ( indent ? ": " : ":" ) ; token + padding
				out .= %this_fn%(v, indent, lvl) ; value
				.  ( indent ? ",`n" . indt : "," ) ; token + indent
			}

			if (out != "")
			{
				out := Trim(out, ",`n" . indent)
				if (indent != "")
					out := "`n" . indt . out . "`n" . SubStr(indt, StrLen(indent)+1)
			}

			return is_array ? "[" . out . "]" : "{" . out . "}"
		}

		; Number
		else if (ObjGetCapacity([obj], 1) == "")
			return obj

		; String (null -> not supported by AHK)
		if (obj != "")
		{
			obj := StrReplace(obj,  "\",    "\\")
			, obj := StrReplace(obj,  "/",    "\/")
			, obj := StrReplace(obj,    q, "\" . q)
			, obj := StrReplace(obj, "`b",    "\b")
			, obj := StrReplace(obj, "`f",    "\f")
			, obj := StrReplace(obj, "`n",    "\n")
			, obj := StrReplace(obj, "`r",    "\r")
			, obj := StrReplace(obj, "`t",    "\t")

			static needle := (A_AhkVersion<"2" ? "O)" : "") . "[^\x20-\x7e]"
			while RegExMatch(obj, needle, m)
				obj := StrReplace(obj, m[0], Format("\u{:04X}", Ord(m[0])))
		}

		return q . obj . q
	}

	Jxon_True()
	{
		static obj := {}
		return obj
	}

	Jxon_False()
	{
		static obj := {}
		return obj
	}
}
Why use a BoundFunc?
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Basic Authentication with Chrome.ahk

29 Jun 2021, 19:54

because each page most likely has its own unique credentials and its cleaner that way
ultimately, u dont have to
rgoijre23
Posts: 20
Joined: 24 May 2021, 13:43

Re: Basic Authentication with Chrome.ahk

30 Jun 2021, 11:57

swagfag wrote:
29 Jun 2021, 19:54
because each page most likely has its own unique credentials and its cleaner that way
ultimately, u dont have to
Apologies, I never mentioned that each website will pass the same credentials. So in that case, I do not need to use a BoundFunc correct?
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Basic Authentication with Chrome.ahk

30 Jun 2021, 16:24

if they all used Basic Authentication, then i suppose not
but even if they didnt, as i already said, "ultimately u dont have to" - a script can be coded in many different ways, some perhaps messier than others, but functional nonetheless
rgoijre23
Posts: 20
Joined: 24 May 2021, 13:43

Re: Basic Authentication with Chrome.ahk

01 Jul 2021, 13:25

swagfag wrote:
30 Jun 2021, 16:24
if they all used Basic Authentication, then i suppose not
but even if they didnt, as i already said, "ultimately u dont have to" - a script can be coded in many different ways, some perhaps messier than others, but functional nonetheless
I appreciate the input! Would you know how to do it for more than one website with the same credentials? I've been trying to figure it out, but no luck :(

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: masheen, mikeyww and 177 guests