Introduction   Download   Documentation   Examples   Thanks

Py-TOC

This is abandonware, but I understand some people are still interested in using/improving it. Please see http://bitbucket.org/jamwt/py-toc/ . Feel free to fork it.

Py-TOC is a Python module that allows you to design and program automated entities--often referred to as "bots"--that participate on the AOL Instant Messenger network. It allows you to utilize the incredible power of the Python language and library when writing scripts that interact with users of AOL Instant Messenger and its free equivalents.

Attention was taken to ensure that the details of the TOC layer (the protocol unofficial AOL clients typically use) is sufficiently abstracted from the implementer. This was accomplished by using OOP and Python classes. This also lowers the bar when it comes time to write your systems; through inheritance, you need only to add code that provides the particular handling and processing your bot requires. A further explanation of this is provided in the documentation, and working examples are available as well.

The module was written by Jamie Turner. Please direct all bugs, documentation errors, questions, suggestions, etc, to him. If you'd like to contribute an example script, he'd be happy to have it. Furthermore, if you implement Py-TOC in some useful, clever way, he would be thrilled to hear the details of your success.

The license is BSD-style.

Download

Latest version of toc.py is version 2.4

Changes from 2.4: Updated for TOC2 by Andrew Herron and Moss Collum.

BotManager, a class for TOC reconnects, bot multi-threading, and single script multi-bot implementations, was added in 2.0. See the updated walkthrough, the new examples, and the reference to start learning to use BotManager.

2.X is fully backwards-compatible with 1.X. All your existing bots should work.

Python 2.X is required by Py-TOC. Please see the documentation below for installation instructions.

Freshmeat is probably the best place to get a sense of a project "changelog."

Documentation

(Installing)   (Walkthrough)   (FAQ)   (Reference)

Installing

To install Py-TOC, simply copy toc.py into some location in your PYTHONPATH. The proper place is usually

/usr/local/lib/python(version)/site-packages/

Replace (version) with your version number of Python, such as 2.2. To test your installation, change directory to your $HOME and:

$ python
> import toc
>

If toc is imported without error, your installation was successful.

Walkthrough

In order to make a bot, first you must import the TocTalk class from the toc module. This will be the basis of your bot class.

from toc import TocTalk

Creating a subclass that inherits the attributes of TocTalk is our next step:

from toc import TocTalk

class MyBot(TocTalk):
	# put something useful here later

To create functionality for your bot, you need to implement event handler functions and use action functions.

Handler functions are how you receive events from other Instant Messenger users, such as an IM, chat message, or user profile. Adding to our example from above, we can show a common handle to add:

from toc import TocTalk

class MyBot(TocTalk):
	#whenever someone IMs us
	def on_IM_IN(self,data):
		print "We've been messaged."

Believe it or not, this is a complete, albeit simple, bot. Every time someone IMed the bot above, it would print "We've been messaged." to STDOUT.

In order to do something more useful than this, we need to be able to both receive events and trigger our own. This is where action functions come in. MyBot, by inheriting TocTalk's methods and attributes, has a number of action functions that allow us to communicate with other users.

Here is something slightly more complex:

-- start testbot.py --

from toc import TocTalk

class MyBot(TocTalk):
	#whenever someone IMs us
	def on_IM_IN(self,data):
		# data contains "screenname:flag:message", such as
		# "jamwt:F:hey, ben.. how's work?"
		data_components = data.split(":",2) #maxsplit for handling 
		                                    #in-message colons                                    

		screenname = data_components[0]  # the sender's screenname
		message = data_components[2]     # in case the sender 
		                                        # used a colon in their 
		                                        # message

		# TocTalk also includes a special helper function called
		# strip_html().  Many AIM clients like Windows AIM use HTML
		# code.  strip_html() will remove HTML tags and make it text
		message = self.strip_html(message)

		# now we use our first action function...
		# let's just repeat back to the person what
		# they said to us
		self.do_SEND_IM(screenname, "Echo!  You said: " + message )
		
# if this file is run directly
if __name__ == "__main__":
	
	# create the bot, specify some AIM account to use
	bot = MyBot("IMscreenname","password")
	bot.go()

-- end testbot.py --

This is still a relatively simple example, but it demonstrates all of the key principles behind making bots with Py-TOC. In just a few lines, you can have a working bot.

The next step is to employ use of the BotManager class. We can use the BotManager class to create and manage multiple bots, and to have multiple threads of execution.

In this basic example, we'll use BotManager to have two threads of execution.

-- start TickBot.py --
########################################################
#
#        File: TickBot.py
#        Author: Jamie Turner <jamwt@jamwt.com>
#        Date: 4/24/02
#
#        Description:
#
#        Demonstrates one bot, two threads of execution 
#        using the BotManager class
#
#        Anyone who IMs it will get a friendly "tick" IM
#        every 60 seconds.
#

sn1 = "IMscreenname"
pass1 = "password"

from toc import TocTalk,BotManager

import time


wantticks = []

class TickBot(TocTalk):

	def on_IM_IN(self,data):
		global wantticks
		# get the screenname to IM back--
		# we don't care about the message
		# in this case!
		screenname = data.split(":")[0]

		if not screenname in wantticks:
			wantticks.append(screenname)

		# send them the appropriate message
		self.do_SEND_IM(screenname, 
'''Ok, you're gonna get "ticked" every 60 seconds.''')

if __name__ == "__main__":
	
	bot = TickBot(sn1, pass1)
	bot._info = "IM me:  I'll tick you off (the minutes)."

	bm = BotManager()
	bm.addBot(bot,"myBot") # start it up

	while 1:
		time.sleep(60)
		for i in wantticks:
			bot.do_SEND_IM(i,
'''<I>**TICK!**</I>  It's been 60 seconds.  Did you miss me?''')

-- end TickBot.py --

Want more? Check out the examples

FAQ

1. Does it run on MS Windows?

Yes. I've tested it on XP. I've had reports that it works on 2000, NT 4.0, and ME. I'm not sure about 95/98, but I would imagine it would work there as well.

2. How do I execute something right after the bot logs in? Once I execute (bot).go(), it never returns...

go() calls a special function called start(). TocTalk's start() method is blank. Just implement a 'def start(self):' method in your bot class, and it will be executed immediately after the bot logs in.

3. TocTalk sets my user information (as seen in the bot's "profile") upon connection. How do I have it use my own message instead?

Before you call (bot).go(), set (bot)._info = "your information here".

4. I need to know more details about the I/O; or, I don't want the TocTalk class to output anything at all!!

(bot)._debug can be set to [0..2]. The default is one. Before you call (bot).go(), set (bot)._debug = <some int>. Higher numbers yield more verbose output.

5. I want output from the TocTalk class, but not to STDOUT. How can I redirect that output for logging?

Before you call (bot).go(), set (bot)._logfd to any file descriptor with a .write() method.

Here's an example using all options mentioned in questions 3-5:

mybot = SomeBot("username","password")

# Set the "profile" information
mybot._info  = "Hi, I'm SomeBot.  Message me to learn more."

# be verbose in bot I/O
mybot._debug = 2

# log to log.txt
# 3rd argument here makes the output unbuffered, so lines are written
# to file immediately as events happen, and '$ tail -f <file>
# gives some useful output
mybot._logfd  = open("log.txt","a",0)

# kick it off...
mybot.go()

6. I don't care about events, or responding. How do I just get the capability to IM people?

Do this:

from toc import TocTalk, BotManager
import time

bm = BotManager()
bot = TocTalk("screenname","password")

#bot._debug = 0     # uncomment if you want NO output
bm.addBot(bot,"bot")
time.sleep(4)  # time to login

# now you're ready to go.  Do this as many times as you want,
# to whoever you want. 
bot.do_SEND_IM("dest_screenname","Message goes here")

7. When I use a screenname with a lot of buddies, I don't receive UPDATE_BUDDY events for any of them. Why?

UPDATE_BUDDY informs you of actions taken by entities on your buddy list. However, in TOC you technically don't *have* a buddy list until you call toc_add_buddy for each buddy you wish to receive notifications for.

This means that the CONFIG data sent to you by the server is just your "saved settings," to refresh your memory when it's time to add_buddy.

TOC allows more flexibility this way; you're in a neutral state to start. Perhaps you only want notifications from a few specific people this session, not everyone in your config.

The key is to add_buddy for each member in your saved config. Then you'll receive update_buddy notifications for each.

Here's some sample code that demonstrates this:

from toc import TocTalk
import time

cfgset = 0
class MyBot(TocTalk):
	def on_UPDATE_BUDDY(self,data):
		print data # this will work for each buddy in your config

	def on_CONFIG(self,data):
		global cfgset

		# first time logging in--add buddies from config...
		if not cfgset:
			cfgset = 1

			buds = []

			# remember the format of config data here:
			# "m 1\ng Buddies\nb bouncebot\nb perlaim\n"
			for item in data.split("\n"):
				if item == '':
					continue
				if item[0] == "b":
					buds.append(item[1:].strip())

				#add no more than ~20 at a time, msg len restrictions
				if len(buds) == 20:
					self.do_ADD_BUDDY(buds)
					time.sleep(0.2) # don't SLAM the server...
					buds = []

			if len(buds):
				self.do_ADD_BUDDY(buds)
					

if __name__ == "__main__":
	bot = MyBot("screenname", "password")
	bot.go()

Reference

BotManager

BotManager()
Create a new instance of the BotManager class.

BotManager methods

  • addBot(bot,botref,go=1,reconnect=1,delay=30)
    Add a bot to the bot manager.

    bot: (TocTalk or derived instance) The bot object you would like to add.

    botref: (string) A label for the bot, used to refer to it in further calls.

    go: (int) Start the bot now, or wait? Default is to start.

    reconnect: (int) Non-zero value indicates that the BotManager should try to reconnect the bot to the TOC server if it is disconnected. Default is non-zero.

    delay: (int) How long should the BotManager wait before trying to reconnect, in seconds. We shouldn't slam AOL *too* hard. Default is 30.

  • botGo(botref)
    Start up an added, but not started, bot.

    botref: (string) The label you gave to the bot when you added it.

  • botStop(botref)
    Make the bot sign off.

    botref: (string) The label you gave to the bot when you added it.

  • botPause(botref,val=1)
    Make the bot pause/unpause. A paused bot stays online, but does not respond to events until unpaused.

    botref: (string) The label you gave to the bot when you added it.

    botref: (val) Non-zero pauses the bot, zero unpauses. Default is non-zero.

  • getBot(self,botref)
    Returns the bot object you passed to addBot.

    botref: (string) The label you gave to the bot when you added it.

  • wait()
    Give up the main thread of execution. Usually called after multiple bots are started and the programmer only desires event-based processing.

TocTalk

TocTalk(nick,passwd)
Create a new instance of the TocTalk class.

nick: (string) The nickname of the AOL Instant Messenger account you would like to use.

passwd: (string) Password corresponding to this account

Event Handler Functions

All of the following has been adapted from the AOL TOC 1.0 specification, as found in the PROTOCOL document distributed with TiK.

  • on_IM_IN(self,data)
    Receive an IM from some one.

    Format of data : Colon delimited set of three values. The first is the username of the sender. The second , either a T or an F, indicates whether the message was sent as an auto-response. The third and final field is the message body itself.

    A example would be "jamwt:F:foo and bar are so alike."

    Take special note that

    message=data.split(":")[2]
    is not valid, because the message may contain colons. You need to use the optional maxsplit argument.
    message=data.split(":",2)[2]
    would be correct.

  • on_CONFIG(self,data)
    Incoming user configuration. Config can be empty in which case the host was not able to retrieve it, or a config didn't exist for the user.

    Format of data : The config information is a string containing a series of lines with the first character in each being the item type, followed by a space, with the rest of the line being the item value. Only letters, numbers, and spaces should be used.

    Item types are:

    • g - Buddy Group (All Buddies until the next g or the end of config are in this group.)
    • b - A Buddy
    • p - Person on permit list
    • d - Person on deny list
    • m - Permit/Deny Mode. Possible values are:
      • 1 - Permit All
      • 2 - Deny All
      • 3 - Permit Some
      • 4 - Deny Some

    A example would be "m 1\ng Buddies\nb bouncebot\nb perlaim\n"

  • on_UPDATE_BUDDY(self,data)
    This command handles arrivals, departures, and updates of members on your bots buddy list.

    Format of data : Colon delimited; first is screenname, then T/F for online status, warning percentage, time of sign-on in Unix epoc format, idle time in minutes, and finally a "User Class" string of 2 or three characters:

    • First Character:
      • ' ' - Ignore
      • 'A' - On AOL
    • Second Character:
      • ' ' - Ignore
      • 'A' - Oscar Admin
      • 'U' - Oscar Unconfirmed
      • 'O' - Oscar Normal
    • Third Character:
      • '\0' - Ignore
      • ' ' - Ignore
      • 'U' - The user has set their unavailable flag.

  • on_ERROR(self,data)
    Triggered upon an error condition.

    Format of data : Colon delimited. First field is an error code. Second is an optional argument that pertains to the error.

    Error codes are as follows:

    • General Errors:
      • 901 - argument not currently available
      • 902 - Warning of argument not currently available
      • 903 - A message has been dropped, you are exceeding the server speed limit
    • Chat Errors
      • 950 - Chat in argument is unavailable.
    • IM & Info Errors
      • 960 - You are sending message too fast to argument
      • 961 - You missed an im from argument because it was too big.
      • 962 - You missed an im from argument because it was sent too fast.
    • Dir Errors
      • 970 - Failure
      • 971 - Too many matches
      • 972 - Need more qualifiers
      • 973 - Dir service temporarily unavailable
      • 974 - Email lookup restricted
      • 975 - Keyword Ignored
      • 976 - No Keywords
      • 977 - Language not supported
      • 978 - Country not supported
      • 979 - Failure unknown argument
    • Auth errors
      • 980 - Incorrect nickname or password.
      • 981 - The service is temporarily unavailable.
      • 982 - Your warning level is currently too high to sign on.
      • 983 - You have been connecting and disconnecting too frequently. Wait 10 minutes and try again. If you continue to try, you will need to wait even longer.
      • 989 - An unknown signon error has occurred argument

    Please Note: The TocTalk class handles Authentication errors by default. If you implement an event handler function for ERROR, you will potentially catch all codes except 980-989.

  • on_EVILED(self,data)
    Triggered when you have been warned by someone.

    Format of data : Colon delimited, 2 fields. First field is new warn percentage, second field is the name of the person who warned you, or blank if anonymous warn. Example: "45:jamwt"

  • on_CHAT_JOIN(self,data)
    We were able to join a chat room previously requested.

    Format of data : Colon delimited, 2 fields. Chat room ID, then chat room name.

  • on_CHAT_IN(self,data)
    A chat message was triggered in a chat room.

    Format of data : Four fields. Chat room ID, messaging nickname, Whisper value (T/F), and message. Example: "98722582:jamwt:F:Hey all!"

    Whisper value indicates whether the message was spoken only to you, or was broadcast to all members in the chat room.

    The same catch from SEND_IM messages applies here: Messages may contain a colon. Parse accordingly.

  • on_CHAT_UPDATE_BUDDY(self,data)
    Arrivals and departures from a chat room. Upon first entering a chat room, this event will be triggered with all users currently in the room.

    Format of data : Colon delimited, 3 or more fields. The first field is the chat room ID. Second field is (T/F), true indicating the user(s) are now in the room, and vice versa. data will contain one or more fields after these, each being a username to which the previous two parameters apply. Example: "7262909:F:jamwt:SimpBot"

  • on_CHAT_INVITE(self,data)
    Bot is being invited to a chat room.

    Format of data : Four fields: Chat room name, chat room id, screenname of inviting user, message accompanying invitation. Example: "Py-TOC Talk:1123223:jamwt:Best chat in town."

    The message may contain colons.

  • on_CHAT_LEFT(self,data)
    Chat room has been closed.

    Format of data : Chat room ID.

  • on_GOTO_URL(self,data)
    Event requesting the client to make an HTTP request to some URL. This is usually in response to a request for user information.

    Format of data : Colon delimited, two fields. First field is Window Name--a suggested internal name for the window to use. Second field is the URL. Note: This almost certainly *will* contain colons.

Action Functions

  • self.do_SEND_IM(user,message,autoaway=0)
    Sends an IM to some user.

    user: (string) The screenname of the user you would like to message.

    message: (string) The message you would like to send.

    autoaway: (int) Non-zero value indicates that the message is an autoresponse. Default is 0.

  • self.do_ADD_BUDDY(buddies)
    Add a buddy to your buddy list. Note: This is only effective for current session. You need to do_SET_CONFIG() if you want to make permanent alterations.

    buddies: (list) Contains all the buddies you would like to add.

  • self.do_ADD_PERMIT(buddies)
    Un-block user(s) so that he or she can see your online status. Note: This is only effective for current session. You need to do_SET_CONFIG() if you want to make permanent alterations.

    buddies: (list) Contains all the buddies you would like to permit to be notified of your presence.

  • self.do_ADD_DENY(buddies)
    Block user(s) so that he or she cannot see your online status. Note: This is only effective for current session. You need to do_SET_CONFIG() if you want to make permanent alterations.

    buddies: (list) Contains all the buddies you would like to block.

  • self.do_REMOVE_BUDDY(buddies)
    Remove buddies from your buddy list. Note: This is only effective for current session. You need to do_SET_CONFIG() if you want to make permanent alterations.

    buddies: (list) Contains all the buddies you would like to remove.

  • self.do_SET_IDLE(itime)
    Set your idle time. It is the client's responsibility to do this. The TOC spec asks that you only notify once of idle time, and the TOC server will count up from there.

    itime: (int) Idle time in seconds.

  • self.do_SET_AWAY(awaymess)
    Mark yourself away.

    awaymess: (string) Away message. Use "" to remove away status.

  • self.do_SET_CONFIG(configstr)
    Save some configuration of buddies, permit/deny lists, to the TOC system.

    configstr: (string) A string with the format as specified by the on_CONFIG event documentation previously listed on this page.

  • self.do_GET_INFO(user)
    Request information for another user. Expect a GOTO_URL event in return. An HTTP request to the URL should return HTML formatted information about the user.

    user: (string) The screenname of the user whose information you would like to retrieve.

  • self.do_SET_INFO(info)
    Set your user information.

    info: (string) Plain-text or HTML string for Get Member Info... requests.

  • self.do_EVIL(user,anon=0)
    Warn a buddy.

    user: (string) Screenname of the buddy you would like to warn.

    anon: (int) Optional argument: pass 1 to warn anonymously. Anonymous warning does not affect the user as greatly as id'd warning would.

  • Chat functions implemented as well. Use the source, Luke.

Examples

CLAIM

CLAIM is a command-line instant messenging client. It handles buddy lists, a hotlist, and warnings. It is not intended to be a comprehensive client: it won't set your idle time, it doesn't handle away messages, you can't alter your buddy list, etc. But it works very well to chat with your buddies using an account you've already set up.

Check out the docstrings at the top of the python file for instructions.

 
 

In addition to CLAIM and the basic examples found in the walk-through, a few simple bots are provided here. The easiest way to see how each works is just to run them! All you need is some AIM account(s), Python, and Py-TOC.

If you want to see a more sophisticated implementation of Py-TOC, add 'SimpBot' to your buddy list. Talk to it about The Simpsons.

NewSNBot
Contributed by Dylan Thomas

This is a screenname change bot. When you change your screenname, let this bot help get the word out.

Run this bot with your old screenname and password, and whenever someone IMs your old name, it will tell them about about your new one.

ProxyBot

A bot that you can use as a proxy to send/recieve messages. See the comments at the top of the file for instructions.

AwayBot

Weird little bot that sets its away message according to viewers incoming IMs.

Self-Defense Bot

Self-Defense bot. When you message it, it just bounces back a reminder not to warn it. When someone warns it, it quickly tries to warn them three times-- taking a 2 second break between each as not to violate speed rules. It then blocks them so that they cannot converse with it/warn it additionally.

GoogleBot
Contributed by Matthew King

GoogleBot will perform Google searches using PyGoogle. You need an official Google license key to run this one.

Relay Bots

Demonstrates using BotManager for script-side data sharing between two bots. Say things to one bot and another will echo them back to you.

Battle Bots

Two bots are launched using BotManager, and they duke it out in a demented automated warning-war.

Don't use your primary screenname for either of these bots. The warning percentages will get very high.

Thanks

Net::AIM by Aryeh Goldsmith was an excellent reference to better understand the protocol. Some sections of toc.py are adapted from his code.

Thanks to AOL for drafting the TOC protocol document and making it GPL so that unofficial clients could participate.

To Kenytt Avery for suggesting modifying the _logfd example to use the third argument of open() to set the fd buffer size to 0.

Keith Dart for making toc.py raise exceptions instead of sys.exit()ing.

To Carlos Eberhardt for suggesting the maxsplits method of parsing messages--an improvement on my hack!

Jim Gruin, for the autoresponse patch, and input on Jython compatibility.

Thanks to Bob Pruett for digging in and finding a few bugs in early versions.

Thanks to Matthew King for the bug report about misformatted buddy lists on the CHAT functions.

Dylan Thomas, for helping out with debugging and coding a few test bots. Check out 'PostgresQuickRef' sometime to see one of his.

 
 
© 2002 - jamwt.com