Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Retrieve Chat history in Windows Live Messenger


  • Please log in to reply
10 replies to this topic
sosaited
  • Members
  • 278 posts
  • Last active: Oct 06 2009 06:08 PM
  • Joined: 24 Feb 2005
I have been trying to get a script working which will retrieve the messages sent (chat history) in a Windows Live Messenger conversation without activating the window, and with the awesome ACC.ahk and COM.ahk libraries by Sean and a function made by Lexikos on my request, here is the working version.

Requires ACC.ahk and COM.ahk libraries found here A big thanks to Sean for this.

Set contact's Name (WLM 8.1, 8.5) or email (WLM 2009) to the variable "title". The chat history is displayed in a message box and saved in a text file.

#Include ACC.ahk
#Include COM.ahk

SetTitleMatchMode, 2

title = [email protected] ; In Windows Live v 8.1 - 8.5, use the contact display name. Email will work in Windows Live Messenger 2009 only

WinGet, winid, ID, %title%
controlid := DllCall("FindWindowEx", Uint, winid, Uint, 0, str, "DirectUIHWND", int, 0)
ACC_Init()
controlpacc := Acc_AccessibleObjectFromWindow(controlid)
number := acc_ChildCount(controlpacc)
childpacc := ACC_AccessibleChildren(controlpacc, varChildren)

Loop, %number%
{
apacc := GetAccessibleChildFromArray(childpacc, varChildren, A_Index)
name := acc_Name(apacc, _idChild_)
If name = History
	{
       	history := acc_value(apacc, idChild_)
 	MsgBox, %history%
       	FileAppend, %history%, Chat History of %title%.txt
        Break
       }
}
ACC_Term()

; The following function was written by Lexikos to get the Variant array data returned by AccessibleChildren, so a big Thanks to him
GetAccessibleChildFromArray(pacc, ByRef array, i)
{
    vt := NumGet(array, i*16, "ushort")
    val := NumGet(array, i*16 + 8)
    ; If array[i].vt == VT_DISPATCH, array[i].pdispVal contains an IDispatch interface pointer.
    if vt = 9
       return acc_Query(val)
    ; Otherwise it should be VT_I4 and array[i].lVal should contain the child's id.
    ; if vt = 3
    ; return acc_Child(pacc, val)
    ; return 0
}

Alternate version by Sean:

#Include ACC.ahk
#Include COM.ahk

SetTitleMatchMode, 2

title = [email protected] ; In Windows Live v 8.1 - 8.5, use the contact display name. Email will work in Windows Live Messenger 2009 only

WinGet, winid, ID, %title%
controlid := DllCall("FindWindowEx", Uint, winid, Uint, 0, str, "DirectUIHWND", int, 0)

controlpacc := Acc_AccessibleObjectFromWindow(controlid)
Acc_Init()
pacc := Acc_AccessibleObjectFromWindow(controlid)
penm := COM_QueryInterface(pacc, "{00020404-0000-0000-C000-000000000046}")
Loop, %   COM_Invoke(pacc, "accChildCount")
If   pobj := COM_Invoke(pacc, "accChild", A_Index)
{
   COM_Invoke(pobj, "accName", 0)="History" ? history:=COM_Invoke(pobj, "accValue", 0):"", COM_Release(pobj)
   If   history
   {
      MsgBox, %history%
      FileAppend, %history%, Chat History of %title%.txt
      Break
   }
}
COM_Release(penm)
COM_Release(pacc)
Acc_Term()

My small "thanks" to AHK in shape of these dedicated 3d images
Posted Image

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
I'm glad to see you've got it working. :)


Actually, I've discovered a flaw in my logic:
if vt = 3
        return acc_Child(pacc, val)
AccessibleChildren will sometimes return a child as an ID (vt == 3) instead of an IDispatch interface pointer (vt == 9). My original intention was to call acc_Child to convert each ID to an IDispatch interface pointer, but I think that will never work in practice since an ID seems to be returned only if the child has no IDispatch interface in the first place.

If an ID is returned the code needs to pass it to each function; for instance, if "pacc" is the IAccessible interface pointer which was used to get the child ID "child_id", to get the child's name you'd need to do this:
name := acc_Name(pacc, child_id)
If your code works, I suppose the child you are accessing always has an IDispatch interface; I recommend removing the two lines shown above. Then again, it might work in some obscure case, and probably isn't harmful.

jaco0646
  • Moderators
  • 3165 posts
  • Last active: Apr 01 2014 01:46 AM
  • Joined: 07 Oct 2006
Is this something different than the option that WLM has under
Tools -> Options -> Messages -> "Automatically keep a history of my conversations" :?:

sosaited
  • Members
  • 278 posts
  • Last active: Oct 06 2009 06:08 PM
  • Joined: 24 Feb 2005

If your code works, I suppose the child you are accessing always has an IDispatch interface; I recommend removing the two lines shown above. Then again, it might work in some obscure case, and probably isn't harmful.


I have tried removing the If expressions one by one, and on my pc it works even without the If vt = 9 expression, so I have commented out the rest of the stuff until some more members can test either version.

Is this something different than the option that WLM has under
Tools -> Options -> Messages -> "Automatically keep a history of my conversations" :?:

It is different than that in a few ways. First of all that option saves every chat you have with every contact, or it doesn't save any. Second it only saves the log after you close the chat window or sign out. Both of which were a problem for my scenario.
My small "thanks" to AHK in shape of these dedicated 3d images
Posted Image

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007
I expected you to pursue in the direction of WinEvent as you asked me to translate the code using it. Anyway, not bad as the first trial of Accessibility, however, one thing is that you never released any COM object which can leak memory. And, I often felt that AccessibleChildren is somewhat a waste as it always retrieves all the children even if it's unnecessary. I'd rather approach it as the following. (not tested)

Acc_Init()
pacc := Acc_AccessibleObjectFromWindow(controlid)
penm := COM_QueryInterface(pacc, "{00020404-0000-0000-C000-000000000046}")
While	COM_Enumerate(penm, pobj, vt)=0
If	vt=9
{
	COM_Invoke(pobj, "accName", 0)="History" ? history:=COM_Invoke(pobj, "accValue", 0):"", COM_Release(pobj)
	If	history
	{
		MsgBox, %history%
		FileAppend, %history%, Chat History of %title%.txt
		Break
	}
}
COM_Release(penm)
COM_Release(pacc)
Acc_Term()


sosaited
  • Members
  • 278 posts
  • Last active: Oct 06 2009 06:08 PM
  • Joined: 24 Feb 2005

I expected you to pursue in the direction of WinEvent as you asked me to translate the code using it. Anyway, not bad as the first trial of Accessibility, however, one thing is that you never released any COM object which can leak memory

I couldn't figure out what exactly Min and Maximum event values to set in SetWinEvent function, so I didn't work on that. And basically I was just angry why AccessbleObjectFromPoint was working but not *fromWindow method.

I thought that Acc_Term() which in turn terminated the COM as well took care of memory leaks?. So should I used COM_Release(apacc) OR COM_Release(pName) and COM_Release(pValue)?

By the way the code you posted doesn't work for some reason. And while we are on the subject, how do calls like these work:
DllCall(NumGet(NumGet(1*ppv)+0)
Aren't DLL functions static inside a .dll file?
My small "thanks" to AHK in shape of these dedicated 3d images
Posted Image

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

By the way the code you posted doesn't work for some reason.

Exactly where did it fail? As a matter of fact, I assumed a few as Live Messenger is a MS product. Notice that I replaced acc_Name/acc_Value functions with COM_Invoke. If I could test, I might even have replaced
While   COM_Enumerate(penm, pobj, vt)=0
If   vt=9
with
Loop, %	COM_Invoke(pacc, "accChildCount")
If	pobj := COM_Invoke(pacc, "accChild", A_Index)
; 1-based index is correct here, but, not in your code which should have been 0-based
Or maybe your history was empty when you tested? In that case, there will be no prompt.

I thought that Acc_Term() which in turn terminated the COM as well took care of memory leaks? So should I used COM_Release(apacc) OR COM_Release(pName) and COM_Release(pValue)?

I mentioned it lightly to suggest to try to do always in the right way as possible as you can, but since you asked, remember Messenger is not in your process, but running as a separate process. Actually you're more likely leaking resources in Messenger. And, COM_Release is not just a memory-cleaner. You should apply it only on COM objects.

DllCall(NumGet(NumGet(1*ppv)+0)
Aren't DLL functions static inside a .dll file?

What you mean by static? What's your programming background, btw? Anyway, static/dynamic is not an issue here, the issue is how to access the function, regrdless whether static or not.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

on my pc it works even without the If vt = 9 expression, so I have commented out the rest of the stuff

If some IAccessible object returns a child as an ID, the function will try to interpret "val" as an IDispatch interface pointer and trouble may ensue. I would recommend leaving "If vt = 9" where it was, even if it doesn't seem necessary in practice.

sosaited
  • Members
  • 278 posts
  • Last active: Oct 06 2009 06:08 PM
  • Joined: 24 Feb 2005

Exactly where did it fail? As a matter of fact, I assumed a few as Live Messenger is a MS product. Notice that I replaced acc_Name/acc_Value functions with COM_Invoke. If I could test, I might even have replaced

I didn't run the script when the history object was empty. I just replaced the two lines you mentioned, and now it works. But its doesn't work with first code. Thanks

What you mean by static? What's your programming background, btw? Anyway, static/dynamic is not an issue here, the issue is how to access the function, regrdless whether static or not.

Sorry for asking, but there was no need to mock me like that. I started using AHK way back and that is when I read everything about the commands, and at that time DLLCall didn't support calls to function pointers. And after that I wasn't active for almost tow years, and my mistake that I didn't realize that you were using a pointer which is now supported in AHK, and it wasn't something uber-clever and new in the WinAPI or MSAA.

I would recommend leaving "If vt = 9" where it was, even if it doesn't seem necessary in practice.

Updated. Thanks man.
My small "thanks" to AHK in shape of these dedicated 3d images
Posted Image

Sean
  • Members
  • 2462 posts
  • Last active: Feb 07 2012 04:00 AM
  • Joined: 12 Feb 2007

Sorry for asking, but there was no need to mock me like that.

Just out of curiosity, why did you think that I was mocking you? It was a simple question. FYI, I didn't have any other programming background myself.

Stefan V
  • Members
  • 78 posts
  • Last active: Oct 11 2010 03:51 AM
  • Joined: 16 Jun 2009
@Sosaited Awesome script thanks; this tool will be invaluable. Well done ;)

StringReplace, History, History, %EncRectangle%, `n, All ; change to the small rectangle made from encoding issues.
FileAppend, %History%, Msn.txt


@Sean

FYI, I didn't have any other programming background myself.

*Sniff*