A Stroll Down Memory Lane: Scripting AOL

When I started working at my current job I was surprised to see that everyone used IRC as their primary means of communication - much more so than email or IM. I recently wrote a small irc bot library in python - it was a ton of fun and reminded me of some of the first programs I wrote that were bots and scripts for America Online:

Remember AOHell? I wrote probably 10 different kinds of ripoffs (mass-mailers, room busters, punters?) but my favorite AOL scripts were always chat bots. There was string parsing involved, real time interaction, and of course it was fun to try and predict how people would pwn your bot and then write clever checks to make sure this didn't happen.

Coincidentally, I was going through my backup drive and ran across a bunch of old source code I'd squirreled away -- stuff I'd written almost 15 years ago! So, here's a guided tour of AOL bot writing, from me 15 years ago.

Get a .bas file

In Visual Basic 3.0 parlance, the .bas file was a module full of constants, globals, subroutines and functions. In order to do any serious work it was necessary to talk to AOL through the Windows API, sending fake messages directly to AOL's user-interface. This bad-boy:

Declare Function SendMessage & Lib "User" ( ByVal hWnd% , ByVal wMsg% , ByVal wParam% , ByVal lParam As Any )

Windows API was divided up into a few big .dll files, User, GDI, Kernel and probably some more, and to call out to the API you needed to throw a declaration like the one above in your code. This was useful for, say, killing the hourglass, which one would do by invoking the modal "About Window" then killing it (don't ask me why this worked, it just did):

Sub TB_KillWait () 'Gets rid of the hourglass Dim hWnd% , about% , x hWnd% = FindWindow ( "AOL Frame25" , 0 & ) Call RunMenuByString ( hWnd% , "&About America Online" ) Do : DoEvents Loop Until FindWindow ( "_AOL_Modal" , 0 & ) <> 0 about% = FindWindow ( "_AOL_Modal" , 0 & ) x = SendMessageByNum ( about% , WM_DESTROY , 0 & , 0 & ) x = SendMessageByNum ( about% , WM_CLOSE , 0 & , 0 & ) End Sub

The .bas file I had was comprised of functions like TBC_Send, which would send a string into the currently active chat room:

Sub TBC_Send ( Text ) 'Sends text to the chatroom AOL% = FindWindow ( "AOL FRAME25" , 0 & ) List% = TB_FindChildByClass ( AOL% , "_AOL_Listbox" ) If List% = 0 Then Exit Sub End If Room% = GetParent ( List% ) TXT% = TB_FindChildByClass ( Room% , "_AOL_EDIT" ) SendBtn% = TB_FindChildByTitle ( Room% , "Send" ) x = SendMessageByString ( TXT% , WM_SETTEXT , 0 , Text ) x = SendMessageByNum ( TXT% , WM_CHAR , 13 , 0 ) timeout . 05 End Sub

As you can see, it identifies the chatroom naively as "the room with the listbox" (!), then it finds the edit box, sets the text in it and appends an "Enter" key.

VB is Slow

In addition to SendMessage , the FindWindow and GetWindow API calls were also very useful, as they would find controls, textboxes, windows, etc that could then be manipulated with calls to SendMessage. I ran into some problems with VB being pokey, so I ended up writing a small .dll file to find child windows by their titles or class-names (I've taken the liberty of cleaning up the variable names as they were originally all one-letter and making the indentation uniform):

#include <string.h> #include <windows.h> extern "C" HANDLE _export _far _pascal TB_FindChildByTitle ( HANDLE hWnd , char title []) { HANDLE chld , tmp_chld ; char tmp [ 200 ]; int len ; chld = GetWindow ( hWnd , GW_CHILD ); while ( chld ) { len = GetWindowText ( chld , tmp , 199 ); tmp [ len ] = '\0' ; if ( ! strcmpi ( tmp , title )) return chld ; tmp_chld = TB_FindChildByTitle ( chld , title ); if ( tmp_chld ) return tmp_chld ; chld = GetWindow ( chld , GW_HWNDNEXT ); } return NULL ; }

The fun stuff

There was this really handy file called VBMsg.vbx that basically allowed your program to listen in on events that were sent to other windows. This was useful when the chat textbox was updated - just listen for WM_SETTEXT and parse the incoming bytes. The following is taken from an "AFK Bot", basically an answering machine for when you're idling in chat or just wanted to seem cool:

Sub VBMsg1_WindowMessage ( hWindow As Integer , Msg As Integer , wParam As Integer , lParam As Long , RetVal As Long , CallDefProc As Integer ) ChatText$ = agGetSTringfromLPSTR ( lParam ) Colon% = InStr ( ChatText$ , ":" ) SN$ = Mid ( ChatText$ , 3 , Colon% - 3 ) TextSaid$ = Mid ( ChatText$ , Colon% + 2 ) If UCase ( Left ( TextSaid$ , 4 )) = "/MSG" Then Mesg$ = Mid ( TextSaid$ , 5 ) List1 . AddItem SN$ & ": " & Mesg$ TBC_Send ( CBracket ( SN$ & ", message saved!" )) End If End Sub

I still totally remember that incantation: $SN = Mid(ChatText, 3, Colon% - 3) -- I think the first two must've been CR/LF, then there was a tab before the colon.

Another fun one was the 8-Ball Bot which would just respond with a randomly chosen phrase:

Sub VBMsg1_WindowMessage ( hWindow As Integer , Msg As Integer , wParam As Integer , LPARAM As Long , RetVal As Long , CallDefProc As Integer ) ChatText$ = agGetStringfromLPSTR ( LPARAM ) Colon% = InStr ( ChatText$ , ":" ) SN$ = Mid ( ChatText$ , 3 , Colon% - 3 ) TextSaid$ = UCase ( Mid ( ChatText$ , Colon% + 2 )) If InStr ( TextSaid$ , "/8BALL" ) Then x = Int ( Rnd * 7 ) + 1 Select Case x Case 1 TBC_Send ( "<-·´¯`·.·°( " & SN$ & ", Outlook good" ) Case 2 TBC_Send ( "<-·´¯`·.·°( " & SN$ & ", Reply hazy, try again" ) Case 3 TBC_Send ( "<-·´¯`·.·°( " & SN$ & ", Sources point to 'Yes'" ) Case 4 TBC_Send ( "<-·´¯`·.·°( " & SN$ & ", Don't count on it" ) Case 5 TBC_Send ( "<-·´¯`·.·°( " & SN$ & ", Definetely 'no'" ) Case 6 TBC_Send ( "<-·´¯`·.·°( " & SN$ & ", Yes" ) Case 7 TBC_Send ( "<-·´¯`·.·°( " & SN$ & ", No" ) End Select End If End Sub

The Warez Server

The last snippet I'll paste is from a "warez server" -- to me this was like the holy grail! The way that it worked was you'd go into a chat room and somebody from a warez clan (these were a thing) would say, "I'm going to start serving". Everyone would get excited. Once the server started, usually announced with a flood of ASCII art, people could type in something like "/Send List" and the bot would email you a list of everything the guy had in his mailbox. If you saw something you liked, you could say "/Send 123" or whatever number it was on the list and the bot would forward you a copy which you could then download. Pretty ingenious! This source code comes from the last AOL script I wrote, some time around 1997 or so -- it processes several lists (aptly named List1, List2, and List3).

List1 -> people waiting for a list of all the warez

List2 -> Individual requests for specific emails

List3 -> Search requests

It's worth noting that it does no caching, no prioritizing, and there's even the use of a few "GoTo" statements with descriptive labels like "HD" and "H". One of the neat tricks I see is it repeatedly clicks "Send" and checks for either the compose window going away or an error window appearing. If an error window appears it scans it for problematic screen names and tries again. Also, all those GetWindow calls with integer arguments -- 2 = gw_next (the window's sibling).

Sub Timer1_Timer () AOL% = FindWindow ( "AOL Frame25" , 0 & ) NewMail% = TB_FindChildByTitle ( AOL% , "New Mail" ) Tree% = TB_FindChildByClass ( NewMail% , "_AOL_TREE" ) ReadBtn% = TB_FindChildByTitle ( NewMail% , "Read" ) KeepAs% = TB_FindChildByTitle ( NewMail% , "Keep As New" ) MailCount% = SendMessageByNum ( Tree% , LB_GETCOUNT , 0 , 0 ) If List1 . ListCount Then Label4 . Caption = Str ( Val ( Label4 . Caption ) + 1 ) MailLst$ = "<p align = center><font size=2 color=""#000080""><B>" & TBC_Bracket2 ( "micro server by timeBomb" ) & Chr ( 13 ) & TBC_Bracket2 ( "warez server list [" & MailCount% & "] items" ) & Chr ( 13 ) & "</p><p align=left></b><br>" For i = 0 To MailCount% - 1 MailStr$ = String $ ( 255 , " " ) Q% = SendMessageByString ( Tree% , LB_GETTEXT , i , MailStr$ ) NoDate$ = Mid$ ( MailStr$ , InStr ( MailStr$ , "/" ) + 4 ) NoSN$ = Mid$ ( NoDate$ , InStr ( NoDate$ , Chr ( 9 )) + 1 ) MailLst$ = MailLst$ & Chr ( 13 ) & "[" & i & "] ~" & TBT_TrimNull ( NoSN$ ) DoEvents Next i For i = 0 To List1 . ListCount - 1 m$ = m$ & List1 . List ( i ) & ", " Next i TBA_RunMenu 3 , 1 Do : DoEvents AOL% = FindWindow ( "AOL Frame25" , 0 & ) MailWin% = TB_FindChildByTitle ( AOL% , "Compose Mail" ) Loop Until MailWin% <> 0 MailTo% = TB_FindChildByClass ( MailWin% , "_AOL_Edit" ) D% = GetWindow ( MailTo% , 2 ) D% = GetWindow ( D% , 2 ) D% = GetWindow ( D% , 2 ) Subj% = GetWindow ( D% , 2 ) Rich% = TB_FindChildByClass ( MailWin% , "RICHCNTL" ) F = SendMessageByString ( MailTo% , WM_SETTEXT , 0 & , m$ ) F = SendMessageByString ( Subj% , WM_SETTEXT , 0 & , TBC_Bracket ( "micro server ~ list" )) D% = GetWindow ( Subj% , 2 ) D% = GetWindow ( D% , 2 ) Body% = GetWindow ( D% , 2 ) F = SendMessageByString ( Body% , WM_SETTEXT , 0 & , MailLst$ ) F = SendMessageByString ( Rich% , WM_SETTEXT , 0 & , MailLst$ ) Button% = TB_FindChildByClass ( MailWin% , "_AOL_Icon" ) GoTo Hd Hd : F = SendMessageByNum ( Button% , WM_LBUTTONDOWN , & HD , 0 & ) F = SendMessageByNum ( Button% , WM_LBUTTONUP , & HD , 0 & ) Timeout (. 1 ) Do : DoEvents TBW_Click Button% Timeout (. 15 ) MailWin% = TB_FindChildByTitle ( AOL% , "Compose Mail" ) ErrorWin% = TB_FindChildByTitle ( AOL% , "Error" ) Loop Until MailWin% = 0 Or ErrorWin% <> 0 If ErrorWin% <> 0 Then ViewStr$ = UCase ( TBW_GetWinText ( FindChildByClass ( ErrorWin% , "_AOL_View" ))) For i = 0 To List1 . ListCount - 1 If InStr ( ViewStr$ , UCase ( List1 . List ( i ))) Then List1 . RemoveItem i End If Next i m$ = "" For i = 0 To List1 . ListCount - 1 m$ = m$ & List1 . List ( i ) & ", " Next i F = SendMessageByString ( MailTo% , WM_SETTEXT , 0 & , m$ ) GoTo Hd End If List1 . Clear DoEvents List1 . Clear TBC_Send ( "" ) TBC_Send ( TBC_Bracket ( "pending lists sent out!" )) End If If List2 . ListCount Then Label4 . Caption = Str ( Val ( Label4 . Caption ) + 1 ) m$ = List2 . List ( 0 ) List2 . RemoveItem 0 Req = Left ( m$ , InStr ( m$ , ":" ) - 1 ) SN$ = Mid ( m$ , InStr ( m$ , ":" ) + 1 ) If Not IsNumeric ( Req ) Then Exit Sub If Req > MailCount% Then Exit Sub x = SendMessageByNum ( Tree% , LB_SETCURSEL , Req , 0 & ) Do : DoEvents TBW_Click ReadBtn% Timeout (. 2 ) Stat1% = TBM_MMForwardWin ( False ) Loop Until Stat1% <> 0 Forward% = Stat1% FwdBtn% = TBM_MMForwardWin ( True ) Do : DoEvents TBW_Click FwdBtn% Timeout (. 5 ) SendWin% = TBM_MMFindFwd () Loop Until SendWin% <> 0 ToEd% = TB_FindChildByClass ( SendWin% , "_AOL_EDIT" ) SubjEd% = TBW_GetChild ( SendWin% , "_AOL_EDIT" , 3 ) If TBA_AOLVer () = 25 Then MainEd% = TBW_GetChild ( SendWin% , "_AOL_EDIT" , 4 ) Else MainEd% = TB_FindChildByClass ( SendWin% , "RICHCNTL" ) End If SendBttn% = TB_FindChildByClass ( SendWin% , "_AOL_ICON" ) DoEvents x = SendMessageByString ( ToEd% , WM_SETTEXT , 0 & , SN$ ) DoEvents x = SendMessageByString ( MainEd% , WM_SETTEXT , 0 & , "<p align=center><font color=""#000080""><B>" & TBC_Bracket2 ( "micro server by timeBomb" ) & "<BR>" & TBC_Bracket2 ( "warez server item index [" & Req & "]" )) DoEvents SubjText$ = TBW_GetWinText ( SubjEd% ) SubjText$ = Mid$ ( SubjText$ , 5 ) x = SendMessageByString ( SubjEd% , WM_SETTEXT , 0 & , SubjText$ ) Do : DoEvents TBW_Click SendBttn% Timeout ( 1 ) SendWin% = TBM_MMFindFwd () ErrorWin% = TB_FindChildByTitle ( AOL% , "Error" ) Loop Until SendWin% = 0 Or ErrorWin% <> 0 If ErrorWin% <> 0 Then TBW_KillWin ErrorWin% TBW_KillWin Forward% TBW_KillWin SendWin% TBC_Send ( "" ) TBC_Send ( TBC_Bracket ( SN$ & ", unable to send mail" )) GoTo H End If TBW_KillWin Forward% GoTo H H : DoEvents TBW_Click KeepAs% Timeout (. 01 ) TBW_Click KeepAs% TBC_Send ( "" ) TBC_Send ( TBC_Bracket ( SN$ & ", item #" & Req & " was sent!" )) End If If List3 . ListCount Then Label4 . Caption = Str ( Val ( Label4 . Caption ) + 1 ) Colon% = InStr ( List3 . List ( 0 ), ":" ) reqz$ = Left ( List3 . List ( 0 ), Colon% - 1 ) SN$ = Mid ( List3 . List ( 0 ), Colon% + 1 ) reqz$ = UCase ( reqz$ ) List3 . RemoveItem 0 For i = 0 To MailCount% - 1 MailStr$ = String $ ( 255 , " " ) Q% = SendMessageByString ( Tree% , LB_GETTEXT , i , MailStr$ ) NoDate$ = Mid$ ( MailStr$ , InStr ( MailStr$ , "/" ) + 4 ) NoSN$ = Mid$ ( NoDate$ , InStr ( NoDate$ , Chr ( 9 )) + 1 ) NoSN$ = TBT_TrimNull ( NoSN$ ) If InStr ( UCase ( NoSN$ ), UCase ( reqz$ )) Then iCnt% = iCnt% + 1 sRes$ = sRes$ & "Item [" & i & "] ~ " & NoSN$ & Chr ( 13 ) End If DoEvents Next i TBA_RunMenu 3 , 1 Do : DoEvents AOL% = FindWindow ( "AOL Frame25" , 0 & ) MailWin% = TB_FindChildByTitle ( AOL% , "Compose Mail" ) Loop Until MailWin% <> 0 MailTo% = TB_FindChildByClass ( MailWin% , "_AOL_Edit" ) D% = GetWindow ( MailTo% , 2 ) D% = GetWindow ( D% , 2 ) D% = GetWindow ( D% , 2 ) Subj% = GetWindow ( D% , 2 ) Rich% = TB_FindChildByClass ( MailWin% , "RICHCNTL" ) F = SendMessageByString ( MailTo% , WM_SETTEXT , 0 & , SN$ ) F = SendMessageByString ( Subj% , WM_SETTEXT , 0 & , "micro tools server found: [" & iCnt% & "]" ) D% = GetWindow ( Subj% , 2 ) D% = GetWindow ( D% , 2 ) Body% = GetWindow ( D% , 2 ) F = SendMessageByString ( Body% , WM_SETTEXT , 0 & , "<p align = center><font size=2 color=""#000080""><B>" & TBC_Bracket2 ( "micro server by timeBomb" ) & Chr ( 13 ) & TBC_Bracket2 ( "warez server find results [" & iCnt% & "] items" ) & Chr ( 13 ) & ( "<p align=left>" ) & sRes$ & " " ) F = SendMessageByString ( Rich% , WM_SETTEXT , 0 & , "<p align = center><font size=2 color=""#000080""><B>" & TBC_Bracket2 ( "micro server by timeBomb" ) & Chr ( 13 ) & TBC_Bracket2 ( "warez server find results [" & iCnt% & "] items" ) & Chr ( 13 ) & ( "<p align=left>" ) & sRes$ & " " ) Button% = TB_FindChildByClass ( MailWin% , "_AOL_Icon" ) F = SendMessageByNum ( Button% , WM_LBUTTONDOWN , & HD , 0 & ) F = SendMessageByNum ( Button% , WM_LBUTTONUP , & HD , 0 & ) Timeout (. 1 ) Do : DoEvents TBW_Click Button% Timeout (. 15 ) MailWin% = TB_FindChildByTitle ( AOL% , "Compose Mail" ) ErrorWin% = TB_FindChildByTitle ( AOL% , "Error" ) Loop Until MailWin% = 0 Or ErrorWin% <> 0 If ErrorWin% <> 0 Then TBW_KillWin ErrorWin% TBW_KillWin MailWin% TBC_Send ( "" ) TBC_Send ( TBC_Bracket ( SN$ & ", unable to send mail" )) Exit Sub End If DoEvents TBC_Send ( "" ) TBC_Send ( TBC_Bracket ( SN$ & ", find results sent!" )) End If End Sub

Conclusion

I hope you found this entertaining! I wish I had some screen-shots - I'll see about getting a Windows 95 VM going tomorrow and take some. Edit screen shots added!

If you remember writing proggies, using them, any anecdotes, feel free to post them in the comments! What epic things did you do to get your account TOSed?

AppActivate "America Online" ' remember how it had two spaces?!?! SendKeys "%{F4}"

Links

The Truth - an open letter by "Happy Hardcore", the guy that wrote AOL4Free

History of warez - hilarious

Commenting has been closed, but please feel free to contact me