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.
|