Detect clickable link in Chrome

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
reverberation
Posts: 314
Joined: 13 Dec 2015, 20:48

Detect clickable link in Chrome

04 May 2020, 22:43

I made this script to quickly unsubscribe from junk emails within Gmail. However, there are times where the email puts the link in "CLICK HERE" instead of "Unsubscribe". How do I improve on the code to cater for BOTH situations?

Image

Code: Select all

#If WinActive("ahk_exe Chrome.exe") ;Find Unsubscribe Button hit it
^u:: 
Send ^f
Sleep 200
SendInput Unsubscribe
Sendinput {esc}
Sleep 100
SendInput ^{enter}
return
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: Detect clickable link in Chrome

05 May 2020, 03:17

reverberation wrote:
04 May 2020, 22:43
I made this script to quickly unsubscribe from junk emails within Gmail. However, there are times where the email puts the link in "CLICK HERE" instead of "Unsubscribe". How do I improve on the code to cater for BOTH situations?

Image

Code: Select all

#If WinActive("ahk_exe Chrome.exe") ;Find Unsubscribe Button hit it
^u:: 
Send ^f
Sleep 200
SendInput Unsubscribe
Sendinput {esc}
Sleep 100
SendInput ^{enter}
return
Autohotkey may not be very suitable for this job. Maybe to some extent, but very unlikely to be really productive.
One of the most efficient solutions is to use a browser-based macro application such as iMacros.
https://imacros.net/
AHKStudent
Posts: 1472
Joined: 05 May 2018, 12:23

Re: Detect clickable link in Chrome

05 May 2020, 06:58

There is a way to do this with ACC but I cannot figure out why when the search box is in focus this does not work maybe @jeeswg would know, all ACC things I learned from his posts. The ACC code here (minus the few lines related to Chrome) is from what he posted a while ago.

To test run the program

open Chrome

Click ctrl F

Type some words in the search box and click somewhere outside the search box (while leaving the search box open)

Then hit q

Let me know what happens

@jeeswg or someone else might explain the mystery why this wont work unless you click away from the search box

Code: Select all

q::
WinGet, hwnd, ID, ahk_class Chrome_WidgetWin_1,, Skype
oAcc := Acc_Get("Object", "4.2.1.1.2", 0, "ahk_id " hwnd) ; possible path on your Chrome is different

if (oAcc.accName(0) = "No results")
	MsgBox, No results
else	
	msgbox, % oAcc.accName(0)
return






Acc_Init()
{
	Static	h
	If Not	h
		h:=DllCall("LoadLibrary","Str","oleacc","Ptr")
}
Acc_ObjectFromEvent(ByRef _idChild_, hWnd, idObject, idChild)
{
	Acc_Init()
	If	DllCall("oleacc\AccessibleObjectFromEvent", "Ptr", hWnd, "UInt", idObject, "UInt", idChild, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0
	Return	ComObjEnwrap(9,pacc,1), _idChild_:=NumGet(varChild,8,"UInt")
}

Acc_ObjectFromPoint(ByRef _idChild_ = "", x = "", y = "")
{
	Acc_Init()
	If	DllCall("oleacc\AccessibleObjectFromPoint", "Int64", x==""||y==""?0*DllCall("GetCursorPos","Int64*",pt)+pt:x&0xFFFFFFFF|y<<32, "Ptr*", pacc, "Ptr", VarSetCapacity(varChild,8+2*A_PtrSize,0)*0+&varChild)=0
	Return	ComObjEnwrap(9,pacc,1), _idChild_:=NumGet(varChild,8,"UInt")
}

Acc_ObjectFromWindow(hWnd, idObject = -4)
{
	Acc_Init()
	If	DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject&=0xFFFFFFFF, "Ptr", -VarSetCapacity(IID,16)+NumPut(idObject==0xFFFFFFF0?0x46000000000000C0:0x719B3800AA000C81,NumPut(idObject==0xFFFFFFF0?0x0000000000020400:0x11CF3C3D618736E0,IID,"Int64"),"Int64"), "Ptr*", pacc)=0
	Return	ComObjEnwrap(9,pacc,1)
}

Acc_WindowFromObject(pacc)
{
	If	DllCall("oleacc\WindowFromAccessibleObject", "Ptr", IsObject(pacc)?ComObjValue(pacc):pacc, "Ptr*", hWnd)=0
	Return	hWnd
}

Acc_GetRoleText(nRole)
{
	nSize := DllCall("oleacc\GetRoleText", "Uint", nRole, "Ptr", 0, "Uint", 0)
	VarSetCapacity(sRole, (A_IsUnicode?2:1)*nSize)
	DllCall("oleacc\GetRoleText", "Uint", nRole, "str", sRole, "Uint", nSize+1)
	Return	sRole
}

Acc_GetStateText(nState)
{
	nSize := DllCall("oleacc\GetStateText", "Uint", nState, "Ptr", 0, "Uint", 0)
	VarSetCapacity(sState, (A_IsUnicode?2:1)*nSize)
	DllCall("oleacc\GetStateText", "Uint", nState, "str", sState, "Uint", nSize+1)
	Return	sState
}

Acc_SetWinEventHook(eventMin, eventMax, pCallback)
{
	Return	DllCall("SetWinEventHook", "Uint", eventMin, "Uint", eventMax, "Uint", 0, "Ptr", pCallback, "Uint", 0, "Uint", 0, "Uint", 0)
}

Acc_UnhookWinEvent(hHook)
{
	Return	DllCall("UnhookWinEvent", "Ptr", hHook)
}
/*	Win Events:

	pCallback := RegisterCallback("WinEventProc")
	WinEventProc(hHook, event, hWnd, idObject, idChild, eventThread, eventTime)
	{
		Critical
		Acc := Acc_ObjectFromEvent(_idChild_, hWnd, idObject, idChild)
		; Code Here:

	}
*/

; Written by jethrow
Acc_Role(Acc, ChildId=0) {
	try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetRoleText(Acc.accRole(ChildId)):"invalid object"
}
Acc_State(Acc, ChildId=0) {
	try return ComObjType(Acc,"Name")="IAccessible"?Acc_GetStateText(Acc.accState(ChildId)):"invalid object"
}
Acc_Location(Acc, ChildId=0, byref Position="") { ; adapted from Sean's code
	try Acc.accLocation(ComObj(0x4003,&x:=0), ComObj(0x4003,&y:=0), ComObj(0x4003,&w:=0), ComObj(0x4003,&h:=0), ChildId)
	catch
		return
	Position := "x" NumGet(x,0,"int") " y" NumGet(y,0,"int") " w" NumGet(w,0,"int") " h" NumGet(h,0,"int")
	return	{x:NumGet(x,0,"int"), y:NumGet(y,0,"int"), w:NumGet(w,0,"int"), h:NumGet(h,0,"int")}
}
Acc_Parent(Acc) { 
	try parent:=Acc.accParent
	return parent?Acc_Query(parent):
}
Acc_Child(Acc, ChildId=0) {
	try child:=Acc.accChild(ChildId)
	return child?Acc_Query(child):
}
Acc_Query(Acc) { ; thanks Lexikos - www.autohotkey.com/forum/viewtopic.php?t=81731&p=509530#509530
	try return ComObj(9, ComObjQuery(Acc,"{618736e0-3c3d-11cf-810c-00aa00389b71}"), 1)
}
Acc_Error(p="") {
	static setting:=0
	return p=""?setting:setting:=p
}
Acc_Children(Acc) {
	if ComObjType(Acc,"Name") != "IAccessible"
		ErrorLevel := "Invalid IAccessible Object"
	else {
		Acc_Init(), cChildren:=Acc.accChildCount, Children:=[]
		if DllCall("oleacc\AccessibleChildren", "Ptr",ComObjValue(Acc), "Int",0, "Int",cChildren, "Ptr",VarSetCapacity(varChildren,cChildren*(8+2*A_PtrSize),0)*0+&varChildren, "Int*",cChildren)=0 {
			Loop %cChildren%
				i:=(A_Index-1)*(A_PtrSize*2+8)+8, child:=NumGet(varChildren,i), Children.Insert(NumGet(varChildren,i-8)=9?Acc_Query(child):child), NumGet(varChildren,i-8)=9?ObjRelease(child):
			return Children.MaxIndex()?Children:
		} else
			ErrorLevel := "AccessibleChildren DllCall Failed"
	}
	if Acc_Error()
		throw Exception(ErrorLevel,-1)
}
Acc_ChildrenByRole(Acc, Role) {
	if ComObjType(Acc,"Name")!="IAccessible"
		ErrorLevel := "Invalid IAccessible Object"
	else {
		Acc_Init(), cChildren:=Acc.accChildCount, Children:=[]
		if DllCall("oleacc\AccessibleChildren", "Ptr",ComObjValue(Acc), "Int",0, "Int",cChildren, "Ptr",VarSetCapacity(varChildren,cChildren*(8+2*A_PtrSize),0)*0+&varChildren, "Int*",cChildren)=0 {
			Loop %cChildren% {
				i:=(A_Index-1)*(A_PtrSize*2+8)+8, child:=NumGet(varChildren,i)
				if NumGet(varChildren,i-8)=9
					AccChild:=Acc_Query(child), ObjRelease(child), Acc_Role(AccChild)=Role?Children.Insert(AccChild):
				else
					Acc_Role(Acc, child)=Role?Children.Insert(child):
			}
			return Children.MaxIndex()?Children:, ErrorLevel:=0
		} else
			ErrorLevel := "AccessibleChildren DllCall Failed"
	}
	if Acc_Error()
		throw Exception(ErrorLevel,-1)
}
Acc_Get(Cmd, ChildPath="", ChildID=0, WinTitle="", WinText="", ExcludeTitle="", ExcludeText="") {
	static properties := {Action:"DefaultAction", DoAction:"DoDefaultAction", Keyboard:"KeyboardShortcut"}
	AccObj :=   IsObject(WinTitle)? WinTitle
			:   Acc_ObjectFromWindow( WinExist(WinTitle, WinText, ExcludeTitle, ExcludeText), 0 )
	if ComObjType(AccObj, "Name") != "IAccessible"
		ErrorLevel := "Could not access an IAccessible Object"
	else {
		StringReplace, ChildPath, ChildPath, _, %A_Space%, All
		AccError:=Acc_Error(), Acc_Error(true)
		Loop Parse, ChildPath, ., %A_Space%
			try {
				if A_LoopField is digit
					Children:=Acc_Children(AccObj), m2:=A_LoopField ; mimic "m2" output in else-statement
				else
					RegExMatch(A_LoopField, "(\D*)(\d*)", m), Children:=Acc_ChildrenByRole(AccObj, m1), m2:=(m2?m2:1)
				if Not Children.HasKey(m2)
					throw
				AccObj := Children[m2]
			} catch {
				ErrorLevel:="Cannot access ChildPath Item #" A_Index " -> " A_LoopField, Acc_Error(AccError)
				if Acc_Error()
					throw Exception("Cannot access ChildPath Item", -1, "Item #" A_Index " -> " A_LoopField)
				return
			}
		Acc_Error(AccError)
		StringReplace, Cmd, Cmd, %A_Space%, , All
		properties.HasKey(Cmd)? Cmd:=properties[Cmd]:
		try {
			if (Cmd = "Location")
				AccObj.accLocation(ComObj(0x4003,&x:=0), ComObj(0x4003,&y:=0), ComObj(0x4003,&w:=0), ComObj(0x4003,&h:=0), ChildId)
			  , ret_val := "x" NumGet(x,0,"int") " y" NumGet(y,0,"int") " w" NumGet(w,0,"int") " h" NumGet(h,0,"int")
			else if (Cmd = "Object")
				ret_val := AccObj
			else if Cmd in Role,State
				ret_val := Acc_%Cmd%(AccObj, ChildID+0)
			else if Cmd in ChildCount,Selection,Focus
				ret_val := AccObj["acc" Cmd]
			else
				ret_val := AccObj["acc" Cmd](ChildID+0)
		} catch {
			ErrorLevel := """" Cmd """ Cmd Not Implemented"
			if Acc_Error()
				throw Exception("Cmd Not Implemented", -1, Cmd)
			return
		}
		return ret_val, ErrorLevel:=0
	}
	if Acc_Error()
		throw Exception(ErrorLevel,-1)
}

GuiClose:
ExitApp

EnumProcesses()
{
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms682489(v=vs.85).aspx
    local hSnapshot := 0
    if ( (hSnapshot := DLLCall("Kernel32.dll\CreateToolhelp32Snapshot", "UInt", 2, "UInt", 0, "Ptr")) == -1 )    ; TH32CS_SNAPPROCESS = 2 | INVALID_HANDLE_VALUE = -1
        return 0
    
    local PROCESSENTRY32 := ""
    NumPut(VarSetCapacity(PROCESSENTRY32, A_PtrSize == 4 ? 556 : 568), &PROCESSENTRY32, "UInt")
    
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms684834(v=vs.85).aspx
    local OutputVar := []
    if ( DllCall("Kernel32.dll\Process32FirstW", "Ptr", hSnapshot, "UPtr", &PROCESSENTRY32) )
        loop
            OutputVar[A_Index] := {       ProcessId: NumGet(&PROCESSENTRY32 +                          8,   "UInt")
                                  , ParentProcessId: NumGet(&PROCESSENTRY32 + (A_PtrSize == 4 ? 24 : 32),   "UInt")
                                  ,     ProcessName: StrGet(&PROCESSENTRY32 + (A_PtrSize == 4 ? 36 : 44), "UTF-16")
                                  ,         Threads: NumGet(&PROCESSENTRY32 + (A_PtrSize == 4 ? 20 : 28),   "UInt") }
        ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms684836(v=vs.85).aspx
        until (!DllCall("Kernel32.dll\Process32NextW", "Ptr", hSnapshot, "UPtr", &PROCESSENTRY32))
    
    ; https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx
    DllCall("Kernel32.dll\CloseHandle", "Ptr", hSnapshot)

    return ObjLength(OutputVar) ? OutputVar : 0
} ;https://msdn.microsoft.com/en-us/library/windows/desktop/ms684834(v=vs.85).aspx
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect clickable link in Chrome

05 May 2020, 08:17

If using accsessible, it's easier to get acc-object of Chrome address bar and run javascript from it, which searches for a link with specified text and clicks it.

Code: Select all

SetBatchLines, -1

linkText := "05 May 2020, 06:43"

js =
(
(() => {
   const links = document.links;
   for (let i = 0; i < links.length; i++) {
      if (links[i].innerText == '%linkText%') {
         links[i].click();
         break
      }
   }
})();
)

$F1:: RunJsFromChromeAddressBar(js)

RunJsFromChromeAddressBar(js, exe := "chrome.exe") {
   static WM_GETOBJECT := 0x3D
        , ROLE_SYSTEM_TEXT := 0x2A
        , STATE_SYSTEM_FOCUSABLE := 0x100000
        , SELFLAG_TAKEFOCUS := 0x1
   if !AccAddrBar {
      window := "ahk_class Chrome_WidgetWin_1 ahk_exe " . exe
      SendMessage, WM_GETOBJECT, 0, 1, Chrome_RenderWidgetHostHWND1, % window
      AccChrome := AccObjectFromWindow( WinExist(window) )
      AccAddrBar := SearchElement(AccChrome, {Role: ROLE_SYSTEM_TEXT, State: STATE_SYSTEM_FOCUSABLE})
   }
   AccAddrBar.accValue(0) := "javascript:" . js
   AccAddrBar.accSelect(SELFLAG_TAKEFOCUS, 0)
   ControlSend,, {Enter}, % window, Chrome Legacy Window
}

SearchElement(parentElement, params)
{
   found := true
   for k, v in params {
      try {
         if (k = "ChildCount")
            (parentElement.accChildCount != v && found := false)
         else if (k = "State")
            (!(parentElement.accState(0) & v) && found := false)
         else
            (parentElement["acc" . k](0) != v && found := false)
      }
      catch 
         found := false
   } until !found
   if found
      Return parentElement
   
   for k, v in AccChildren(parentElement)
      if obj := SearchElement(v, params)
         Return obj
}

AccObjectFromWindow(hWnd, idObject = 0) {
   static IID_IDispatch   := "{00020400-0000-0000-C000-000000000046}"
        , IID_IAccessible := "{618736E0-3C3D-11CF-810C-00AA00389B71}"
        , OBJID_NATIVEOM  := 0xFFFFFFF0, VT_DISPATCH := 9, F_OWNVALUE := 1
        , h := DllCall("LoadLibrary", "Str", "oleacc", "Ptr")
        
   VarSetCapacity(IID, 16), idObject &= 0xFFFFFFFF
   DllCall("ole32\CLSIDFromString", "Str", idObject = OBJID_NATIVEOM ? IID_IDispatch : IID_IAccessible, "Ptr", &IID)
   if DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject, "Ptr", &IID, "PtrP", pAcc) = 0
      Return ComObject(VT_DISPATCH, pAcc, F_OWNVALUE)
}

AccChildren(Acc) {
   static VT_DISPATCH := 9
   Loop 1  {
      if ComObjType(Acc, "Name") != "IAccessible"  {
         error := "Invalid IAccessible Object"
         break
      }
      try cChildren := Acc.accChildCount
      catch
         Return ""
      Children := []
      VarSetCapacity(varChildren, cChildren*(8 + A_PtrSize*2), 0)
      res := DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0
                                                , "Int", cChildren, "Ptr", &varChildren, "IntP", cChildren)
      if (res != 0) {
         error := "AccessibleChildren DllCall Failed"
         break
      }
      Loop % cChildren  {
         i := (A_Index - 1)*(A_PtrSize*2 + 8)
         child := NumGet(varChildren, i + 8)
         Children.Push( (b := NumGet(varChildren, i) = VT_DISPATCH) ? AccQuery(child) : child )
         ( b && ObjRelease(child) )
      }
   }
   if error
      ErrorLevel := error
   else
      Return Children.MaxIndex() ? Children : ""
}

AccQuery(Acc) {
   static IAccessible := "{618736e0-3c3d-11cf-810c-00aa00389b71}", VT_DISPATCH := 9, F_OWNVALUE := 1
   try Return ComObject(VT_DISPATCH, ComObjQuery(Acc, IAccessible), F_OWNVALUE)
}
AHKStudent
Posts: 1472
Joined: 05 May 2018, 12:23

Re: Detect clickable link in Chrome

05 May 2020, 08:30

@teadrinker thats genius never heard of such a ability within the regular chrome :thumbup:

wonder what other common things this type of method can be used for
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect clickable link in Chrome

05 May 2020, 08:50

Such method can be used for performing any action on the page or gettind any data.
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: Detect clickable link in Chrome

05 May 2020, 09:36

teadrinker wrote:
05 May 2020, 08:17
If using accsessible, it's easier to get acc-object of Chrome address bar and run javascript from it, which searches for a link with specified text and clicks it.

Code: Select all

SetBatchLines, -1

linkText := "05 May 2020, 06:43"

js =
(
(() => {
   const links = document.links;
   for (let i = 0; i < links.length; i++) {
      if (links[i].innerText == '%linkText%') {
         links[i].click();
         break
      }
   }
})();
)

$F1:: RunJsFromChromeAddressBar(js)

RunJsFromChromeAddressBar(js, exe := "chrome.exe") {
   static WM_GETOBJECT := 0x3D
        , ROLE_SYSTEM_TEXT := 0x2A
        , STATE_SYSTEM_FOCUSABLE := 0x100000
        , SELFLAG_TAKEFOCUS := 0x1
   if !AccAddrBar {
      window := "ahk_class Chrome_WidgetWin_1 ahk_exe " . exe
      SendMessage, WM_GETOBJECT, 0, 1, Chrome_RenderWidgetHostHWND1, % window
      AccChrome := AccObjectFromWindow( WinExist(window) )
      AccAddrBar := SearchElement(AccChrome, {Role: ROLE_SYSTEM_TEXT, State: STATE_SYSTEM_FOCUSABLE})
   }
   AccAddrBar.accValue(0) := "javascript:" . js
   AccAddrBar.accSelect(SELFLAG_TAKEFOCUS, 0)
   ControlSend,, {Enter}, % window, Chrome Legacy Window
}

SearchElement(parentElement, params)
{
   found := true
   for k, v in params {
      try {
         if (k = "ChildCount")
            (parentElement.accChildCount != v && found := false)
         else if (k = "State")
            (!(parentElement.accState(0) & v) && found := false)
         else
            (parentElement["acc" . k](0) != v && found := false)
      }
      catch 
         found := false
   } until !found
   if found
      Return parentElement
   
   for k, v in AccChildren(parentElement)
      if obj := SearchElement(v, params)
         Return obj
}

AccObjectFromWindow(hWnd, idObject = 0) {
   static IID_IDispatch   := "{00020400-0000-0000-C000-000000000046}"
        , IID_IAccessible := "{618736E0-3C3D-11CF-810C-00AA00389B71}"
        , OBJID_NATIVEOM  := 0xFFFFFFF0, VT_DISPATCH := 9, F_OWNVALUE := 1
        , h := DllCall("LoadLibrary", "Str", "oleacc", "Ptr")
        
   VarSetCapacity(IID, 16), idObject &= 0xFFFFFFFF
   DllCall("ole32\CLSIDFromString", "Str", idObject = OBJID_NATIVEOM ? IID_IDispatch : IID_IAccessible, "Ptr", &IID)
   if DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject, "Ptr", &IID, "PtrP", pAcc) = 0
      Return ComObject(VT_DISPATCH, pAcc, F_OWNVALUE)
}

AccChildren(Acc) {
   static VT_DISPATCH := 9
   Loop 1  {
      if ComObjType(Acc, "Name") != "IAccessible"  {
         error := "Invalid IAccessible Object"
         break
      }
      try cChildren := Acc.accChildCount
      catch
         Return ""
      Children := []
      VarSetCapacity(varChildren, cChildren*(8 + A_PtrSize*2), 0)
      res := DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0
                                                , "Int", cChildren, "Ptr", &varChildren, "IntP", cChildren)
      if (res != 0) {
         error := "AccessibleChildren DllCall Failed"
         break
      }
      Loop % cChildren  {
         i := (A_Index - 1)*(A_PtrSize*2 + 8)
         child := NumGet(varChildren, i + 8)
         Children.Push( (b := NumGet(varChildren, i) = VT_DISPATCH) ? AccQuery(child) : child )
         ( b && ObjRelease(child) )
      }
   }
   if error
      ErrorLevel := error
   else
      Return Children.MaxIndex() ? Children : ""
}

AccQuery(Acc) {
   static IAccessible := "{618736e0-3c3d-11cf-810c-00aa00389b71}", VT_DISPATCH := 9, F_OWNVALUE := 1
   try Return ComObject(VT_DISPATCH, ComObjQuery(Acc, IAccessible), F_OWNVALUE)
}
Wow, this is amazing. I never knew this kind of interaction with Chrome was possible.

 Can you also give us examples of sending and receiving text?
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect clickable link in Chrome

05 May 2020, 09:52

The easiest way to get text is to put it on Clipboard.
Example of retrieving HTML code (thanks @malcev) :

Code: Select all

SetBatchLines, -1
js =
(
   javascript:
   (() => {
      if (window.location.protocol === 'https:') {
         document.documentElement.focus();
         const timer = setInterval(() => {
            if (document.hasFocus()) {
               clearInterval(timer);
               navigator.clipboard.writeText(document.documentElement.outerHTML);
            }
         }, 10);
      }
      else {
         const textArea = document.createElement('textarea');
         textArea.value = document.documentElement.outerHTML;
         textArea.wrap = 'off';
         textArea.rows = 100000;
         textArea.style.position = 'fixed';
         document.documentElement.appendChild(textArea);
         textArea.focus();
         textArea.select();
         document.execCommand('copy');
         textArea.parentNode.removeChild(textArea);
      }
   })();
)
Return

$F1::
   Clipboard := ""
   RunJsFromChromeAddressBar(js)
   ClipWait, 2
   MsgBox, % ErrorLevel ? "Failed to get value" : Clipboard
   Return
   
RunJsFromChromeAddressBar(js, exe := "chrome.exe") {
   static WM_GETOBJECT := 0x3D
        , ROLE_SYSTEM_TEXT := 0x2A
        , STATE_SYSTEM_FOCUSABLE := 0x100000
        , SELFLAG_TAKEFOCUS := 0x1
        , AccAddrBar
   if !AccAddrBar {
      window := "ahk_class Chrome_WidgetWin_1 ahk_exe " . exe
      SendMessage, WM_GETOBJECT, 0, 1, Chrome_RenderWidgetHostHWND1, % window
      AccChrome := AccObjectFromWindow( WinExist(window) )
      AccAddrBar := SearchElement(AccChrome, {Role: ROLE_SYSTEM_TEXT, State: STATE_SYSTEM_FOCUSABLE})
   }
   AccAddrBar.accValue(0) := "javascript:" . js
   AccAddrBar.accSelect(SELFLAG_TAKEFOCUS, 0)
   ControlSend,, {Enter}, % window, Chrome Legacy Window
}

SearchElement(parentElement, params)
{
   found := true
   for k, v in params {
      try {
         if (k = "ChildCount")
            (parentElement.accChildCount != v && found := false)
         else if (k = "State")
            (!(parentElement.accState(0) & v) && found := false)
         else
            (parentElement["acc" . k](0) != v && found := false)
      }
      catch 
         found := false
   } until !found
   if found
      Return parentElement
   
   for k, v in AccChildren(parentElement)
      if obj := SearchElement(v, params)
         Return obj
}

AccObjectFromWindow(hWnd, idObject = 0) {
   static IID_IDispatch   := "{00020400-0000-0000-C000-000000000046}"
        , IID_IAccessible := "{618736E0-3C3D-11CF-810C-00AA00389B71}"
        , OBJID_NATIVEOM  := 0xFFFFFFF0, VT_DISPATCH := 9, F_OWNVALUE := 1
        , h := DllCall("LoadLibrary", "Str", "oleacc", "Ptr")
        
   VarSetCapacity(IID, 16), idObject &= 0xFFFFFFFF
   DllCall("ole32\CLSIDFromString", "Str", idObject = OBJID_NATIVEOM ? IID_IDispatch : IID_IAccessible, "Ptr", &IID)
   if DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject, "Ptr", &IID, "PtrP", pAcc) = 0
      Return ComObject(VT_DISPATCH, pAcc, F_OWNVALUE)
}

AccChildren(Acc) {
   static VT_DISPATCH := 9
   Loop 1  {
      if ComObjType(Acc, "Name") != "IAccessible"  {
         error := "Invalid IAccessible Object"
         break
      }
      try cChildren := Acc.accChildCount
      catch
         Return ""
      Children := []
      VarSetCapacity(varChildren, cChildren*(8 + A_PtrSize*2), 0)
      res := DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0
                                                , "Int", cChildren, "Ptr", &varChildren, "IntP", cChildren)
      if (res != 0) {
         error := "AccessibleChildren DllCall Failed"
         break
      }
      Loop % cChildren  {
         i := (A_Index - 1)*(A_PtrSize*2 + 8)
         child := NumGet(varChildren, i + 8)
         Children.Push( (b := NumGet(varChildren, i) = VT_DISPATCH) ? AccQuery(child) : child )
         ( b && ObjRelease(child) )
      }
   }
   if error
      ErrorLevel := error
   else
      Return Children.MaxIndex() ? Children : ""
}

AccQuery(Acc) {
   static IAccessible := "{618736e0-3c3d-11cf-810c-00aa00389b71}", VT_DISPATCH := 9, F_OWNVALUE := 1
   try Return ComObject(VT_DISPATCH, ComObjQuery(Acc, IAccessible), F_OWNVALUE)
}
Anyway to use this method, you must have at least basic Javascript knowledge.
hasantr
Posts: 933
Joined: 05 Apr 2016, 14:18
Location: İstanbul

Re: Detect clickable link in Chrome

05 May 2020, 10:07

teadrinker wrote:
05 May 2020, 09:52
The easiest way to get text is to put it on Clipboard.
Example of retrieving HTML code (thanks @malcev) :

Code: Select all

SetBatchLines, -1
js =
(
   javascript:
   (() => {
      if (window.location.protocol === 'https:') {
         document.documentElement.focus();
         const timer = setInterval(() => {
            if (document.hasFocus()) {
               clearInterval(timer);
               navigator.clipboard.writeText(document.documentElement.outerHTML);
            }
         }, 10);
      }
      else {
         const textArea = document.createElement('textarea');
         textArea.value = document.documentElement.outerHTML;
         textArea.wrap = 'off';
         textArea.rows = 100000;
         textArea.style.position = 'fixed';
         document.documentElement.appendChild(textArea);
         textArea.focus();
         textArea.select();
         document.execCommand('copy');
         textArea.parentNode.removeChild(textArea);
      }
   })();
)
Return

$F1::
   Clipboard := ""
   RunJsFromChromeAddressBar(js)
   ClipWait, 2
   MsgBox, % ErrorLevel ? "Failed to get value" : Clipboard
   Return
   
RunJsFromChromeAddressBar(js, exe := "chrome.exe") {
   static WM_GETOBJECT := 0x3D
        , ROLE_SYSTEM_TEXT := 0x2A
        , STATE_SYSTEM_FOCUSABLE := 0x100000
        , SELFLAG_TAKEFOCUS := 0x1
        , AccAddrBar
   if !AccAddrBar {
      window := "ahk_class Chrome_WidgetWin_1 ahk_exe " . exe
      SendMessage, WM_GETOBJECT, 0, 1, Chrome_RenderWidgetHostHWND1, % window
      AccChrome := AccObjectFromWindow( WinExist(window) )
      AccAddrBar := SearchElement(AccChrome, {Role: ROLE_SYSTEM_TEXT, State: STATE_SYSTEM_FOCUSABLE})
   }
   AccAddrBar.accValue(0) := "javascript:" . js
   AccAddrBar.accSelect(SELFLAG_TAKEFOCUS, 0)
   ControlSend,, {Enter}, % window, Chrome Legacy Window
}

SearchElement(parentElement, params)
{
   found := true
   for k, v in params {
      try {
         if (k = "ChildCount")
            (parentElement.accChildCount != v && found := false)
         else if (k = "State")
            (!(parentElement.accState(0) & v) && found := false)
         else
            (parentElement["acc" . k](0) != v && found := false)
      }
      catch 
         found := false
   } until !found
   if found
      Return parentElement
   
   for k, v in AccChildren(parentElement)
      if obj := SearchElement(v, params)
         Return obj
}

AccObjectFromWindow(hWnd, idObject = 0) {
   static IID_IDispatch   := "{00020400-0000-0000-C000-000000000046}"
        , IID_IAccessible := "{618736E0-3C3D-11CF-810C-00AA00389B71}"
        , OBJID_NATIVEOM  := 0xFFFFFFF0, VT_DISPATCH := 9, F_OWNVALUE := 1
        , h := DllCall("LoadLibrary", "Str", "oleacc", "Ptr")
        
   VarSetCapacity(IID, 16), idObject &= 0xFFFFFFFF
   DllCall("ole32\CLSIDFromString", "Str", idObject = OBJID_NATIVEOM ? IID_IDispatch : IID_IAccessible, "Ptr", &IID)
   if DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject, "Ptr", &IID, "PtrP", pAcc) = 0
      Return ComObject(VT_DISPATCH, pAcc, F_OWNVALUE)
}

AccChildren(Acc) {
   static VT_DISPATCH := 9
   Loop 1  {
      if ComObjType(Acc, "Name") != "IAccessible"  {
         error := "Invalid IAccessible Object"
         break
      }
      try cChildren := Acc.accChildCount
      catch
         Return ""
      Children := []
      VarSetCapacity(varChildren, cChildren*(8 + A_PtrSize*2), 0)
      res := DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0
                                                , "Int", cChildren, "Ptr", &varChildren, "IntP", cChildren)
      if (res != 0) {
         error := "AccessibleChildren DllCall Failed"
         break
      }
      Loop % cChildren  {
         i := (A_Index - 1)*(A_PtrSize*2 + 8)
         child := NumGet(varChildren, i + 8)
         Children.Push( (b := NumGet(varChildren, i) = VT_DISPATCH) ? AccQuery(child) : child )
         ( b && ObjRelease(child) )
      }
   }
   if error
      ErrorLevel := error
   else
      Return Children.MaxIndex() ? Children : ""
}

AccQuery(Acc) {
   static IAccessible := "{618736e0-3c3d-11cf-810c-00aa00389b71}", VT_DISPATCH := 9, F_OWNVALUE := 1
   try Return ComObject(VT_DISPATCH, ComObjQuery(Acc, IAccessible), F_OWNVALUE)
}
Anyway to use this method, you must have at least basic Javascript knowledge.
Thanks.
We had no idea of ​​all this. All your work is great, but little known. I think you need to share it as a library. :)
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect clickable link in Chrome

05 May 2020, 10:30

hasantr wrote: you need to share it as a library.
I think about it. :)
garry
Posts: 3770
Joined: 22 Dec 2013, 12:50

Re: Detect clickable link in Chrome

05 May 2020, 10:56

@teadrinker , great script , I like also your translator
https://www.autohotkey.com/boards/viewtopic.php?t=63835
reverberation
Posts: 314
Joined: 13 Dec 2015, 20:48

Re: Detect clickable link in Chrome

05 May 2020, 21:37

teadrinker wrote:
05 May 2020, 09:52
The easiest way to get text is to put it on Clipboard.
Example of retrieving HTML code (thanks @malcev) :

Code: Select all

SetBatchLines, -1
js =
(
   javascript:
   (() => {
      if (window.location.protocol === 'https:') {
         document.documentElement.focus();
         const timer = setInterval(() => {
            if (document.hasFocus()) {
               clearInterval(timer);
               navigator.clipboard.writeText(document.documentElement.outerHTML);
            }
         }, 10);
      }
      else {
         const textArea = document.createElement('textarea');
         textArea.value = document.documentElement.outerHTML;
         textArea.wrap = 'off';
         textArea.rows = 100000;
         textArea.style.position = 'fixed';
         document.documentElement.appendChild(textArea);
         textArea.focus();
         textArea.select();
         document.execCommand('copy');
         textArea.parentNode.removeChild(textArea);
      }
   })();
)
Return

$F1::
   Clipboard := ""
   RunJsFromChromeAddressBar(js)
   ClipWait, 2
   MsgBox, % ErrorLevel ? "Failed to get value" : Clipboard
   Return
   
RunJsFromChromeAddressBar(js, exe := "chrome.exe") {
   static WM_GETOBJECT := 0x3D
        , ROLE_SYSTEM_TEXT := 0x2A
        , STATE_SYSTEM_FOCUSABLE := 0x100000
        , SELFLAG_TAKEFOCUS := 0x1
        , AccAddrBar
   if !AccAddrBar {
      window := "ahk_class Chrome_WidgetWin_1 ahk_exe " . exe
      SendMessage, WM_GETOBJECT, 0, 1, Chrome_RenderWidgetHostHWND1, % window
      AccChrome := AccObjectFromWindow( WinExist(window) )
      AccAddrBar := SearchElement(AccChrome, {Role: ROLE_SYSTEM_TEXT, State: STATE_SYSTEM_FOCUSABLE})
   }
   AccAddrBar.accValue(0) := "javascript:" . js
   AccAddrBar.accSelect(SELFLAG_TAKEFOCUS, 0)
   ControlSend,, {Enter}, % window, Chrome Legacy Window
}

SearchElement(parentElement, params)
{
   found := true
   for k, v in params {
      try {
         if (k = "ChildCount")
            (parentElement.accChildCount != v && found := false)
         else if (k = "State")
            (!(parentElement.accState(0) & v) && found := false)
         else
            (parentElement["acc" . k](0) != v && found := false)
      }
      catch 
         found := false
   } until !found
   if found
      Return parentElement
   
   for k, v in AccChildren(parentElement)
      if obj := SearchElement(v, params)
         Return obj
}

AccObjectFromWindow(hWnd, idObject = 0) {
   static IID_IDispatch   := "{00020400-0000-0000-C000-000000000046}"
        , IID_IAccessible := "{618736E0-3C3D-11CF-810C-00AA00389B71}"
        , OBJID_NATIVEOM  := 0xFFFFFFF0, VT_DISPATCH := 9, F_OWNVALUE := 1
        , h := DllCall("LoadLibrary", "Str", "oleacc", "Ptr")
        
   VarSetCapacity(IID, 16), idObject &= 0xFFFFFFFF
   DllCall("ole32\CLSIDFromString", "Str", idObject = OBJID_NATIVEOM ? IID_IDispatch : IID_IAccessible, "Ptr", &IID)
   if DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject, "Ptr", &IID, "PtrP", pAcc) = 0
      Return ComObject(VT_DISPATCH, pAcc, F_OWNVALUE)
}

AccChildren(Acc) {
   static VT_DISPATCH := 9
   Loop 1  {
      if ComObjType(Acc, "Name") != "IAccessible"  {
         error := "Invalid IAccessible Object"
         break
      }
      try cChildren := Acc.accChildCount
      catch
         Return ""
      Children := []
      VarSetCapacity(varChildren, cChildren*(8 + A_PtrSize*2), 0)
      res := DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0
                                                , "Int", cChildren, "Ptr", &varChildren, "IntP", cChildren)
      if (res != 0) {
         error := "AccessibleChildren DllCall Failed"
         break
      }
      Loop % cChildren  {
         i := (A_Index - 1)*(A_PtrSize*2 + 8)
         child := NumGet(varChildren, i + 8)
         Children.Push( (b := NumGet(varChildren, i) = VT_DISPATCH) ? AccQuery(child) : child )
         ( b && ObjRelease(child) )
      }
   }
   if error
      ErrorLevel := error
   else
      Return Children.MaxIndex() ? Children : ""
}

AccQuery(Acc) {
   static IAccessible := "{618736e0-3c3d-11cf-810c-00aa00389b71}", VT_DISPATCH := 9, F_OWNVALUE := 1
   try Return ComObject(VT_DISPATCH, ComObjQuery(Acc, IAccessible), F_OWNVALUE)
}
Anyway to use this method, you must have at least basic Javascript knowledge.
Hi guys, I'm totally lost here. does the code above serve the purpose of original topic? I don't have basic JS knowledge and based on the code itself (without comments).. I'm unable to understand what it does line by line.. especially how it finds "unsubscribe" link without any mention of unsubscribe at all.

Will also be keen to implement for "Submit" button if this works!

I tried it in a simple email with Unsubscribe link and it returned this:

Image
AHKStudent
Posts: 1472
Joined: 05 May 2018, 12:23

Re: Detect clickable link in Chrome

05 May 2020, 23:43

@reverberation this is the code based on @teadrinker first code, try it, I do not know JS but made changes (all I did was change so it checks for two words) that do the job, someone else with js experience can review to see if there is a proper way to write it to check multiple words.

To test;

1. Run the scripe

2. Open one of those gmails that has either word

3. Press q

Please test and report back

Code: Select all

SetBatchLines, -1

linkText := "Unsubscribe"
linkText2 := "CLICK HERE"

js =
(
(() => {
   const links = document.links;
   for (let i = 0; i < links.length; i++) {
      if (links[i].innerText == '%linkText%' || links[i].innerText == '%linkText2%') {
         links[i].click();
         break
      }
   }
})();
)

q:: RunJsFromChromeAddressBar(js)

RunJsFromChromeAddressBar(js, exe := "chrome.exe") {
   static WM_GETOBJECT := 0x3D
        , ROLE_SYSTEM_TEXT := 0x2A
        , STATE_SYSTEM_FOCUSABLE := 0x100000
        , SELFLAG_TAKEFOCUS := 0x1
   if !AccAddrBar {
      window := "ahk_class Chrome_WidgetWin_1 ahk_exe " . exe
      SendMessage, WM_GETOBJECT, 0, 1, Chrome_RenderWidgetHostHWND1, % window
      AccChrome := AccObjectFromWindow( WinExist(window) )
      AccAddrBar := SearchElement(AccChrome, {Role: ROLE_SYSTEM_TEXT, State: STATE_SYSTEM_FOCUSABLE})
   }
   AccAddrBar.accValue(0) := "javascript:" . js
   AccAddrBar.accSelect(SELFLAG_TAKEFOCUS, 0)
   ControlSend,, {Enter}, % window, Chrome Legacy Window
}

SearchElement(parentElement, params)
{
   found := true
   for k, v in params {
      try {
         if (k = "ChildCount")
            (parentElement.accChildCount != v && found := false)
         else if (k = "State")
            (!(parentElement.accState(0) & v) && found := false)
         else
            (parentElement["acc" . k](0) != v && found := false)
      }
      catch 
         found := false
   } until !found
   if found
      Return parentElement
   
   for k, v in AccChildren(parentElement)
      if obj := SearchElement(v, params)
         Return obj
}

AccObjectFromWindow(hWnd, idObject = 0) {
   static IID_IDispatch   := "{00020400-0000-0000-C000-000000000046}"
        , IID_IAccessible := "{618736E0-3C3D-11CF-810C-00AA00389B71}"
        , OBJID_NATIVEOM  := 0xFFFFFFF0, VT_DISPATCH := 9, F_OWNVALUE := 1
        , h := DllCall("LoadLibrary", "Str", "oleacc", "Ptr")
        
   VarSetCapacity(IID, 16), idObject &= 0xFFFFFFFF
   DllCall("ole32\CLSIDFromString", "Str", idObject = OBJID_NATIVEOM ? IID_IDispatch : IID_IAccessible, "Ptr", &IID)
   if DllCall("oleacc\AccessibleObjectFromWindow", "Ptr", hWnd, "UInt", idObject, "Ptr", &IID, "PtrP", pAcc) = 0
      Return ComObject(VT_DISPATCH, pAcc, F_OWNVALUE)
}

AccChildren(Acc) {
   static VT_DISPATCH := 9
   Loop 1  {
      if ComObjType(Acc, "Name") != "IAccessible"  {
         error := "Invalid IAccessible Object"
         break
      }
      try cChildren := Acc.accChildCount
      catch
         Return ""
      Children := []
      VarSetCapacity(varChildren, cChildren*(8 + A_PtrSize*2), 0)
      res := DllCall("oleacc\AccessibleChildren", "Ptr", ComObjValue(Acc), "Int", 0
                                                , "Int", cChildren, "Ptr", &varChildren, "IntP", cChildren)
      if (res != 0) {
         error := "AccessibleChildren DllCall Failed"
         break
      }
      Loop % cChildren  {
         i := (A_Index - 1)*(A_PtrSize*2 + 8)
         child := NumGet(varChildren, i + 8)
         Children.Push( (b := NumGet(varChildren, i) = VT_DISPATCH) ? AccQuery(child) : child )
         ( b && ObjRelease(child) )
      }
   }
   if error
      ErrorLevel := error
   else
      Return Children.MaxIndex() ? Children : ""
}

AccQuery(Acc) {
   static IAccessible := "{618736e0-3c3d-11cf-810c-00aa00389b71}", VT_DISPATCH := 9, F_OWNVALUE := 1
   try Return ComObject(VT_DISPATCH, ComObjQuery(Acc, IAccessible), F_OWNVALUE)
}
reverberation
Posts: 314
Joined: 13 Dec 2015, 20:48

Re: Detect clickable link in Chrome

06 May 2020, 19:33

just tried one that has a "Click Here" link. The URL bar flashes quickly but nothing happens subsequently.

I also wonder if the link text is prioritised in the code - i.e. it tries to find an unsubscribe link first Before searching for a "Click Now", which may not lead to unsubscribing.
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect clickable link in Chrome

06 May 2020, 19:49

@reverberation
Can you show HTML code of the link you need to click?
reverberation
Posts: 314
Joined: 13 Dec 2015, 20:48

Re: Detect clickable link in Chrome

06 May 2020, 21:37

teadrinker wrote:
06 May 2020, 19:49
@reverberation
Can you show HTML code of the link you need to click?
not sure if this is what you're looking for. I just hit view source while in the email page. 3 different samples attached:
https://pastebin.com/KYG6TBr3
teadrinker
Posts: 4331
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect clickable link in Chrome

07 May 2020, 10:08

In your code there is not something like «Click here to unsubscribe».

 Image

What I need is <a href="./viewtopic.php?p=327386#p327386">07 May 2020, 05:37</a>.
MauroOfCol
Posts: 2
Joined: 07 May 2020, 11:34

Re: Detect clickable link in Chrome

07 May 2020, 11:47

teadrinker wrote:
05 May 2020, 08:17
If using accsessible, it's easier to get acc-object of Chrome address bar and run javascript from it, which searches for a link with specified text and clicks it.
@teadrinker This is an amazing solution, isn't that an XSS procedure? if that is the case, then if a website is protected against it this script will not execute correctly, am I wrong?
MauroOfCol
Posts: 2
Joined: 07 May 2020, 11:34

Re: Detect clickable link in Chrome

07 May 2020, 12:20

Hi guys, I'm totally lost here. does the code above serve the purpose of original topic? I don't have basic JS knowledge and based on the code itself (without comments).. I'm unable to understand what it does line by line.. especially how it finds "unsubscribe" link without any mention of unsubscribe at all.

Will also be keen to implement for "Submit" button if this works!

I tried it in a simple email with Unsubscribe link and it returned this:
@reverberation Indeed this code does what is intended to do, I strongly recommend you to study the fundamentals of how websites work in order to advance in web scripting. Most websites are based on three pillars, HTML, CSS and JavaScript, the first one provides structure the second the visuals and the third the interactivity. Now this code goes in to the last category and works injecting some JS code directly in the website to retrieve the element that is required. Learning HTML and CSS is fast and easy, in a week you can get a good grasp on it, for JS I recommend the book Head First JavaScript Programming, A Brain Friendly Guide by Eric Freeman and Elisabeth Robson, this is the friendliest book I've seen about JS.

Now commenting code is it marketed as best practice, in reality it does nothing if the code itself is bad, hence the real good practice is produce good code that is high level, succinct, well indented or in other word easy to read and easy to maintain, looking at the code provided by teadrinker I can tell you that this code is high quality. In real life you comment code to remember specific things like fix or polish certain blocks or "TODO" things inside the code, why? because comments are lines of text that do nothing inside the code and when you are reading the code, the comment cuts the logical flow in your head.

This leads to some advice to improve your game. When naming variables be very specific about what the variable does, flp=0 is not easy to read it tells you nothing, flowerPots=0 gives you a real idea what the variable does, it is more typing but in the long run is easier to maintain, also, read the documentation, there is no way to stress this enough, the only way to get better at programming is understanding what the code does, so in top of reading the code line by line you need to understand the function of each word and character on its context, the best way to do so is experimenting with the concept unknown for you in a simple way and reading from the source what does that do (a.k.a. google it, then, trial and error).

Hope this helps in some way, programming is a long journey that requires lots of discipline and hard individual work.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: CrowexBR, demon740, ht55cd3 and 274 guests