Initial revision

This commit is contained in:
Jeremy Fincher 2003-03-12 06:26:59 +00:00
commit 7801c84d84
84 changed files with 17645 additions and 0 deletions

3
.cvsignore Normal file
View File

@ -0,0 +1,3 @@
*.pyc
*.pyo
*~

6
ACKS Normal file
View File

@ -0,0 +1,6 @@
GnuVince, who's helped tremendously with testing.
johhnyace, who gave me the modem that helped me tremendously in development.
inkedmn, who wrote the:
gkstats,
weather,
and zipcode commands for the Http module.

2
BUGS Normal file
View File

@ -0,0 +1,2 @@
I'm sure there are tons of them. When you find them, send them to me and I'll
fix them ASAP. I'd love to have a bugless bot someday...

209
CHANGELOG Normal file
View File

@ -0,0 +1,209 @@
* Implemented the FreeBSD module for searching ports.
* Implemented the Unix module for *nix-specific stuff.
* Implemented a few more commands for the Http module.
* Changed names of modules to follow a standard convention.
* Implemented @morehelp system.
* Gave modules a .public attribute to determine if they should
be in @list.
* Made threaded callbacks nestable :)
* Removed 'commands' attribute/complication from irclib.Irc.
2002-Sep-03 Jeremy Fincher <jemfinch@users.sourceforge.net>
* Version 0.60.0
* Fixed TONS and TONS and TONS of bugs.
* Included sample.conf, to show what a config file should look like.
* Data for supybot/data has been separated out into its own package to
decrease upload/download times.
* A new Anagrams module has been added, as has a new Notes module.
* All classes are new-style classes now.
* I wrote a new reload function that both works more efficiently (no
need to keep a cache of old objects anymore), more consistently (I can
use new-style classes now), and in less code (it's much simpler the way
it works). This is a big gain.
* I've written the beginnings of a ChannelStats class that handles the
"seen" command, and later will collect information about different
messages (how many JOINs, TOPICs, QUITs, etc.) and different stats like
how many characters, words, messages sent to a channel in toto.
* COMMAND NESTING R0X0RS!!!
* Improved the telnet REPL slightly, a better login, printing, etc.
* Improved the command-line version of the bot.
* Added several new commands: xor, cfactive, version, crypt,
timecommand, last, nslookup,
2002-Aug-27 Jeremy Fincher <jemfinch@users.sourceforge.net>
* Version 0.5 -- We're at least halfway to 1.0 :)
* Again, fixed TONS AND TONS AND TONS of bugs.
* callbacks.Privmsg.reply is no longer implemented via an exception;
subclasses can now self.reply more than once.
* Minor API change in callbacks.Privmsg.getArgs; getArgsLastOptional
has been removed in favor of the more-flexible optional keyword arg to
getArgs.
* Added several commands: exec, (un)setdefaultcapability, (un)settrace,
calc (in FunCommands)
* Added cdb library for constant databases that'll be independent of
platform.
* Added an asyncore-based interpreter via a socket that lets you telnet
into the bot and execute code in the bot process. Very useful for when
I break things and need to reload but can't talk to the bot anymore :)
* plugins.Forums is *really* useful :)
* plugins.Http is getting useful :)
* TONS of bugs fixed!
* callbacks.Privmsg now has a "threaded" attribute that you can set if
you want your subclass to spawn a new thread for every command.
2002-Aug-21 Jeremy Fincher <jemfinch@users.sourceforge.net>
* Version 0.37.0 -- enough bugs to warrant a new minor release :)
* Fixed ***TONS*** of bugs.
* Added LICENSE to the top of all .py files written by me.
* Added plugins/parter.py, which automatically parts a channel on JOIN.
Useful for networks where, to determine who's a bot, they send everyone
to a channel such as #botsmustgo and akill those who don't leave.
* Added plugins/http.py, which hasn't been tested because I don't have
threads on this Python install.
* Made FunCommands.port work in reverse, too. That's why the size of
this tarball is significantly increased.
* Added plugins/moobot.py, a duplication of some of the functionality
of moobot, another bot written in Python.
* Made ircutils.nick class, to support irc-case-insensitive comparison
of nicks (and hashing) automatically.
* Added unit testing framework for classes subclassing
callbacks.Privmsg.
2002-Aug-07 Jeremy Fincher <jemfinch@user.sourceforge.net>
* Version 0.36.3 (this header forgotten in the real release)
* Ok, fine. I'm using List Comprehensions now. Happy?
* Changed conf.makeClass to use imp instead of exec.
* Changed traceback logging over to a modified text form of cgitb.
* Added "find" to FactoidsCommands.
* Added "capabilities" and "auth" and "unauth" to UserCommands.
* Lots of little bugfixes.
2002-Jul-31 Jeremy Fincher <jemfinch@users.sourceforge.net>
* Version 0.36.2
* A whole lotta bug fixes, in general.
* Fixed bug in FunCommands.timefun.
* Added 'reconfig' command to OwnerCommands, to configuration changes
can be enacted without restarting the bot.
* Added 'protected' to the default turned-off capabilities in an
IrcChannel.
* Fixed callbacks.ChannelEnforcer so it wouldn't enforce MODES against
the sender of the MODE itself).
* Fixed username to make it lookup the hostmask of a nick given to it.
* Fixed bug in sections command, and moved it to privmsgs.UserCommands
since it's referenced by OwnerCommands.unload.
* Fixed what I hope is the LAST bug in the help command.
* Added users/channels default arguments to ircdb.checkIgnored and
ircdb.checkCapability so they don't *have* to rely on global state.
* Added CTCP SOURCE command.
2002-Jul-31 Jeremy Fincher <jemfinch@users.sourceforge.net>
* Version 0.36.
* Added makeFactoids.py and dumpFactoids.py to the tools/ subdirectory
to facilitate the transfer of the bot from one Python installation to
another.
* Added Logger, a per-channel logger that logs rather similar to mIRC's
channel format.
* Fixed bug in RawLogger -- it was filtering away all messages from
ever being sent to the network.
* Added a factoids plugin.
* Added __eq__ and __hash__ methods to ircmsgs.IrcMsg so it can be
compared for equality and used in hash tables. As a side note, I also
changed .args to be a tuple rather than a list.
* Added 'sections' command to FunCommands, to see what Privmsg-derived
classes are hanging about in the bot.
* Added 'unload' command to OwnerCommands, to remove a callback in a
running Irc instance.
* Added 'load' command to OwnerCommands, allowing owners to dynamically
load a module that wasn't previously loaded and insert it into a
running Irc instance.
* Made UserCommands.addhostmask require that the hostmask not already
be an identifier for any other user.
* Fixed bug in help command (again :))
2002-Jul-30 Jeremy Fincher <tweedgeezer@hotmail.com>
* Version 0.35.
* Started moving some stuff out to the plugins/ subdirectory. Haven't
yet developed an adequate interface for plugin modules, though -- I'm
not entirely sure that it's needed.
* Began writing tests. At least I know the format of the test *data*,
I just haven't quite developed the framework in which to execute it :)
* Wrote ctcp.Ctcp, an example of PrivmsgRegexp use and a module to
respond to CTCP commands.
* Changed callbacks.PrivmsgRegexp to be much easier to use.
* Fixed bug in ircdb -- channels needed to have a !op capability by
default, otherwise everyone can do anything requiring op capabilities.
Found several of these type of bugs; had to revert to the old config
style (using a dictionary for capabilities instead of a list) in order
to resolve them.
* Generalized exception propogation mechanism so any exception in
conf.deadlyExceptions would be propogated, not just asyncore.ExitNow.
* Added command-line parsing and the ability to specify a configuration
file as an option.
* Added ircmsgs.IrcMsg.__len__, so calculating the (probable) length of
an IRC message doesn't require making a string of it first.
* Added code to make the Irc object ping the server every
conf.pingInterval seconds. I've always found that having the bot ping
the server leads to a more robust bot (easier detection of bad network
or a dropped connection).
* Made AsyncoreDriver capable of reconnecting.
* Moved AsynchatDriver to asyncoreDrivers.py, renamed some things, and
redesigned it so there's always an asyncore polling driver and the
individual asyncore drivers don't poll. The code is much simpler and
clearer this way.
* Fixed bug in ircutils.separateModes (tried to pop arguments on modes
that didn't take an argument.)
* Renamed the "unixstats" command in FunCommands to "progstats".
* Fixed bug in callbacks.Privmsg.help.
* Removed ircdb.checkPrivmsgCapability.
2002-Jul-29 Jeremy Fincher <tweedgeezer@hotmail.com>
* Version 0.34.
* Changed FunCommands.cputime to FunCommands.cpustats.
* Moved bot.upkeep to world.upkeep (superReload can't reload __main__,
so having it in world allows it to be reloaded.)
* Added timestamp to debug message printing in irclib.
* Changed IrcMsg .{nick,user,host} to simple attributes.
* Added OwnerCommands.{set, unset} to set and unset temporary runtime
variables. As an example of their use, look at bot.upkeep.
* Added conf.timestampFormat, so timestamps can be universally of the
same form.
* Added FunCommands.netstats.
* Added callbacks.RootWarner, to warn people when they join a channel
as root.
* Added callbacks.ChannelEnforcer, to enforce certain capabilities on
channel.
* added ircdb.checkCapabilties and ircdb.checkPrivmsgCapabilities,
which check for any capability in a list of capabilities.
* added privmsgs.ChannelCommands.{voice,halfop}
* added ircdb.IrcChannel.checkBan
* Fixed bug in IrcUser.__init__
2002-Jul-27 Jeremy Fincher <tweedgeezer@hotmail.com>
* Version 0.33 (third release).
* Bot now exits cleanly on @quit.
* Renamed former 'irccontrol' module to the much more appropriate name
'ircdb', which is also much easier to type and read.
* Changed slightly the config format for conf/users and conf/channels.
* Added a new command in ChannelCommands, 'setdefaultcapability', to
set the default capability response for a channel.
* Rewrote ircdb.checkCapability because the old version was buggy.
Hopefully this one isn't.
* Added callbacks.Privmsg.reply and .error, to make things a bit in
client code. Major caveat, though: you can't blindly "except:" any
longer. You *must* stick a "except self.reserved: raise" in there.
"except Exception, e:" should still work, since the classes raised
don't inherit from Exception.

54
LICENSE Normal file
View File

@ -0,0 +1,54 @@
Copyright (c) 2002, Jeremiah Fincher
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions, and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions, and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the author of this software nor the name of
contributors to this software may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###

42
README Normal file
View File

@ -0,0 +1,42 @@
EVERYONE:
---------
Read LICENSE. It's a 2-clause BSD license, but you should read it anyway.
PROGRAMMERS:
------------
Read OVERVIEW to see what the modules are used for. Read EXAMPLE to see some
examples of callbacks and command written for the bot. Read STYLE if you ever
wish to contribute.
All contributions need to assign the copyright to Jeremiah Fincher for
licensing and modification purposes. I'll probably change the modules to fit
STYLE if they don't already fit. (Of course, that doesn't mean 'all
modifications'; only if you want your code to be included in the *standard
distribution* of Supybot do you need to assign to me the copyright. If you
don't mind distributing your modifications or additions elsewhere, by all means
keep the copyright.)
Use PyChecker. Believe me, it makes things better.
USERS:
------
Read conf.py to see what kind of configuration options you have available. In
order to actually connect to a server on the internet, you'll want to modify
the "drivers" option, changing the server "localhost" to the appropriate value.
You'll probably also want to change the channels that are joined by modifying
the configuration for the "callbacks.ChannelJoiner" callback. This should all
be done in a separate file, as per the example in sample.conf.
To run the bot, do it from this directory, like this:
src/bot.py
If you have your own configuration file (the bot will still use conf.py as its
set of defaults, but will override it with whatever it finds in your file) use:
src/bot.py -c yourconfig.py
I've included slightly patched versions of asyncore and asynchat so they don't
make PyChecker complain, and use some newer features (mostly {}.iter*) of 2.2.

15
TODO Normal file
View File

@ -0,0 +1,15 @@
Write tests (both unit tests and regression tests on the whole shebang).
-- Ugh. I don't want to have to do this.
Continue to document a bit more. Perhaps use doctest on ircutils and ircmsgs.
-- Let me know what isn't instantly understandable, and I'll recode or write
documentation so it *is* instantly understandable.
Redo configuration.
Write DCC stuff.
Write the following callbacks:
-- News
-- Debian
-- FreeBSD Ports

17
docs/CAPABILITIES Normal file
View File

@ -0,0 +1,17 @@
Ok, some some explanation of the capabilities system is probably in order.
With most IRC bots (including the ones I've written myself prior to this one) "what a user can do" is set in one of two ways. On the *really* simple bots, each user has a numeric "level" and commands check to see if a user has a "high enough level" to perform some operation. On bots that are slightly more complicated, users have a list of "flags" whose meanings are hardcoded, and the bot checks to see if a user possesses the necessary flag before performing some operation. Both methods, IMO, are rather arbitrary, and force the user and the programmer to be unduly comfined to less expressive constructs.
This bot is different. Every user has a set of "capabilities" that is consulted every time they give the bot a command. Commands, rather than checking for a user level of 100, or checking if the user has an "o" flag, are instead able to check if a user has the "owner" capability. At this point such a difference might not seem revolutionary, but at least we can already tell that this method is self-documenting, and easier for users and developers to understand what's truly going on.
If that was all, well, the capability system would be "cool", but not many people would say it was "awesome". But it *is* awesome! Several things are happening behind the scene that make it awesome, and these are things that couldn't happen if the bot was using numeric userlevels or single-character flags. First, whenever a user issues the bot a command, the command dispatcher checks to make sure the user doesn't have the "anticapability" for that command. An anticapability is a capability that, instead of saying "what a user can do", says what a user *cannot* do. It's formed rather simply by adding an exclamation point ("!") to the beginning of a capability; "rot13" is a capability, and "!rot13" is an anticapability. Anyway, when a user issues the bot a command, perhaps "calc" or "help", the bot first checks to make sure the user doesn't have the "!calc" or the "!help" capabilities before even considering responding to the user. So commands can be turned on or off on a *per user* basis, offering finegrained control not often (if at all!) seen in other bots.
But that's not all! The capabilities system also supports *Channel* capabilities, which are capabilities that only apply to a specific channel; they're of the form "#channel.capability". Whenever a user issues a command to the bot in a channel, the command dispatcher also checks to make sure the user doesn't have the anticapability for that command *in that channel*, and if the user does, the bot won't respond to the user in the channel. Thus now, in addition to having the ability to turn individual commands on or off for an individual user, we can now turn commands on or off for an individual user on an individual channel!
So when a user "foo" sends a command "bar" to the bot on channel "#baz", first the bot checks to see if the user has the anticapability for the command by itself, "!bar". If so, it returns right then and there, compltely ignoring the fact that the user issued that command to it. If the user doesn't have that anticapability, then the bot checks to see if the user issued the command over a channel, and if so, checks to see if the user has the antichannelcapability for that command, "#baz.!bar". If so, it sets a flag that reminds it later on to respond to the user privately, rather than on the channel itself. If neither of these anticapabilities are present, then the bot just responds to the user like normal.
From a programmatical perspective, capabilties are easy to use and flexible. Any command can check if a user has any capability, even ones not thought of when the bot was originally written. Commands/Callbacks can add their own capabilities -- it's as easy as just checking for a capability and documenting somewhere that a user needs that capability to do something.
From an end-user perspective, capabilities remove a lot of the mystery and esotery of bot control, in addition to giving the user absolutely finegrained control over what users are allowed to do with the bot. Additionally, defaults can be set by the end-user for both individual channels and for the bot as a whole, letting an end-user set the policy he wants the bot to follow for users that haven't yet registered in his user database.
It's really a revolution!

202
docs/EXAMPLE Normal file
View File

@ -0,0 +1,202 @@
Here's an example of how to code a few callbacks for SupyBot.
Let's say you want to make an annoying "Mimic" callback that repeats everything
anyone says to the bot or on the channels the bot is in. Here's what it looks
like:
[code]
class AnnoyingMimic(irclib.IrcCallback):
def doPrivmsg(self, irc, msg):
irc.queueMsg(ircmsgs.privmsg(msg.args[0], msg.args[1]))
[/code]
Almost every callback will inherit from irclib.IrcCallback somewhere in their
class hierarchy. irclib.IrcCallback does a lot of the basic stuff that call-
backs have to do, and inheriting from it relieves the programmer from such
pedantries. All you have to do to start writing callbacks inheriting from
irclib.IrcCallback is write functions of the form "doCommand", where "Command"
is a valid ircCommand. The "ChannelJoiner" function in the callbacks module
is a good illustrative example of a callback being called on different
commands.
The "irc" argument there is the irc object that is calling the callback. To
see what kind of interface it provides, read the class definition in irclib.
Really, you only need to know a few methods if you're writing simple callbacks:
'queueMsg', which queues a message to be sent later, and 'sendMsg' which tries
to send it right away (well, as soon as the Irc object's driver asks for
another message to send, which is generally right away). The Irc object also
provides some attributes that might come in useful, most notably "nick" (the
nick of the bot) and "state" (an IrcState object that does various useful
things like keeping a history of the most recent irc messages.)
Irc messsages are represented by the IrcMsg class in ircmsgs. It has several
useful methods and attributes, but it's probably easier for you to read the
code than for me to tell you about it. The ircmsgs module also provides a set
of useful little commands to create IrcMsg objects that do particular little
things; for instance, ircmsgs.privmsg(recipient, msg) sends a PRIVMSG command
to a channel or user (whatever recipient turns out to be). Check out the code
to see other functions for making IrcMsg objects.
Now, that wasn't too bad. Now, however you're going to have to get it into the
bot. Note that AnnoyingMimic doesn't have an __init__. This'll make it pretty
simple to get it into the configuration system. Look for the section of the
config file where you see all the configurations for Irc objects. These
configurations are going to be lists of (class name, args, kwargs) tuples which
contain the name of a callback class to be instantiated, a tuple of the argu-
ments to be passed to the __init__ function for that class, and a dictionary
of the keyword arguments to be passed to the __init__ function of that class.
For instance, if AnnoyingMimic was in a file 'mycallbacks.py', its config-
uration in the config file would look like this:
[code]
('mycallbacks.AnnoyingMimic', (), {})
[/code]
Since it doesn't have an __init__, there are no arguments or keyword arguments
to pass to the class. Just throw something like that in a list of callbacks
that you use for your bot (you can have several lists, you'll notice later on
in the 'drivers' variable that they're used), and you're ready to go!
Now, let's say you want to make your AnnoyingMimic class a little less
annoying. Now, you only want to mimic people *you* find annoying. The easiest
way to do that is to make it so you tell the class who to mimic when you
instantiate it. This means adding an __init__ function, and modifying your
configuration slightly.
[code]
class AnnoyingMimic(irclib.IrcCallback):
def __init__(self, nicksToAnnoy):
self.nicksToAnnoy = nicksToAnnoy
def doPrivmsg(self, irc, msg):
if msg.nick() in self.nicksToAnnoy:
irc.queueMsg(ircmsgs.privmsg(msg.args[0], msg.args[1]))
[/code]
(Now, really, to make this efficient, you'd want a slightly different version
that turned the nicksToAnnoy argument into a dictionary so nick lookups would
be O(1) instead of O(n) in the length of the list of nicks to annoy, but that
would obfuscate the problem. I'll leave that as an exercise left up to the
reader.)
So now your AnnoyingMimic class has an __init__ function that accepts a list
of nicks to annoy, but how do you pass it those nicks? Simple! Change the
configuration slightly:
[code]
('mycallbacks.AnnoyingMimic', (['jemfinch', 'GnuVince'],), {})
[/code]
That's the wonder of this configuration system -- you can use all the Python
syntax you want, so you have practically unlimited flexibility.
(Note that since the 'arguments' member of that tuple is a single-member tuple,
you'll have to stick a comma after the first (only) element because otherwise
Python wouldn't believe it's a tuple.)
So, again, you choose to make your AnnoyingMimic less annoying -- really, you
decide to make it not annoying at all by making it only mimic people who ask to
be repeated. You want to make a class that has an "echo" command that repeats
the message to those who ask it. You want people to be able to tell the bot,
"echo The quick brown fox jumps over the lazy dog!" and have the bot say right
back, "The quick brown fox jumps over the lazy dog!". That's easy! Here's the
code:
[code]
reply = callbacks.reply
class Echo(callbacks.Privmsg):
def echo(self, irc, msg, args):
"<text>"
text = self.getArgs(args)
self.reply(text)
[/code]
So that seemed pretty simple there, too. Let's explain what's going on:
callbacks.Privmsg is an easy way to create "commands" which are simple named
functions that use a universal scheme for delimiting arguments -- basically,
they'll all act the same in how they get their arguments. callbacks.Privmsg
takes a Privmsg (it has a doPrivmsg function and inherits from
irclib.IrcCallback) and first determines if it's addressed to the bot -- the
message must either be PRIVMSGed directly to the bot, or PRIVMSGed over a
channel the bot is in and either start with a character in conf.prefixchars or
start with the bot's name. Don't worry, callbacks.Privmsg almost always does
The Right Thing. After deciding that the bot has been addressed,
callbacks.Privmsg then parses the text of the message into a list of strings.
Here are a few examples of what it would do:
"""arg1 arg2 arg3"""
['arg1', 'arg2', 'arg3']
"""'arg1 arg2 arg3' arg4""" # Note the quotes.
['arg1 arg2 arg3', 'arg4']
getArgs is a function that just a little bit of magic. It takes an optional
argument (that defaults to 1) of the number of args needed. If more than one
argument is needed, it checks that the proper number of arguments has been
given, and then returns a tuple of those arguments. So if you wanted 3 args
from a message, you'd do something like this:
(name, oldpassword, newpassword) = self.getArgs(args, 3)
See how simple that is? If getArgs only needs one argument, however, it does
something a bit magic -- first of all, it doesn't return a tuple, it just
returns the argument itself. This makes it so you can type:
text = self.getArgs(args)
Instead of:
(text,) = self.getArgs(args)
It just makes things easier that way. Also, however, if *only* one argument
is needed, it does something a bit more magical. A lot of commands take only
one argument and then do some processing on it -- for example, look at the
privmsgs module, the "FunCommands" callback, at the commands 'leet' and
'rot13'. This is all great, but because of the way args are normally parsed
by callbacks.Privmsg, you'd have to always enclose that argument in quotes.
For instance, you'd have to type this:
bot: leet "The quick brown fox jumps over the lazy dog."
From experience, I can tell you that most people will forget the quotes almost
every time they talk to the bot. Since having only one argument is such a
command case, getArgs special-cases it to string.join all the args with spaces.
Now you can say:
bot: leet The quick brown fox jumps over the lazy dog.
And it'll return the same exact thing as above. Of course, the original still
works, but since people forget the quotes so often, it's good to go easy on
them :) We're actually using that behavior with our callback above: by using
getArgs, now our users can say:
echo foo bar baz
Instead of always having to say:
echo "foo bar baz"
Anyway, you're probably wondering how that callback works. It inherits from
callbacks.Privmsg, which as I mentioned before, has a doPrivmsg callback. So
when callbacks.Privmsg receives a PRIVMSG command, it parses it and then tries
to find if it has a method by the same name as the command -- if it does, and
that method looks like this:
def method(self, irc, msg, args):
...
Then it calls that method with the appropriate arguments. Easy, huh? Don't
worry, it gets even cooler :) So you write a command like echo and you want
to provide the user with some help using it. You were probably wondering why
the docstring to that "echo" method above looked so weird, but now you know:
it *is* the help for the command! callbacks.Privmsg has its own command, help,
which will return the *docstring* for any other command! So it's cake to write
your own commands and help.
(This, of course, means that if you *don't* write a help string for your
command, you have no excuse and are just plain lazy. So write help strings!)
There's a bit more I could tutorialize on, but it would be more esoteric, and
better a reference material than as a tutorial. I'll put that in another file.

4
docs/HINTS Normal file
View File

@ -0,0 +1,4 @@
There are just little tidbits that you might need/want to know.
In order to make a module dynamically loadable, all you gotta do is have a
callback named "Class" and throw it in plugins/ .

60
docs/OVERVIEW Normal file
View File

@ -0,0 +1,60 @@
So here's a general *programming* introduction to what the different modules do
and what services they provide. It is, however, only an introduction. Read
the modules themselves for a much more detailed explanation :)
fix.py: Just some stuff that needs to be done to make the bot work in some
older versions of Python (like 2.2 :))
cdb.py: A constant database library, translated from C (and my O'Caml version)
More information available at http://cr.yp.to/cdb.html .
ansi.py: Contains different ANSI color sequences.
Mostly used by the debug module.
debug.py: Functions for handling bugs and errors in a consistent manner
throughout the bot.
conf.py: The configuration file for the bot -- it sets a lot of variables that
other modules check for when they have questions about what they need
to do.
world.py: Just a dropping off place for some globals that need to be shared
among all the modules. It's obviously not used *a lot*, but some
things seem to fit better here than anywhere else.
bot.py: The topmost module, the one that runs the bot. It's also probably the
'dirtiest' module right now, but I'll add proper command line parsing
and clean it up a bit later.
privmsgs.py: The privmsg commands that are included by default in the bot.
It's probably most useful from a programming standpoint simply so
a new programmer can see what commands look like.
callbacks.py: A few basic callbacks used by default in the bot. You'll likely
be inheriting from Privmsg quite a bit, and using the reply
function a bit as well.
ircdb.py: The users and channels databases are here, as well as the
IrcUser and IrcChannel classes. Look here when you want to
restrict a command to only certain users.
irclib.py: Provides the most important class in the irclib, Irc. It represents
a connection to an IRC server, but it's entirely separate from the
network implementation. It's connected to the network (or whatever
else drives it) by a "driver" module which uses its feedMsg/takeMsg
functions.
drivers.py: The baseclass (IrcDriver) for various drivers to drive irclib.Irc
classes. Also, the driving mechanism.
asyncoreDrivers.py: The asyncore-based drivers for use with the bot.
ircmsgs.py: The IrcMsg class (get to know it :)) and various functions for
making the creation of IrcMsgs easier.
ircutils.py: Various utility functions for Irc -- read the module to see what
goodies are there :)
schedule.py: A schedule driver (which is automatically registered with the
drivers module) to run things at a particular time, or at
specified periods of time.

32
docs/STYLE Normal file
View File

@ -0,0 +1,32 @@
Maximum line length is 79 characters.
Identation is 4 spaces per level. No tabs.
Single quotes are used for all string literals that aren't docstrings.
They're just easier to type.
Triple double quotes (""") are always used for docstrings, except for the
docstrings on callbacks deriving from callbacks.Privmsg, which use just
double quotes (").
Spaces go around all operators (except around '=' in default arguments to
functions) and after all commas (unless doing so keeps a line within the 79
character limit).
Class names are StudlyCaps. Method and function names are camelCaps
(StudlyCaps with an initial lowercase letter). If variable and attribute
names can maintain readability without being camelCaps, then they should be
entirely in lowercase, otherwise they should also use camelCaps.
Imports should always happen at the top of the module, one import per line
(so if imports need to be added or removed later, it can be done easily).
A blank line should be between all consecutive method declarations in a
class definition. Two blank lines should be between all consecutive class
definitions in a file. Comments are even better than blank lines for
separating classes.
Code should pass PyChecker with no warnings.
Read PEP 8 (Guido's Style Guide) and know that we use almost all the same style
guidelines.

2
examples/channels.conf Normal file
View File

@ -0,0 +1,2 @@
{'#coderforums': IrcChannel(bans=[], ignores=[], capabilities={'protected': False, 'voice': False, 'halfop': False, 'op': False}, lobotomized=False, defaultAllow=True)
}

2
examples/users.conf Normal file
View File

@ -0,0 +1,2 @@
{'jemfinch': IrcUser(ignore=False, auth=None, password='f0ob4r', capabilities=set('owner', '#programming.op', '#arstechnica.op', '#linux.op'), hostmasks=['jemfinch!jemfinch@*.homenet.ohio-state.edu', 'jemfinch!~1@ts*.homenet.ohio-state.edu'])
}

3972
others/SOAP.py Normal file

File diff suppressed because it is too large Load Diff

274
others/amazon.py Normal file
View File

@ -0,0 +1,274 @@
"""Python wrapper for Amazon web APIs
This module allows you to access Amazon's web APIs,
to do things like search Amazon and get the results programmatically.
Described here:
http://www.amazon.com/webservices
You need a Amazon-provided license key to use these services.
Follow the link above to get one. These functions will look in
several places (in this order) for the license key:
- the "license_key" argument of each function
- the module-level LICENSE_KEY variable (call setLicense once to set it)
- an environment variable called AMAZON_LICENSE_KEY
- a file called ".amazonkey" in the current directory
- a file called "amazonkey.txt" in the current directory
- a file called ".amazonkey" in your home directory
- a file called "amazonkey.txt" in your home directory
- a file called ".amazonkey" in the same directory as amazon.py
- a file called "amazonkey.txt" in the same directory as amazon.py
Sample usage:
>>> import amazon
>>> amazon.setLicense('...') # must get your own key!
>>> pythonBooks = amazon.searchByKeyword('Python')
>>> pythonBooks[0].ProductName
u'Learning Python (Help for Programmers)'
>>> pythonBooks[0].URL
...
>>> pythonBooks[0].OurPrice
...
Other available functions:
- browseBestSellers
- searchByASIN
- searchByUPC
- searchByAuthor
- searchByArtist
- searchByActor
- searchByDirector
- searchByManufacturer
- searchByListMania
- searchSimilar
Other usage notes:
- Most functions can take product_line as well, see source for possible values
- All functions can take type="lite" to get less detail in results
- All functions can take page=N to get second, third, fourth page of results
- All functions can take license_key="XYZ", instead of setting it globally
- All functions can take http_proxy="http://x/y/z" which overrides your system setting
"""
__author__ = "Mark Pilgrim (f8dy@diveintomark.org)"
__version__ = "0.4"
__cvsversion__ = "$Revision$"[11:-2]
__date__ = "$Date$"[7:-2]
__copyright__ = "Copyright (c) 2002 Mark Pilgrim"
__license__ = "Python"
from xml.dom import minidom
import os, sys, getopt, cgi, urllib
try:
import timeoutsocket # http://www.timo-tasi.org/python/timeoutsocket.py
timeoutsocket.setDefaultSocketTimeout(10)
except ImportError:
pass
LICENSE_KEY = None
HTTP_PROXY = None
# don't touch the rest of these constants
class AmazonError(Exception): pass
class NoLicenseKey(Exception): pass
_amazonfile1 = ".amazonkey"
_amazonfile2 = "amazonkey.txt"
_licenseLocations = (
(lambda key: key, 'passed to the function in license_key variable'),
(lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'),
(lambda key: os.environ.get('AMAZON_LICENSE_KEY', None), 'an environment variable called AMAZON_LICENSE_KEY'),
(lambda key: _contentsOf(os.getcwd(), _amazonfile1), '%s in the current directory' % _amazonfile1),
(lambda key: _contentsOf(os.getcwd(), _amazonfile2), '%s in the current directory' % _amazonfile2),
(lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile1), '%s in your home directory' % _amazonfile1),
(lambda key: _contentsOf(os.environ.get('HOME', ''), _amazonfile2), '%s in your home directory' % _amazonfile2),
(lambda key: _contentsOf(_getScriptDir(), _amazonfile1), '%s in the amazon.py directory' % _amazonfile1),
(lambda key: _contentsOf(_getScriptDir(), _amazonfile2), '%s in the amazon.py directory' % _amazonfile2)
)
## administrative functions
def version():
print """PyAmazon %(__version__)s
%(__copyright__)s
released %(__date__)s
""" % globals()
## utility functions
def setLicense(license_key):
"""set license key"""
global LICENSE_KEY
LICENSE_KEY = license_key
def getLicense(license_key = None):
"""get license key
license key can come from any number of locations;
see module docs for search order"""
for get, location in _licenseLocations:
rc = get(license_key)
if rc: return rc
raise NoLicenseKey, 'get a license key at http://www.amazon.com/webservices'
def setProxy(http_proxy):
"""set HTTP proxy"""
global HTTP_PROXY
HTTP_PROXY = http_proxy
def getProxy(http_proxy = None):
"""get HTTP proxy"""
return http_proxy or HTTP_PROXY
def getProxies(http_proxy = None):
http_proxy = getProxy(http_proxy)
if http_proxy:
proxies = {"http": http_proxy}
else:
proxies = None
return proxies
def _contentsOf(dirname, filename):
filename = os.path.join(dirname, filename)
if not os.path.exists(filename): return None
fsock = open(filename)
contents = fsock.read()
fsock.close()
return contents
def _getScriptDir():
if __name__ == '__main__':
return os.path.abspath(os.path.dirname(sys.argv[0]))
else:
return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__))
class Bag: pass
def unmarshal(element):
rc = Bag()
if isinstance(element, minidom.Element) and (element.tagName == 'Details'):
rc.URL = element.attributes["url"].value
childElements = [e for e in element.childNodes if isinstance(e, minidom.Element)]
if childElements:
for child in childElements:
key = child.tagName
if hasattr(rc, key):
if type(getattr(rc, key)) <> type([]):
setattr(rc, key, [getattr(rc, key)])
setattr(rc, key, getattr(rc, key) + [unmarshal(child)])
else:
setattr(rc, key, unmarshal(child))
else:
rc = "".join([e.data for e in element.childNodes if isinstance(e, minidom.Text)])
if element.tagName == 'SalesRank':
rc = int(rc.replace(',', ''))
return rc
def buildURL(search_type, keyword, product_line, type, page, license_key):
url = "http://xml.amazon.com/onca/xml?v=1.0&f=xml&t=webservices-20"
url += "&dev-t=%s" % license_key.strip()
url += "&type=%s" % type
if page:
url += "&page=%s" % page
if product_line:
url += "&mode=%s" % product_line
url += "&%s=%s" % (search_type, urllib.quote(keyword))
# print url
return url
## main functions
def search(search_type, keyword, product_line, type="heavy", page=None,
license_key = None, http_proxy = None):
"""search Amazon
You need a license key to call this function; see
http://www.amazon.com/webservices
to get one. Then you can either pass it to
this function every time, or set it globally; see the module docs for details.
Parameters:
keyword - keyword to search
search_type - in (KeywordSearch, BrowseNodeSearch, AsinSearch, UpcSearch, AuthorSearch, ArtistSearch, ActorSearch, DirectorSearch, ManufacturerSearch, ListManiaSearch, SimilaritySearch)
product_line - type of product to search for. restrictions based on search_type
UpcSearch - in (music, classical)
AuthorSearch - must be "books"
ArtistSearch - in (music, classical)
ActorSearch - in (dvd, vhs, video)
DirectorSearch - in (dvd, vhs, video)
ManufacturerSearch - in (electronics, kitchen, videogames, software, photo, pc-hardware)
http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages
Returns: list of Bags, each Bag may contain the following attributes:
Asin - Amazon ID ("ASIN" number) of this item
Authors - list of authors
Availability - "available", etc.
BrowseList - list of related categories
Catalog - catalog type ("Book", etc)
CollectiblePrice - ?, format "$34.95"
ImageUrlLarge - URL of large image of this item
ImageUrlMedium - URL of medium image of this item
ImageUrlSmall - URL of small image of this item
Isbn - ISBN number
ListPrice - list price, format "$34.95"
Lists - list of ListMania lists that include this item
Manufacturer - manufacturer
Media - media ("Paperback", "Audio CD", etc)
NumMedia - number of different media types in which this item is available
OurPrice - Amazon price, format "$24.47"
ProductName - name of this item
ReleaseDate - release date, format "09 April, 1999"
Reviews - reviews (AvgCustomerRating, plus list of CustomerReview with Rating, Summary, Content)
SalesRank - sales rank (integer)
SimilarProducts - list of Product, which is ASIN number
ThirdPartyNewPrice - ?, format "$34.95"
URL - URL of this item
"""
license_key = getLicense(license_key)
url = buildURL(search_type, keyword, product_line, type, page, license_key)
proxies = getProxies(http_proxy)
u = urllib.FancyURLopener(proxies)
usock = u.open(url)
xmldoc = minidom.parse(usock)
usock.close()
data = unmarshal(xmldoc).ProductInfo
if hasattr(data, 'ErrorMsg'):
raise AmazonError, data.ErrorMsg
else:
return data.Details
def searchByKeyword(keyword, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None):
return search("KeywordSearch", keyword, product_line, type, page, license_key, http_proxy)
def browseBestSellers(browse_node, product_line="books", type="heavy", page=1, license_key=None, http_proxy=None):
return search("BrowseNodeSearch", browse_node, product_line, type, page, license_key, http_proxy)
def searchByASIN(ASIN, type="heavy", license_key=None, http_proxy=None):
return search("AsinSearch", ASIN, None, type, None, license_key, http_proxy)
def searchByUPC(UPC, type="heavy", license_key=None, http_proxy=None):
return search("UpcSearch", UPC, None, type, None, license_key, http_proxy)
def searchByAuthor(author, type="heavy", page=1, license_key=None, http_proxy=None):
return search("AuthorSearch", author, "books", type, page, license_key, http_proxy)
def searchByArtist(artist, product_line="music", type="heavy", page=1, license_key=None, http_proxy=None):
if product_line not in ("music", "classical"):
raise AmazonError, "product_line must be in ('music', 'classical')"
return search("ArtistSearch", artist, product_line, type, page, license_key, http_proxy)
def searchByActor(actor, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None):
if product_line not in ("dvd", "vhs", "video"):
raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
return search("ActorSearch", actor, product_line, type, page, license_key, http_proxy)
def searchByDirector(director, product_line="dvd", type="heavy", page=1, license_key=None, http_proxy=None):
if product_line not in ("dvd", "vhs", "video"):
raise AmazonError, "product_line must be in ('dvd', 'vhs', 'video')"
return search("DirectorSearch", director, product_line, type, page, license_key, http_proxy)
def searchByManufacturer(manufacturer, product_line="pc-hardware", type="heavy", page=1, license_key=None, http_proxy=None):
if product_line not in ("electronics", "kitchen", "videogames", "software", "photo", "pc-hardware"):
raise AmazonError, "product_line must be in ('electronics', 'kitchen', 'videogames', 'software', 'photo', 'pc-hardware')"
return search("ManufacturerSearch", manufacturer, product_line, type, page, license_key, http_proxy)
def searchByListMania(listManiaID, type="heavy", page=1, license_key=None, http_proxy=None):
return search("ListManiaSearch", listManiaID, None, type, page, license_key, http_proxy)
def searchSimilar(ASIN, type="heavy", page=1, license_key=None, http_proxy=None):
return search("SimilaritySearch", ASIN, None, type, page, license_key, http_proxy)

293
others/asynchat.py Normal file
View File

@ -0,0 +1,293 @@
# -*- Mode: Python; tab-width: 4 -*-
# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
# Author: Sam Rushing <rushing@nightmare.com>
# ======================================================================
# Copyright 1996 by Sam Rushing
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Sam
# Rushing not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# ======================================================================
r"""A class supporting chat-style (command/response) protocols.
This class adds support for 'chat' style protocols - where one side
sends a 'command', and the other sends a response (examples would be
the common internet protocols - smtp, nntp, ftp, etc..).
The handle_read() method looks at the input stream for the current
'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
for multi-line output), calling self.found_terminator() on its
receipt.
for example:
Say you build an async nntp client using this class. At the start
of the connection, you'll have self.terminator set to '\r\n', in
order to process the single-line greeting. Just before issuing a
'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
command will be accumulated (using your own 'collect_incoming_data'
method) up to the terminator, and then control will be returned to
you - by calling your self.found_terminator() method.
"""
import socket
import asyncore
class async_chat (asyncore.dispatcher):
"""This is an abstract class. You must derive from this class, and add
the two methods collect_incoming_data() and found_terminator()"""
# these are overridable defaults
ac_in_buffer_size = 4096
ac_out_buffer_size = 4096
def __init__ (self, conn=None):
self.ac_in_buffer = ''
self.ac_out_buffer = ''
self.producer_fifo = fifo()
asyncore.dispatcher.__init__ (self, conn)
def set_terminator (self, term):
"Set the input delimiter. Can be a fixed string of any length, an integer, or None"
self.terminator = term
def get_terminator (self):
return self.terminator
# grab some more data from the socket,
# throw it to the collector method,
# check for the terminator,
# if found, transition to the next state.
def handle_read (self):
try:
data = self.recv (self.ac_in_buffer_size)
except socket.error, _:
self.handle_error()
return
self.ac_in_buffer = self.ac_in_buffer + data
# Continue to search for self.terminator in self.ac_in_buffer,
# while calling self.collect_incoming_data. The while loop
# is necessary because we might read several data+terminator
# combos with a single recv(1024).
while self.ac_in_buffer:
lb = len(self.ac_in_buffer)
terminator = self.get_terminator()
if terminator is None:
# no terminator, collect it all
self.collect_incoming_data (self.ac_in_buffer)
self.ac_in_buffer = ''
elif type(terminator) == type(0):
# numeric terminator
n = terminator
if lb < n:
self.collect_incoming_data (self.ac_in_buffer)
self.ac_in_buffer = ''
self.terminator = self.terminator - lb
else:
self.collect_incoming_data (self.ac_in_buffer[:n])
self.ac_in_buffer = self.ac_in_buffer[n:]
self.terminator = 0
self.found_terminator()
else:
# 3 cases:
# 1) end of buffer matches terminator exactly:
# collect data, transition
# 2) end of buffer matches some prefix:
# collect data to the prefix
# 3) end of buffer does not match any prefix:
# collect data
terminator_len = len(terminator)
index = self.ac_in_buffer.find(terminator)
if index != -1:
# we found the terminator
if index > 0:
# don't bother reporting the empty string (source of subtle bugs)
self.collect_incoming_data (self.ac_in_buffer[:index])
self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
# This does the Right Thing if the terminator is changed here.
self.found_terminator()
else:
# check for a prefix of the terminator
index = find_prefix_at_end (self.ac_in_buffer, terminator)
if index:
if index != lb:
# we found a prefix, collect up to the prefix
self.collect_incoming_data (self.ac_in_buffer[:-index])
self.ac_in_buffer = self.ac_in_buffer[-index:]
break
else:
# no prefix, collect it all
self.collect_incoming_data (self.ac_in_buffer)
self.ac_in_buffer = ''
def handle_write (self):
self.initiate_send ()
def handle_close (self):
self.close()
def push (self, data):
self.producer_fifo.push (simple_producer (data))
self.initiate_send()
def push_with_producer (self, producer):
self.producer_fifo.push (producer)
self.initiate_send()
def readable (self):
"predicate for inclusion in the readable for select()"
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
def writable (self):
"predicate for inclusion in the writable for select()"
# return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
# this is about twice as fast, though not as clear.
return not (
(self.ac_out_buffer == '') and
self.producer_fifo.is_empty() and
self.connected
)
def close_when_done (self):
"automatically close this channel once the outgoing queue is empty"
self.producer_fifo.push (None)
# refill the outgoing buffer by calling the more() method
# of the first producer in the queue
def refill_buffer (self):
_string_type = type('')
while 1:
if len(self.producer_fifo):
p = self.producer_fifo.first()
# a 'None' in the producer fifo is a sentinel,
# telling us to close the channel.
if p is None:
if not self.ac_out_buffer:
self.producer_fifo.pop()
self.close()
return
elif type(p) is _string_type:
self.producer_fifo.pop()
self.ac_out_buffer = self.ac_out_buffer + p
return
data = p.more()
if data:
self.ac_out_buffer = self.ac_out_buffer + data
return
else:
self.producer_fifo.pop()
else:
return
def initiate_send (self):
obs = self.ac_out_buffer_size
# try to refill the buffer
if (len (self.ac_out_buffer) < obs):
self.refill_buffer()
if self.ac_out_buffer and self.connected:
# try to send the buffer
try:
num_sent = self.send (self.ac_out_buffer[:obs])
if num_sent:
self.ac_out_buffer = self.ac_out_buffer[num_sent:]
except socket.error, _:
self.handle_error()
return
def discard_buffers (self):
# Emergencies only!
self.ac_in_buffer = ''
self.ac_out_buffer = ''
while self.producer_fifo:
self.producer_fifo.pop()
class simple_producer:
def __init__ (self, data, buffer_size=512):
self.data = data
self.buffer_size = buffer_size
def more (self):
if len (self.data) > self.buffer_size:
result = self.data[:self.buffer_size]
self.data = self.data[self.buffer_size:]
return result
else:
result = self.data
self.data = ''
return result
class fifo:
def __init__ (self, list=None):
if not list:
self.list = []
else:
self.list = list
def __len__ (self):
return len(self.list)
def is_empty (self):
return self.list == []
def first (self):
return self.list[0]
def push (self, data):
self.list.append (data)
def pop (self):
if self.list:
result = self.list[0]
del self.list[0]
return (1, result)
else:
return (0, None)
# Given 'haystack', see if any prefix of 'needle' is at its end. This
# assumes an exact match has already been checked. Return the number of
# characters matched.
# for example:
# f_p_a_e ("qwerty\r", "\r\n") => 1
# f_p_a_e ("qwerty\r\n", "\r\n") => 2
# f_p_a_e ("qwertydkjf", "\r\n") => 0
# this could maybe be made faster with a computed regex?
# [answer: no; circa Python-2.0, Jan 2001]
# python: 18307/s
# re: 12820/s
# regex: 14035/s
def find_prefix_at_end (haystack, needle):
nl = len(needle)
result = 0
for i in range (1,nl):
if haystack[-(nl-i):] == needle[:(nl-i)]:
result = nl-i
break
return result

551
others/asyncore.py Normal file
View File

@ -0,0 +1,551 @@
# -*- Mode: Python -*-
# Id: asyncore.py,v 2.51 2000/09/07 22:29:26 rushing Exp
# Author: Sam Rushing <rushing@nightmare.com>
# ======================================================================
# Copyright 1996 by Sam Rushing
#
# All Rights Reserved
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose and without fee is hereby
# granted, provided that the above copyright notice appear in all
# copies and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of Sam
# Rushing not be used in advertising or publicity pertaining to
# distribution of the software without specific, written prior
# permission.
#
# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# ======================================================================
"""Basic infrastructure for asynchronous socket service clients and servers.
There are only two ways to have a program on a single processor do "more
than one thing at a time". Multi-threaded programming is the simplest and
most popular way to do it, but there is another very different technique,
that lets you have nearly all the advantages of multi-threading, without
actually using multiple threads. it's really only practical if your program
is largely I/O bound. If your program is CPU bound, then pre-emptive
scheduled threads are probably what you really need. Network servers are
rarely CPU-bound, however.
If your operating system supports the select() system call in its I/O
library (and nearly all do), then you can use it to juggle multiple
communication channels at once; doing other work while your I/O is taking
place in the "background." Although this strategy can seem strange and
complex, especially at first, it is in many ways easier to understand and
control than multi-threaded programming. The module documented here solves
many of the difficult problems for you, making the task of building
sophisticated high-performance network servers and clients a snap.
"""
import exceptions
import select
import socket
import sys
import os
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
ENOTCONN, ESHUTDOWN, EINTR, EISCONN
try:
socket_map
except NameError:
socket_map = {}
class ExitNow (exceptions.Exception):
pass
DEBUG = 0
def poll (timeout=0.0, map=None):
if map is None:
map = socket_map
if map:
r = []; w = []; e = []
for fd, obj in map.iteritems():
if obj.readable():
r.append (fd)
if obj.writable():
w.append (fd)
try:
r,w,e = select.select (r,w,e, timeout)
except select.error, err:
if err[0] != EINTR:
raise
if DEBUG:
print r,w,e
for fd in r:
try:
obj = map[fd]
except KeyError:
continue
try:
obj.handle_read_event()
except ExitNow:
raise ExitNow
except:
obj.handle_error()
for fd in w:
try:
obj = map[fd]
except KeyError:
continue
try:
obj.handle_write_event()
except ExitNow:
raise ExitNow
except:
obj.handle_error()
def poll2 (timeout=0.0, map=None):
import poll
if map is None:
map=socket_map
if timeout is not None:
# timeout is in milliseconds
timeout = int(timeout*1000)
if map:
l = []
for fd, obj in map.iteritems():
flags = 0
if obj.readable():
flags = poll.POLLIN
if obj.writable():
flags = flags | poll.POLLOUT
if flags:
l.append ((fd, flags))
r = poll.poll (l, timeout)
for fd, flags in r:
try:
obj = map[fd]
except KeyError:
continue
try:
if (flags & poll.POLLIN):
obj.handle_read_event()
if (flags & poll.POLLOUT):
obj.handle_write_event()
except ExitNow:
raise ExitNow
except:
obj.handle_error()
def poll3 (timeout=0.0, map=None):
# Use the poll() support added to the select module in Python 2.0
if map is None:
map=socket_map
if timeout is not None:
# timeout is in milliseconds
timeout = int(timeout*1000)
pollster = select.poll()
if map:
for fd, obj in map.iteritems():
flags = 0
if obj.readable():
flags = select.POLLIN
if obj.writable():
flags = flags | select.POLLOUT
if flags:
pollster.register(fd, flags)
try:
r = pollster.poll (timeout)
except select.error, err:
if err[0] != EINTR:
raise
r = []
for fd, flags in r:
try:
obj = map[fd]
except KeyError:
continue
try:
if (flags & select.POLLIN):
obj.handle_read_event()
if (flags & select.POLLOUT):
obj.handle_write_event()
except ExitNow:
raise ExitNow
except:
obj.handle_error()
def loop (timeout=30.0, use_poll=0, map=None):
if map is None:
map=socket_map
if use_poll:
if hasattr (select, 'poll'):
poll_fun = poll3
else:
poll_fun = poll2
else:
poll_fun = poll
while map:
poll_fun (timeout, map)
class dispatcher:
debug = 0
connected = 0
accepting = 0
closing = 0
addr = None
def __init__ (self, sock=None, map=None):
if sock:
self.set_socket (sock, map)
# I think it should inherit this anyway
self.socket.setblocking (0)
self.connected = 1
# XXX Does the constructor require that the socket passed
# be connected?
try:
self.addr = sock.getpeername()
except socket.error:
# The addr isn't crucial
pass
else:
self.socket = None
def __repr__ (self):
status = [self.__class__.__module__+"."+self.__class__.__name__]
if self.accepting and self.addr:
status.append ('listening')
elif self.connected:
status.append ('connected')
if self.addr is not None:
try:
status.append ('%s:%d' % self.addr)
except TypeError:
status.append (repr(self.addr))
return '<%s at %#x>' % (' '.join (status), id (self))
def add_channel (self, map=None):
#self.log_info ('adding channel %s' % self)
if map is None:
map=socket_map
map [self._fileno] = self
def del_channel (self, map=None):
fd = self._fileno
if map is None:
map=socket_map
if map.has_key (fd):
#self.log_info ('closing channel %d:%s' % (fd, self))
del map [fd]
def create_socket (self, family, type):
self.family_and_type = family, type
self.socket = socket.socket (family, type)
self.socket.setblocking(0)
self._fileno = self.socket.fileno()
self.add_channel()
def set_socket (self, sock, map=None):
self.socket = sock
## self.__dict__['socket'] = sock
self._fileno = sock.fileno()
self.add_channel (map)
def set_reuse_addr (self):
# try to re-use a server port if possible
try:
self.socket.setsockopt (
socket.SOL_SOCKET, socket.SO_REUSEADDR,
self.socket.getsockopt (socket.SOL_SOCKET,
socket.SO_REUSEADDR) | 1
)
except socket.error:
pass
# ==================================================
# predicates for select()
# these are used as filters for the lists of sockets
# to pass to select().
# ==================================================
def readable (self):
return 1
if os.name == 'mac':
# The macintosh will select a listening socket for
# write if you let it. What might this mean?
def writable (self):
return not self.accepting
else:
def writable (self):
return 1
# ==================================================
# socket object methods.
# ==================================================
def listen (self, num):
self.accepting = 1
if os.name == 'nt' and num > 5:
num = 1
return self.socket.listen (num)
def bind (self, addr):
self.addr = addr
return self.socket.bind (addr)
def connect (self, address):
self.connected = 0
err = self.socket.connect_ex(address)
if err in (EINPROGRESS, EALREADY, EWOULDBLOCK):
return
if err in (0, EISCONN):
self.addr = address
self.connected = 1
self.handle_connect()
else:
raise socket.error, err
def accept (self):
try:
conn, addr = self.socket.accept()
return conn, addr
except socket.error, why:
if why[0] == EWOULDBLOCK:
pass
else:
raise socket.error, why
def send (self, data):
try:
result = self.socket.send (data)
return result
except socket.error, why:
if why[0] == EWOULDBLOCK:
return 0
else:
raise socket.error, why
return 0
def recv (self, buffer_size):
try:
data = self.socket.recv (buffer_size)
if not data:
# a closed connection is indicated by signaling
# a read condition, and having recv() return 0.
self.handle_close()
return ''
else:
return data
except socket.error, why:
# winsock sometimes throws ENOTCONN
if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
self.handle_close()
return ''
else:
raise socket.error, why
def close (self):
self.del_channel()
self.socket.close()
# cheap inheritance, used to pass all other attribute
# references to the underlying socket object.
def __getattr__ (self, attr):
return getattr (self.socket, attr)
# log and log_info maybe overriden to provide more sophisitcated
# logging and warning methods. In general, log is for 'hit' logging
# and 'log_info' is for informational, warning and error logging.
def log (self, message):
sys.stderr.write ('log: %s\n' % str(message))
def log_info (self, message, type='info'):
if __debug__ or type != 'info':
print '%s: %s' % (type, message)
def handle_read_event (self):
if self.accepting:
# for an accepting socket, getting a read implies
# that we are connected
if not self.connected:
self.connected = 1
self.handle_accept()
elif not self.connected:
self.handle_connect()
self.connected = 1
self.handle_read()
else:
self.handle_read()
def handle_write_event (self):
# getting a write implies that we are connected
if not self.connected:
self.handle_connect()
self.connected = 1
self.handle_write()
def handle_expt_event (self):
self.handle_expt()
def handle_error (self):
nil, t, v, tbinfo = compact_traceback()
# sometimes a user repr method will crash.
try:
self_repr = repr (self)
except:
self_repr = '<__repr__ (self) failed for object at %0x>' % id(self)
self.log_info (
'uncaptured python exception, closing channel %s (%s:%s %s)' % (
self_repr,
t,
v,
tbinfo
),
'error'
)
self.close()
def handle_expt (self):
self.log_info ('unhandled exception', 'warning')
def handle_read (self):
self.log_info ('unhandled read event', 'warning')
def handle_write (self):
self.log_info ('unhandled write event', 'warning')
def handle_connect (self):
self.log_info ('unhandled connect event', 'warning')
def handle_accept (self):
self.log_info ('unhandled accept event', 'warning')
def handle_close (self):
self.log_info ('unhandled close event', 'warning')
self.close()
# ---------------------------------------------------------------------------
# adds simple buffered output capability, useful for simple clients.
# [for more sophisticated usage use asynchat.async_chat]
# ---------------------------------------------------------------------------
class dispatcher_with_send (dispatcher):
def __init__ (self, sock=None):
dispatcher.__init__ (self, sock)
self.out_buffer = ''
def initiate_send (self):
num_sent = 0
num_sent = dispatcher.send (self, self.out_buffer[:512])
self.out_buffer = self.out_buffer[num_sent:]
def handle_write (self):
self.initiate_send()
def writable (self):
return (not self.connected) or len(self.out_buffer)
def send (self, data):
if self.debug:
self.log_info ('sending %s' % repr(data))
self.out_buffer = self.out_buffer + data
self.initiate_send()
# ---------------------------------------------------------------------------
# used for debugging.
# ---------------------------------------------------------------------------
def compact_traceback ():
t,v,tb = sys.exc_info()
tbinfo = []
while 1:
tbinfo.append ((
tb.tb_frame.f_code.co_filename,
tb.tb_frame.f_code.co_name,
str(tb.tb_lineno)
))
tb = tb.tb_next
if not tb:
break
# just to be safe
del tb
file, function, line = tbinfo[-1]
info = '[' + '] ['.join(map(lambda x: '|'.join(x), tbinfo)) + ']'
return (file, function, line), t, v, info
def close_all (map=None):
if map is None:
map=socket_map
for x in map.itervalues():
x.socket.close()
map.clear()
# Asynchronous File I/O:
#
# After a little research (reading man pages on various unixen, and
# digging through the linux kernel), I've determined that select()
# isn't meant for doing doing asynchronous file i/o.
# Heartening, though - reading linux/mm/filemap.c shows that linux
# supports asynchronous read-ahead. So _MOST_ of the time, the data
# will be sitting in memory for us already when we go to read it.
#
# What other OS's (besides NT) support async file i/o? [VMS?]
#
# Regardless, this is useful for pipes, and stdin/stdout...
import os
if os.name == 'posix':
import fcntl
class file_wrapper:
# here we override just enough to make a file
# look like a socket for the purposes of asyncore.
def __init__ (self, fd):
self.fd = fd
def recv (self, *args):
return apply (os.read, (self.fd,)+args)
def send (self, *args):
return apply (os.write, (self.fd,)+args)
read = recv
write = send
def close (self):
return os.close (self.fd)
def fileno (self):
return self.fd
class file_dispatcher (dispatcher):
def __init__ (self, fd):
dispatcher.__init__ (self)
self.connected = 1
# set it to non-blocking mode
flags = fcntl.fcntl (fd, fcntl.F_GETFL, 0)
flags = flags | os.O_NONBLOCK
fcntl.fcntl (fd, fcntl.F_SETFL, flags)
self.set_file (fd)
def set_file (self, fd):
self._fileno = fd
self.socket = file_wrapper (fd)
self.add_channel()

282
others/cgitb.py Normal file
View File

@ -0,0 +1,282 @@
"""Handle exceptions in CGI scripts by formatting tracebacks into nice HTML.
To enable this module, do:
import cgitb; cgitb.enable()
at the top of your CGI script. The optional arguments to enable() are:
display - if true, tracebacks are displayed in the web browser
logdir - if set, tracebacks are written to files in this directory
context - number of lines of source code to show for each stack frame
By default, tracebacks are displayed but not saved, and context is 5.
Alternatively, if you have caught an exception and want cgitb to display it
for you, call cgitb.handler(). The optional argument to handler() is a 3-item
tuple (etype, evalue, etb) just like the value of sys.exc_info()."""
__author__ = 'Ka-Ping Yee'
__version__ = '$Revision$'
import sys
def reset():
"""Return a string that resets the CGI and browser to a known state."""
return '''<!--: spam
Content-Type: text/html
<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> -->
<body bgcolor="#f0f0f8"><font color="#f0f0f8" size="-5"> --> -->
</font> </font> </font> </script> </object> </blockquote> </pre>
</table> </table> </table> </table> </table> </font> </font> </font>'''
__UNDEF__ = [] # a special sentinel object
def small(text): return '<small>' + text + '</small>'
def strong(text): return '<strong>' + text + '</strong>'
def grey(text): return '<font color="#909090">' + text + '</font>'
def lookup(name, frame, locals):
"""Find the value for a given name in the given environment."""
if name in locals:
return 'local', locals[name]
if name in frame.f_globals:
return 'global', frame.f_globals[name]
return None, __UNDEF__
def scanvars(reader, frame, locals):
"""Scan one logical line of Python and look up values of variables used."""
import tokenize, keyword
vars, lasttoken, parent, prefix = [], None, None, ''
for ttype, token, start, end, line in tokenize.generate_tokens(reader):
if ttype == tokenize.NEWLINE: break
if ttype == tokenize.NAME and token not in keyword.kwlist:
if lasttoken == '.':
if parent is not __UNDEF__:
value = getattr(parent, token, __UNDEF__)
vars.append((prefix + token, prefix, value))
else:
where, value = lookup(token, frame, locals)
vars.append((token, where, value))
elif token == '.':
prefix += lasttoken + '.'
parent = value
else:
parent, prefix = None, ''
lasttoken = token
return vars
def html((etype, evalue, etb), context=5):
"""Return a nice HTML document describing a given traceback."""
import os, types, time, traceback, linecache, inspect, pydoc
if type(etype) is types.ClassType:
etype = etype.__name__
pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
date = time.ctime(time.time())
head = '<body bgcolor="#f0f0f8">' + pydoc.html.heading(
'<big><big><strong>%s</strong></big></big>' % str(etype),
'#ffffff', '#6622aa', pyver + '<br>' + date) + '''
<p>A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they occurred.'''
indent = '<tt>' + small('&nbsp;' * 5) + '&nbsp;</tt>'
frames = []
records = inspect.getinnerframes(etb, context)
for frame, file, lnum, func, lines, index in records:
file = file and os.path.abspath(file) or '?'
link = '<a href="file://%s">%s</a>' % (file, pydoc.html.escape(file))
args, varargs, varkw, locals = inspect.getargvalues(frame)
call = ''
if func != '?':
call = 'in ' + strong(func) + \
inspect.formatargvalues(args, varargs, varkw, locals,
formatvalue=lambda value: '=' + pydoc.html.repr(value))
highlight = {}
def reader(lnum=[lnum]):
highlight[lnum[0]] = 1
try: return linecache.getline(file, lnum[0])
finally: lnum[0] += 1
vars = scanvars(reader, frame, locals)
rows = ['<tr><td bgcolor="#d8bbff">%s%s %s</td></tr>' %
('<big>&nbsp;</big>', link, call)]
if index is not None:
i = lnum - index
for line in lines:
num = small('&nbsp;' * (5-len(str(i))) + str(i)) + '&nbsp;'
line = '<tt>%s%s</tt>' % (num, pydoc.html.preformat(line))
if i in highlight:
rows.append('<tr><td bgcolor="#ffccee">%s</td></tr>' % line)
else:
rows.append('<tr><td>%s</td></tr>' % grey(line))
i += 1
done, dump = {}, []
for name, where, value in vars:
if name in done: continue
done[name] = 1
if value is not __UNDEF__:
if where == 'global': name = '<em>global</em> ' + strong(name)
elif where == 'local': name = strong(name)
else: name = where + strong(name.split('.')[-1])
dump.append('%s&nbsp;= %s' % (name, pydoc.html.repr(value)))
else:
dump.append(name + ' <em>undefined</em>')
rows.append('<tr><td>%s</td></tr>' % small(grey(', '.join(dump))))
frames.append('''<p>
<table width="100%%" cellspacing=0 cellpadding=0 border=0>
%s</table>''' % '\n'.join(rows))
exception = ['<p>%s: %s' % (strong(str(etype)), str(evalue))]
if type(evalue) is types.InstanceType:
for name in dir(evalue):
value = pydoc.html.repr(getattr(evalue, name))
exception.append('\n<br>%s%s&nbsp;=\n%s' % (indent, name, value))
import traceback
return head + ''.join(frames) + ''.join(exception) + '''
<!-- The above is a description of an error in a Python program, formatted
for a Web browser because the 'cgitb' module was enabled. In case you
are not reading this in a Web browser, here is the original traceback:
%s
-->
''' % ''.join(traceback.format_exception(etype, evalue, etb))
def text((etype, evalue, etb), context=5):
"""Return a plain text document describing a given traceback."""
import os, types, time, traceback, linecache, inspect, pydoc
if type(etype) is types.ClassType:
etype = etype.__name__
pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
date = time.ctime(time.time())
head = "%s\n%s\n%s\n" % (str(etype), pyver, date) + '''
A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they occurred.
'''
indent = ' ' * 5 + ' '
frames = []
records = inspect.getinnerframes(etb, context)
for frame, file, lnum, func, lines, index in records:
file = file and os.path.abspath(file) or '?'
args, varargs, varkw, locals = inspect.getargvalues(frame)
call = ''
if func != '?':
call = 'in ' + func + \
inspect.formatargvalues(args, varargs, varkw, locals,
formatvalue=lambda value: '=' + pydoc.text.repr(value))
highlight = {}
def reader(lnum=[lnum]):
highlight[lnum[0]] = 1
try: return linecache.getline(file, lnum[0])
finally: lnum[0] += 1
vars = scanvars(reader, frame, locals)
rows = [' %s %s' % (file, call)]
if index is not None:
i = lnum - index
for line in lines:
num = '%5d ' % i
rows.append(num+line.rstrip())
i += 1
done, dump = {}, []
for name, where, value in vars:
if name in done: continue
done[name] = 1
if value is not __UNDEF__:
if where == 'global': name = 'global ' + name
elif where == 'local': name = name
else: name = where + name.split('.')[-1]
dump.append('%s = %s' % (name, pydoc.text.repr(value)))
else:
dump.append(name + ' undefined')
rows.append('\n'.join(dump))
frames.append('\n%s\n' % '\n'.join(rows))
exception = ['%s: %s' % (str(etype), str(evalue))]
if type(evalue) is types.InstanceType:
for name in dir(evalue):
value = pydoc.text.repr(getattr(evalue, name))
exception.append('\n%s%s =\n%s' % (indent, name, value))
import traceback
return head + ''.join(frames) + ''.join(exception) + '''
The above is a description of an error in a Python program. Here is
the original traceback:
%s
''' % ''.join(traceback.format_exception(etype, evalue, etb))
class Hook:
"""A hook to replace sys.excepthook that shows tracebacks in HTML."""
def __init__(self, display=1, logdir=None, context=5, file=None,
format="html"):
self.display = display # send tracebacks to browser if true
self.logdir = logdir # log tracebacks to files if not None
self.context = context # number of source code lines per frame
self.file = file or sys.stdout # place to send the output
self.format = format
def __call__(self, etype, evalue, etb):
self.handle((etype, evalue, etb))
def handle(self, info=None):
info = info or sys.exc_info()
if self.format == "html":
self.file.write(reset())
formatter = (self.format=="html") and html or text
plain = 0
try:
doc = formatter(info, self.context)
except: # just in case something goes wrong
import traceback
doc = ''.join(traceback.format_exception(*info))
plain = 1
if self.display:
if plain:
doc = doc.replace('&', '&amp;').replace('<', '&lt;')
self.file.write('<pre>' + doc + '</pre>\n')
else:
self.file.write(doc + '\n')
else:
self.file.write('<p>A problem occurred in a Python script.\n')
if self.logdir is not None:
import os, tempfile
name = tempfile.mktemp(['.txt', '.html'][self.format=="html"])
path = os.path.join(self.logdir, os.path.basename(name))
try:
file = open(path, 'w')
file.write(doc)
file.close()
msg = '<p> %s contains the description of this error.' % path
except:
msg = '<p> Tried to save traceback to %s, but failed.' % path
self.file.write(msg + '\n')
try:
self.file.flush()
except: pass
handler = Hook().handle
def enable(display=1, logdir=None, context=5, format="html"):
"""Install an exception handler that formats tracebacks as HTML.
The optional argument 'display' can be set to 0 to suppress sending the
traceback to the browser, and 'logdir' can be set to a directory to cause
tracebacks to be written to files there."""
sys.excepthook = Hook(display=display, logdir=logdir,
context=context, format=format)

432
others/google.py Normal file
View File

@ -0,0 +1,432 @@
"""Python wrapper for Google web APIs
This module allows you to access Google's web APIs through SOAP,
to do things like search Google and get the results programmatically.
Described here:
http://www.google.com/apis/
You need a Google-provided license key to use these services.
Follow the link above to get one. These functions will look in
several places (in this order) for the license key:
- the "license_key" argument of each function
- the module-level LICENSE_KEY variable (call setLicense once to set it)
- an environment variable called GOOGLE_LICENSE_KEY
- a file called ".googlekey" in the current directory
- a file called "googlekey.txt" in the current directory
- a file called ".googlekey" in your home directory
- a file called "googlekey.txt" in your home directory
- a file called ".googlekey" in the same directory as google.py
- a file called "googlekey.txt" in the same directory as google.py
Sample usage:
>>> import google
>>> google.setLicense('...') # must get your own key!
>>> data = google.doGoogleSearch('python')
>>> data.meta.searchTime
0.043221000000000002
>>> data.results[0].URL
'http://www.python.org/'
>>> data.results[0].title
'<b>Python</b> Language Website'
See documentation of SearchResultsMetaData and SearchResult classes
for other available attributes.
"""
__author__ = "Mark Pilgrim (f8dy@diveintomark.org)"
__version__ = "0.5.2"
__cvsversion__ = "$Revision$"[11:-2]
__date__ = "$Date$"[7:-2]
__copyright__ = "Copyright (c) 2002 Mark Pilgrim"
__license__ = "Python"
__credits__ = """David Ascher, for the install script
Erik Max Francis, for the command line interface
Michael Twomey, for HTTP proxy support"""
import SOAP
import os, sys, getopt
LICENSE_KEY = None
HTTP_PROXY = None
# don't touch the rest of these constants
class NoLicenseKey(Exception): pass
_url = 'http://api.google.com/search/beta2'
_namespace = 'urn:GoogleSearch'
_false = SOAP.booleanType(0)
_true = SOAP.booleanType(1)
_googlefile1 = ".googlekey"
_googlefile2 = "googlekey.txt"
_licenseLocations = (
(lambda key: key, 'passed to the function in license_key variable'),
(lambda key: LICENSE_KEY, 'module-level LICENSE_KEY variable (call setLicense to set it)'),
(lambda key: os.environ.get('GOOGLE_LICENSE_KEY', None), 'an environment variable called GOOGLE_LICENSE_KEY'),
(lambda key: _contentsOf(os.getcwd(), _googlefile1), '%s in the current directory' % _googlefile1),
(lambda key: _contentsOf(os.getcwd(), _googlefile2), '%s in the current directory' % _googlefile2),
(lambda key: _contentsOf(os.environ.get('HOME', ''), _googlefile1), '%s in your home directory' % _googlefile1),
(lambda key: _contentsOf(os.environ.get('HOME', ''), _googlefile2), '%s in your home directory' % _googlefile2),
(lambda key: _contentsOf(_getScriptDir(), _googlefile1), '%s in the google.py directory' % _googlefile1),
(lambda key: _contentsOf(_getScriptDir(), _googlefile2), '%s in the google.py directory' % _googlefile2)
)
## administrative functions
def version():
print """PyGoogle %(__version__)s
%(__copyright__)s
released %(__date__)s
Thanks to:
%(__credits__)s""" % globals()
def usage():
program = os.path.basename(sys.argv[0])
print """Usage: %(program)s [options] [querytype] query
options:
-k, --key= <license key> Google license key (see important note below)
-1, -l, --lucky show only first hit
-m, --meta show meta information
-r, --reverse show results in reverse order
-x, --proxy= <url> use HTTP proxy
-h, --help print this help
-v, --version print version and copyright information
-t, --test run test queries
querytype:
-s, --search= <query> search (default)
-c, --cache= <url> retrieve cached page
-p, --spelling= <word> check spelling
IMPORTANT NOTE: all Google functions require a valid license key;
visit http://www.google.com/apis/ to get one. %(program)s will look in
these places (in order) and use the first license key it finds:
* the key specified on the command line""" % vars()
for get, location in _licenseLocations[2:]:
print " *", location
## utility functions
def setLicense(license_key):
"""set license key"""
global LICENSE_KEY
LICENSE_KEY = license_key
def getLicense(license_key = None):
"""get license key
license key can come from any number of locations;
see module docs for search order"""
for get, location in _licenseLocations:
rc = get(license_key)
if rc: return rc
usage()
raise NoLicenseKey, 'get a license key at http://www.google.com/apis/'
def setProxy(http_proxy):
"""set HTTP proxy"""
global HTTP_PROXY
HTTP_PROXY = http_proxy
def getProxy(http_proxy = None):
"""get HTTP proxy"""
return http_proxy or HTTP_PROXY
def _contentsOf(dirname, filename):
filename = os.path.join(dirname, filename)
if not os.path.exists(filename): return None
fsock = open(filename)
contents = fsock.read()
fsock.close()
return contents
def _getScriptDir():
if __name__ == '__main__':
return os.path.abspath(os.path.dirname(sys.argv[0]))
else:
return os.path.abspath(os.path.dirname(sys.modules[__name__].__file__))
def _marshalBoolean(value):
if value:
return _true
else:
return _false
## output formatters
def makeFormatter(outputFormat):
classname = "%sOutputFormatter" % outputFormat.capitalize()
return globals()[classname]()
def output(results, params):
formatter = makeFormatter(params.get("outputFormat", "text"))
outputmethod = getattr(formatter, params["func"])
outputmethod(results, params)
class OutputFormatter:
def boil(self, data):
if type(data) == type(u""):
return data.encode("ISO-8859-1", "replace")
else:
return data
class TextOutputFormatter(OutputFormatter):
def common(self, data, params):
if params.get("showMeta", 0):
meta = data.meta
for category in meta.directoryCategories:
print "directoryCategory: %s" % self.boil(category["fullViewableName"])
for attr in [node for node in dir(meta) if node <> "directoryCategories" and node[:2] <> '__']:
print "%s:" % attr, self.boil(getattr(meta, attr))
def doGoogleSearch(self, data, params):
results = data.results
if params.get("feelingLucky", 0):
results = results[:1]
if params.get("reverseOrder", 0):
results.reverse()
for result in results:
for attr in dir(result):
if attr == "directoryCategory":
print "directoryCategory:", self.boil(result.directoryCategory["fullViewableName"])
elif attr[:2] <> '__':
print "%s:" % attr, self.boil(getattr(result, attr))
print
self.common(data, params)
def doGetCachedPage(self, data, params):
print data
self.common(data, params)
doSpellingSuggestion = doGetCachedPage
## search results classes
class _SearchBase:
def __init__(self, params):
for k, v in params.items():
if isinstance(v, SOAP.structType):
v = v._asdict
try:
if isinstance(v[0], SOAP.structType):
v = [node._asdict for node in v]
except:
pass
self.__dict__[str(k)] = v
class SearchResultsMetaData(_SearchBase):
"""metadata of search query results
Available attributes:
documentFiltering - flag indicates whether duplicate page filtering was perfomed in this search
searchComments - human-readable informational message (example: "'the' is a very common word
and was not included in your search")
estimatedTotalResultsCount - estimated total number of results for this query
estimateIsExact - flag indicates whether estimatedTotalResultsCount is an exact value
searchQuery - search string that initiated this search
startIndex - index of first result returned (zero-based)
endIndex - index of last result returned (zero-based)
searchTips - human-readable informational message on how to use Google bette
directoryCategories - list of dictionaries like this:
{'fullViewableName': Open Directory category,
'specialEncoding': encoding scheme of this directory category}
searchTime - total search time, in seconds
"""
pass
class SearchResult(_SearchBase):
"""search result
Available attributes:
URL - URL
title - title (HTML)
snippet - snippet showing query context (HTML)
cachedSize - size of cached version of this result, (KB)
relatedInformationPresent - flag indicates that the "related:" keyword is supported for this URL
hostName: When filtering occurs, a maximum of two results from any given host is returned.
When this occurs, the second resultElement that comes from that host contains
the host name in this parameter.
directoryCategory: dictionary like this:
{'fullViewableName': Open Directory category,
'specialEncoding': encoding scheme of this directory category}
directoryTitle: Open Directory title of this result (or blank)
summary - Open Directory summary for this result (or blank)
"""
pass
class SearchReturnValue:
"""complete search results for a single query
Available attributes:
meta - SearchResultsMetaData
results - list of SearchResult
"""
def __init__(self, metadata, results):
self.meta = metadata
self.results = results
## main functions
def doGoogleSearch(q, start=0, maxResults=10, filter=1, restrict='',
safeSearch=0, language='', inputencoding='', outputencoding='',
license_key = None, http_proxy = None):
"""search Google
You need a license key to call this function; see
http://www.google.com/apis/ to get one. Then you can either pass it to
this function every time, or set it globally; see the module docs for details.
Parameters:
q - search string. Anything you could type at google.com, you can pass here.
See http://www.google.com/help/features.html for examples of advanced features.
start (optional) - zero-based index of first desired result (for paging through
multiple pages of results)
maxResults (optional) - maximum number of results, currently capped at 10
filter (optional) - set to 1 to filter out similar results, set to 0 to see everything
restrict (optional) - restrict results by country or topic. Examples:
Ukraine - search only sites located in Ukraine
linux - search Linux sites only
mac - search Mac sites only
bsd - search FreeBSD sites only
See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html)
for more advanced examples and a full list of country codes and topics.
safeSearch (optional) - set to 1 to filter results with SafeSearch (no adult material)
language (optional) - restricts search to documents in one or more languages. Example:
lang_en - only return pages in English
lang_fr - only return pages in French
See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html)
for more advanced examples and a full list of language codes.
inputencoding (optional) - sets the character encoding of q parameter
outputencoding (optional) - sets the character encoding of the returned results
See the APIs_reference.html file in the SDK (http://www.google.com/apis/download.html)
for a full list of encodings.
http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages
Returns: SearchReturnValue
.meta - SearchMetaData
.results - list of SearchResult
See documentation of these individual classes for list of available attributes
"""
http_proxy = getProxy(http_proxy)
remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy)
license_key = getLicense(license_key)
filter = _marshalBoolean(filter)
safeSearch = _marshalBoolean(safeSearch)
data = remoteserver.doGoogleSearch(license_key, q, start, maxResults, filter, restrict,
safeSearch, language, inputencoding, outputencoding)
metadata = data._asdict
del metadata["resultElements"]
metadata = SearchResultsMetaData(metadata)
results = [SearchResult(node._asdict) for node in data.resultElements]
return SearchReturnValue(metadata, results)
def doGetCachedPage(url, license_key = None, http_proxy = None):
"""get page from Google cache
You need a license key to call this function; see
http://www.google.com/apis/ to get one. Then you can either pass it to
this function every time, or set it globally; see the module docs for details.
Parameters:
url - address of page to get
license_key (optional) - Google license key
http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages
Returns: string, text of cached page
"""
http_proxy = getProxy(http_proxy)
remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy)
license_key = getLicense(license_key)
return remoteserver.doGetCachedPage(license_key, url)
def doSpellingSuggestion(phrase, license_key = None, http_proxy = None):
"""get spelling suggestions from Google
You need a license key to call this function; see
http://www.google.com/apis/ to get one. Then you can either pass it to
this function every time, or set it globally; see the module docs for details.
Parameters:
phrase - word or phrase to spell-check
http_proxy (optional) - address of HTTP proxy to use for sending and receiving SOAP messages
Returns: text of suggested replacement, or None
"""
http_proxy = getProxy(http_proxy)
remoteserver = SOAP.SOAPProxy(_url, namespace=_namespace, http_proxy=http_proxy)
license_key = getLicense(license_key)
return remoteserver.doSpellingSuggestion(license_key, phrase)
## functional test suite (see googletest.py for unit test suite)
def test():
try:
getLicense(None)
except NoLicenseKey:
return
print "Searching for Python at google.com..."
data = doGoogleSearch("Python")
output(data, {"func": "doGoogleSearch"})
print "\nSearching for 5 _French_ pages about Python, encoded in ISO-8859-1..."
data = doGoogleSearch("Python", language='lang_fr', outputencoding='ISO-8859-1', maxResults=5)
output(data, {"func": "doGoogleSearch"})
phrase = "Pyhton programming languager"
print "\nTesting spelling suggetions for '%s'..." % phrase
data = doSpellingSuggestion(phrase)
output(data, {"func": "doSpellingSuggestion"})
## main driver for command-line use
def main(argv):
if not argv:
usage()
return
q = None
func = None
http_proxy = None
license_key = None
feelingLucky = 0
showMeta = 0
reverseOrder = 0
runTest = 0
outputFormat = "text"
try:
opts, args = getopt.getopt(argv, "s:c:p:k:lmrx:hvt1",
["search=", "cache=", "spelling=", "key=", "lucky", "meta", "reverse", "proxy=", "help", "version", "test"])
except getopt.GetoptError:
usage()
sys.exit(2)
for opt, arg in opts:
if opt in ("-s", "--search"):
q = arg
func = "doGoogleSearch"
elif opt in ("-c", "--cache"):
q = arg
func = "doGetCachedPage"
elif opt in ("-p", "--spelling"):
q = arg
func = "doSpellingSuggestion"
elif opt in ("-k", "--key"):
license_key = arg
elif opt in ("-l", "-1", "--lucky"):
feelingLucky = 1
elif opt in ("-m", "--meta"):
showMeta = 1
elif opt in ("-r", "--reverse"):
reverseOrder = 1
elif opt in ("-x", "--proxy"):
http_proxy = arg
elif opt in ("-h", "--help"):
usage()
elif opt in ("-v", "--version"):
version()
elif opt in ("-t", "--test"):
runTest = 1
if runTest:
setLicense(license_key)
setProxy(http_proxy)
test()
if args and not q:
q = args[0]
func = "doGoogleSearch"
if func:
results = globals()[func](q, http_proxy=http_proxy, license_key=license_key)
output(results, locals())
if __name__ == '__main__':
main(sys.argv[1:])

212
others/shlex.py Normal file
View File

@ -0,0 +1,212 @@
"""A lexical analyzer class for simple shell-like syntaxes."""
# Module and documentation by Eric S. Raymond, 21 Dec 1998
# Input stacking and error message cleanup added by ESR, March 2000
# push_source() and pop_source() made explicit by ESR, January 2001.
import os.path
import sys
__all__ = ["shlex"]
class shlex:
"A lexical analyzer class for simple shell-like syntaxes."
def __init__(self, instream=None, infile=None):
if instream is not None:
self.instream = instream
self.infile = infile
else:
self.instream = sys.stdin
self.infile = None
self.commenters = '#'
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_')
self.whitespace = ' \t\r\n'
self.quotes = '\'"'
self.state = ' '
self.pushback = []
self.lineno = 1
self.debug = 0
self.token = ''
self.filestack = []
self.source = None
if self.debug:
print 'shlex: reading from %s, line %d' \
% (self.instream, self.lineno)
def push_token(self, tok):
"Push a token onto the stack popped by the get_token method"
if self.debug >= 1:
print "shlex: pushing token " + `tok`
self.pushback = [tok] + self.pushback
def push_source(self, newstream, newfile=None):
"Push an input source onto the lexer's input source stack."
self.filestack.insert(0, (self.infile, self.instream, self.lineno))
self.infile = newfile
self.instream = newstream
self.lineno = 1
if self.debug:
if newfile is not None:
print 'shlex: pushing to file %s' % (self.infile,)
else:
print 'shlex: pushing to stream %s' % (self.instream,)
def pop_source(self):
"Pop the input source stack."
self.instream.close()
(self.infile, self.instream, self.lineno) = self.filestack[0]
self.filestack = self.filestack[1:]
if self.debug:
print 'shlex: popping to %s, line %d' \
% (self.instream, self.lineno)
self.state = ' '
def get_token(self):
"Get a token from the input stream (or from stack if it's nonempty)"
if self.pushback:
tok = self.pushback[0]
self.pushback = self.pushback[1:]
if self.debug >= 1:
print "shlex: popping token " + `tok`
return tok
# No pushback. Get a token.
raw = self.read_token()
# Handle inclusions
while raw == self.source:
spec = self.sourcehook(self.read_token())
if spec:
(newfile, newstream) = spec
self.push_source(newstream, newfile)
raw = self.get_token()
# Maybe we got EOF instead?
while raw == "":
if len(self.filestack) == 0:
return ""
else:
self.pop_source()
raw = self.get_token()
# Neither inclusion nor EOF
if self.debug >= 1:
if raw:
print "shlex: token=" + `raw`
else:
print "shlex: token=EOF"
return raw
def read_token(self):
"Read a token from the input stream (no pushback or inclusions)"
while 1:
nextchar = self.instream.read(1)
if nextchar == '\n':
self.lineno = self.lineno + 1
if self.debug >= 3:
print "shlex: in state", repr(self.state), \
"I see character:", repr(nextchar)
if self.state is None:
self.token = '' # past end of file
break
elif self.state == ' ':
if not nextchar:
self.state = None # end of file
break
elif nextchar in self.whitespace:
if self.debug >= 2:
print "shlex: I see whitespace in whitespace state"
if self.token:
break # emit current token
else:
continue
elif nextchar in self.commenters:
self.instream.readline()
self.lineno = self.lineno + 1
elif nextchar in self.wordchars:
self.token = nextchar
self.state = 'a'
elif nextchar in self.quotes:
self.token = nextchar
self.state = nextchar
else:
self.token = nextchar
if self.token:
break # emit current token
else:
continue
elif self.state in self.quotes:
self.token = self.token + nextchar
if nextchar == self.state:
self.state = ' '
break
elif not nextchar: # end of file
if self.debug >= 2:
print "shlex: I see EOF in quotes state"
# XXX what error should be raised here?
raise ValueError, "No closing quotation"
elif self.state == 'a':
if not nextchar:
self.state = None # end of file
break
elif nextchar in self.whitespace:
if self.debug >= 2:
print "shlex: I see whitespace in word state"
self.state = ' '
if self.token:
break # emit current token
else:
continue
elif nextchar in self.commenters:
self.instream.readline()
self.lineno = self.lineno + 1
elif nextchar in self.wordchars:
self.token = self.token + nextchar
elif nextchar in self.quotes:
self.state = nextchar
break
else:
self.pushback = [nextchar] + self.pushback
if self.debug >= 2:
print "shlex: I see punctuation in word state"
self.state = ' '
if self.token:
break # emit current token
else:
continue
result = self.token
self.token = ''
if self.debug > 1:
if result:
print "shlex: raw token=" + `result`
else:
print "shlex: raw token=EOF"
return result
def sourcehook(self, newfile):
"Hook called on a filename to be sourced."
if newfile[0] == '"':
newfile = newfile[1:-1]
# This implements cpp-like semantics for relative-path inclusion.
if type(self.infile) == type("") and not os.path.isabs(newfile):
newfile = os.path.join(os.path.dirname(self.infile), newfile)
return (newfile, open(newfile, "r"))
def error_leader(self, infile=None, lineno=None):
"Emit a C-compiler-like, Emacs-friendly error-message leader."
if infile is None:
infile = self.infile
if lineno is None:
lineno = self.lineno
return "\"%s\", line %d: " % (infile, lineno)
if __name__ == '__main__':
if len(sys.argv) == 1:
lexer = shlex()
else:
file = sys.argv[1]
lexer = shlex(open(file), file)
while 1:
tt = lexer.get_token()
if tt:
print "Token: " + repr(tt)
else:
break

722
others/unittest.py Normal file
View File

@ -0,0 +1,722 @@
#!/usr/bin/env python
'''
Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's
Smalltalk testing framework.
This module contains the core framework classes that form the basis of
specific test cases and suites (TestCase, TestSuite etc.), and also a
text-based utility class for running the tests and reporting the results
(TextTestRunner).
Simple usage:
import unittest
class IntegerArithmenticTestCase(unittest.TestCase):
def testAdd(self): ## test method names begin 'test*'
self.assertEquals((1 + 2), 3)
self.assertEquals(0 + 1, 1)
def testMultiply(self):
self.assertEquals((0 * 10), 0)
self.assertEquals((5 * 8), 40)
if __name__ == '__main__':
unittest.main()
Further information is available in the bundled documentation, and from
http://pyunit.sourceforge.net/
Copyright (c) 1999, 2000, 2001 Steve Purcell
This module is free software, and you may redistribute it and/or modify
it under the same terms as Python itself, so long as this copyright message
and disclaimer are retained in their original form.
IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
'''
__author__ = "Steve Purcell"
__email__ = "stephen_purcell at yahoo dot com"
__version__ = "$Revision$"[11:-2]
import time
import sys
import traceback
import string
import os
import types
##############################################################################
# Test framework core
##############################################################################
class TestResult:
"""Holder for test result information.
Test results are automatically managed by the TestCase and TestSuite
classes, and do not need to be explicitly manipulated by writers of tests.
Each instance holds the total number of tests run, and collections of
failures and errors that occurred among those test runs. The collections
contain tuples of (testcase, exceptioninfo), where exceptioninfo is the
formatted traceback of the error that occurred.
"""
def __init__(self):
self.failures = []
self.errors = []
self.testsRun = 0
self.shouldStop = 0
def startTest(self, test):
"Called when the given test is about to be run"
self.testsRun = self.testsRun + 1
def stopTest(self, test):
"Called when the given test has been run"
pass
def addError(self, test, err):
"""Called when an error has occurred. 'err' is a tuple of values as
returned by sys.exc_info().
"""
self.errors.append((test, self._exc_info_to_string(err)))
def addFailure(self, test, err):
"""Called when an error has occurred. 'err' is a tuple of values as
returned by sys.exc_info()."""
self.failures.append((test, self._exc_info_to_string(err)))
def addSuccess(self, test):
"Called when a test has completed successfully"
pass
def wasSuccessful(self):
"Tells whether or not this result was a success"
return len(self.failures) == len(self.errors) == 0
def stop(self):
"Indicates that the tests should be aborted"
self.shouldStop = 1
def _exc_info_to_string(self, err):
"""Converts a sys.exc_info()-style tuple of values into a string."""
return string.join(apply(traceback.format_exception, err), '')
def __repr__(self):
return "<%s run=%i errors=%i failures=%i>" % \
(self.__class__, self.testsRun, len(self.errors),
len(self.failures))
class TestCase:
"""A class whose instances are single test cases.
By default, the test code itself should be placed in a method named
'runTest'.
If the fixture may be used for many test cases, create as
many test methods as are needed. When instantiating such a TestCase
subclass, specify in the constructor arguments the name of the test method
that the instance is to execute.
Test authors should subclass TestCase for their own tests. Construction
and deconstruction of the test's environment ('fixture') can be
implemented by overriding the 'setUp' and 'tearDown' methods respectively.
If it is necessary to override the __init__ method, the base class
__init__ method must always be called. It is important that subclasses
should not change the signature of their __init__ method, since instances
of the classes are instantiated automatically by parts of the framework
in order to be run.
"""
# This attribute determines which exception will be raised when
# the instance's assertion methods fail; test methods raising this
# exception will be deemed to have 'failed' rather than 'errored'
failureException = AssertionError
def __init__(self, methodName='runTest'):
"""Create an instance of the class that will use the named test
method when executed. Raises a ValueError if the instance does
not have a method with the specified name.
"""
try:
self.__testMethodName = methodName
testMethod = getattr(self, methodName)
self.__testMethodDoc = testMethod.__doc__
except AttributeError:
raise ValueError, "no such test method in %s: %s" % \
(self.__class__, methodName)
def setUp(self):
"Hook method for setting up the test fixture before exercising it."
pass
def tearDown(self):
"Hook method for deconstructing the test fixture after testing it."
pass
def countTestCases(self):
return 1
def defaultTestResult(self):
return TestResult()
def shortDescription(self):
"""Returns a one-line description of the test, or None if no
description has been provided.
The default implementation of this method returns the first line of
the specified test method's docstring.
"""
doc = self.__testMethodDoc
return doc and string.strip(string.split(doc, "\n")[0]) or None
def id(self):
return "%s.%s" % (self.__class__, self.__testMethodName)
def __str__(self):
return "%s (%s)" % (self.__testMethodName, self.__class__)
def __repr__(self):
return "<%s testMethod=%s>" % \
(self.__class__, self.__testMethodName)
def run(self, result=None):
return self(result)
def __call__(self, result=None):
if result is None: result = self.defaultTestResult()
result.startTest(self)
testMethod = getattr(self, self.__testMethodName)
try:
try:
self.setUp()
except KeyboardInterrupt:
raise
except:
result.addError(self, self.__exc_info())
return
ok = 0
try:
testMethod()
ok = 1
except self.failureException:
result.addFailure(self, self.__exc_info())
except KeyboardInterrupt:
raise
except:
result.addError(self, self.__exc_info())
try:
self.tearDown()
except KeyboardInterrupt:
raise
except:
result.addError(self, self.__exc_info())
ok = 0
if ok: result.addSuccess(self)
finally:
result.stopTest(self)
def debug(self):
"""Run the test without collecting errors in a TestResult"""
self.setUp()
getattr(self, self.__testMethodName)()
self.tearDown()
def __exc_info(self):
"""Return a version of sys.exc_info() with the traceback frame
minimised; usually the top level of the traceback frame is not
needed.
"""
exctype, excvalue, tb = sys.exc_info()
if sys.platform[:4] == 'java': ## tracebacks look different in Jython
return (exctype, excvalue, tb)
newtb = tb.tb_next
if newtb is None:
return (exctype, excvalue, tb)
return (exctype, excvalue, newtb)
def fail(self, msg=None):
"""Fail immediately, with the given message."""
raise self.failureException, msg
def failIf(self, expr, msg=None):
"Fail the test if the expression is true."
if expr: raise self.failureException, msg
def failUnless(self, expr, msg=None):
"""Fail the test unless the expression is true."""
if not expr: raise self.failureException, msg
def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
"""Fail unless an exception of class excClass is thrown
by callableObj when invoked with arguments args and keyword
arguments kwargs. If a different type of exception is
thrown, it will not be caught, and the test case will be
deemed to have suffered an error, exactly as for an
unexpected exception.
"""
try:
apply(callableObj, args, kwargs)
except excClass:
return
else:
if hasattr(excClass,'__name__'): excName = excClass.__name__
else: excName = str(excClass)
raise self.failureException, excName
def failUnlessEqual(self, first, second, msg=None):
"""Fail if the two objects are unequal as determined by the '!='
operator.
"""
if first != second:
raise self.failureException, \
(msg or '%s != %s' % (`first`, `second`))
def failIfEqual(self, first, second, msg=None):
"""Fail if the two objects are equal as determined by the '=='
operator.
"""
if first == second:
raise self.failureException, \
(msg or '%s == %s' % (`first`, `second`))
assertEqual = assertEquals = failUnlessEqual
assertNotEqual = assertNotEquals = failIfEqual
assertRaises = failUnlessRaises
assert_ = failUnless
class TestSuite:
"""A test suite is a composite test consisting of a number of TestCases.
For use, create an instance of TestSuite, then add test case instances.
When all tests have been added, the suite can be passed to a test
runner, such as TextTestRunner. It will run the individual test cases
in the order in which they were added, aggregating the results. When
subclassing, do not forget to call the base class constructor.
"""
def __init__(self, tests=()):
self._tests = []
self.addTests(tests)
def __repr__(self):
return "<%s tests=%s>" % (self.__class__, self._tests)
__str__ = __repr__
def countTestCases(self):
cases = 0
for test in self._tests:
cases = cases + test.countTestCases()
return cases
def addTest(self, test):
self._tests.append(test)
def addTests(self, tests):
for test in tests:
self.addTest(test)
def run(self, result):
return self(result)
def __call__(self, result):
for test in self._tests:
if result.shouldStop:
break
test(result)
return result
def debug(self):
"""Run the tests without collecting errors in a TestResult"""
for test in self._tests: test.debug()
class FunctionTestCase(TestCase):
"""A test case that wraps a test function.
This is useful for slipping pre-existing test functions into the
PyUnit framework. Optionally, set-up and tidy-up functions can be
supplied. As with TestCase, the tidy-up ('tearDown') function will
always be called if the set-up ('setUp') function ran successfully.
"""
def __init__(self, testFunc, setUp=None, tearDown=None,
description=None):
TestCase.__init__(self)
self.__setUpFunc = setUp
self.__tearDownFunc = tearDown
self.__testFunc = testFunc
self.__description = description
def setUp(self):
if self.__setUpFunc is not None:
self.__setUpFunc()
def tearDown(self):
if self.__tearDownFunc is not None:
self.__tearDownFunc()
def runTest(self):
self.__testFunc()
def id(self):
return self.__testFunc.__name__
def __str__(self):
return "%s (%s)" % (self.__class__, self.__testFunc.__name__)
def __repr__(self):
return "<%s testFunc=%s>" % (self.__class__, self.__testFunc)
def shortDescription(self):
if self.__description is not None: return self.__description
doc = self.__testFunc.__doc__
return doc and string.strip(string.split(doc, "\n")[0]) or None
##############################################################################
# Locating and loading tests
##############################################################################
class TestLoader:
"""This class is responsible for loading tests according to various
criteria and returning them wrapped in a Test
"""
testMethodPrefix = 'test'
sortTestMethodsUsing = cmp
suiteClass = TestSuite
def loadTestsFromTestCase(self, testCaseClass):
"""Return a suite of all tests cases contained in testCaseClass"""
return self.suiteClass(map(testCaseClass,
self.getTestCaseNames(testCaseClass)))
def loadTestsFromModule(self, module):
"""Return a suite of all tests cases contained in the given module"""
tests = []
for name in dir(module):
obj = getattr(module, name)
if type(obj) == types.ClassType and issubclass(obj, TestCase):
tests.append(self.loadTestsFromTestCase(obj))
return self.suiteClass(tests)
def loadTestsFromName(self, name, module=None):
"""Return a suite of all tests cases given a string specifier.
The name may resolve either to a module, a test case class, a
test method within a test case class, or a callable object which
returns a TestCase or TestSuite instance.
The method optionally resolves the names relative to a given module.
"""
parts = string.split(name, '.')
if module is None:
if not parts:
raise ValueError, "incomplete test name: %s" % name
else:
parts_copy = parts[:]
while parts_copy:
try:
module = __import__(string.join(parts_copy,'.'))
break
except ImportError:
del parts_copy[-1]
if not parts_copy: raise
parts = parts[1:]
obj = module
for part in parts:
obj = getattr(obj, part)
if type(obj) == types.ModuleType:
return self.loadTestsFromModule(obj)
elif type(obj) == types.ClassType and issubclass(obj, unittest.TestCase):
return self.loadTestsFromTestCase(obj)
elif type(obj) == types.UnboundMethodType:
return obj.im_class(obj.__name__)
elif callable(obj):
test = obj()
if not isinstance(test, unittest.TestCase) and \
not isinstance(test, unittest.TestSuite):
raise ValueError, \
"calling %s returned %s, not a test" % (obj,test)
return test
else:
raise ValueError, "don't know how to make test from: %s" % obj
def loadTestsFromNames(self, names, module=None):
"""Return a suite of all tests cases found using the given sequence
of string specifiers. See 'loadTestsFromName()'.
"""
suites = []
for name in names:
suites.append(self.loadTestsFromName(name, module))
return self.suiteClass(suites)
def getTestCaseNames(self, testCaseClass):
"""Return a sorted sequence of method names found within testCaseClass
"""
testFnNames = filter(lambda n,p=self.testMethodPrefix: n[:len(p)] == p,
dir(testCaseClass))
for baseclass in testCaseClass.__bases__:
for testFnName in self.getTestCaseNames(baseclass):
if testFnName not in testFnNames: # handle overridden methods
testFnNames.append(testFnName)
if self.sortTestMethodsUsing:
testFnNames.sort(self.sortTestMethodsUsing)
return testFnNames
defaultTestLoader = TestLoader()
##############################################################################
# Patches for old functions: these functions should be considered obsolete
##############################################################################
def _makeLoader(prefix, sortUsing, suiteClass=None):
loader = TestLoader()
loader.sortTestMethodsUsing = sortUsing
loader.testMethodPrefix = prefix
if suiteClass: loader.suiteClass = suiteClass
return loader
def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp):
return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass)
def makeSuite(testCaseClass, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass)
def findTestCases(module, prefix='test', sortUsing=cmp, suiteClass=TestSuite):
return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module)
##############################################################################
# Text UI
##############################################################################
class _WritelnDecorator:
"""Used to decorate file-like objects with a handy 'writeln' method"""
def __init__(self,stream):
self.stream = stream
def __getattr__(self, attr):
return getattr(self.stream,attr)
def writeln(self, *args):
if args: apply(self.write, args)
self.write('\n') # text-mode streams translate to \r\n if needed
class _TextTestResult(TestResult):
"""A test result class that can print formatted text results to a stream.
Used by TextTestRunner.
"""
separator1 = '=' * 70
separator2 = '-' * 70
def __init__(self, stream, descriptions, verbosity):
TestResult.__init__(self)
self.stream = stream
self.showAll = verbosity > 1
self.dots = verbosity == 1
self.descriptions = descriptions
def getDescription(self, test):
if self.descriptions:
return test.shortDescription() or str(test)
else:
return str(test)
def startTest(self, test):
TestResult.startTest(self, test)
if self.showAll:
self.stream.write(self.getDescription(test))
self.stream.write(" ... ")
def addSuccess(self, test):
TestResult.addSuccess(self, test)
if self.showAll:
self.stream.writeln("ok")
elif self.dots:
self.stream.write('.')
def addError(self, test, err):
TestResult.addError(self, test, err)
if self.showAll:
self.stream.writeln("ERROR")
elif self.dots:
self.stream.write('E')
def addFailure(self, test, err):
TestResult.addFailure(self, test, err)
if self.showAll:
self.stream.writeln("FAIL")
elif self.dots:
self.stream.write('F')
def printErrors(self):
if self.dots or self.showAll:
self.stream.writeln()
self.printErrorList('ERROR', self.errors)
self.printErrorList('FAIL', self.failures)
def printErrorList(self, flavour, errors):
for test, err in errors:
self.stream.writeln(self.separator1)
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
self.stream.writeln(self.separator2)
self.stream.writeln("%s" % err)
class TextTestRunner:
"""A test runner class that displays results in textual form.
It prints out the names of tests as they are run, errors as they
occur, and a summary of the results at the end of the test run.
"""
def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1):
self.stream = _WritelnDecorator(stream)
self.descriptions = descriptions
self.verbosity = verbosity
def _makeResult(self):
return _TextTestResult(self.stream, self.descriptions, self.verbosity)
def run(self, test):
"Run the given test case or test suite."
result = self._makeResult()
startTime = time.time()
test(result)
stopTime = time.time()
timeTaken = float(stopTime - startTime)
result.printErrors()
self.stream.writeln(result.separator2)
run = result.testsRun
self.stream.writeln("Ran %d test%s in %.3fs" %
(run, run == 1 and "" or "s", timeTaken))
self.stream.writeln()
if not result.wasSuccessful():
self.stream.write("FAILED (")
failed, errored = map(len, (result.failures, result.errors))
if failed:
self.stream.write("failures=%d" % failed)
if errored:
if failed: self.stream.write(", ")
self.stream.write("errors=%d" % errored)
self.stream.writeln(")")
else:
self.stream.writeln("OK")
return result
##############################################################################
# Facilities for running tests from the command line
##############################################################################
class TestProgram:
"""A command-line program that runs a set of tests; this is primarily
for making test modules conveniently executable.
"""
USAGE = """\
Usage: %(progName)s [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
Examples:
%(progName)s - run default set of tests
%(progName)s MyTestSuite - run suite 'MyTestSuite'
%(progName)s MyTestCase.testSomething - run MyTestCase.testSomething
%(progName)s MyTestCase - run all 'test*' test methods
in MyTestCase
"""
def __init__(self, module='__main__', defaultTest=None,
argv=None, testRunner=None, testLoader=defaultTestLoader):
if type(module) == type(''):
self.module = __import__(module)
for part in string.split(module,'.')[1:]:
self.module = getattr(self.module, part)
else:
self.module = module
if argv is None:
argv = sys.argv
self.verbosity = 1
self.defaultTest = defaultTest
self.testRunner = testRunner
self.testLoader = testLoader
self.progName = os.path.basename(argv[0])
self.parseArgs(argv)
self.runTests()
def usageExit(self, msg=None):
if msg: print msg
print self.USAGE % self.__dict__
sys.exit(2)
def parseArgs(self, argv):
import getopt
try:
options, args = getopt.getopt(argv[1:], 'hHvq',
['help','verbose','quiet'])
for opt, value in options:
if opt in ('-h','-H','--help'):
self.usageExit()
if opt in ('-q','--quiet'):
self.verbosity = 0
if opt in ('-v','--verbose'):
self.verbosity = 2
if len(args) == 0 and self.defaultTest is None:
self.test = self.testLoader.loadTestsFromModule(self.module)
return
if len(args) > 0:
self.testNames = args
else:
self.testNames = (self.defaultTest,)
self.createTests()
except getopt.error, msg:
self.usageExit(msg)
def createTests(self):
self.test = self.testLoader.loadTestsFromNames(self.testNames,
self.module)
def runTests(self):
if self.testRunner is None:
self.testRunner = TextTestRunner(verbosity=self.verbosity)
result = self.testRunner.run(self.test)
sys.exit(not result.wasSuccessful())
main = TestProgram
##############################################################################
# Executing this module from the command line
##############################################################################
if __name__ == "__main__":
main(module=None)

1114
others/urllib2.py Normal file

File diff suppressed because it is too large Load Diff

107
plugins/BadWords.py Normal file
View File

@ -0,0 +1,107 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import re
import ircdb
import ircmsgs
import privmsgs
import callbacks
class BadWords(callbacks.Privmsg):
def __init__(self):
callbacks.Privmsg.__init__(self)
self.badwords = set()
def outFilter(self, irc, msg):
if hasattr(self, 'regexp') and msg.command == 'PRIVMSG':
s = msg.args[1]
s = self.regexp.sub('!@#$', s)
return ircmsgs.privmsg(msg.args[0], s)
else:
return msg
def makeRegexp(self):
self.regexp = re.compile('|'.join(self.badwords), re.I)
def addbadword(self, irc, msg, args):
"<word>"
if ircdb.checkCapability(msg.prefix, 'admin'):
word = privmsgs.getArgs(args)
self.badwords.add(word)
self.makeRegexp()
irc.reply(msg, conf.replySuccess)
return
else:
irc.error(msg, conf.replyNoCapability % 'admin')
return
def addbadwords(self, irc, msg, args):
"<word> [<word> ...]"
if ircdb.checkCapability(msg.prefix, 'admin'):
words = privmsgs.getArgs(args).split()
self.badwords.extend(words)
self.makeRegexp()
irc.reply(msg, conf.replySuccess)
return
else:
irc.error(msg, conf.replyNoCapability % 'admin')
return
def removebadword(self, irc, msg, args):
"<word>"
if ircdb.checkCapability(msg.prefix, 'admin'):
word = privmsgs.getArgs(args)
self.badwords.remove(word)
self.makeRegexp()
irc.reply(msg, conf.replySuccess)
return
else:
irc.error(msg, conf.replyNoCapability % 'admin')
return
def removebadwords(self, irc, msg, args):
"<word> [<word> ...]"
if ircdb.checkCapability(msg.prefix, 'admin'):
words = privmsgs.getArgs(args).split()
for word in words:
self.badwords.remove(word)
self.makeRegexp()
irc.reply(msg, conf.replySuccess)
return
else:
irc.error(msg, conf.replyNoCapability % 'admin')
return
Class = BadWords

153
plugins/ChannelLogger.py Normal file
View File

@ -0,0 +1,153 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""Logger: Logs channels in a format similar to most IRC clients."""
from baseplugin import *
import re
import time
from cStringIO import StringIO
import conf
import debug
import world
import irclib
import ircmsgs
###
# Logger: Handles logging of IRC channels to files.
###
class ChannelLogger(irclib.IrcCallback):
logs = {}
actionre = re.compile('\x01ACTION (.*)\x01')
def __init__(self):
self.laststate = None
world.flushers.append(self.flush)
def __call__(self, irc, msg):
super(self.__class__, self).__call__(irc, msg)
#self.__class__.__bases__[0].__call__(self, irc, msg)
self.laststate = irc.state.copy()
def reset(self):
for log in self.logs.itervalues():
log.close()
self.logs = {}
def flush(self):
for log in self.logs.itervalues():
log.flush()
def getLog(self, channel):
if channel in self.logs:
return self.logs[channel]
else:
try:
log = file(os.path.join(conf.logDir, '%s.log' % channel), 'a')
self.logs[channel] = log
return log
except IOError:
debug.recoverableException()
return StringIO()
def timestamp(self, log):
log.write(time.strftime(conf.timestampFormat))
log.write(' ')
def doPrivmsg(self, irc, msg):
(recipients, text) = msg.args
for channel in recipients.split(','):
log = self.getLog(channel)
self.timestamp(log)
m = re.match(self.actionre, text)
if m:
log.write('* %s %s\n' % (msg.nick, m.group(1)))
else:
log.write('<%s> %s\n' % (msg.nick, text))
def doNotice(self, irc, msg):
(recipients, text) = msg.args
for channel in recipients.split(','):
log = self.getLog(channel)
self.timestamp(log)
log.write('-%s- %s\n' % (msg.nick, text))
def doJoin(self, irc, msg):
for channel in msg.args[0].split(','):
log = self.getLog(channel)
self.timestamp(log)
log.write('*** %s has joined %s\n' %\
(msg.nick or msg.prefix, channel))
def doKick(self, irc, msg):
(channel, target, kickmsg) = msg.args
log = self.getLog(channel)
self.timestamp(log)
log.write('*** %s was kicked by %s (%s)\n' % \
(target, msg.nick, kickmsg))
def doPart(self, irc, msg):
for channel in msg.args[0].split(','):
log = self.getLog(channel)
self.timestamp(log)
log.write('*** %s has left %s\n' % (msg.nick, channel))
def doMode(self, irc, msg):
channel = msg.args[0]
log = self.getLog(channel)
self.timestamp(log)
log.write('*** %s sets mode: %s %s\n' % \
(msg.nick or msg.prefix, msg.args[1], ' '.join(msg.args[2:])))
def doTopic(self, irc, msg):
channel = msg.args[0]
log = self.getLog(channel)
self.timestamp(log)
log.write('*** %s changes topic to "%s"\n' % (msg.nick, msg.args[1]))
def doQuit(self, irc, msg):
for (channel, chan) in self.laststate.channels.iteritems():
if msg.nick in chan.users:
log = self.getLog(channel)
log.write('*** %s has quit IRC\n' % msg.nick)
def outFilter(self, irc, msg):
# Gotta catch my own messages *somehow* :)
# Let's try this little trick...
if msg.command == 'PRIVMSG' or msg.command == 'NOTICE':
newmsgstr = ':%s!x@y %s %s :%s' % (irc.nick, msg.command,
msg.args[0], msg.args[1:])
self(irc, ircmsgs.IrcMsg(newmsgstr))
return msg
Class = ChannelLogger

80
plugins/ChannelStats.py Normal file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import re
import time
import privmsgs
import ircutils
import callbacks
smileys = (':)', ';)', ':]', ':-)', ':-D', ':D', ':P', ':p', '(=', '=)')
frowns = (':|', ':-/', ':-\\', ':\\', ':/', ':(', ':-(', ':\'(')
smileyRegexp = re.compile('|'.join([re.escape(s) for s in smileys]))
frownRegexp = re.compile('|'.join([re.escape(s) for s in frowns]))
class ChannelStats(callbacks.Privmsg, DBHandler):
def __init__(self):
callbacks.Privmsg.__init__(self)
DBHandler.__init__(self, '.stats')
def doPrivmsg(self, irc, msg):
recipient = msg.args[0]
if ircutils.isChannel(recipient):
db = self.getDb(recipient)
db[msg.nick] = (time.time(), msg.args[1])
#callbacks.Privmsg.doPrivmsg(self, irc, msg)
super(self.__class__, self).doPrivmsg(irc, msg)
#self.__class__.__bases__[0].doPrivmsg(self, irc, msg)
def seen(self, irc, msg, args):
"<nick>"
channel = privmsgs.getChannel(msg, args)
nick = ircutils.nick(privmsgs.getArgs(args))
db = self.getDb(channel)
try:
(t, saying) = db[nick]
if t == 0.0: # Default record time.
raise KeyError
t = time.localtime(t)
ret = '%s was last seen on %s on %s at %s saying {%s}' %\
(nick, channel, time.strftime('%d-%b-%Y', t),
time.strftime('%H:%M:%S'), saying)
irc.reply(msg, ret)
except KeyError:
irc.reply(msg, 'I haven\'t seen any user by that nick.')
Class = ChannelStats

74
plugins/Ctcp.py Normal file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import os
import sys
import time
sys.path.append(os.pardir)
import ircmsgs
import callbacks
notice = ircmsgs.notice
class Ctcp(callbacks.PrivmsgRegexp):
public = False
def ping(self, irc, msg, match):
"\x01PING (.*)\x01"
irc.queueMsg(notice(msg.nick, '\x01PING %s\x01' % match.group(1)))
def version(self, irc, msg, match):
"\x01VERSION\x01"
s = '\x01VERSION SupyBot %s\x01' % world.version
irc.queueMsg(notice(msg.nick, s))
def userinfo(self, irc, msg, match):
"\x01USERINFO\x01"
irc.queueMsg(notice(msg.nick, '\x01USERINFO\x01'))
def time(self, irc, msg, match):
"\x01TIME\x01"
irc.queueMsg(notice(msg.nick, '\x01%s\x01' % time.ctime()))
def finger(self, irc, msg, match):
"\x01FINGER\x01"
s = '\x01SupyBot, the best Python bot in existence!\x01'
irc.queueMsg(notice(msg.nick, s))
def source(self, irc, msg, match):
"\x01SOURCE\x01"
s = 'http://www.sourceforge.net/projects/supybot/'
irc.queueMsg(notice(msg.nick, s))
Class = Ctcp

212
plugins/Factoids.py Normal file
View File

@ -0,0 +1,212 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import time
import os.path
import sqlite
import conf
import ircdb
import privmsgs
import callbacks
class Factoids(DBHandler, callbacks.Privmsg):
def __init__(self):
DBHandler.__init__(self)
callbacks.Privmsg.__init__(self)
def makeDb(self, filename):
if os.path.exists(filename):
return sqlite.connect(filename)
db = sqlite.connect(filename)
cursor = db.cursor()
cursor.execute("""CREATE TABLE keys (
id INTEGER PRIMARY KEY,
key TEXT,
locked BOOLEAN
)""")
cursor.execute("""CREATE TABLE factoids (
id INTEGER PRIMARY KEY,
key_id INTEGER,
added_by TEXT,
added_at TIMESTAMP,
fact TEXT
)""")
cursor.execute("""CREATE TRIGGER remove_factoids
BEFORE DELETE ON keys
BEGIN
DELETE FROM factoids WHERE key_id = old.id;
END
""")
db.commit()
return db
def addfactoid(self, irc, msg, args):
"[<channel>] (If not sent in the channel itself) <key> <value>"
channel = privmsgs.getChannel(msg, args)
(key, factoid) = privmsgs.getArgs(args, needed=2)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, locked FROM keys WHERE key=%s""", key)
if cursor.rowcount == 0:
cursor.execute("""INSERT INTO keys VALUES (NULL, %s, 0)""", key)
db.commit()
cursor.execute("""SELECT id, locked FROM keys WHERE key=%s""", key)
(id, locked) = map(int, cursor.fetchone())
capability = ircdb.makeChannelCapability(channel, 'factoids')
if not locked:
if not ircdb.checkCapability(msg.prefix, capability):
irc.error(msg, conf.replyNoCapability % capability)
return
if ircdb.users.hasUser(msg.prefix):
name = ircdb.users.getUserName(msg.prefix)
else:
name = msg.nick
cursor.execute("""INSERT INTO factoids VALUES
(NULL, %s, %s, %s, %s)""",
id, name, int(time.time()), factoid)
db.commit()
irc.reply(msg, conf.replySuccess)
else:
irc.error(msg, 'That factoid is locked.')
def lookupfactoid(self, irc, msg, args):
"[<channel>] (If not sent in the channel itself) <key> [<number>]"
channel = privmsgs.getChannel(msg, args)
(key, number) = privmsgs.getArgs(args, optional=1)
try:
number = int(number)
except ValueError:
key += number
number = 0
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT factoids.fact FROM factoids, keys WHERE
keys.key=%s AND factoids.key_id=keys.id
ORDER BY factoids.id""", key)
results = cursor.fetchall()
if len(results) == 0:
irc.error(msg, 'No factoid matches that key.')
else:
factoid = results[number][0]
irc.reply(msg, '%s/%s: %s' % (key, number, factoid))
def lockfactoid(self, irc, msg, args):
"[<channel>] (If not sent in the channel itself) <key>"
channel = privmsgs.getChannel(msg, args)
key = privmsgs.getArgs(args)
db = self.getDb(channel)
capability = ircdb.makeChannelCapability(channel, 'factoids')
if ircdb.checkCapability(msg.prefix, capability):
cursor = db.cursor()
cursor.execute("""UPDATE keys SET locked = 1 WHERE key=%s""", key)
db.commit()
irc.reply(msg, conf.replySuccess)
else:
irc.error(msg, conf.replyNoCapability % capability)
def unlockfactoid(self, irc, msg, args):
"[<channel>] (If not sent in the channel itself) <key>"
channel = privmsgs.getChannel(msg, args)
key = privmsgs.getArgs(args)
db = self.getDb(channel)
capability = ircdb.makeChannelCapability(channel, 'factoids')
if ircdb.checkCapability(msg.prefix, capability):
cursor = db.cursor()
cursor.execute("""UPDATE keys SET locked = 0 WHERE key=%s""", key)
db.commit()
irc.reply(msg, conf.replySuccess)
else:
irc.error(msg, conf.replyNoCapability % capability)
def removefactoid(self, irc, msg, args):
"[<channel>] (If not sent in the channel itself) <key>"
channel = privmsgs.getChannel(msg, args)
key = privmsgs.getArgs(args)
db = self.getDb(channel)
capability = ircdb.makeChannelCapability(channel, 'factoids')
if ircdb.checkCapability(msg.prefix, capability):
cursor = db.cursor()
cursor.execute("""DELETE FROM keys WHERE key=%s""", key)
db.commit()
irc.reply(msg, conf.replySuccess)
else:
irc.error(msg, conf.replyNoCapability % capability)
def randomfactoid(self, irc, msg, args):
"[<channel>] (If not sent in the channel itself)"
channel = privmsgs.getChannel(msg, args)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT fact, key_id FROM factoids
ORDER BY random()
LIMIT 1""")
if cursor.rowcount != 0:
(factoid, keyId) = cursor.fetchone()
cursor.execute("""SELECT key FROM keys WHERE id=%s""", keyId)
key = cursor.fetchone()[0]
irc.reply(msg, '%s: %s' % (key, factoid))
else:
irc.error(msg, 'I couldn\'t find a factoid.')
def factoidinfo(self, irc, msg, args):
"[<channel>] (If not sent in the channel itself) <key>"
channel = privmsgs.getChannel(msg, args)
key = privmsgs.getArgs(args)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, locked FROM keys WHERE key=%s""", key)
if cursor.rowcount == 0:
irc.error(msg, 'No factoid matches that key.')
return
(id, locked) = map(int, cursor.fetchone())
cursor.execute("""SELECT added_by, added_at FROM factoids
WHERE key_id=%s
ORDER BY id""", id)
factoids = cursor.fetchall()
L = []
counter = 0
for (added_by, added_at) in factoids:
added_at = time.strftime(conf.timestampFormat,
time.localtime(int(added_at)))
L.append('#%s was added by %s at %s' % (counter,added_by,added_at))
counter += 1
factoids = '; '.join(L)
s = 'Key %r is %s and has %s factoids associated with it: %s' % \
(key, locked and 'locked' or 'not locked', counter, '; '.join(L))
irc.reply(msg, s)
Class = Factoids

55
plugins/FixRelayBot.py Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import re
import irclib
import ircmsgs
class FixRelayBot(irclib.IrcCallback):
_re = re.compile('<([^@]+)@([^>])+>\s+(.*)')
def inFilter(self, irc, msg):
if msg.command == 'PRIVMSG':
#debug.printf('Message command was PRIVMSG')
m = self._re.match(msg.args[1])
if m:
#debug.printf('Regexp matched: %r, %r, %r' % m.groups())
nick = m.group(1)
network = m.group(2)
newprefix = '%s!%s@%s' % (nick, nick, network)
msg = ircmsgs.IrcMsg(command='PRIVMSG', prefix=newprefix,
args=(msg.args[0], m.group(3)))
return msg
Class = FixRelayBot

219
plugins/FreeBSD.py Executable file
View File

@ -0,0 +1,219 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import string
import os.path
import sqlite
import debug
import privmsgs
import callbacks
indexFile = '/usr/ports/INDEX'
dbFile = os.path.join(conf.dataDir, 'FreeBSD.db')
def getIndex():
"""Returns a file-like object that is the Ports index."""
return file(indexFile, 'r')
def makeDb(dbfilename, indexfd, replace=False):
if os.path.exists(dbfilename):
if replace:
os.remove(dbfilename)
else:
indexfd.close()
return sqlite.connect(dbfilename)
db = sqlite.connect(dbfilename)
cursor = db.cursor()
cursor.execute("""CREATE TABLE ports (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE ON CONFLICT IGNORE,
path TEXT,
info TEXT,
maintainer TEXT,
website TEXT
)""")
cursor.execute("""CREATE TABLE categories (
id INTEGER PRIMARY KEY,
name TEXT UNIQUE ON CONFLICT IGNORE
)""")
cursor.execute("""CREATE TABLE in_category (
port_id INTEGER,
category_id INTEGER
)""")
cursor.execute("""CREATE TABLE depends (
port_id INTEGER,
depends_id INTEGER
)""")
lines = map(lambda s: s.rstrip().split('|'), indexfd)
for fields in lines:
# First, add all the entries to the ports table.
name = fields[0]
path = fields[1]
info = fields[3]
maintainer = fields[5]
category = fields[6]
website = fields[9]
cursor.execute("""INSERT INTO ports
VALUES (NULL, %s, %s, %s, %s, %s)""",
name, path, info, maintainer, website)
for category in category.split():
cursor.execute("INSERT INTO categories VALUES (NULL, %s)",category)
cursor.execute("""INSERT INTO in_category
SELECT ports.id, categories.id
FROM ports, categories
WHERE ports.name=%s AND categories.name=%s""",
name, category)
for fields in lines:
# Now, add dependencies.
name = fields[0]
b_deps = fields[7]
r_deps = fields[8]
for dep in b_deps.split():
cursor.execute("""SELECT id FROM ports WHERE name=%s""", name)
port_id = cursor.fetchone()[0]
cursor.execute("""SELECT id FROM ports WHERE name=%s""", dep)
depends_id = cursor.fetchone()[0]
cursor.execute("INSERT INTO depends VALUES (%s, %s)",
port_id, depends_id)
indexfd.close()
db.commit()
return db
class FreeBSD(callbacks.Privmsg):
"""
Module for FreeBSD-specific stuff. Currently contains only the commands
for searching the Ports database.
"""
def __init__(self):
callbacks.Privmsg.__init__(self)
self.db = makeDb(dbFile, getIndex())
def numports(self, irc, msg, args):
"""takes no arguments
Returns the total number of ports in the database.
"""
cursor = self.db.cursor()
cursor.execute("""SELECT COUNT(id) FROM ports""")
number = cursor.fetchone()[0]
irc.reply(msg, 'There are %s ports in my database.' % number)
_globtrans = string.maketrans('?*', '_%')
def searchports(self, irc, msg, args):
"""[name=<glob>] [category=<glob>] [depends=<glob>] """\
"""[info=<glob>] [maintainer=<glob>] [website=<glob>]
Returns the names of ports matching the constraints given.
"""
(args, kwargs) = privmsgs.getKeywordArgs(irc, msg)
if args and kwargs:
pass
#irc.error(msg, 'There must not be named and unnamed arguments.')
#return
elif args and not kwargs:
kwargs['name'] = ' '.join(args)
tables = ['ports']
if 'depends' in kwargs:
tables.append('depends')
if 'category' in kwargs:
tables += ['categories', 'in_category']
tables = ', '.join(tables)
wheres = {}
for (key, value) in kwargs.iteritems():
if key not in ('name', 'category', 'depends',
'info', 'maintainer', 'website'):
irc.error(msg, '%s isn\'t a valid named argument.' % key)
return
value = value.translate(self._globtrans)
if '%' not in value and '_' not in value:
wheres['%s=%%s' % key] = value
else:
wheres['%s LIKE %%s' % key] = value
where = ' AND '.join(wheres)
cursor = self.db.cursor()
sql = """SELECT name FROM ports WHERE %s""" % where
cursor.execute(sql, *(wheres.values()))
if cursor.rowcount == 0:
irc.reply(msg, 'No ports matched those constraints')
elif cursor.rowcount > 50:
irc.reply(msg,
'There were %s matches. Please narrow your search.' %\
cursor.rowcount)
else:
ports = map(lambda t: t[0], cursor.fetchall())
irc.reply(msg, ', '.join(ports))
def randomport(self, irc, msg, args):
"""takes no arguments
Returns a random port from the database.
"""
cursor = self.db.cursor()
cursor.execute("""SELECT name, info, website, maintainer FROM ports
ORDER BY random()
LIMIT 1""")
(name, info, website, maintainer) = cursor.fetchone()
s = '%s: %s (maintained by %s; more info at %s)' %\
(name, info, website, maintainer)
irc.reply(msg, s)
def portinfo(self, irc, msg, args):
"""<port name>
Gives the information from the database on a given port.
"""
name = privmsgs.getArgs(args)
cursor = self.db.cursor()
cursor.execute("""SELECT id, info, maintainer, website FROM ports
WHERE name=%s""", name)
if cursor.rowcount == 0:
irc.reply(msg, 'No port matched the %r.' % name)
return
(id, info, maintainer, website) = cursor.fetchone()
cursor.execute("""SELECT categories.name FROM categories, in_category
WHERE in_category.port_id=%s
AND categories.id=in_category.category_id""", id)
categories = map(lambda t: t[0], cursor.fetchall())
irc.reply(msg, '%s; Categories: %s; Maintainer: %s; Website: %s' %
(info, ', '.join(categories), maintainer, website))
Class = FreeBSD
if __name__ == '__main__':
makeDb(dbFile, getIndex(), replace=True)

327
plugins/FunCommands.py Normal file
View File

@ -0,0 +1,327 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import os
import gc
import re
import sys
import new
import md5
import sha
import time
import math
import cmath
import types
import string
import random
import urllib
import inspect
import binascii
import threading
import mimetypes
#import conf
import debug
import ircmsgs
import privmsgs
import callbacks
class FunCommands(callbacks.Privmsg):
def __init__(self):
callbacks.Privmsg.__init__(self)
self.sentMsgs = 0
self.recvdMsgs = 0
self.sentBytes = 0
self.recvdBytes = 0
self._startTime = time.time()
def inFilter(self, irc, msg):
self.recvdMsgs += 1
self.recvdBytes += len(str(msg))
return msg
def outFilter(self, irc, msg):
self.sentMsgs += 1
self.sentBytes += len(str(msg))
return msg
def netstats(self, irc, msg, args):
"takes no arguments"
#debug.printf('HEY I GOT RELOADED')
irc.reply(msg,
'I have received %s messages for a total of %s bytes. '\
'I have sent %s messages for a total of %s bytes.' %\
(self.recvdMsgs, self.recvdBytes,
self.sentMsgs, self.sentBytes))
def hexlify(self, irc, msg, args):
"<text>; turn string into a hexstring."
text = privmsgs.getArgs(args)
irc.reply(msg, binascii.hexlify(text))
def unhexlify(self, irc, msg, args):
"<even number of hexadecimal digits>"
text = privmsgs.getArgs(args)
try:
s = binascii.unhexlify(text)
irc.reply(msg, s)
except TypeError:
irc.error(msg, 'Invalid input.')
def xor(self, irc, msg, args):
"<password> <text>"
(password, text) = privmsgs.getArgs(args, 2)
passwordlen = len(password)
i = 0
ret = []
for c in text:
ret.append(chr(ord(c) ^ ord(password[i])))
i = (i + 1) % passwordlen
irc.reply(msg, repr(''.join(ret)))
def mimetype(self, irc, msg, args):
"<filename>"
filename = privmsgs.getArgs(args)
(type, encoding) = mimetypes.guess_type(filename)
if type is not None:
irc.reply(msg, type)
else:
s = 'I couldn\'t figure out that filename.'
irc.reply(msg, s)
def md5(self, irc, msg, args):
"<text>; returns the md5 sum of the text."
text = privmsgs.getArgs(args)
irc.reply(msg, md5.md5(text).hexdigest())
def sha(self, irc, msg, args):
"<text>; returns the sha sum of the text."
text = privmsgs.getArgs(args)
irc.reply(msg, sha.sha(text).hexdigest())
def urlquote(self, irc, msg, args):
"<text>; returns the URL quoted form of the text."
text = privmsgs.getArgs(args)
irc.reply(msg, urllib.quote(text))
def urlunquote(self, irc, msg, args):
"<text>; returns the text un-URL quoted."
text = privmsgs.getArgs(args)
s = urllib.unquote(text)
irc.reply(msg, s)
def rot13(self, irc, msg, args):
"<text>"
text = privmsgs.getArgs(args)
irc.reply(msg, text.encode('rot13'))
def coin(self, irc, msg, args):
"takes no arguments"
if random.randrange(0, 2):
irc.reply(msg, 'The coin landed heads.')
else:
irc.reply(msg, 'The coin landed tails.')
_dicere = re.compile(r'(\d+)d(\d+)')
def dice(self, irc, msg, args):
"<dice>d<sides> (e.g., 2d6 will roll 2 six-sided dice)"
arg = privmsgs.getArgs(args)
m = re.match(self._dicere, arg)
if m:
(dice, sides) = [int(s) for s in m.groups()]
if dice > 6:
irc.error(msg, 'You can\'t roll more than 6 dice.')
elif sides > 100:
irc.error(msg, 'Dice can\'t have more than 100 sides.')
else:
L = [0] * dice
for i in xrange(dice):
L[i] = random.randrange(1, sides+1)
irc.reply(msg, ', '.join([str(x) for x in L]))
else:
irc.error(msg, 'Dice must be of the form <dice>d<sides>')
_leettrans = string.maketrans('oOaAeElBTiIts', '004433187!1+5')
_leetres = ((re.compile(r'\b(?:(?:[yY][o0O][oO0uU])|u)\b'), 'j00'),
(re.compile(r'fear'), 'ph33r'),
(re.compile(r'[aA][tT][eE]'), '8'),
(re.compile(r'[aA][tT]'), '@'),
(re.compile(r'[sS]\b'), 'z'),
(re.compile(r'x'), '><'),)
def leet(self, irc, msg, args):
"<text>"
s = privmsgs.getArgs(args)
for (r, sub) in self._leetres:
s = re.sub(r, sub, s)
s = s.translate(self._leettrans)
irc.reply(msg, s)
def cpustats(self, irc, msg, args):
"takes no arguments"
(user, system, childUser, childSystem, elapsed) = os.times()
timeRunning = time.time() - self._startTime
response ='I have taken %s seconds of user time and %s seconds of '\
'system time, for a total of %s seconds of CPU time. My '\
'children have taken %s seconds of user time and %s seconds'\
' of system time for a total of %s seconds of CPU time. ' \
'I\'ve taken a total of %s%% of this computer\'s time. ' \
'I currently have %s active threads.' %\
(user, system, user + system,
childUser, childSystem, childUser + childSystem,
(user+system+childUser+childSystem)/timeRunning,
threading.activeCount())
irc.reply(msg, response)
def uptime(self, irc, msg, args):
"takes no arguments"
elapsed = time.time() - self._startTime
days, elapsed = elapsed // 86400, elapsed % 86400
hours, elapsed = elapsed // 3600, elapsed % 3600
minutes, seconds = elapsed // 60, elapsed % 60
response = 'I have been running for %i %s, %i %s, %i %s, and %i %s.' %\
(days, days == 1 and 'day' or 'days',
hours, hours == 1 and 'hour' or 'hours',
minutes, minutes == 1 and 'minute' or 'minutes',
seconds, seconds == 1 and 'second' or 'seconds')
irc.reply(msg, response)
###
# So this is how the 'calc' command works:
# First, we make a nice little safe environment for evaluation; basically,
# the names in the 'math' and 'cmath' modules. Then, we remove the ability
# of a random user to get ints evaluated: this means we have to turn all
# int literals (even octal numbers and hexadecimal numbers) into floats.
# Then we delete all square brackets, underscores, and whitespace, so no
# one can do list comprehensions or call __...__ functions.
###
_mathEnv = {}
_mathEnv.update(math.__dict__)
_mathEnv.update(cmath.__dict__)
_mathEnv['__builtins__'] = new.module('__builtins__')
_mathInt = re.compile(r'(?<!\d|\.)(\d+)(?!\d+|\.|\.\d+)')
_mathHex = re.compile(r'(0x[A-Fa-f\d]+)')
_mathOctal = re.compile(r'(^|[^\dA-Fa-f])(0[0-7]+)')
def calc(self, irc, msg, args):
"<math expr>"
text = privmsgs.getArgs(args)
text = text.translate(string.ascii, '_[] \t')
text = text.replace('lambda', '')
def hex2float(m):
literal = m.group(1)
i = long(literal, 16)
return '%s.0' % i
def oct2float(m):
(previous, literal) = m.groups()
i = long(literal, 8)
return '%s%s.0' % (previous, i)
text = self._mathHex.sub(hex2float, text)
debug.printf('After unhexing: %r' % text)
text = self._mathOctal.sub(oct2float, text)
debug.printf('After unocting: %r' % text)
text = self._mathInt.sub(r'\1.0', text)
debug.printf('After uninting: %r' % text)
try:
x = complex(eval(text, self._mathEnv, self._mathEnv))
real = x.real
imag = x.imag
if real == int(real):
real = int(real)
if real < 1e-12:
real = 0
if imag < 1e-12:
imag = 0
if real < 1e-12 and imag < 1e-12:
irc.reply(msg, '0')
elif imag < 1e-12:
irc.reply(msg, '%s' % real)
elif real < 1e-12:
irc.reply(msg, '%s' % imag)
else:
irc.reply(msg, '%s + %si' % (real, imag))
except Exception, e:
irc.reply(msg, debug.exnToString(e))
math = calc
def objects(self, irc, msg, args):
"takes no arguments. Returns the number of Python objects in memory."
objs = gc.get_objects()
classes = len([obj for obj in objs if inspect.isclass(obj)])
functions = len([obj for obj in objs if inspect.isroutine(obj)])
modules = len([obj for obj in objs if inspect.ismodule(obj)])
dicts = len([obj for obj in objs if type(obj) == types.DictType])
lists = len([obj for obj in objs if type(obj) == types.ListType])
tuples = len([obj for obj in objs if type(obj) == types.TupleType])
response = 'I have %s objects: %s modules, %s classes, %s functions, '\
'%s dictionaries, %s lists, and %s tuples (and a few other'\
' different types).' %\
(len(objs), modules, classes, functions,
dicts, lists, tuples)
irc.reply(msg, response)
def last(self, irc, msg, args):
"[<channel>] <message number (defaults to 1, the last message)>"
channel = privmsgs.getChannel(msg, args)
n = privmsgs.getArgs(args, needed=0, optional=1)
if n == '':
n = 1
else:
n = int(n)
n += 1 # To remove the last question asked.
for msg in reviter(irc.state.history):
if msg.command == 'PRIVMSG' and msg.args[0] == channel and n == 1:
irc.reply(msg, msg.args[1])
return
else:
n -= 1
if n > 1:
s = 'I don\'t have a history of that many messages.'
irc.error(msg, s)
def lastfrom(self, irc, msg, args):
"[<channel>] <nick>"
channel = privmsgs.getChannel(msg, args)
nick = privmsgs.getArgs(args)
for m in reviter(irc.state.history):
if m.command == 'PRIVMSG' and m.nick == nick:
if ircmsgs.isAction(m):
irc.reply(msg, '* %s %s' % (nick, ircmsgs.unAction(m)))
else:
irc.reply(msg, '<%s> %s' % (nick, m.args[1]))
return
irc.error(msg, 'I don\'t remember a message from that person.')
Class = FunCommands

300
plugins/FunDB.py Executable file
View File

@ -0,0 +1,300 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import string
import os.path
import sqlite
import conf
import ircmsgs
import ircutils
import privmsgs
import callbacks
dbFilename = os.path.join(conf.dataDir, 'FunDB.db')
def makeDb(dbfilename, replace=False):
if os.path.exists(dbfilename):
if replace:
os.remove(dbfilename)
else:
return sqlite.connect(dbfilename)
db = sqlite.connect(dbfilename)
cursor = db.cursor()
cursor.execute("""CREATE TABLE insults (
id INTEGER PRIMARY KEY,
insult TEXT
)""")
cursor.execute("""CREATE TABLE excuses (
id INTEGER PRIMARY KEY,
excuse TEXT
)""")
cursor.execute("""CREATE TABLE larts (
id INTEGER PRIMARY KEY,
lart TEXT
)""")
cursor.execute("""CREATE TABLE words (
id INTEGER PRIMARY KEY,
word TEXT UNIQUE ON CONFLICT IGNORE,
sorted_word_id INTEGER
)""")
cursor.execute("""CREATE INDEX sorted_word_id ON words (sorted_word_id)""")
cursor.execute("""CREATE TABLE sorted_words (
id INTEGER PRIMARY KEY,
word TEXT UNIQUE ON CONFLICT IGNORE
)""")
cursor.execute("""CREATE INDEX sorted_words_word ON sorted_words (word)""")
db.commit()
return db
def addWord(db, word, commit=False):
word = word.strip().lower()
L = list(word)
L.sort()
sorted = ''.join(L)
cursor = db.cursor()
cursor.execute("""INSERT INTO sorted_words VALUES (NULL, %s)""", sorted)
cursor.execute("""INSERT INTO words VALUES (NULL, %s,
(SELECT id FROM sorted_words
WHERE word=%s))""", word, sorted)
if commit:
db.commit()
class FunDB(callbacks.Privmsg):
"""
Contains the 'fun' commands that require a database. Currently includes
database-backed commands for crossword puzzle solving, anagram searching,
larting, excusing, and insulting.
"""
def __init__(self):
callbacks.Privmsg.__init__(self)
self.db = makeDb(dbFilename)
def die(self):
self.db.close()
def insult(self, irc, msg, args):
"""<nick>"""
nick = privmsgs.getArgs(args)
cursor = self.db.cursor()
cursor.execute("""SELECT id, insult FROM insults
WHERE insult NOTNULL
ORDER BY random()
LIMIT 1""")
(id, insult) = cursor.fetchone()
if nick == irc.nick:
insultee = msg.nick
else:
insultee = nick
if ircutils.isChannel(msg.args[0]):
means = msg.args[0]
s = '%s: %s (#%s)' % (insultee, insult, id)
else:
means = insultee
s = insult
irc.queueMsg(ircmsgs.privmsg(means, s))
def addinsult(self, irc, msg, args):
"""<insult>"""
insult = privmsgs.getArgs(args)
cursor = self.db.cursor()
cursor.execute("""INSERT INTO insults VALUES (NULL, %s)""", insult)
self.db.commit()
irc.reply(msg, conf.replySuccess)
def removeinsult(self, irc, msg, args):
"""<number>"""
id = privmsgs.getArgs(args)
try:
id = int(id)
except ValueError:
irc.error(msg, 'You must give a numeric id.')
return
cursor = self.db.cursor()
cursor.execute("""DELETE FROM insults WHERE id=%s""", id)
self.db.commit()
irc.reply(msg, conf.replySuccess)
def crossword(self, irc, msg, args):
"""<word>
Gives the possible crossword completions for <word>; use underscores
('_') to denote blank spaces.
"""
word = privmsgs.getArgs(args).lower()
cursor = self.db.cursor()
if '%' in word:
irc.error(msg, '"%" isn\'t allowed in the word.')
return
cursor.execute("""SELECT word FROM words
WHERE word LIKE %s
ORDER BY word""", word)
words = map(lambda t: t[0], cursor.fetchall())
irc.reply(msg, ', '.join(words))
def excuse(self, irc, msg, args):
"""takes no arguments"""
cursor = self.db.cursor()
cursor.execute("""SELECT id, excuse FROM excuses
WHERE excuse NOTNULL
ORDER BY random()
LIMIT 1""")
(id, excuse) = cursor.fetchone()
irc.reply(msg, '%s (#%s)' % (excuse, id))
def addexcuse(self, irc, msg, args):
"""<excuse>"""
excuse = privmsgs.getArgs(args)
cursor = self.db.cursor()
cursor.execute("""INSERT INTO excuses VALUES (NULL, %s)""", excuse)
self.db.commit()
irc.reply(msg, conf.replySuccess)
def removeexcuse(self, irc, msg, args):
"""<number>"""
id = privmsgs.getArgs(args)
try:
id = int(id)
except ValueError:
irc.error(msg, 'You must give a numeric id.')
return
cursor = self.db.cursor()
cursor.execute("""DELETE FROM excuses WHERE id=%s""", id)
self.db.commit()
irc.reply(msg, conf.replySuccess)
def lart(self, irc, msg, args):
"""[<channel>] <nick>
The <channel> argument is only necessary if the message isn't being
sent in the channel itself.
"""
channel = privmsgs.getChannel(msg, args)
nick = privmsgs.getArgs(args)
cursor = self.db.cursor()
cursor.execute("""SELECT id, lart FROM larts
WHERE lart NOTNULL
ORDER BY random()
LIMIT 1""")
(id, lart) = cursor.fetchone()
if nick == irc.nick:
lartee = msg.nick
else:
lartee = nick
lart = lart.replace("$who", lartee)
irc.queueMsg(ircmsgs.action(channel, '%s (#%s)' % (lart, id)))
def addlart(self, irc, msg, args):
"""<lart>"""
lart = privmsgs.getArgs(args)
if lart.find('$who') == -1:
irc.error(msg, 'There must be an $who in the lart somewhere.')
return
cursor = self.db.cursor()
cursor.execute("""INSERT INTO larts VALUES (NULL, %s)""", lart)
self.db.commit()
def removelart(self, irc, msg, args):
"""<number>"""
id = privmsgs.getArgs(args)
try:
id = int(id)
except ValueError:
irc.error(msg, 'You must give a numeric id.')
return
cursor = self.db.cursor()
cursor.execute("""DELETE FROM larts WHERE id=%s""", id)
self.db.commit()
irc.reply(msg, conf.replySuccess)
def addword(self, irc, msg, args):
"""<word>"""
word = privmsgs.getArgs(args)
if word.translate(string.ascii, string.ascii_letters) != '':
irc.error(msg, 'Word must contain only letters')
addWord(self.db, word, commit=True)
irc.reply(msg, conf.replySuccess)
def anagram(self, irc, msg, args):
"""<word>"""
word = privmsgs.getArgs(args).strip().lower()
cursor = self.db.cursor()
cursor.execute("""SELECT words.word FROM words
WHERE sorted_word_id=(
SELECT sorted_word_id FROM words
WHERE word=%s)""", word)
words = map(lambda t: t[0], cursor.fetchall())
try:
words.remove(word)
except ValueError:
pass
if words:
irc.reply(msg, ', '.join(words))
else:
irc.reply(msg, 'That word has no anagrams that I know of.')
Class = FunDB
if __name__ == '__main__':
import sys
if len(sys.argv) < 3:
print 'Usage: %s <words|larts|excuses|insults> file' % sys.argv[0]
sys.exit(-1)
category = sys.argv[1]
filename = sys.argv[2]
db = makeDb(dbFilename)
cursor = db.cursor()
for line in open(filename, 'r'):
line = line.rstrip()
if not line:
continue
if category == 'words':
cursor.execute("""PRAGMA cache_size = 50000""")
addWord(db, line)
elif category == 'larts':
if line.find('$who') != -1:
cursor.execute("""INSERT INTO larts VALUES (NULL, %s)""", line)
else:
print 'Invalid lart: %s' % line
elif category == 'insults':
cursor.execute("""INSERT INTO insults VALUES (NULL, %s)""", line)
elif category == 'excuses':
cursor.execute("""INSERT INTO excuses VALUES (NULL, %s)""", line)
db.commit()
db.close()

174
plugins/Http.py Normal file
View File

@ -0,0 +1,174 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import re
import urllib2
import threading
#import xml.dom.minidom
import debug
import privmsgs
import callbacks
_htmlstripper = re.compile('<[^>]+>')
def stripHtml(s):
return _htmlstripper.sub('', s)
class FreshmeatException(Exception):
pass
class Http(callbacks.Privmsg):
threaded = True
_fmProject = re.compile('<projectname_full>(.*?)</projectname_full>')
_fmVersion = re.compile('<latest_version>(.*?)</latest_version>')
_fmVitality = re.compile('<vitality_percent>(.*?)</vitality_percent>')
_fmPopularity =re.compile('<popularity_percent>(.*?)</popularity_percent>')
_fmLastUpdated = re.compile('<date_updated>(.*?)</date_updated>')
def freshmeat(self, irc, msg, args):
"<project name>"
project = privmsgs.getArgs(args)
url = 'http://www.freshmeat.net/projects-xml/%s' % project
try:
fd = urllib2.urlopen(url)
text = fd.read()
if text.startswith('Error'):
raise FreshmeatException, text
project = self._fmProject.search(text).group(1)
version = self._fmVersion.search(text).group(1)
vitality = self._fmVitality.search(text).group(1)
popularity = self._fmPopularity.search(text).group(1)
lastupdated = self._fmLastUpdated.search(text).group(1)
irc.reply(msg,
'%s, last updated %s, with a vitality percent of %s '\
'and a popularity of %s, is in version %s.' % \
(project, lastupdated, vitality, popularity, version))
except FreshmeatException, e:
irc.reply(msg, debug.exnToString(e))
except Exception, e:
debug.recoverableException()
irc.reply(msg, debug.exnToString(e))
_htmlTag = re.compile('<.*?>')
_htmlEntity = re.compile('&.*?;')
_html = re.compile('<.*?>|&.*?;')
def cfactive(self, irc, msg, args):
"takes no arguments."
try:
fd = urllib2.urlopen('http://www.coderforums.net/')
except Exception, e:
irc.error(msg, debug.exnToString(e))
text = fd.read()
text = self._htmlTag.sub('', text)
lines = text.splitlines()
sent = False
for i in range(len(lines) - 1):
if lines[i].strip().startswith('Most'):
sent = True
irc.reply(msg, lines[i+1].strip())
break
if not sent:
irc.reply(msg, 'The format of the page seems odd.')
def stockquote(self, irc, msg, args):
"<company symbol>"
symbol = privmsgs.getArgs(args)
url = 'http://finance.yahoo.com/d/quotes.csv?s=%s'\
'&f=sl1d1t1c1ohgv&e=.csv' % symbol
try:
quote = urllib2.urlopen(url).read()
except Exception, e:
irc.reply(msg, debug.exnToString(e))
return
data = quote.split(',')
#debug.printf(data) # debugging
if data[1] != '0.00':
irc.reply(msg,
'The current price of %s is %s, as of %s EST. '\
'A change of %s from the last business day.' %\
(data[0][1:-1], data[1], data[3][1:-1], data[4]))
return
else:
m = 'I couldn\'t find a listing for %s' % symbol
irc.error(msg, m)
return
def foldoc(self, irc, msg, args):
"<something to lookup on foldoc>"
search = '+'.join(args)
url = 'http://foldoc.doc.ic.ac.uk/foldoc/foldoc.cgi?query=%s' % search
try:
html = urllib2.urlopen(url).read()
except Exception, e:
irc.error(msg, debug.exnToString(e))
text = html.split('<P>\n', 2)[1]
text = text.replace('.\n', ' ')
text = text.replace('\n', ' ')
text = self._html.sub('', text)
irc.reply(msg, text.strip())
_gkrating = re.compile(r'<font color="#FFFF33">(\d+)</font>')
_gkgames = re.compile(r's:&nbsp;&nbsp;</td><td class=sml>(\d+)</td></tr>')
_gkrecord = re.compile(r'percentile(\d+), .*?%(\d+), .*?%(\d+)')
def gkstats(self, irc, msg, args):
"<name>"
name = privmsgs.getArgs(args)
gkprofile = 'http://www.gameknot.com/stats.pl?%s' % name
try:
profile = urllib2.urlopen(gkprofile).read()
rating = self._gkrating.search(profile).group(1)
games = self._gkgames.search(profile).group(1)
profile = stripHtml(profile)
(w, l, d) = self._gkrecord.search(profile).groups()
irc.reply(msg, '%s is rated %s and has %s active games; '
'W-%s, L-%s, D-%s' % (name, rating, games, w, l, d))
except AttributeError:
irc.error(msg, 'The format of the page was odd.')
except urllib2.URLError:
irc.error(msg, 'Couldn\'t connect to gameknot.com.')
_zipcode = re.compile(r'Local Forecast for (.*), (.*?) ')
def zipcode(self, irc, msg, args):
"<US zip code>"
zip = privmsgs.getArgs(args)
url = "http://www.weather.com/weather/local/%s?lswe=%s" % (zip, zip)
try:
html = urllib2.urlopen(url).read()
(city, state) = self._zipcode.search(html).groups()
irc.reply(msg, '%s, %s' % (city, state))
except AttributeError:
irc.error(msg, 'the format of the page was odd.')
except urllib2.URLError:
irc.error(msg, 'Couldn\'t open search page.')
Class = Http

185
plugins/Moobot.py Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
###
# Note the exception above the declaration of _code.
###
"""Module to replicate some of moobot's functionality."""
from baseplugin import *
import base64
import ircutils
import privmsgs
import callbacks
class Moobot(callbacks.Privmsg):
def __init__(self):
super(self.__class__, self).__init__()
#callbacks.Privmsg.__init__(self)
if not self._revcode:
for (k, v) in self._code.iteritems():
self._revcode[v.strip()] = k
def cool(self, irc, msg, args):
"<something>"
something = privmsgs.getArgs(args)
irc.reply(msg, ':cool: %s :cool:' % something)
# Stolen shamelessless from moobot, so I suppose this data structure is
# GPLed. Don't worry, it doesn't infect the rest of the code because it's
# not essential to the operation of the bot.
_code = {
"A" : ".- ",
"B" : "-... ",
"C" : "-.-. ",
"D" : "-.. ",
"E" : ". ",
"F" : "..-. ",
"G" : "--. ",
"H" : ".... ",
"I" : ".. ",
"J" : ".--- ",
"K" : "-.- ",
"L" : ".-.. ",
"M" : "-- ",
"N" : "-. ",
"O" : "--- ",
"P" : ".--. ",
"Q" : "--.- ",
"R" : ".-. ",
"S" : "... ",
"T" : "- ",
"U" : "..- ",
"V" : "...- ",
"W" : ".-- ",
"X" : "-..- ",
"Y" : "-.-- ",
"Z" : "--.. ",
"0" : "----- ",
"1" : ".---- ",
"2" : "..--- ",
"3" : "...-- ",
"4" : "....- ",
"5" : "..... ",
"6" : "-.... ",
"7" : "--... ",
"8" : "---.. ",
"9" : "----. ",
" " : " "
}
_revcode = {}
def unmorse(self, irc, msg, args):
"<morse code text>"
text = privmsgs.getArgs(args)
L = []
for code in text.split():
try:
L.append(self._revcode[code])
except KeyError:
L.append(code)
irc.reply(msg, ''.join(L))
def morse(self, irc, msg, args):
"<text>"
text = privmsgs.getArgs(args)
L = []
for c in text.upper():
if c in self._code:
L.append(self._code[c])
else:
L.append(c)
irc.reply(msg, ' '.join(L))
ditdaw = morse
def hi(self, irc, msg, args):
"takes no arguments"
irc.reply(msg, 'howdy, %s!' % msg.nick)
def reverse(self, irc, msg, args):
"<text>"
text = privmsgs.getArgs(args)
L = list(text)
L.reverse()
irc.reply(msg, ''.join(L))
def mime(self, irc, msg, args):
"<text>"
text = privmsgs.getArgs(args)
s = base64.encodestring(text).strip()
if ircutils.validArgument(s):
irc.reply(msg, s)
else:
irc.error(msg, 'Base64 requires a newline in that string. '\
'Try a smaller string.')
def unmime(self, irc, msg, args):
"<text>"
text = privmsgs.getArgs(args)
s = base64.decodestring(text)
if ircutils.validArgument(s):
irc.reply(msg, s)
else:
irc.error(msg, 'I can\'t send \\n, \\r, or \\0.')
_stack = []
def stack(self, irc, msg, args):
"<'push'|'pop'|'size'|'xray'> <text>"
(command, value) = privmsgs.getArgs(args, optional=1)
if command == 'pop':
if self._stack:
x = self._stack.pop()
irc.reply(msg, x)
else:
irc.error(msg, 'Stack is empty.')
elif command == 'push':
self._stack.append(value)
irc.reply(msg, '"%s" pushed.' % value)
elif command == 'size':
irc.reply(msg, 'Stack size is %s.' % len(self._stack))
elif command == 'xray':
i = int(value)
try:
irc.reply(msg, self._stack[-i])
except IndexError:
irc.error(msg, 'Invalid position %s' % i)
else:
irc.error(msg, 'I don\'t recognize that stack command.')
Class = Moobot

80
plugins/NickServ.py Normal file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""NickServ: Handles management of nicks with NickServ."""
from baseplugin import *
import re
import ircdb
import ircmsgs
import privmsgs
import ircutils
import callbacks
class NickServ(callbacks.Privmsg):
def __init__(self):
callbacks.Privmsg.__init__(self)
self.started = False
def startnickserv(self, irc, msg, args):
"<bot's nick> <name of the NICKSERV> <password>"
if ircutils.isChannel(msg.args[0]):
irc.error(msg, 'Command must not be done in a channel.')
if ircdb.checkCapability(msg.prefix, 'owner'):
(self.nick, self.nickserv, self.password)=privmsgs.getArgs(args, 3)
self.sentGhost = 0
self._ghosted = re.compile('%s.*killed' % self.nick)
irc.reply(msg, conf.replySuccess)
else:
irc.error(msg, conf.replyNoCapability % 'owner')
_owned = re.compile('nick.*(?:(?<!not)(?:registered|protected|owned))')
def doNotice(self, irc, msg):
if self.started:
if msg.nick == self.nickserv:
if self._owned.search(msg.args[1]):
# NickServ told us the nick is registered.
identify = 'IDENTIFY %s' % self.password
irc.queueMsg(ircmsgs.privmsg(self.nickserv, identify))
elif self._ghosted.search(msg.args[1]):
# NickServ told us the nick has been ghost-killed.
irc.queueMsg(ircmsgs.nick(self.nick))
def do376(self, irc, msg):
if self.started:
if irc.nick != self.nick:
ghost = 'GHOST %s %s' % (self.nick, self.password)
irc.queueMsg(ircmsgs.privmsg(self.nickserv, ghost))
Class = NickServ

72
plugins/Parter.py Normal file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import conf
import ircdb
import irclib
import ircmsgs
import privmsgs
import callbacks
class Parter(callbacks.Privmsg):
def autopartchannel(self, irc, msg, args):
"<channel to part automatically>"
channel = privmsgs.getArgs(args)
if ircdb.checkCapability(msg.prefix, 'admin'):
if not hasattr(self, 'channels'):
self.channels = [channel]
else:
self.channels.append(channel)
irc.reply(msg, conf.replySuccess)
else:
irc.error(msg, conf.replyNoCapability % 'admin')
def removeautopartchannel(self, irc, msg, args):
"<channel to stop parting automatically>"
channel = privmsgs.getArgs(args)
if ircdb.checkCapability(msg.prefix, 'admin'):
if hasattr(self, 'channels'):
self.channels = [x for x in self.channels if x != channel]
irc.reply(msg, conf.replySuccess)
else:
irc.error(msg, conf.replyNoCapability % 'admin')
def doJoin(self, irc, msg):
if irc.nick == msg.nick:
channels = msg.args[0].split(',')
for channel in channels:
if channel in self.channels:
irc.sendMsg(ircmsgs.part(channel))
Class = Parter

169
plugins/Quotes.py Normal file
View File

@ -0,0 +1,169 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import re
import time
import os.path
import sqlite
import ircdb
import privmsgs
import callbacks
class Quotes(DBHandler, callbacks.Privmsg):
def __init__(self):
DBHandler.__init__(self)
callbacks.Privmsg.__init__(self)
def makeDb(self, filename):
if os.path.exists(filename):
return sqlite.connect(db=filename, mode=0755,
converters={'bool': bool})
#else:
db = sqlite.connect(db=filename, mode=0755, coverters={'bool': bool})
cursor = db.cursor()
cursor.execute("""CREATE TABLE quotes (
id INTEGER PRIMARY KEY,
added_by VARCHAR(255),
added_at TIMESTAMP,
quote TEXT
);""")
db.commit()
return db
def addquote(self, irc, msg, args):
"[<channel>] (if not sent through the channel itself) <quote>"
channel = privmsgs.getChannel(msg, args)
quote = privmsgs.getArgs(args)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""INSERT INTO quotes
VALUES(NULL, %s, %s, %s)""",
msg.nick, int(time.time()), quote)
db.commit()
irc.reply(msg, conf.replySuccess)
def maxquote(self, irc, msg, args):
"[<channel>] (if not sent through the channel itself)"
channel = privmsgs.getChannel(msg, args)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT max(id) FROM quotes""")
maxid = cursor.fetchone()[0]
if maxid is None:
maxid = 0
s = 'There are approximately %s quotes in the database.' % maxid
irc.reply(msg, s)
def quote(self, irc, msg, args):
"[<channel>] (if not sent through the channel itself) <number|regexp>"
channel = privmsgs.getChannel(msg, args)
value = privmsgs.getArgs(args)
db = self.getDb(channel)
cursor = db.cursor()
try:
id = int(value)
cursor.execute("""SELECT quote FROM quotes WHERE id=%s""", id)
ret = cursor.fetchall()
if ret:
irc.reply(msg, ret[0][0])
return
else:
irc.reply(msg, "That quote doesn't exist.")
return
except ValueError: # It's not an int.
r = re.compile(value, re.I)
def p(s):
return bool(r.match(s))
db.create_function('p', 1, p)
cursor.execute("""SELECT id, quote FROM quotes WHERE p(quote)""")
if cursor.rowcount == 0:
irc.reply(msg, 'No quotes matched that regexp.')
return
elif cursor.rowcount == 1:
(id, quote) = cursor.fetchone()
irc.reply(msg, 'Quote %s: %s' % (id, quote))
return
elif cursor.rowcount > 5:
ids = [t[0] for t in cursor.fetchall()]
irc.reply(msg, 'Quotes %s matched.' % ', '.join(ids))
return
else:
L = ['%s: %s' % (id,s[:30]) for (id,s) in cursor.fetchall()]
irc.reply(msg, 'These quotes matched: %s' % ', '.join(L))
return
def randomquote(self, irc, msg, args):
"[<channel>] (if not sent through the channel itself)"
channel = privmsgs.getChannel(msg, args)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT quote FROM quotes
ORDER BY random()
LIMIT 1""")
quote = cursor.fetchone()[0]
irc.reply(msg, quote)
return
def quoteinfo(self, irc, msg, args):
"[<channel>] (if not sent through the channel itself) <number>"
channel = privmsgs.getChannel(msg, args)
id = privmsgs.getArgs(args)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT * FROM quotes WHERE id=%s""", id)
row = cursor.fetchone()
if row:
irc.reply(msg, 'Quote %r added by %s at %s.' % \
(row.quote, row.added_by, time.strftime(conf.timestampFormat)))
return
else:
irc.reply(msg, 'There isn\'t a quote with that id.')
return
def removequote(self, irc, msg, args):
"[<channel>] (if not sent through the channel itself) <number>"
channel = privmsgs.getChannel(msg, args)
id = privmsgs.getArgs(args)
db = self.getDb(channel)
cursor = db.cursor()
capability = ircdb.makeChannelCapability(channel, 'op')
if ircdb.checkCapability(msg.prefix, capability):
cursor.execute("""DELETE FROM quotes WHERE id=%s""", id)
irc.reply(msg, conf.replySuccess)
else:
irc.error(msg, conf.replyNoCapability % capability)
Class = Quotes

55
plugins/RawLogger.py Normal file
View File

@ -0,0 +1,55 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import conf
import world
import irclib
###
# RawLogger: Logs the *raw* IRC messages.
###
class RawLogger(irclib.IrcCallback):
def __init__(self):
self.fd = file(os.path.join(conf.logDir, 'raw.log'), 'a')
world.flushers.append(self.fd.flush)
def inFilter(self, irc, msg):
self.fd.write(str(msg))
return msg
def outFilter(self, irc, msg):
self.fd.write(str(msg))
return msg
Class = RawLogger

41
plugins/Relay.py Normal file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import privmsgs
import callbacks
class Relay(callbacks.Privmsg):
def relayconnect(self, irc, msg, args):
"<domain:port> (port defaults to 6667)"
pass

52
plugins/RootWarner.py Normal file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import irclib
import ircmsgs
import ircutils
###
# RootWarner: Warns users who join a channel as root.
###
class RootWarner(irclib.IrcCallback):
def doJoin(self, irc, msg):
user = ircutils.userFromHostmask(msg.prefix)
if user == 'root' or user == '~root':
irc.queueMsg(ircmsgs.privmsg(msg.nick,
'Don\'t IRC as root -- it\'s very possible that there is a '\
'security flaw in latent in your irc client (remember the '\
'BitchX format string vulnerabilities of days past?) and if '\
'You\'re IRCing as root, your entire box could be compromised.'))
Class = RootWarner

View File

@ -0,0 +1,62 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import socket
import ircutils
import privmsgs
import callbacks
class ThreadedFunCommands(callbacks.Privmsg):
threaded = True
def nslookup(self, irc, msg, args):
"<host|ip>"
host = privmsgs.getArgs(args)
if ircutils.isIP(host):
hostname = socket.getfqdn(host)
if hostname == host:
irc.error(msg, 'Host not found.')
else:
irc.reply(msg, hostname)
else:
try:
ip = socket.gethostbyname(host)
irc.reply(msg, ip)
except socket.error:
irc.error(msg, 'Host not found.')
host = nslookup
dns = nslookup
Class = ThreadedFunCommands

102
plugins/Topic.py Normal file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import re
import random
import debug
import ircdb
import ircmsgs
import privmsgs
import callbacks
class Topic(callbacks.Privmsg):
topicSeparator = ' || '
topicFormatter = '%s (%s)'
topicUnformatter = re.compile('(.*) \((.*)\)')
def addtopic(self, irc, msg, args):
"[<channel>] (if not sent in the channel itself) <topic>"
channel = privmsgs.getChannel(msg, args)
topic = privmsgs.getArgs(args)
if ircdb.checkCapability(msg.prefix, 'topic'):
if topic.find(self.topicSeparator) != -1:
s = 'You can\'t have %s in your topic' % self.topicSeparator
irc.error(msg, s)
return
currentTopic = irc.state.getTopic(channel)
name = ircdb.users.getUserName(msg.prefix)
formattedTopic = self.topicFormatter % (topic, name)
if currentTopic:
newTopic = self.topicSeparator.join((currentTopic,
formattedTopic))
else:
newTopic = formattedTopic
irc.queueMsg(ircmsgs.topic(channel, newTopic))
else:
irc.error(msg, conf.replyNoCapability % 'topic')
def shuffletopic(self, irc, msg, args):
"[<channel>] (if not sent in the channel itself)"
channel = privmsgs.getChannel(msg, args)
if ircdb.checkCapability(msg.prefix, 'topic'):
topics = irc.state.getTopic(channel).split(self.topicSeparator)
random.shuffle(topics)
newtopic = self.topicSeparator.join(topics)
irc.queueMsg(ircmsgs.topic(channel, newtopic))
else:
irc.error(msg, conf.replyNoCapability % 'topic')
def removetopic(self, irc, msg, args):
"[<channel>] (if not sent in the channel itself) <topic number>"
channel = privmsgs.getChannel(msg, args)
try:
number = int(privmsgs.getArgs(args))
except ValueError:
irc.error(msg, 'The argument must be a number.')
return
if ircdb.checkCapability(msg.prefix, 'topic'):
topics = irc.state.getTopic(channel).split(self.topicSeparator)
topic = topics.pop(number)
debug.printf(topic)
(topic, name) = self.topicUnformatter.match(topic).groups()
if name != ircdb.users.getUserName(msg.prefix) and \
not ircdb.checkCapabilities(msg.prefix, ('op', 'admin')):
irc.error('You can only remove your own topics.')
return
newTopic = self.topicSeparator.join(topics)
irc.queueMsg(ircmsgs.topic(channel, newTopic))
else:
irc.error(msg, conf.replyNoCapability % 'topic')
Class = Topic

89
plugins/Unix.py Normal file
View File

@ -0,0 +1,89 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import os
import re
import pwd
import crypt
import errno
import random
import struct
import privmsgs
import callbacks
class Unix(callbacks.Privmsg):
def errno(self, irc, msg, args):
"<error number or code>"
s = privmsgs.getArgs(args)
try:
i = int(s)
name = errno.errorcode[i]
except ValueError:
name = s.upper()
try:
i = getattr(errno, name)
except AttributeError:
irc.reply(msg, 'I can\'t find the errno number for that code.')
return
except KeyError:
name = '(unknown)'
irc.reply(msg, '%s (#%s): %s' % (name, i, os.strerror(i)))
def progstats(self, irc, msg, args):
"takes no arguments"
pw = pwd.getpwuid(os.getuid())
response = 'Process ID %i running as user "%s" and as group "%s" '\
'from directory "%s" with the command line "%s". '\
'Running on Python %s.' %\
(os.getpid(), pw[0], pw[3],
os.getcwd(), " ".join(sys.argv),
sys.version.translate(string.ascii, '\r\n'))
irc.reply(msg, response)
_cryptre = re.compile(r'[./0-9A-Za-z]')
def crypt(self, irc, msg, args):
"<password> [<salt>]"
def makeSalt():
s = '\x00'
while self._cryptre.sub('', s) != '':
s = struct.pack('<h', random.randrange(2**16))
return s
(password, salt) = privmsgs.getArgs(args, optional=1)
if salt == '':
salt = makeSalt()
irc.reply(msg, crypt.crypt(password, salt))
Class = Unix

44
plugins/Utils.py Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import privmsgs
import callbacks
class Utils(callbacks.Privmsg):
def strjoin(self, irc, msg, args):
"<separator> <strings to join>"
(sep, text) = privmsgs.getArgs(args, needed=2)
irc.reply(msg, sep.join(text.split()))
Class = Utils

45
plugins/baseplugin.py Normal file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env python
import sys
sys.path.insert(0, 'src')
sys.path.insert(0, 'others')
from fix import *
import os
import cdb
import conf
import world
import callbacks
class DBHandler(object):
"""A class to handle database stuff for individual channels transparently.
"""
suffix = '.db'
def __init__(self, suffix='.db'):
self.dbCache = {}
suffix = self.suffix
if self.suffix and self.suffix[0] != '.':
suffix = '.' + suffix
self.suffix = suffix
def makeFilename(self, channel):
prefix = '%s-%s%s' % (channel, self.__class__.__name__, self.suffix)
return os.path.join(conf.dataDir, prefix)
def makeDb(self, filename):
return cdb.shelf(filename)
def getDb(self, channel):
try:
return self.dbCache[channel]
except KeyError:
db = self.makeDb(self.makeFilename(channel))
self.dbCache[channel] = db
return db
def die(self):
for db in self.dbCache.itervalues():
db.close()

34
plugins/template.py Normal file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *

19
sandbox/8ball.dat Normal file
View File

@ -0,0 +1,19 @@
outlook not so good.
my reply is no.
don't count on it.
you may rely on it.
ask again later.
most likely.
cannot predict now.
yes.
yes, most definitely.
better not tell you now.
it is certain.
very doubtful.
it is decidedly so.
concentrate and ask again.
signs point to yes.
my sources say no.
without a doubt.
reply hazy, try again.
as I see it, yes.

117
sandbox/Debian.py Normal file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import string
import random
import os.path
import urllib2
import sqlite
import debug
import privmsgs
import callbacks
dbFile = os.path.join(conf.dataDir, 'Debian.db')
def getIndex():
return urllib2.urlopen('ftp://ftp.us.debian.org' \
'/debian/dists/unstable/main/binary-i386/Packages.gz')
def makeDb(dbfilename, indexfd, replace=False):
if os.path.exists(filename):
if replace:
os.remove(filename)
else:
indexfd.close()
return sqlite.connect(dbfilename)
db = sqlite.connect(filename)
cursor = db.cursor()
cursor.execute("""CREATE TABLE packages (
id INTEGER PRIMARY KEY,
package TEXT,
priority TEXT,
section TEXT,
installed_size INTEGER,
maintainer TEXT,
architecture TEXT,
source TEXT,
version TEXT,
depends TEXT,
filename TEXT,
size INTEGER,
md5sum TEXT,
short_description TEXT,
long_description TEXT
)""")
lines = []
for line in indexfd:
if line == '\n':
# Last line of record.
s = ''.join(lines)
m = email.message_from_string(s)
descrlines = map(str.strip, m['description'].splitlines())
shortdescr = descrlines.pop(0)
longdescr = ' '.join(descrlines)
cursor.execute("""INSERT INTO packages VALUES (
NULL, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s
)""", m['package'], m['priority'], m['section'],
m['installed-size'],m['maintainer'],m['architecture'],
m['source'],m['version'],m['depends'],m['filename'],
m['size'], m['md5sum'], shortdescr, longdescr)
lines = []
else:
lines.append(line)
indexfd.close()
db.commit()
return db
class Debian(callbacks.Privmsg):
def __init__(self):
callbacks.Privmsg.__init__(self)
self.db = makeDb(dbFile, getIndex())
def numdebs(self, irc, msg, args):
"takes no arguments"
cursor = self.db.cursor()
cursor.execute("""SELECT COUNT(id) FROM packages""")
number = cursor.fetchone()[0]
irc.reply(msg, 'There are %s ports in my database.' % number)
Class = Debian
if __name__ == '__main__':
makeDb(dbFile, getIndex(), replace=True)

126
sandbox/anagrams.py Normal file
View File

@ -0,0 +1,126 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import cdb
class AnagramDatabase:
def __init__(self, filename):
self.filename = filename
self.db = cdb.shelf(self.filename)
def _makeKey(self, key):
chars = list(key)
chars.sort()
return ''.join(chars)
def find(self, key):
key = self._makeKey(key)
return self.db[key]
def add(self, word):
key = self._makeKey(word)
initial = self.db.get(key, [])
initial.append(word)
self.db[key] = initial
def remove(self, word):
key = self._makeKey(word)
l = self.db[key]
if key not in l:
raise KeyError, word
else:
self.db[key] = [x for x in l if x != key]
def close(self):
self.db.close()
def flush(self):
self.db.flush()
class Anagrams(BasePlugin):
def startanagrams(self, irc, msg, args):
"<filename of anagrams database>"
if ircdb.checkCapability(msg.prefix, 'admin'):
self.filename = os.path.join(world.dataDir, self.getArgs(args))
self.db = AnagramDatabase(self.filename)
self.started = True
self.reply(privmsgs.replySuccess)
else:
self.error(privmsgs.replyNoCapability % 'admin')
def stopanagrams(self, irc, msg, args):
"<takes no arguments; stop the anagrams plugin."
if ircdb.checkCapability(msg.prefix, 'admin'):
self.db.close()
self.started = False
self.reply(privmsgs.replySuccess)
else:
self.error(privmsgs.replyNoCapability % 'admin')
def addanagramword(self, irc, msg, args):
"<word>"
word = self.getArgs(args)
self.db.add(word)
self.reply(privmsgs.replySuccess)
def delanagramword(self, irc, msg, args):
"<word>"
word = self.getArgs(args)
self.db.remove(word)
self.reply(privmsgs.replySuccess)
def anagram(self, irc, msg, args):
"<word>"
word = self.getArgs(args)
try:
l = [s for s in self.db.find(word) if s != word]
if l:
self.reply(', '.join(self.db.find(word)))
else:
self.error('%s has no anagrams.' % word)
except KeyError:
self.error('%s isn\'t in my anagram dictionary.' % word)
Class = Anagrams
###
# This makes an anagram database from a list of words on stdin.
###
if __name__ == '__main__':
import sys, string
db = AnagramDatabase(os.path.join(world.dataDir, 'anagrams.db'))
trans = string.maketrans(string.ascii_uppercase, string.ascii_lowercase)
for line in sys.stdin:
db.add(line.translate(trans, '\n'))
db.flush()

18
sandbox/averagePrefix.py Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python
import sys
import ircmsgs
import ircutils
sys.path.append('..')
if __name__ == '__main__':
total = 0
lines = 0
for line in sys.stdin:
msg = ircmsgs.IrcMsg(line)
if ircutils.isUserHostmask(msg.prefix):
total += len(msg.prefix)
lines += 1
print total / lines

60
sandbox/debian.py Normal file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import re
import cdb
import conf
import callbacks
debianDir = os.path.join(conf.dataDir, 'debian')
r = re.compile('(.*)\s+(.*)$')
def makeContentsDb(filename=os.path.join(debianDir, 'Contents')):
fd = file(filename)
maker = cdb.Maker(filename + '.db')
while not fd.readline().startswith('FILE'):
pass
for line in fd:
try:
(key, value) = r.match(line.strip()).groups()
except:
print repr(line)
maker.add(key, value)
maker.finish()
if __name__ == '__main__':
import sys, time
start = time.time()
makeContentsDb(sys.argv[1])
print 'Took %s seconds.' % time.time() - start

450
sandbox/excuses.dat Normal file
View File

@ -0,0 +1,450 @@
clock speed
solar flares
electromagnetic radiation from satellite debris
static from nylon underwear
static from plastic slide rules
global warming
poor power conditioning
static buildup
doppler effect
hardware stress fractures
magnetic interferance from money/credit cards
dry joints on cable plug
we're waiting for [the phone company] to fix that line
sounds like a Windows problem, try calling Microsoft support
temporary routing anomoly
somebody was calculating pi on the server
fat electrons in the lines
excess surge protection
floating point processor overflow
divide-by-zero error
POSIX complience problem
monitor resolution too high
improperly oriented keyboard
network packets travelling uphill (use a carrier pigeon)
Decreasing electron flux
first Saturday after first full moon in Winter
radiosity depletion
CPU radiator broken
It works the way the Wang did, what's the problem
positron router malfunction
cellular telephone interference
techtonic stress
pizeo-electric interference
(l)user error
working as designed
dynamic software linking table corrupted
heavy gravity fluctuation, move computer to floor rapidly
secretary plugged hairdryer into UPS
terrorist activities
not enough memory, go get system upgrade
interrupt configuration error
spaghetti cable cause packet failure
boss forgot system password
bank holiday - system operating credits not recharged
virus attack, luser responsible
waste water tank overflowed onto computer
Complete Transient Lockout
bad ether in the cables
Bogon emissions
Change in Earth's rotational speed
Cosmic ray particles crashed through the hard disk platter
Smell from unhygenic janitorial staff wrecked the tape heads
Little hamster in running wheel had coronary; waiting for replacement to be Fedexed from Wyoming
Evil dogs hypnotized the night shift
Plumber mistook routing panel for decorative wall fixture
Electricians made popcorn in the power supply
Groundskeepers stole the root password
high pressure system failure
failed trials, system needs redesigned
system has been recalled
not approved by the FCC
need to wrap system in aluminum foil to fix problem
not properly grounded, please bury computer
CPU needs recalibration
system needs to be rebooted
bit bucket overflow
descramble code needed from software company
only available on a need to know basis
knot in cables caused data stream to become twisted and kinked
nesting roaches shorted out the ether cable
The file system is full of it
Satan did it
Daemons did it
You're out of memory
There isn't any problem
Unoptimized hard drive
Typo in the code
Yes, yes, its called a desgin limitation
Look, buddy: Windows 3.1 IS A General Protection Fault.
That's a great computer you have there; have you considered how it would work as a BSD machine?
Please excuse me, I have to circuit an AC line through my head to get this database working.
Yeah, yo mama dresses you funny and you need a mouse to delete files.
Support staff hung over, send aspirin and come back LATER.
Someone is standing on the ethernet cable, causeing a kink in the cable
Windows 95 undocumented "feature"
Runt packets
Password is too complex to decrypt
Boss' kid fucked up the machine
Electromagnetic energy loss
Budget cuts
Mouse chewed through power cable
Stale file handle (next time use Tupperware(tm)!)
Feature not yet implimented
Internet outage
Pentium FDIV bug
Vendor no longer supports the product
Small animal kamikaze attack on power supplies
The vendor put the bug there.
SIMM crosstalk.
IRQ dropout
Collapsed Backbone
Power company testing new voltage spike (creation) equipment
operators on strike due to broken coffee machine
backup tape overwritten with copy of system manager's favourite CD
UPS interrupted the server's power
The electrician didn't know what the yellow cable was so he yanked the ethernet out.
The keyboard isn't plugged in
The air conditioning water supply pipe ruptured over the machine room
The electricity substation in the car park blew up.
The rolling stones concert down the road caused a brown out
The salesman drove over the CPU board.
The monitor is plugged into the serial port
Root nameservers are out of sync
electro-magnetic pulses from French above ground nuke testing.
your keyboard's space bar is generating spurious keycodes.
the real ttys became pseudo ttys and vice-versa.
the printer thinks its a router.
the router thinks its a printer.
evil hackers from Serbia.
we just switched to FDDI.
halon system went off and killed the operators.
because Bill Gates is a Jehovah's witness and so nothing can work on St. Swithin's day.
user to computer ratio too high.
user to computer ration too low.
we just switched to Sprint.
it has Intel Inside
Sticky bits on disk.
Power Company having EMP problems with their reactor
The ring needs another token
new management
telnet: Unable to connect to remote host: Connection refused
SCSI Chain overterminated
It's not plugged in.
because of network lag due to too many people playing deathmatch
You put the disk in upside down.
Daemons loose in system.
User was distributing pornography on server; system seized by FBI.
BNC (brain not (user brain not connected)
UBNC (user brain not connected)
LBNC (luser brain not connected)
disks spinning backwards - toggle the hemisphere jumper.
new guy cross-connected phone lines with ac power bus.
had to use hammer to free stuck disk drive heads.
Too few computrons available.
Flat tire on station wagon with tapes. ("Never underestimate the bandwidth of a station wagon full of tapes hurling down the highway" Andrew S. Tanenbaum)
Communications satellite used by the military for star wars.
Party-bug in the Aloha protocol.
Insert coin for new game
Dew on the telephone lines.
Arcserve crashed the server again.
Some one needed the powerstrip, so they pulled the switch plug.
My pony-tail hit the on/off switch on the power strip.
Big to little endian conversion error
You can tune a file system, but you can't tune a fish (from most tunefs man pages)
Dumb terminal
Zombie processes haunting the computer
Incorrect time syncronization
Defunct processes
Stubborn processes
non-redundant fan failure
monitor VLF leakage
bugs in the RAID
no "any" key on keyboard
root rot
Backbone Scoliosis
/pub/lunch
excessive collisions & not enough packet ambulances
le0: no carrier: transceiver cable problem?
broadcast packets on wrong frequency
popper unable to process jumbo kernel
NOTICE: alloc: /dev/null: filesystem full
pseudo-user on a pseudo-terminal
Recursive traversal of loopback mount points
Backbone adjustment
OS swapped to disk
vapors from evaporating sticky-note adhesives
sticktion
short leg on process table
multicasts on broken packets
ether leak
Atilla the Hub
endothermal recalibration
filesystem not big enough for Jumbo Kernel Patch
loop found in loop in redundant loopback
system consumed all the paper for paging
permission denied
Reformatting Page. Wait...
..disk or the processor is on fire.
SCSI's too wide.
Proprietary Information.
Just type 'mv * /dev/null'.
runaway cat on system.
Did you pay the new Support Fee?
We only support a 1200 bps connection.
We only support a 28000 bps connection.
Me no internet, only janitor, me just wax floors.
I'm sorry a pentium won't do, you need an SGI to connect with us.
Post-it Note Sludge leaked into the monitor.
the curls in your keyboard cord are losing electricity.
The monitor needs another box of pixels.
RPC_PMAP_FAILURE
kernel panic: write-only-memory (/dev/wom0) capacity exceeded.
Write-only-memory subsystem too slow for this machine. Contact your local dealer.
Just pick up the phone and give modem connect sounds. "Well you said we should get more lines so we don't have voice lines."
Quantum dynamics are affecting the transistors
Police are examining all internet packets in the search for a narco-net-traficer
We are currently trying a new concept of using a live mouse. Unfortuantely, one has yet to survive being hooked up to the computer.....please bear with us.
Your mail is being routed through Germany ... and they're censoring us.
Only people with names beginning with 'A' are getting mail this week (a la Microsoft)
We didn't pay the Internet bill and it's been cut off.
Lightning strikes.
Of course it doesn't work. We've performed a software upgrade.
Change your language to Finnish.
Flourescent lights are generating negative ions. If turning them off doesn't work, take them out and put tin foil on the ends.
High nuclear activity in your area.
What office are you in? Oh, that one. Did you know that your building was built over the universities first nuclear research site? And wow, are'nt you the lucky one, your office is right over where the core is buried!
The MGs ran out of gas.
The UPS doesn't have a battery backup.
Recursivity. Call back if it happens again.
Someone thought The Big Red Button was a light switch.
The mainframe needs to rest. It's getting old, you know.
I'm not sure. Try calling the Internet's head office -- it's in the book.
The lines are all busy (busied out, that is -- why let them in to begin with?).
Jan 9 16:41:27 huber su: 'su root' succeeded for .... on /dev/pts/1
It's those computer people in X {city of world}. They keep stuffing things up.
A star wars satellite accidently blew up the WAN.
Fatal error right in front of screen
That function is not currently supported, but Bill Gates assures us it will be featured in the next upgrade.
wrong polarity of neutron flow
Lusers learning curve appears to be fractal
We had to turn off that service to comply with the CDA Bill.
Ionisation from the air-conditioning
TCP/IP UDP alarm threshold is set too low.
Someone is broadcasting pigmy packets and the router dosn't know how to deal with them.
The new frame relay network hasn't bedded down the software loop transmitter yet.
Fanout dropping voltage too much, try cutting some of those little traces
Plate voltage too low on demodulator tube
You did wha... oh _dear_....
CPU needs bearings repacked
Too many little pins on CPU confusing it, bend back and forth until 10-20% are neatly removed. Do _not_ leave metal bits visible!
_Rosin_ core solder? But...
Software uses US measurements, but the OS is in metric...
The computer fletely, mouse and all.
Your cat tried to eat the mouse.
The Borg tried to assimilate your system. Resistance is futile.
It must have been the lightning storm we had (yesterdy) (last week) (last month)
Due to Federal Budget problems we have been forced to cut back on the number of users able to access the system at one time. (namely none allowed....)
Too much radiation coming from the soil.
Unfortunately we have run out of bits/bytes/whatever. Don't worry, the next supply will be coming next week.
Program load too heavy for processor to lift.
Processes running slowly due to weak power supply
Our ISP is having {switching,routing,SMDS,frame relay} problems
We've run out of licenses
Interference from lunar radiation
Standing room only on the bus.
You need to install an RTFM interface.
That would be because the software doesn't work.
That's easy to fix, but I can't be bothered.
Someone's tie is caught in the printer, and if anything else gets printed, he'll be in it too.
We're upgrading /dev/null
The Usenet news is out of date
Our POP server was kidnapped by a weasel.
It's stuck in the Web.
Your modem doesn't speak English.
The mouse escaped.
All of the packets are empty.
The UPS is on strike.
Neutrino overload on the nameserver
Melting hard drives
Someone has messed up the kernel pointers
The kernel license has expired
Netscape has crashed
The cord jumped over and hit the power switch.
It was OK before you touched it.
Bit rot
U.S. Postal Service
Your Flux Capacitor has gone bad.
The Dilithium Cyrstals need to be rotated.
The static electricity routing is acting up...
Traceroute says that there is a routing problem in the backbone. It's not our problem.
The co-locator cannot verify the frame-relay gateway to the ISDN server.
High altitude condensation from U.S.A.F prototype aircraft has contaminated the primary subnet mask. Turn off your computer for 9 days to avoid damaging it.
Lawn mower blade in your fan need sharpening
Electrons on a bender
Telecommunications is upgrading.
Telecommunications is downgrading.
Telecommunications is downshifting.
Hard drive sleeping. Let it wake up on it's own...
Interference between the keyboard and the chair.
The CPU has shifted, and become decentralized.
Due to the CDA, we no longer have a root account.
We ran out of dial tone and we're and waiting for the phone company to deliver another bottle.
You must've hit the wrong anykey.
PCMCIA slave driver
The Token fell out of the ring. Call us when you find it.
The hardware bus needs a new token.
Too many interrupts
Not enough interrupts
The data on your hard drive is out of balance.
Digital Manipulator exceeding velocity parameters
appears to be a Slow/Narrow SCSI-0 Interface problem
microelectronic Riemannian curved-space fault in write-only file system
fractal radiation jamming the backbone
routing problems on the neural net
IRQ-problems with the Un-Interruptable-Power-Supply
CPU-angle has to be adjusted because of vibrations coming from the nearby road
emissions from GSM-phones
CD-ROM server needs recalibration
firewall needs cooling
asynchronous inode failure
transient bus protocol violation
incompatible bit-registration operators
your process is not ISO 9000 compliant
You need to upgrade your VESA local bus to a MasterCard local bus.
The recent proliferation of Nuclear Testing
Elves on strike. (Why do they call EMAG Elf Magic)
Internet exceeded Luser level, please wait until a luser logs off before attempting to log back on.
Your EMAIL is now being delivered by the USPS.
Your computer hasn't been returning all the bits it gets from the Internet.
You've been infected by the Telescoping Hubble virus.
Scheduled global CPU outage
Your Pentium has a heating problem - try cooling it with ice cold water.(Do not turn of your computer, you do not want to cool down the Pentium Chip while he isn't working, do you?)
Your processor has processed too many intructions. Turn it off emideately, do not type any commands!!
Your packets were eaten by the terminator
Your processor does not develop enough heat.
We need a licensed electrician to replace the light bulbs in the computer room.
The POP server is out of Coke
Fiber optics caused gas main leak
Server depressed, needs Prozak
quatnum decoherence
those damn racoons!
suboptimal routing experience
A plumber is needed, the network drain is clogged
50% of the manual is in .pdf readme files
the AA battery in the wallclock sends magnetic interference
the xy axis in the trackball is coordinated with the summer soltice
the butane lighter causes the pincushioning
old inkjet cartridges emanate barium-based fumes
manager in the cable duct
Well fix that in the next (upgrade, update, patch release, service pack).
HTTPD Error 666 : BOFH was here
HTTPD Error 4004 : very old Intel cpu - insufficient processing power
The ATM board has run out of 10 pound notes. We are having a whip round to refill it, care to contribute ?
Network failure - call NBC
Having to manually track the satellite.
Your/our computer(s) had suffered a memory leak, and we are waiting for them to be topped up.
The rubber band broke
We're on Token Ring, and it looks like the token got loose.
Stray Alpha Particles from memory packaging caused Hard Memory Error on Server.
paradigm shift...without a clutch
PEBKAC (Problem Exists Between Keyboard And Chair)
The cables are not the same length.
Second-sytem effect.
Chewing gum on /dev/sd3c
Boredom in the Kernel.
the daemons! the daemons! the terrible daemons!
I'd love to help you -- it's just that the Boss won't let me near the computer.
struck by the Good Times virus
YOU HAVE AN I/O ERROR -> Incompetent Operator error
Your parity check is overdrawn and you're out of cache.
Communist revolutionaries taking over the server room and demanding all the computers in the building or they shoot the sysadmin. Poor misguided fools.
Plasma conduit breach
Out of cards on drive D:
Sand fleas eating the Internet cables
parallel processors running perpendicular today
ATM cell has no roaming feature turned on, notebooks can't connect
Webmasters kidnapped by evil cult.
Failure to adjust for daylight savings time.
Virus transmitted from computer to sysadmins.
Virus due to computers having unsafe sex.
Incorrectly configured static routes on the corerouters.
Forced to support NT servers; sysadmins quit.
Suspicious pointer corrupted virtual machine
Its the InterNIC's fault.
Root name servers corrupted.
Budget cuts forced us to sell all the power cords for the servers.
Someone hooked the twisted pair wires into the answering machine.
Operators killed by year 2000 bug bite.
We've picked COBOL as the language of choice.
Operators killed when huge stack of backup tapes fell over.
Robotic tape changer mistook operator's tie for a backup tape.
Someone was smoking in the computer room and set off the halon systems.
Your processor has taken a ride to Heaven's Gate on the UFO behind Hale-Bopp's comet.
t's an ID-10-T error
Dyslexics retyping hosts file on servers
The Internet is being scanned for viruses.
Your computer's union contract is set to expire at midnight.
Bad user karma.
/dev/clue was linked to /dev/null
Increased sunspot activity.
We already sent around a notice about that.
It's union rules. There's nothing we can do about it. Sorry.
Interferance from the Van Allen Belt.
Jupiter is aligned with Mars.
Redundant ACLs.
Mail server hit by UniSpammer.
T-1's congested due to porn traffic to the news server.
Data for intranet got routed through the extranet and landed on the internet.
We are a 100% Microsoft Shop.
We are Microsoft. What you are experiencing is not a problem; it is an undocumented feature.
Sales staff sold a product we don't offer.
Secretary sent chain letter to all 5000 employees.
Sysadmin didn't hear pager go off due to loud music from bar-room speakers.
Sysadmin accidentally destroyed pager with a large hammer.
Sysadmins unavailable because they are in a meeting talking about why they are unavailable so much.
Bad cafeteria food landed all the sysadmins in the hospital.
Route flapping at the NAP.
Computers under water due to SYN flooding.
The vulcan-death-grip ping has been applied.
Electrical conduits in machine room are melting.
Traffic jam on the Information Superhighway.
Radial Telemetry Infiltration
Cow-tippers tipped a cow onto the server.
tachyon emissions overloading the system
Maintence window broken
We're out of slots on the server
Computer room being moved. Our systems are down for the weekend.
Sysadmins busy fighting SPAM.
Repeated reboots of the system failed to solve problem
Feature was not beta tested
Domain controler not responding
Someone else stole your IP address, call the Internet detectives!
It's not RFC-822 compliant.
operation failed because: there is no message for this error (#1014)
stop bit received
internet is needed to catch the etherbunny
network down, IP packets delivered via UPS
Firmware update in the coffee machine
Temporal anomaly
Mouse has out-of-cheese-error
Borg implants are failing
Borg nanites have infested the server
error: one bad user found in front of screen
Please state the nature of the technical emergency
Internet shut down due to maintainance
Daemon escaped from pentagram
crop circles in the corn shell
sticky bit has come loose
Hot Java has gone cold
Cache miss - please take better aim next time
Hash table has woodworm
Trojan horse ran out of hay
Zombie processess detected, machine is haunted.
overflow error in /dev/null
Browser's cookie is corrupted -- someone's been nibbling on it.
Mailer-daemon is busy burning your message in hell.
According to Microsoft, it's by design
vi needs to be upgraded to vii
greenpeace free'd the mallocs
Terorists crashed an airplane into the server room, have to remove /bin/laden. (rm -rf /bin/laden)

47
sandbox/friendly.py Normal file
View File

@ -0,0 +1,47 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import callbacks
class Friendly(callbacks.PrivmsgRegexp):
def greet(self, irc, msg, match):
"(?:heya?|(?:w(?:hat'?s\b|as)s?up)|howdy|hi|hello)"
if re.search(irc.nick, msg.args[1]):
self.reply('howdy, %s' % msg.nick)
def goodbye(self, irc, msg, match):
"(?:good)?bye|adios|vale|ciao|au revoir|seeya|night"
if re.search(irc.nick, msg.args[1]):
self.reply('vale, amic(e|a)!')

23
sandbox/larts.dat Normal file
View File

@ -0,0 +1,23 @@
--purges $who
beats $who senseless with a 50lb Unix manual
cats /dev/urandom into $who's ear
chops $who in half with a free AOL CD
chops $who in half with a free Solaris 7 CD
decapitates $who conan the destroyer style
does a little 'renice 20 -u $who'
drops a truckload of VAXen on $who
duct-tapes $who to the floor and drools on him
frags $who with his BFG9000
holds $who to the floor and spanks him with a cat-o-nine-tails
judo chops $who
pours hot grits down the front of $who's pants
pulls out his louisville slugger and uses $who's head to break the homerun record
pushes the wall down onto $who whilst whistling innocently
resizes $who's terminal to 40x24
rm -rf's $who
stabs $who
steals $who's mojo
strangles $who with a doohicky mouse cord
urinates on $who
whacks $who with the cluebat
whips out a sword and chops $who in half

60
sandbox/makescript.py Executable file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import sys
print """PRAGMA cache_size = 50000;"""
print """CREATE TABLE words (
id INTEGER PRIMARY KEY,
word TEXT UNIQUE ON CONFLICT IGNORE,
sorted_word_id INTEGER
);"""
print """CREATE INDEX sorted_word_id on words (sorted_word_id);"""
print """CREATE TABLE sorted_words (
id INTEGER PRIMARY KEY,
word TEXT UNIQUE ON CONFLICT IGNORE
);"""
#print """CREATE INDEX sorted_word_word on sorted_words (sorted_word);"""
print """BEGIN TRANSACTION;"""
for line in sys.stdin:
line = line.rstrip()
word = line.strip().lower()
L = list(word)
L.sort()
sorted = ''.join(L)
print "INSERT INTO sorted_words VALUES (NULL, '%s');" % sorted
print """INSERT INTO words VALUES (NULL, '%s', (SELECT id FROM sorted_words WHERE word='%s'));""" % (word, sorted)
print """END TRANSACTION;"""

49
sandbox/rbot.py Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from baseplugin import *
import callbacks
def Rbot(callbacks.Privmsg):
verbs = ('slaps', 'hits', 'smashes', 'beats', 'bashes', 'smacks', 'blats')
tool = ('trout', 'fork', 'mouse', 'bear', 'piano', 'cello',
'vacuum cleaner', 'mosquito')
size = ('large', 'huge', 'small', 'tiny', 'nil')
way = ('around the head', 'viciously', 'repeatedly', 'in the face',
'to death', 'nil')
def slap(self, irc, msg, args):
target = self.getArgs(args)
if target in ('me', 'yourself', 'himself', irc.nick):
target = msg.nick
#'%s %s %s'

18
sandbox/twistedDrivers.py Normal file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python
from fix import *
import drivers
import twisted.protocols
class TwistedDriver(drivers.IrcDriver, twisted.protocols.basic.LineReceiver):
def __init__(self, name, irc, (server, port), reconnect=True):
drivers.IrcDriver.__init__(self, name)
self.name = name
self.server = (server, port)
self.reconnect = reconnect
self.irc = irc
irc.driver = irc
self.delimiter = '\n'

34
sandbox/usersXml.py Normal file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
from fix import *
import xml.sax.handler
import ircdb
class UsersHandler(xml.sax.handler.ContentHandler):
def startElement(self, name, attrs):
if name == 'user':
self.u = ircdb.IrcUser(ignore=attrs.getValue('ignore'),
password=attrs.getValue('password'),
auth=(float(attrs.getValue('authtime')),
attrs.getValue('authmask')))
self.name = attrs.getValue('name')
self.startTag = name
def characters(self, content):
self.chars += content
def endElement(self, name):
assert name == self.startTag
self.startTag = ''
if name == 'capability':
self.u.addCapability(self.chars.strip())
elif name == 'hostmask':
self.u.addHostmask(self.chars.strip())
elif name == 'user':
#ircdb.users.setUser(self.name, self.u)
self.users[self.name] = self.u
self.name = ''
self.u = None
self.chars = ''

17
scripts/clean.py Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env python
import os, sys, os.path, shutil
def removeFiles(arg, dirname, names):
for name in names:
if name[-4:] in ('.pyc', 'pyo'):
os.remove(os.path.join(dirname, name))
elif name[-1] == '~':
os.remove(os.path.join(dirname, name))
if __name__ == '__main__':
for name in os.listdir('logs'):
os.remove(os.path.join('logs', name))
for name in os.listdir('conf'):
os.remove(os.path.join('conf', name))
os.path.walk(os.curdir, removeFiles, None)

18
scripts/dumpdb.py Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env python
import sys
import anydbm
if __name__ == '__main__':
if len(sys.argv) < 2:
print 'Usage: %s <dbfile> <dumpfile>' % sys.argv[0]
sys.exit(-1)
db = anydbm.open(sys.argv[1], 'r')
fd = open(sys.argv[2], 'w')
key = db.firstkey()
while key != None:
fd.write('%s => %s\n' % (key, db[key]))
key = db.nextkey(key)
db.close()
fd.close()

23
scripts/makedb.py Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
import re
import sys
import anydbm
r = r'(.*?)\s+=>\s+(.*?)\n$'
if __name__ == '__main__':
if len(sys.argv) < 3:
print 'Usage: %s <dumpname> <dbname>' % sys.argv[0]
sys.exit(-1)
fd = open(sys.argv[1], 'r')
db = anydbm.open(sys.argv[2], 'c')
for line in fd:
m = re.match(r, line)
if m:
(key, value) = m.groups()[-2:]
db[key] = value
else:
print 'Invalid line: %s' % line.strip()
db.close()
fd.close()

11
scripts/newplugin.py Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env python
import os
import sys
import shutil
if len(sys.argv) < 2:
print 'Usage: %s <plugin name>' % sys.argv[0]
sys.exit(-1)
shutil.copy('plugins/template.py', 'plugins/%s.py' % sys.argv[1])

35
scripts/removeSpaces.py Executable file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env python
import os
import os.path
class MutableX:
def __init__(self):
self.x = None
def visit(bytesRemoved, dirname, names):
filenames = [os.path.join(dirname, s) for s in names if s.endswith('.py')]
for filename in filenames:
tmpname = filename + '.tmp'
tmpfd = file(tmpname, 'w')
fd = file(filename, 'r')
for line in fd:
stripped = line.rstrip()
bytesRemoved.x += len(line) - len(stripped)
tmpfd.write(stripped)
tmpfd.write('\n')
fd.close()
tmpfd.close()
os.rename(tmpname, filename)
if __name__ == '__main__':
import sys
if len(sys.argv) < 2:
dir = '.'
else:
dir = sys.argv[1]
x = MutableX()
x.x = 0
os.path.walk(dir, visit, x)
print '%s bytes removed.' % x.x

112
src/ansi.py Normal file
View File

@ -0,0 +1,112 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
'''
ansi.py
ANSI Terminal Interface
Color Usage:
print RED + 'this is red' + RESET
print BOLD + GREEN + WHITEBG + 'this is bold green on white' + RESET
Commands:
def move(new_x, new_y): 'Move cursor to new_x, new_y'
def moveUp(lines): 'Move cursor up # of lines'
def moveDown(lines): 'Move cursor down # of lines'
def moveForward(chars): 'Move cursor forward # of chars'
def moveBack(chars): 'Move cursor backward # of chars'
def save(): 'Saves cursor position'
def restore(): 'Restores cursor position'
def clear(): 'Clears screen and homes cursor'
def clrtoeol(): 'Clears screen to end of line'
'''
################################
# C O L O R C O N S T A N T S #
################################
BLACK = '\033[30m'
RED = '\033[31m'
GREEN = '\033[32m'
YELLOW = '\033[33m'
BLUE = '\033[34m'
MAGENTA = '\033[35m'
CYAN = '\033[36m'
WHITE = '\033[37m'
RESET = '\033[0;0m'
BOLD = '\033[1m'
REVERSE = '\033[2m'
BLACKBG = '\033[40m'
REDBG = '\033[41m'
GREENBG = '\033[42m'
YELLOWBG = '\033[43m'
BLUEBG = '\033[44m'
MAGENTABG = '\033[45m'
CYANBG = '\033[46m'
WHITEBG = '\033[47m'
#def move(new_x, new_y):
# 'Move cursor to new_x, new_y'
# print '\033[' + str(new_x) + ';' + str(new_y) + 'H'
#
#def moveUp(lines):
# 'Move cursor up # of lines'
# print '\033[' + str(lines) + 'A'
#
#def moveDown(lines):
# 'Move cursor down # of lines'
# print '\033[' + str(lines) + 'B'
#
#def moveForward(chars):
# 'Move cursor forward # of chars'
# print '\033[' + str(chars) + 'C'
#
#def moveBack(chars):
# 'Move cursor backward # of chars'
# print '\033[' + str(chars) + 'D'
#
#def save():
# 'Saves cursor position'
# print '\033[s'
#
#def restore():
# 'Restores cursor position'
# print '\033[u'
#
#def clear():
# 'Clears screen and homes cursor'
# print '\033[2J'
#
#def clrtoeol():
# 'Clears screen to end of line'
# print '\033[K'

237
src/asyncoreDrivers.py Normal file
View File

@ -0,0 +1,237 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import re
import sys
import time
import socket
import asyncore
import asynchat
import repl
import conf
import debug
import ircdb
import world
import drivers
import ircmsgs
import ircutils
import schedule
class AsyncoreRunnerDriver(drivers.IrcDriver):
def run(self):
#debug.printf(asyncore.socket_map)
try:
asyncore.poll(conf.asyncorePoll)
except:
debug.recoverableException()
class AsyncoreDriver(asynchat.async_chat, object):
def __init__(self, (server, port), reconnect=True):
#debug.methodNamePrintf(self, '__init__')
asynchat.async_chat.__init__(self)
self.server = (server, port)
self.reconnect = reconnect
self.irc = None # Satisfy PyChecker.
self.buffer = ''
self.set_terminator('\n')
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.connect(self.server)
def writable(self):
#debug.methodNamePrintf(self, 'writable')
while True:
m = self.irc.takeMsg()
if m:
self.push(str(m))
else:
break
#ret = asynchat.async_chat.writable(self)
#debug.printf(str(ret))
return asynchat.async_chat.writable(self)
def handle_error(self):
debug.recoverableException()
def collect_incoming_data(self, s):
#debug.methodNamePrintf(self, 'collect_incoming_data')
self.buffer += s
def found_terminator(self):
#debug.methodNamePrintf(self, 'found_terminator')
msg = ircmsgs.IrcMsg(self.buffer)
try:
self.irc.feedMsg(msg)
except Exception, e:
debug.recoverableException()
if ircutils.isChannel(msg.args[0]):
recipient = msg.args[0]
else:
recipient = msg.nick
self.irc.queueMsg(ircmsgs.privmsg(recipient,
'Uncaught Irc object exception: %s' % debug.exnToString(e)))
self.buffer = ''
def handle_connect(self):
#debug.methodNamePrintf(self, 'handle_connect')
pass
def handle_close(self):
#debug.methodNamePrintf(self, 'handle_close')
if self.reconnect:
#debug.printf('Yes, reconnect.')
def makeNewDriver():
#debug.printf('Called makeNewDriver')
self.irc.reset()
driver = self.__class__(self.server, reconnect=self.reconnect)
driver.irc = self.irc
driver.irc.driver = driver
schedule.addEvent(makeNewDriver, time.time() + 60)
self.die()
def die(self):
#debug.methodNamePrintf(self, 'die')
self.close()
class ReplListener(asyncore.dispatcher, object):
def __init__(self, port=conf.telnetPort):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(('', port))
self.listen(5)
def handle_accept(self):
(sock, addr) = self.accept()
debug.debugMsg('Connection made to telnet-REPL: ' + str(addr),'normal')
Repl((sock, addr))
class Repl(asynchat.async_chat, object):
filename = 'repl'
def __init__(self, (sock, addr)):
asynchat.async_chat.__init__(self, sock)
self.buffer = ''
self.prompt = """SupyBot version %s.
Python %s
Type disconnect() to disconnect.
Name: """ % (world.version, sys.version.translate(string.ascii, '\r\n'))
self.u = None
self.authed = False
self.set_terminator('\r\n')
self.repl = repl.Repl(addr[0])
self.repl.namespace['disconnect'] = self.close
self.push(self.prompt)
self.tries = 0
_re = re.compile(r'(?<!\r)\n')
def push(self, data):
asynchat.async_chat.push(self, self._re.sub('\r\n', data))
def collect_incoming_data(self, data):
if self.tries >= 3:
self.close()
self.buffer += data
if len(self.buffer) > 1024:
self.close()
def handle_close(self):
self.close()
def handle_error(self):
self.close()
def found_terminator(self):
if self.u is None:
try:
name = self.buffer
self.buffer = ''
self.u = ircdb.users.getUser(name)
self.prompt = 'Password: '
except KeyError:
self.push('Unknown user.\n')
self.tries += 1
self.prompt = 'Name: '
msg = 'Unknown user %s on telnet REPL.' % name
debug.debugMsg(msg,'high')
self.push(self.prompt)
elif self.u is not None and not self.authed:
password = self.buffer
self.buffer = ''
if self.u.checkPassword(password):
if self.u.checkCapability('owner'):
self.authed = True
self.prompt = '>>> '
else:
self.push('Only owners can use this feature.\n')
self.close()
msg = 'Attempted non-owner user %s on telnet REPL' % name
debug.debugMsg(msg, 'high')
else:
self.push('Incorrect Password.\n')
self.prompt = 'Name: '
self.u = None
msg = 'Invalid password for user %s on telnet REPL.' % name
debug.debugMsg(msg, 'high')
self.push(self.prompt)
elif self.authed:
debug.debugMsg('Telnet REPL: %s' % self.buffer)
ret = self.repl.addLine(self.buffer+'\r\n')
self.buffer = ''
if ret is not repl.NotYet:
if ret is not None:
s = self._re.sub('\r\n', str(ret))
self.push(s)
self.push('\r\n')
self.prompt = '>>> '
else:
self.prompt = '... '
self.push(self.prompt)
try:
ignore(poller)
except NameError:
poller = AsyncoreRunnerDriver()
if conf.telnetEnable and __name__ != '__main__':
try:
ignore(_listener)
except NameError:
_listener = ReplListener()
if __name__ == '__main__':
ReplListener()
asyncore.loop()

151
src/bot.py Executable file
View File

@ -0,0 +1,151 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
Main program file for running the bot.
"""
from fix import *
import sys
import email
import conf
import world
import debug
import ircdb
import irclib
import ircmsgs
import drivers
import schedule
import privmsgs
import asyncoreDrivers
sys.path.append(conf.pluginDir)
class ConfigurationDict(dict):
def __init__(self, L=None):
if L is not None:
L = [(key.lower(), value) for (key, value) in L]
dict.__init__(self, L)
def __setitem__(self, key, value):
dict.__setitem__(self, key.lower(), value)
def __getitem__(self, key):
try:
return dict.__getitem__(self, key.lower())
except KeyError:
return ''
def __contains__(self, key):
return dict.__contains__(self, key.lower())
class ConfigAfter376(irclib.IrcCallback):
public = False
def __init__(self, msgs):
self.msgs = msgs
def do376(self, irc, msg):
for msg in self.msgs:
irc.queueMsg(msg)
do377 = do376
def reportConfigError(filename, msg):
debug.recoverableError('%s: %s' % (filename, msg))
def processConfigFile(filename):
try:
fd = file(filename)
m = email.message_from_file(fd)
d = ConfigurationDict(m.items())
if 'nick' not in d:
reportConfigError(filename, 'No nick defined.')
return
if 'server' not in d:
reportConfigError(filename, 'No server defined.')
return
nick = d['nick']
server = d['server']
if server.find(':') != -1:
(server, port) = server.split(':', 1)
try:
server = (server, int(port))
except ValueError:
reportConfigError(filename, 'Server has invalid port.')
return
else:
server = (server, 6667)
user = d['user'] or nick
ident = d['ident'] or nick
irc = irclib.Irc(nick, user, ident)
for cls in privmsgs.standardPrivmsgModules:
irc.addCallback(cls())
ircdb.startup = True
lines = m.get_payload()
if lines.find('\n\n') != -1:
(startup, after376) = lines.split('\n\n')
else:
(startup, after376) = (lines, '')
for line in filter(None, startup.splitlines()):
if not line.startswith('#'):
irc.feedMsg(ircmsgs.privmsg(irc.nick, line))
irc.reset()
ircdb.startup = False
msgs = [ircmsgs.privmsg(irc.nick, s) for s in after376.splitlines()]
irc.addCallback(ConfigAfter376(filter(None, msgs)))
driver = asyncoreDrivers.AsyncoreDriver(server)
driver.irc = irc
except IOError, e:
reportConfigError(filename, e)
except email.Errors.HeaderParseError, e:
s = str(e)
problem = s[s.rfind('`')+1:-2]
msg = 'Invalid configuration format: %s' % problem
reportConfigError(filename, msg)
def main():
for filename in sys.argv[1:]:
processConfigFile(filename)
schedule.addPeriodicEvent(world.upkeep, 300)
try:
while world.ircs:
drivers.run()
except:
try:
debug.recoverableException()
except: # It must have been deadly on purpose.
sys.exit(0)
if __name__ == '__main__':
main()

358
src/callbacks.py Normal file
View File

@ -0,0 +1,358 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import re
import time
import shlex
import inspect
import threading
import sre_constants
from cStringIO import StringIO
import conf
import ircdb
import irclib
import ircmsgs
import ircutils
import debug
import world
###
# Privmsg: handles privmsg commands in a standard fashion.
###
def addressed(nick, msg):
"""If msg is addressed to 'name', returns the portion after the address.
"""
if msg.args[0] == nick:
if msg.args[1][0] in conf.prefixChars:
return msg.args[1][1:].strip()
else:
return msg.args[1].strip()
elif ircutils.nickToLower(msg.args[1]).startswith(nick):
try:
return msg.args[1].split(None, 1)[1].strip()
except IndexError:
return ''
elif msg.args[1] and msg.args[1][0] in conf.prefixChars:
return msg.args[1][1:].strip()
else:
return ''
def canonicalName(command):
"""Turn a command into its canonical form.
Currently, this makes everything lowercase and removes all dashes and
underscores.
"""
return command.translate(string.ascii, '\t -_').lower()
class RateLimiter:
def __init__(self):
self.lastRequest = {}
self.limited = []
self.unlimited = []
def get(self):
if self.unlimited:
return self.unlimited.pop(0)
elif self.limited:
for i in range(len(self.limited)):
msg = self.limited[i]
if not self.limit(msg, penalize=False):
return self.limited.pop(i)
return None
else:
return None
def put(self, msg):
if not self.limit(msg):
self.unlimited.append(msg)
else:
self.limited.append(msg)
def limit(self, msg, penalize=True):
if msg.prefix and ircutils.isUserHostmask(msg.prefix):
(nick, user, host) = ircutils.splitHostmask(msg.prefix)
key = '@'.join((user, host))
now = time.time()
if ircdb.checkCapabilities(msg.prefix, ('owner', 'admin')):
return False
if key in self.lastRequest:
# Here's how we throttle requests. We keep a dictionary of
# (lastRequest, wait period) tuples. When a request arrives,
# we check to see if we have a lastRequest tuple, and if so,
# we check to make sure that lastRequest was more than wait
# seconds ago. If not, we're getting flooded, and we set
# the lastRequest time to the current time and increment wait,
# thus making it even harder for the flooder to get us to
# send them messages.
(t, wait) = self.lastRequest[key]
if now - t <= wait:
if penalize:
self.lastRequest[key] = (now, wait+conf.throttleTime)
else:
self.lastRequest[key] = (now, wait - (now - t))
return True
else:
self.lastRequest[key] = (now, conf.throttleTime)
return False
else:
self.lastRequest[key] = (now, conf.throttleTime)
return False
else:
return False
class Error(Exception):
"""Generic class for errors in Privmsg callbacks."""
pass
class Tokenizer:
quotes = '\'"`'
nonbacktickquotes = '\'"'
validChars = string.ascii[33:].translate(string.ascii, '\'"`[]')
def __init__(self, tokens=''):
self.validChars = self.validChars.translate(string.ascii, tokens)
def handleToken(self, token):
while token and token[0] in self.quotes and token[-1] == token[0]:
if len(token) > 1:
token = eval('"%s"' % token[1:-1])
else:
break
return token
def insideBrackets(self, lexer):
ret = []
while True:
token = lexer.get_token()
if token == '':
raise SyntaxError, 'Missing "]"'
elif token == ']':
return ret
elif token == '[':
ret.append(self.insideBrackets(lexer))
else:
ret.append(self.handleToken(token))
return ret
def tokenize(self, s):
lexer = shlex.shlex(StringIO(s))
lexer.commenters = ''
lexer.quotes = self.quotes
lexer.wordchars = self.validChars
args = []
while True:
token = lexer.get_token()
if token == '':
break
elif token == '[':
args.append(self.insideBrackets(lexer))
elif token == ']':
raise SyntaxError, 'Spurious "["'
else:
args.append(self.handleToken(token))
return args
class IrcObjectProxy:
def __init__(self, irc, msg, args):
#debug.printf('__init__: %s' % args)
self.irc = irc
self.msg = msg
self.args = args
self.counter = 0
self.finalEvaled = False
self.evalArgs()
def findCallback(self, commandName):
for callback in self.irc.callbacks:
if hasattr(callback, 'isCommand'):
if callback.isCommand(commandName):
return callback
return None
def evalArgs(self):
while self.counter < len(self.args):
if type(self.args[self.counter]) == str:
self.counter += 1
else:
IrcObjectProxy(self, self.msg, self.args[self.counter])
return
self.finalEval()
def finalEval(self):
self.finalEvaled = True
name = self.args.pop(0)
callback = self.findCallback(name)
# Use the old Irc object.
try:
callback.callCommand(getattr(callback, name),
(self, self.msg, self.args))
except Exception, e:
debug.recoverableException()
self.reply(self.msg, debug.exnToString(e))
def reply(self, msg, s):
if self.finalEvaled:
if isinstance(self.irc, self.__class__):
self.irc.reply(msg, s)
else:
if ircutils.funkyArgument(s):
s = repr(s)
if ircutils.isChannel(msg.args[0]):
m = ircmsgs.privmsg(msg.args[0], '%s: %s' % (msg.nick, s))
else:
m = ircmsgs.privmsg(msg.nick, s)
if len(m) > 511 - len(self.irc.prefix):
self.reply(msg, 'My response would\'ve been too long.')
else:
self.irc.queueMsg(m)
else:
self.args[self.counter] = s
self.evalArgs()
def error(self, msg, s):
self.reply(msg, 'Error: ' + s)
def killProxy(self):
if not isinstance(self.irc, irclib.Irc):
self.irc.killProxy()
self.__dict__ = {}
def getRealIrc(self):
if issubclass(self.irc, irclib.Irc):
return self.irc
else:
return self.irc.getRealIrc()
def __getattr__(self, attr):
return getattr(self.irc, attr)
class Privmsg(irclib.IrcCallback):
"""Base class for all Privmsg handlers."""
threaded = False
public = True
def __init__(self):
self.rateLimiter = RateLimiter()
def __call__(self, irc, msg):
self.rateLimiter.put(msg)
msg = self.rateLimiter.get()
if msg:
irclib.IrcCallback.__call__(self, irc, msg)
def isCommand(self, methodName):
# This function is ugly, but I don't want users to call methods like
# doPrivmsg or __init__ or whatever, and this is good to stop them.
if hasattr(self, methodName):
method = getattr(self, methodName)
if inspect.ismethod(method):
code = method.im_func.func_code
return inspect.getargs(code) == (['self','irc', 'msg', 'args'],
None, None)
else:
return False
else:
return False
def callCommand(self, f, args):
if self.threaded:
thread = threading.Thread(target=f, args=args)
thread.setDaemon(True)
thread.start()
debug.printf('Spawned new thread: %s' % thread)
else:
f(*args)
_r = re.compile(r'(\w+)')
def doPrivmsg(self, irc, msg):
s = addressed(irc.nick, msg)
#debug.printf('Privmsg.doPrivmsg: s == %r' % s)
if s:
recipient = msg.args[0]
if ircdb.checkIgnored(msg.prefix, recipient):
debug.printf('Privmsg.doPrivmsg: ignoring.')
return
m = self._r.match(s)
if m and self.isCommand(canonicalName(m.group(1))):
IrcObjectProxy(irc, msg, Tokenizer().tokenize(s))
class PrivmsgRegexp(Privmsg):
"""A class to allow a person to create regular expression callbacks.
Much more primitive, but more flexible than the 'normal' method of using
the Privmsg class and its lexer, PrivmsgRegexp allows you to write
callbacks that aren't addressed to the bot, for instance. There are, of
course, several other possibilities. Callbacks are registered with a
string (the regular expression) and a function to be called (with the Irc
object, the IrcMsg object, and the match object) when the regular
expression matches. Callbacks must have the signature (self, irc, msg,
match) to be counted as such.
If you have a standard command-type callback, though, Privmsg is a much
better class to use, at the very least for consistency's sake, but also
because it's much more easily coded and maintained.
"""
threaded = False # Again, like Privmsg...
def __init__(self):
Privmsg.__init__(self)
self.recache = {}
def doPrivmsg(self, irc, msg):
#for name, value in self.__class__.__dict__.iteritems():
self.rateLimiter.put(msg)
msg = self.rateLimiter.get()
if msg:
for name, value in self.__class__.__dict__.items():
value = getattr(self, name)
if name[0] != '_' and inspect.ismethod(value) and \
inspect.getargs(value.im_func.func_code) == \
(['self', 'irc', 'msg', 'match'], None, None):
try:
r = self.recache[value.__doc__]
except KeyError:
try:
r = re.compile(value.__doc__)
self.recache[value.__doc__] = r
except sre_constants.error, e:
s = '%s.%s has an invalid regexp %s: %s' % \
(self.__class__.__name__, name,
value.__doc__, debug.exnToString(e))
debug.debugMsg(s)
m = r.search(msg.args[1])
if m:
self.callCommand(value, (irc, msg, m))

450
src/cdb.py Normal file
View File

@ -0,0 +1,450 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from __future__ import generators
from fix import *
import os
import sys
import struct
import os.path
import cPickle as pickle
def hash(s):
h = 5381
for c in s:
h = ((h + (h << 5)) ^ ord(c)) & 0xFFFFFFFFL
return h
def unpack2Ints(s):
return struct.unpack('<LL', s)
def pack2Ints(i, j):
return struct.pack('<LL', i, j)
def dump(map, fd=sys.stdout):
for (key, value) in map.iteritems():
fd.write('+%s,%s:%s->%s\n' % (len(key), len(value), key, value))
def open(filename, mode='r'):
if mode == 'r':
return Reader(filename)
elif mode == 'w':
return ReaderWriter(filename)
elif mode == 'c':
if os.path.exists(filename):
return ReaderWriter(filename)
else:
maker = Maker(filename)
maker.finish()
return ReaderWriter(filename)
elif mode == 'n':
maker = Maker(filename)
maker.finish()
return ReaderWriter(filename)
else:
raise ValueError, 'Invalid flag: %s' % mode
def shelf(filename):
if os.path.exists(filename):
return Shelf(filename)
else:
maker = Maker(filename)
maker.finish()
return Shelf(filename)
def _readKeyValue(fd):
klen = 0
dlen = 0
s = initchar = fd.read(1)
if s == '':
return (None, None, None)
s = fd.read(1)
while s != ',':
klen = 10 * klen + int(s)
s = fd.read(1)
s = fd.read(1)
while s != ':':
dlen = 10 * dlen + int(s)
s = fd.read(1)
key = fd.read(klen)
assert fd.read(2) == '->'
value = fd.read(dlen)
assert fd.read(1) == '\n'
return (initchar, key, value)
def make(dbFilename, readFilename=None):
if readFilename is None:
readfd = sys.stdin
else:
readfd = file(readFilename, 'r')
maker = Maker(dbFilename)
while 1:
(initchar, key, value) = _readKeyValue(readfd)
if initchar is None:
break
assert initchar == '+'
maker.add(key, value)
readfd.close()
maker.finish()
class Maker(object):
def __init__(self, filename):
self.fd = file(filename, 'w')
self.filename = filename
self.fd.seek(2048)
self.hashPointers = [(0, 0)] * 256
#self.hashes = [[]] * 256 # Can't use this, [] stays the same...
self.hashes = []
for _ in xrange(256):
self.hashes.append([])
def add(self, key, data):
h = hash(key)
hashPointer = h % 256
startPosition = self.fd.tell()
self.fd.write(pack2Ints(len(key), len(data)))
self.fd.write(key)
self.fd.write(data)
self.hashes[hashPointer].append((h, startPosition))
def finish(self):
for i in xrange(256):
hash = self.hashes[i]
self.hashPointers[i] = (self.fd.tell(), self._serializeHash(hash))
self._serializeHashPointers()
self.fd.flush()
self.fd.close()
def _serializeHash(self, hash):
hashLen = len(hash) * 2
a = [(0, 0)] * hashLen
for (h, pos) in hash:
i = (h / 256) % hashLen
while a[i] != (0, 0):
i = (i + 1) % hashLen
a[i] = (h, pos)
for (h, pos) in a:
self.fd.write(pack2Ints(h, pos))
return hashLen
def _serializeHashPointers(self):
self.fd.seek(0)
for (hashPos, hashLen) in self.hashPointers:
self.fd.write(pack2Ints(hashPos, hashLen))
class Reader(IterableMap):
def __init__(self, filename):
self.filename = filename
self.fd = file(filename, 'r')
self.loop = 0
self.khash = 0
self.kpos = 0
self.hpos = 0
self.hslots = 0
self.dpos = 0
self.dlen = 0
def close(self):
self.fd.close()
def _read(self, len, pos):
self.fd.seek(pos)
return self.fd.read(len)
def _match(self, key, pos):
return self._read(len(key), pos) == key
def iteritems(self):
# uses loop/hslots in a strange, non-re-entrant manner.
(self.loop,) = struct.unpack('<i', self._read(4, 0))
self.hslots = 2048
while self.hslots < self.loop:
(klen, dlen) = unpack2Ints(self._read(8, self.hslots))
dpos = self.hslots + 8 + klen
ret = (self._read(klen, self.hslots+8), self._read(dlen, dpos))
self.hslots = dpos + dlen
yield ret
self.loop = 0
self.hslots = 0
def _findnext(self, key):
if not self.loop:
self.khash = hash(key)
(self.hpos, self.hslots) = unpack2Ints(self._read(8,
(self.khash * 8) & 2047))
if not self.hslots:
return False
self.kpos = self.hpos + (((self.khash / 256) % self.hslots) * 8)
while self.loop < self.hslots:
(h, p) = unpack2Ints(self._read(8, self.kpos))
if p == 0:
return False
self.loop += 1
self.kpos += 8
if self.kpos == self.hpos + (self.hslots * 8):
self.kpos = self.hpos
if h == self.khash:
(u, self.dlen) = unpack2Ints(self._read(8, p))
if u == len(key):
if self._match(key, p+8):
self.dpos = p + 8 + u
return True
return False
def _find(self, key, loop=0):
self.loop = loop
return self._findnext(key)
def _getCurrentData(self):
return self._read(self.dlen, self.dpos)
def find(self, key, loop=0):
if self._find(key, loop=loop):
return self._getCurrentData()
else:
try:
return getattr(self, 'default') # For PyChecker.
except AttributeError:
raise KeyError, key
def findall(self, key):
ret = []
while self._findnext(key):
ret.append(self._getCurrentData())
return ret
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def __len__(self):
(start,) = struct.unpack('<i', self._read(4, 0))
self.fd.seek(0, 2)
return ((self.fd.tell() - start) / 16)
has_key = _find
__contains__ = has_key
__getitem__ = find
class ReaderWriter(IterableMap):
def __init__(self, filename, journalName=None, maxmods=0):
if journalName is None:
journalName = filename + '.journal'
self.journalName = journalName
self.maxmods = maxmods
self.mods = 0
self.filename = filename
self._readJournal()
self._openFiles()
self.adds = {}
self.removals = set()
def _openFiles(self):
self.cdb = Reader(self.filename)
self.journal = file(self.journalName, 'w')
def _closeFiles(self):
self.cdb.close()
self.journal.close()
def _journalRemoveKey(self, key):
s = '-%s,%s:%s->%s\n' % (len(key), 0, key, '')
self.journal.write(s)
self.journal.flush()
def _journalAddKey(self, key, value):
s = '+%s,%s:%s->%s\n' % (len(key), len(value), key, value)
self.journal.write(s)
self.journal.flush()
def _readJournal(self):
removals = set()
adds = {}
try:
fd = file(self.journalName, 'r')
while 1:
(initchar, key, value) = _readKeyValue(fd)
if initchar is None:
break
elif initchar == '+':
if key in removals:
removals.remove(key)
adds[key] = value
elif initchar == '-':
if key in adds:
del adds[key]
removals.add(key)
fd.close()
except IOError:
pass
if removals or adds:
tempfilename = mktemp('.db')
maker = Maker(tempfilename)
cdb = Reader(self.filename)
for (key, value) in cdb.iteritems():
if key in removals:
continue
elif key in adds:
value = adds[key]
if value is not None:
maker.add(key, value)
adds[key] = None
else:
maker.add(key, value)
for (key, value) in adds.iteritems():
if value is not None:
maker.add(key, value)
cdb.close()
maker.finish()
os.rename(tempfilename, self.filename)
if os.path.exists(self.journalName):
os.remove(self.journalName)
def close(self):
self.flush()
self._closeFiles()
def flush(self):
self._closeFiles()
self._readJournal()
self._openFiles()
def _flushIfOverLimit(self):
if self.maxmods:
if self.mods > self.maxmods:
self.flush()
self.mods = 0
def __getitem__(self, key):
if key in self.removals:
raise KeyError, key
else:
try:
return self.adds[key]
except KeyError:
return self.cdb[key] # If this raises KeyError, we lack key.
def __delitem__(self, key):
if key in self.removals:
raise KeyError, key
else:
if key in self.adds and key in self.cdb:
self._journalRemoveKey(key)
del self.adds[key]
self.removals.add(key)
elif key in self.adds:
self._journalRemoveKey(key)
del self.adds[key]
elif key in self.cdb:
self._journalRemoveKey(key)
else:
raise KeyError, key
self.mods += 1
self._flushIfOverLimit()
def __setitem__(self, key, value):
if key in self.removals:
self.removals.remove(key)
self._journalAddKey(key, value)
self.adds[key] = value
self.mods += 1
self._flushIfOverLimit()
def __contains__(self, key):
if key in self.removals:
return False
else:
return key in self.adds or key in self.cdb
has_key = __contains__
def iteritems(self):
already = set()
for (key, value) in self.cdb.iteritems():
if key in self.removals or key in already:
continue
elif key in self.adds:
already.add(key)
yield (key, self.adds[key])
else:
yield (key, value)
for (key, value) in self.adds.iteritems():
if key not in already:
yield (key, value)
def setdefault(self, key, value):
try:
return self[key]
except KeyError:
self[key] = value
return value
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
class Shelf(ReaderWriter, IterableMap):
def __getitem__(self, key):
return pickle.loads(ReaderWriter.__getitem__(self, key))
def __setitem__(self, key, value):
ReaderWriter.__setitem__(self, key, pickle.dumps(value, True))
def iteritems(self):
for (key, value) in ReaderWriter.iteritems(self):
yield (key, pickle.loads(value))
if __name__ == '__main__':
if sys.argv[0] == 'cdbdump':
if len(sys.argv) == 2:
fd = file(sys.argv[1], 'r')
else:
fd = sys.stdin
db = Reader(fd)
dump(db)
elif sys.argv[0] == 'cdbmake':
if len(sys.argv) == 2:
make(sys.argv[1])
else:
make(sys.argv[1], sys.argv[2])

173
src/conf.py Normal file
View File

@ -0,0 +1,173 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import os.path
###
# Directions:
#
# Boolean values should be either True or False.
###
###
# Directories.
###
logDir = 'logs'
confDir = 'conf'
dataDir = 'data'
pluginDir = 'plugins'
###
# Files.
###
userfile = os.path.join(confDir, 'users.conf')
channelfile = os.path.join(confDir, 'channels.conf')
ignoresfile = os.path.join(confDir, 'ignores.conf')
rawlogfile = os.path.join(logDir, 'raw.log')
###
# timestampFormat: A format string defining how timestamps should be. Check
# the Python library reference for the "time" module to see
# what the various format specifiers mean.
###
timestampFormat = '[%d-%b-%Y %H:%M:%S]'
###
# throttleTime: A floating point number of seconds to throttle queued messages.
# (i.e., messages will not be sent faster than once per
# throttleTime units.)
###
throttleTime = 1.0
###
# allowEval: True if the owner (and only the owner) should be able to eval
# arbitrary Python code.
###
allowEval = True
###
# defaultCapabilities: Capabilities allowed to everyone by default.
###
defaultCapabilities = []
###
# reply%s: Stock replies for various reasons.
###
replyError = 'An error has occurred and has been logged.'
replyNoCapability = 'You don\'t have the "%s" capability.'
replySuccess = 'The operation succeeded.'
replyIncorrectAuth = 'Your hostmasks don\'t match or your password is wrong.'
replyNoUser = 'I can\'t find that user in my database.'
replyInvalidArgument = 'I can\'t send \\r, \\n, or \\0 (\\x00).'
replyRequiresPrivacy = 'That can\'t be done in a channel.'
replyEvalNotAllowed = 'You must enable conf.allowEval for that to work.'
###
# errorReplyPrivate: True if errors should be reported privately so as not to
# bother the channel.
###
errorReplyPrivate = False
###
# telnetEnable: A boolean saying whether or not to enable the telnet REPL.
# This will allow a user with the 'owner' capability to telnet
# into the bot and see how it's working internally. A lifesaver
# for development.
###
telnetEnable = True
telnetPort = 31337
###
# asyncorePoll: the length of an asyncore's polling term.
# If asyncore drivers are all you're using, feel free to make
# this arbitrarily large -- be warned, however, that all other
# drivers are just sitting around while asyncore waits during
# this poll period (including the schedule). It'll take more
# CPU, but you probably don't want to set this more than 0.01
# when you've got non-asyncore drivers to worry about.
###
asyncorePoll = 1
###
# minHistory: Minimum number of messages kept in an Irc object's state.
###
minHistory = 100
###
# pingInterval: Number of seconds between PINGs to the server.
# 0 means not to ping the server.
###
pingInterval = 120
###
# nickmods: List of ways to 'spice up' a nick so the bot doesn't run out of
# nicks if all his normal ones are taken.
###
nickmods = ['%s^', '^%s^', '__%s__', '%s_', '%s__', '__%s', '^^%s^^', '{%s}',
'[%s]', '][%s][', '}{%s}{', '}{}%s', '^_^%s', '%s^_^', '^_^%s^_^']
###
# defaultAllow: does an IrcUser allow a command by default?
###
defaultAllow = False
###
# defaultChannelAllow: does an IrcChannel allow a command by by default?
###
defaultChannelAllow = True
###
# defaultIgnore: True if users should be ignored by default.
# It's a really easy way to make sure that people who want to
# talk to the bot register first. (Of course, they can't
# register if they're ignored. We'll work on that.)
###
defaultIgnore = False
###
# ignores: Hostmasks to ignore.
###
ignores = []
###
# prefixChars: A string of chars that are valid prefixes to address the bot.
###
prefixChars = '@'
###############################
###############################
###############################
# DO NOT EDIT PAST THIS POINT #
###############################
###############################
###############################

187
src/debug.py Normal file
View File

@ -0,0 +1,187 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import os
import os.path
import sys
import time
import cgitb
import traceback
import ansi
import conf
###
# CONFIGURATION
###
## Uncomment this class to remove the tie to SupyBot's conf module.
## class conf:
## logDir = '.'
# Names of logfiles.
errorfile = os.path.join(conf.logDir, 'error.log')
debugfile = os.path.join(conf.logDir, 'debug.log')
tracefile = os.path.join(conf.logDir, 'trace.log')
# stderr: True if messages should be written to stderr as well as logged.
stderr = True
# colorterm: True if the terminal run on is color.
colorterm = True
# printf: True if printf debugging messages should be printed.
printf = True
# minimumDebugPriority: Lowest priority logged;
# One of {'verbose', 'low', 'normal', 'high'}.
minimumDebugPriority = 'verbose'
# deadlyExceptions: Exceptions that should cause immediate failure.
deadlyExceptions = [KeyboardInterrupt, SystemExit]
###
# END CONFIGURATION
###
_errorfd = file(errorfile, 'a')
_debugfd = file(debugfile, 'a')
_tracefd = file(tracefile, 'w')
minpriority = 1
priorities = { 'verbose': 0,
'low': 1,
'normal': 2,
'high': 3 }
priorityColors = { 'verbose': ansi.BLUE,
'low': ansi.CYAN,
'normal': ansi.GREEN,
'high': ansi.BOLD }
priorityColors.setdefault('')
# This is a queue of the time of the last 10 calls to recoverableException.
# If the most recent time is
lastTimes = [time.time()-1] * 10
def exit(i=-1):
class E(Exception):
pass
if deadlyExceptions:
# Just to be safe, we'll make it a subclass of *all* the deadly
# exceptions :)
for exn in deadlyExceptions:
class E(exn, E):
pass
raise E
else:
os._exit(i)
def _writeNewline(fd, s):
fd.write(s)
if s[-len(os.linesep):] != os.linesep:
fd.write(os.linesep)
def recoverableError(msg):
"""Called with errors that are not critical.
"""
if stderr:
if colorterm:
sys.stderr.write(ansi.BOLD + ansi.RED)
_writeNewline(sys.stderr, msg)
if colorterm:
sys.stderr.write(ansi.RESET)
_writeNewline(_errorfd, msg)
_errorfd.flush()
def unrecoverableError(msg):
recoverableError(msg)
exit(-1)
def recoverableException():
(E, e, tb) = sys.exc_info()
for exn in deadlyExceptions:
if issubclass(e.__class__, exn):
raise
lastTimes.append(time.time())
if lastTimes[-1] - lastTimes[0] < 0.05:
debugMsg('Too many exceptions too quickly. Bailing out.', 'high')
exit()
else:
lastTimes.pop(0)
try:
1/0
text = cgitb.text((E, e, tb))
except:
text = ''.join(traceback.format_exception(E, e, tb))
del tb # just to be safe.
if stderr:
if colorterm:
sys.stderr.write(ansi.BOLD + ansi.RED)
sys.stderr.write(text)
if colorterm:
sys.stderr.write(ansi.RESET)
_errorfd.write(text)
_errorfd.flush()
def unrecoverableException():
recoverableException()
exit(-1)
def debugMsg(msg, priority='low'):
if priorities[priority] >= priorities[minimumDebugPriority]:
if stderr:
if colorterm:
sys.stderr.write(priorityColors.get(priority))
_writeNewline(sys.stderr, msg)
if colorterm:
sys.stderr.write(ansi.RESET)
_writeNewline(_debugfd, msg)
_debugfd.flush()
def printf(msg):
if printf:
print '*** %s' % (msg)
def methodNamePrintf(obj, methodName):
printf('%s: %s' % (obj.__class__.__name__, methodName))
def exnToString(e):
return '%s: %s' % (e.__class__.__name__, e)
def tracer(frame, event, _):
if event == 'call':
s = '%s: %s\n' % (frame.f_code.co_filename, frame.f_code.co_name)
_tracefd.write(s)
_tracefd.flush()

141
src/drivers.py Normal file
View File

@ -0,0 +1,141 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
"""
Contains various drivers (network, file, and otherwise) for using IRC objects.
"""
from fix import *
import re
import os
import sys
import ansi
import debug
import ircmsgs
_drivers = {}
_deadDrivers = []
_newDrivers = []
class IrcDriver(object):
"""Base class for drivers.
"""
def __init__(self):
_newDrivers.append((self.name(), self))
if not hasattr(self, 'irc'):
self.irc = None # This is to satisfy PyChecker.
def run(self):
raise NotImplementedError
def die(self):
# The end of any overrided die method should be
# "super(Class, obj).die()", in order to make
# sure this (and anything else later added) is done.
_deadDrivers.append(self.name())
def name(self):
return self.__class__.__name__
class Interactive(IrcDriver):
"""Interactive (stdin/stdout) driver for testing.
Python statements can be evaluated by beginning the line with a '#'
Variables can be set using the form "$variable=value", where value is
valid Python syntactical construct.
Variables are inserted for $variable in lines.
"""
_varre = r'[^\\](\$[a-zA-Z_][a-zA-Z0-9_]*)'
_setre = re.compile('^' + _varre + r'\s*=\s*(.*)')
_replacere = re.compile(_varre)
_dict = {}
def run(self):
while 1:
msg = self.irc.takeMsg()
if not msg:
break
else:
sys.stdout.write(ansi.BOLD + ansi.YELLOW)
sys.stdout.write(str(msg))
sys.stdout.write(ansi.RESET)
line = sys.stdin.readline()
if line == "":
self.irc.die()
self.die()
elif line == os.linesep:
pass
elif self._setre.match(line):
(var, val) = self._setre.match(line).groups()
self._dict[var] = eval(val)
elif line[0] == '#':
print eval(line[1:])
else:
def f(m):
return self._dict.get(m.group(0), m.group(0))
line = self._replacere.sub(f, line.strip())
msg = ircmsgs.privmsg(self.irc.nick, line)
msg.prefix = 'nick!user@host.domain.tld'
self.irc.feedMsg(msg)
def empty():
return (len(_drivers) + len(_newDrivers)) == 0
def add(name, driver):
_newDrivers.append((name, driver))
def remove(name):
_deadDrivers.append(name)
def run():
#debug.printf(_drivers)
for (name, driver) in _drivers.iteritems():
try:
if name not in _deadDrivers:
driver.run()
except:
debug.recoverableException()
_deadDrivers.append(name)
for name in _deadDrivers:
try:
del _drivers[name]
except KeyError:
pass
while _newDrivers:
(name, driver) = _newDrivers.pop()
if name in _drivers:
_drivers[name].die()
del _drivers[name]
_drivers[name] = driver

204
src/fix.py Normal file
View File

@ -0,0 +1,204 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from __future__ import generators
import sys
import string
sys.path.insert(0, 'others')
string.ascii = string.maketrans('', '')
def ignore(*args, **kwargs):
"""Simply ignore the arguments sent to it."""
pass
def catch(f, *args, **kwargs):
try:
f(*args, **kwargs)
except:
pass
class bool(int):
def __new__(self, val=0):
# This constructor always returns an existing instance
if val:
return True
else:
return False
def __repr__(self):
if self:
return "True"
else:
return "False"
__str__ = __repr__
def __and__(self, other):
if isinstance(other, bool):
return bool(int(self) & int(other))
else:
return int.__and__(self, other)
__rand__ = __and__
def __or__(self, other):
if isinstance(other, bool):
return bool(int(self) | int(other))
else:
return int.__or__(self, other)
__ror__ = __or__
def __xor__(self, other):
if isinstance(other, bool):
return bool(int(self) ^ int(other))
else:
return int.__xor__(self, other)
__rxor__ = __xor__
False = int.__new__(bool, 0)
True = int.__new__(bool, 1)
class set(object):
def __init__(self, *args):
self.d = {}
for x in args:
self.d[x] = None
def __contains__(self, x):
return x in self.d
def __iter__(self):
return self.d.iterkeys()
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, repr(self.d.keys())[1:-1])
def __nonzero__(self):
if self.d:
return True
else:
return False
def __getstate__(self):
return self.d.keys()
def __setstate__(self, t):
for x in t:
self.d[x] = None
def add(self, x):
self.d[x] = None
def remove(self, x):
if x in self.d:
del self.d[x]
class IterableMap(object):
"""Define .iteritems() in a class and subclass this to get the other iters.
"""
def iteritems(self):
raise NotImplementedError
def iterkeys(self):
for (key, _) in self.iteritems():
yield key
def itervalues(self):
for (_, value) in self.iteritems():
yield value
def items(self):
ret = []
for t in self.iteritems():
ret.append(t)
return ret
def keys(self):
ret = []
for key in self.iterkeys():
ret.append(key)
return ret
def values(self):
ret = []
for value in self.itervalues():
ret.append(value)
return ret
def __len__(self):
ret = 0
for _ in self.iteritems():
ret += 1
return ret
def mktemp(suffix=''):
import sha
import md5
import time
import random
r = random.Random()
m = md5.md5(suffix)
r.seed(time.time())
s = str(r.getstate())
for x in xrange(0, random.randrange(400), random.randrange(1, 5)):
m.update(str(x))
m.update(s)
m.update(str(time.time()))
s = m.hexdigest()
return sha.sha(s + str(time.time())).hexdigest() + suffix
def zipiter(*args):
length = len(args[0])
for arg in args:
if len(arg) < length:
length = len(arg)
for i in xrange(length):
yield tuple([arg[i] for arg in args])
def reviter(L):
for i in xrange(len(L) - 1, -1, -1):
yield L[i]
def enumerate(L):
for i in xrange(len(L)):
yield (i, L[i])
def window(L, size):
for i in xrange(len(L) - (size-1)):
yield L[i:i+size]

484
src/ircdb.py Normal file
View File

@ -0,0 +1,484 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import time
import atexit
import string
import conf
import world
import ircutils
def fromChannelCapability(capability):
return capability.split('.', 1)
def isChannelCapability(capability):
if '.' in capability:
(channel, capability) = fromChannelCapability(capability)
return ircutils.isChannel(channel)
else:
return False
def makeChannelCapability(channel, capability):
return '%s.%s' % (channel, capability)
def isAntiCapability(capability):
if isChannelCapability(capability):
(_, capability) = fromChannelCapability(capability)
return capability[0] == '!'
def makeAntiCapability(capability):
if '.' in capability:
(channel, capability) = fromChannelCapability(capability)
if ircutils.isChannel(channel):
return '%s.!%s' % (channel, capability)
else:
return '!' + capability
else:
return '!' + capability
_normal = string.maketrans('\r\n', ' ')
def normalize(s):
return s.translate(_normal)
class IrcUser(object):
"""This class holds the capabilities and authentications for a user.
"""
def __init__(self, ignore=False, password='', auth=None,
capabilities=None, hostmasks=None):
self.auth = auth # The (time, hostmask) a user authenticated under.
self.ignore = ignore # A boolean deciding if the person is ignored.
self.password = password # password (plaintext? hashed?)
self.capabilities = set()
if capabilities is not None:
for capability in capabilities:
self.capabilities.add(capability)
if hostmasks is None:
self.hostmasks = [] # A list of hostmasks used for recognition
else:
self.hostmasks = hostmasks
def __repr__(self):
return '%s(ignore=%s, auth=%r, password=%r, '\
'capabilities=%r, hostmasks=%r)\n' %\
(self.__class__.__name__, self.ignore, self.auth,
self.password, self.capabilities, self.hostmasks)
def addCapability(self, capability):
self.capabilities.add(capability)
def removeCapability(self, capability):
if capability in self.capabilities:
self.capabilities.remove(capability)
def checkCapability(self, capability):
if self.ignore:
if isAntiCapability(capability):
return True
else:
return False
elif capability in self.capabilities:
return True
else:
return False
def setPassword(self, password):
self.password = password
def checkPassword(self, password):
return (self.password == password)
def checkHostmask(self, hostmask):
if self.auth and (hostmask == self.auth[1]):
return True
for pat in self.hostmasks:
if ircutils.hostmaskPatternEqual(pat, hostmask):
return True
return False
def addHostmask(self, hostmask):
self.hostmasks.append(hostmask)
def removeHostmask(self, hostmask):
self.hostmasks = [s for s in self.hostmasks if s != hostmask]
def hasHostmask(self, hostmask):
return hostmask in self.hostmasks
def setAuth(self, hostmask):
self.auth = (time.time(), hostmask)
def unsetAuth(self):
self.auth = None
def checkAuth(self, hostmask):
if self.auth is not None:
(timeSet, prefix) = self.auth
if time.time() - timeSet < 3600:
if hostmask == prefix:
return True
else:
return False
else:
self.unsetAuth()
return False
else:
return False
class IrcChannel(object):
"""This class holds the capabilities, bans, and ignores of a channel.
"""
defaultOff = ['op', 'halfop', 'voice', 'protected']
def __init__(self, bans=None, ignores=None, capabilities=None,
lobotomized=False, defaultAllow=True):
self.defaultAllow = defaultAllow
if bans is None:
self.bans = []
else:
self.bans = bans
if ignores is None:
self.ignores = []
else:
self.ignores = ignores
if capabilities is None:
self.capabilities = {}
else:
self.capabilities = capabilities
for capability in self.defaultOff:
if capability not in self.capabilities:
self.capabilities[capability] = False
self.lobotomized = lobotomized
def __repr__(self):
return '%s(bans=%r, ignores=%r, capabilities=%r, '\
'lobotomized=%r, defaultAllow=%s)\n' %\
(self.__class__.__name__, self.bans, self.ignores,
self.capabilities, self.lobotomized,
self.defaultAllow)
def addBan(self, hostmask):
self.bans.append(hostmask)
def removeBan(self, hostmask):
self.bans = [s for s in self.bans if s != hostmask]
def checkBan(self, hostmask):
for pat in self.bans:
if ircutils.hostmaskPatternEqual(pat, hostmask):
return True
return False
def addIgnore(self, hostmask):
self.ignores.append(hostmask)
def removeIgnore(self, hostmask):
self.ignores = [s for s in self.ignores if s != hostmask]
def addCapability(self, capability, value):
self.capabilities[capability] = value
def removeCapability(self, capability):
del self.capabilities[capability]
def setDefaultCapability(self, v):
self.defaultAllow = v
def checkCapability(self, capability):
if capability in self.capabilities:
return self.capabilities[capability]
elif isAntiCapability(capability):
return not self.defaultAllow
else:
return self.defaultAllow
def checkIgnored(self, hostmask):
if self.lobotomized:
return True
for mask in self.bans:
if ircutils.hostmaskPatternEqual(mask, hostmask):
return True
for mask in self.ignores:
if ircutils.hostmaskPatternEqual(mask, hostmask):
return True
return False
class UsersDictionary(object):
def __init__(self, filename):
self.filename = filename
fd = file(filename, 'r')
s = fd.read()
fd.close()
self.dict = eval(normalize(s))
self.cache = {} # hostmasks to nicks.
self.revcache = {} # nicks to hostmasks.
def resetCache(self, s):
if s in self.cache:
# it's a hostmask.
name = self.cache[s]
del self.cache[s]
else:
# it's already a name.
name = s
# name should always be in self.revcache, this should never KeyError.
if name in self.revcache:
for hostmask in self.revcache[name]:
del self.cache[hostmask]
del self.revcache[name]
def setCache(self, hostmask, name):
self.cache[hostmask] = name
self.revcache.setdefault(name, []).append(hostmask)
def getUser(self, s):
if ircutils.isUserHostmask(s):
name = self.getUserName(s)
else:
name = s
return self.dict[name]
def setUser(self, s, u):
# First, invalidate the cache for this user.
self.resetCache(s)
if ircutils.isUserHostmask(s):
name = self.getUserName(s)
else:
name = s
self.dict[name] = u
def hasUser(self, s):
return (s in self.dict)
def delUser(self, s):
if ircutils.isUserHostmask(s):
name = self.getUserName(s)
else:
name = s
self.resetCache(name)
del self.dict[name]
def getUserName(self, s):
assert ircutils.isUserHostmask(s), 'string must be a hostmask'
if s in self.cache:
return self.cache[s]
else:
for (name, user) in self.dict.iteritems():
if user.checkHostmask(s):
self.cache[s] = name
if name in self.revcache:
self.revcache[name].append(s)
else:
self.revcache[name] = [s]
return name
raise KeyError, s
def flush(self):
fd = file(self.filename, 'w')
fd.write(repr(self.dict))
fd.close()
def reload(self):
self.__init__(self.filename)
class ChannelsDictionary(object):
def __init__(self, filename):
self.filename = filename
fd = file(filename, 'r')
s = fd.read()
fd.close()
self.dict = eval(normalize(s))
def getChannel(self, channel):
channel = channel.lower()
if channel in self.dict:
return self.dict[channel]
else:
c = IrcChannel()
self.dict[channel] = c
return c
def setChannel(self, channel, ircChannel):
channel = channel.lower()
self.dict[channel] = ircChannel
def flush(self):
fd = file(self.filename, 'w')
fd.write(repr(self.dict))
fd.close()
def reload(self):
self.__init__(self.filename)
###
# Later, I might add some special handling for botnet.
###
users = UsersDictionary(conf.userfile)
channels = ChannelsDictionary(conf.channelfile)
atexit.register(users.flush)
atexit.register(channels.flush)
world.flushers.append(users.flush)
world.flushers.append(channels.flush)
###
# Useful functions for checking credentials.
###
def checkIgnored(hostmask, recipient='', users=users, channels=channels):
"""checkIgnored(hostmask, recipient='') -> True/False
Checks if the user is ignored by the recipient of the message.
"""
for ignore in conf.ignores:
if ircutils.hostmaskPatternEqual(ignore, hostmask):
return True
try:
user = users.getUser(hostmask)
except KeyError:
# If there's no user...
if ircutils.isChannel(recipient):
channel = channels.getChannel(recipient)
return channel.checkIgnored(hostmask)
else:
return conf.defaultIgnore
if user.checkCapability('owner'):
# Owners shouldn't ever be ignored.
return False
elif user.ignore:
return True
elif recipient:
if ircutils.isChannel(recipient):
channel = channels.getChannel(recipient)
return channel.checkIgnored(hostmask)
else:
return False
else:
return False
def checkCapability(hostmask, capability, users=users, channels=channels):
"""checkCapability(hostmask, recipient, capability) -> True/False
Checks if the user represented by hostmask has capability with recipient.
"""
###
# This is a hard function to write correctly.
#
# Basically, we want to return whether or not a user has a certain
# capability in a given channel. This should be easy, but the various
# different cases are all hard to get right.
if startup:
# Are we in special startup mode?
return True
try:
u = users.getUser(hostmask)
except KeyError: # the user isn't in the database.
# First, check to see if we're asking for a channel capability:
if isChannelCapability(capability):
# If it is, we'll check the channel.
try:
(channel, capability) = fromChannelCapability(capability)
except ValueError: # unpack list of wrong size
return False # stupid, invalid capability.
# Now, go fetch the channel and check to see what it thinks about
# said capability.
c = channels.getChannel(channel)
# Channels have their own defaults, so we can just directly return
# what the channel has to say about the capability.
return c.checkCapability(capability)
else: # It's not a channel capability.
# If it's not a channel, then the only thing we have to go by is
# conf.defaultCapabilities.
return (capability in conf.defaultCapabilities)
# Good, the user exists.
# First, we check to see if it's an owner -- if it is, it should have all
# capabilities and should not have any negative capabilities.
if u.checkCapability('owner'):
if isAntiCapability(capability):
return False
else:
return True
# Now, we need to check if it's a channel capability or not.
if isChannelCapability(capability):
# First check to see if the user has the capability already; if so,
# it can be returned without checking the channel.
try:
return u.checkCapability(capability)
except KeyError:
# User doesn't have the capability. Check the channel.
try:
(channel, capability) = fromChannelCapability(capability)
except ValueError:
return False # stupid, invalid capability.
c = channels.getChannel(channel)
# And return the channel's opinion.
return c.checkCapability(capability)
else: # It's not a channel capability.
# Just check the user.
try:
return u.checkCapability(capability)
except KeyError:
return (capability in conf.defaultCapabilities)
def checkCapabilities(hostmask, capabilities, requireAll=False):
"""Checks that a user has capabilities in a list.
requireAll is the True if *all* capabilities in the list must be had, False
if *any* of the capabilities in the list must be had.
"""
for capability in capabilities:
if requireAll:
if not checkCapability(hostmask, capability):
return False
else:
if checkCapability(hostmask, capability):
return True
if requireAll:
return True
else:
return False
#################################################
#################################################
#################################################
## Don't even *think* about messing with this. ##
#################################################
#################################################
#################################################
startup = False

389
src/irclib.py Normal file
View File

@ -0,0 +1,389 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import copy
import time
import atexit
import conf
import debug
import world
import ircdb
import ircmsgs
import ircutils
###
# The base class for a callback to be registered with an Irc object. Shows
# the required interface for callbacks -- name(),
# inFilter(irc, msg), outFilter(irc, msg), and __call__(irc, msg) [used so
# functions can be used as callbacks conceivable, and so if refactoring ever
# changes the nature of the callbacks from classes to functions, syntactical
# changes elsewhere won't be required.
###
class IrcCallback(object):
"""Base class for standard callbacks.
Callbacks derived from this class should have methods of the form
"doCommand" -- doPrivmsg, doNick, do433, etc. These will be called
on matching messages.
"""
def name(self):
return self.__class__.__name__
def inFilter(self, irc, msg):
return msg
def outFilter(self, irc, msg):
return msg
def __call__(self, irc, msg):
commandName = 'do' + msg.command.capitalize()
if hasattr(self, commandName):
getattr(self, commandName)(irc, msg)
def reset(self):
pass
def die(self):
pass
###
# Basic queue for IRC messages. It doesn't presently (but should at some
# later point) reorder messages based on priority or penalty calculations.
###
class IrcMsgQueue(object):
"""Class for a queue of IrcMsgs. Eventually, it should be smart.
"""
#__slots__ = ('msgs', 'queue')
def __init__(self):
self.reset()
def reset(self):
self.msgs = set()
self.queue = []
def enqueueMsg(self, msg):
if msg in self.msgs:
debug.debugMsg('Not adding msg %s to queue' % msg, 'normal')
else:
self.msgs.add(msg)
self.queue.append(msg)
def dequeueMsg(self):
if self.queue:
msg = self.queue.pop(0)
if msg in self.msgs:
self.msgs.remove(msg)
return msg
else:
return None
def empty(self):
return self.queue == []
###
# Maintains the state of IRC connection -- the most recent messages, the
# status of various modes (especially ops/halfops/voices) in channels, etc.
###
class Channel(object):
#__slots__ = ('users', 'ops', 'halfops', 'voices')
def __init__(self):
self.topic = ''
self.users = set()
self.ops = set()
self.halfops = set()
self.voices = set()
def removeUser(self, user):
self.users.remove(user)
self.ops.remove(user)
self.halfops.remove(user)
self.voices.remove(user)
class IrcState(object):
"""Maintains state of the Irc connection. Should also become smarter.
"""
#__slots__ = ('history', 'nicksToHostmasks', 'channels')
def __init__(self):
self.reset()
def reset(self):
self.history = []
self.nicksToHostmasks = {}
self.channels = {}
# def __getstate__(self):
# return [getattr(self, s) for s in self.__slots__]
#
# def __setstate__(self, state):
# for (name, value) in zipiter(self.__slots__, state):
# setattr(name, value)
def copy(self):
ret = self.__class__()
ret.history = copy.copy(self.history)
ret.nicksToHostmasks = copy.copy(self.nicksToHostmasks)
ret.channels = copy.copy(self.channels)
return ret
def addMsg(self, irc, msg):
if len(self.history) > conf.minHistory + 10:
del self.history[:10]
self.history.append(msg)
if ircutils.isUserHostmask(msg.prefix):
self.nicksToHostmasks[msg.nick] = msg.prefix
if msg.command == '352': # Response to a WHO command.
(nick, user, host) = (msg.args[2], msg.args[5], msg.args[3])
hostmask = '%s!%s@%s' % (nick, user, host)
self.nicksToHostmasks[nick] = hostmask
elif msg.command == 'JOIN':
channels = [channel.lower() for channel in msg.args[0].split(',')]
for channel in channels:
if channel in self.channels:
# We're already on the channel.
self.channels[channel].users.add(msg.nick)
else:
chan = Channel()
self.channels[channel] = chan
chan.users.add(msg.nick)
elif msg.command == '353':
(_, _, channel, users) = msg.args
chan = self.channels[channel.lower()]
users = [ircutils.nick(user) for user in users.split()]
for user in users:
if user[0] in '@%+':
(marker, user) = (user[0], user[1:])
if marker == '@':
chan.ops.add(user)
elif marker == '%':
chan.halfops.add(user)
elif marker == '+':
chan.voices.add(user)
chan.users.add(user)
elif msg.command == 'PART':
for channel in msg.args[0].split(','):
channel = channel.lower()
chan = self.channels[channel]
if msg.nick == irc.nick:
del self.channels[channel]
else:
chan.removeUser(msg.nick)
elif msg.command == 'KICK':
(channel, users) = msg.args[:2]
channel = channel.lower()
chan = self.channels[channel]
for user in users.split(','):
chan.removeUser(user)
elif msg.command == 'QUIT':
for channel in self.channels.itervalues():
channel.removeUser(msg.nick)
elif msg.command == 'TOPIC':
channel = msg.args[0].lower()
chan = self.channels[channel]
chan.topic = msg.args[1]
elif msg.command == '332':
channel = msg.args[1].lower()
chan = self.channels[channel]
chan.topic = msg.args[2]
def getTopic(self, channel):
return self.channels[channel.lower()].topic
def nickToHostmask(self, nick):
return self.nicksToHostmasks[nick]
###
# The basic class for handling a connection to an IRC server. Accepts
# callbacks of the IrcCallback interface. Public attributes include 'driver',
# 'queue', and 'state', in addition to the standard nick/user/ident attributes.
###
class Irc(object):
"""The base class for an IRC connection.
Handles PING commands already.
"""
_nickSetters = set(('001', '002', '003', '004', '250', '251', '252', '254',
'255', '265', '266', '372', '375', '376', '333', '353',
'332', '366'))
def __init__(self, nick, user='', ident='', callbacks=None):
world.ircs.append(self)
self.nick = nick
self.prefix = ''
self.user = user or nick # Default to nick if user isn't provided.
self.ident = ident or nick # Ditto.
self.callbacks = []
if callbacks is not None:
for callback in callbacks:
self.addCallback(callback)
self._nickmods = copy.copy(conf.nickmods)
self.state = IrcState()
self.queue = IrcMsgQueue()
self.fastqueue = []
self.lastping = time.time()
self.lastTake = 0
self.driver = None # The driver should set this later.
self.queue.enqueueMsg(ircmsgs.user(self.user, self.ident))
self.queue.enqueueMsg(ircmsgs.nick(self.nick))
def reset(self):
self._nickmods = copy.copy(conf.nickmods)
self.state.reset()
self.queue.reset()
self.fastqueue = []
self.queue.enqueueMsg(ircmsgs.user(self.user, self.ident))
self.queue.enqueueMsg(ircmsgs.nick(self.nick))
for callback in self.callbacks:
callback.reset()
def addCallback(self, callback):
self.callbacks.append(callback)
def removeCallback(self, name):
ret = []
for (i, cb) in enumerate(self.callbacks):
if cb.name() == name:
ret.append(self.callbacks[i])
self.callbacks[i] = None
self.callbacks = [cb for cb in self.callbacks if cb is not None]
return ret
def queueMsg(self, msg):
self.queue.enqueueMsg(msg)
def sendMsg(self, msg):
self.fastqueue.append(msg)
def takeMsg(self):
now = time.time()
msg = None
if self.fastqueue:
msg = self.fastqueue.pop(0)
elif not self.queue.empty():
if now - self.lastTake <= conf.throttleTime:
debug.debugMsg('Irc.takeMsg throttling.', 'verbose')
else:
self.lastTake = now
msg = self.queue.dequeueMsg()
elif now > (self.lastping + conf.pingInterval):
if now - self.lastTake <= conf.throttleTime:
debug.debugMsg('Irc.takeMsg throttling.', 'verbose')
else:
self.lastping = now
msg = ircmsgs.ping(str(int(now)))
if msg:
for callback in self.callbacks:
#debug.printf(repr(msg))
msg = callback.outFilter(self, msg)
self.state.addMsg(self,ircmsgs.IrcMsg(msg=msg, prefix=self.prefix))
s = '%s %s' % (time.strftime(conf.timestampFormat), msg)
debug.debugMsg(s, 'low')
if msg.command == 'NICK':
# We don't want a race condition where the server's NICK
# back to us is lost and someone else steals our nick and uses
# it to abuse our 'owner' power we give to ourselves. Ergo, on
# outgoing messages that change our nick, we pre-emptively
# delete the 'owner' user we setup for ourselves.
if ircdb.users.hasUser(self.nick):
ircdb.users.delUser(self.nick)
return msg
else:
return None
def feedMsg(self, msg):
debug.debugMsg('%s %s' % (time.strftime(conf.timestampFormat), msg),
'low')
# First, make sure self.nick is always consistent with the server.
if msg.command == 'NICK' and msg.nick == self.nick:
if ircdb.users.hasUser(self.nick):
ircdb.users.delUser(self.nick)
self.nick = ircutils.nick(msg.args[0])
(nick, user, domain) = ircutils.splitHostmask(msg.prefix)
if ircdb.users.hasUser(self.prefix):
ircdb.users.delUser(self.prefix)
self.prefix = '%s!%s@%s' % (self.nick, user, domain)
elif msg.command in self._nickSetters:
newnick = ircutils.nick(msg.args[0])
if self.nick != newnick:
debug.printf('Hmm...self.nick != newnick. Odd.')
self.nick = newnick
# Respond to PING requests.
elif msg.command == 'PING':
self.sendMsg(ircmsgs.pong(msg.args[0]))
# Send new nicks on 433
elif msg.command == '433' or msg.command == '432':
self.sendMsg(ircmsgs.nick(self._nickmods.pop(0) % self.nick))
if msg.nick == self.nick:
if ircdb.users.hasUser(self.nick):
u = ircdb.users.getUser(self.nick)
if not u.hasHostmask(msg.prefix):
u.addHostmask(msg.prefix)
ircdb.users.setUser(self.nick, u)
else:
u = ircdb.IrcUser(capabilities=['owner'], password=mktemp(),
hostmasks=[msg.prefix])
ircdb.users.setUser(self.nick, u)
atexit.register(lambda: catch(ircdb.users.delUser(self.nick)))
# elif msg.command == 'ERROR':
# if msg.args[0].startswith('Closing Link'):
# self.driver.die()
# Now update the IrcState object.
try:
self.state.addMsg(self, msg)
except:
debug.recoverableException()
# Now call the callbacks.
for callback in self.callbacks:
try:
m = callback.inFilter(self, msg)
if not m:
debugmsg = 'inFilter %s returned None' % callback.name()
debug.debugMsg(debugmsg)
msg = m
except:
debug.recoverableException()
for callback in self.callbacks:
try:
if callback is not None:
callback(self, msg)
except:
debug.recoverableException()
def die(self):
for callback in self.callbacks:
callback.die()
world.ircs.remove(self)

328
src/ircmsgs.py Normal file
View File

@ -0,0 +1,328 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import re
import ircutils
###
# IrcMsg class -- used for representing IRC messages acquired from a network.
###
class IrcMsg(object):
"""Class to represent an IRC message.
"""
# __slots__ = ('_args', '_command', '_host', '_nick', '_prefix', '_user')
def __init__(self, s='', command='', args=None, prefix='', msg=None):
if msg:
prefix = msg.prefix
command = msg.command
args = msg.args
if command: # Must be using command=, args=, prefix= form.
if args is not None:
for arg in args:
if not ircutils.validArgument(arg):
raise ValueError, 'Invalid argument: %r' % arg
else:
args = ()
else: # Must be using a string.
if s[0] == ':':
prefix, s = s[1:].split(None, 1)
else:
prefix = ''
if s.find(' :') != -1:
s, last = s.split(' :', 1)
args = s.split()
args.append(last.strip())
else:
args = s.split()
command = args.pop(0)
if ircutils.isUserHostmask(prefix):
(nick, user, host) = ircutils.splitHostmask(prefix)
else:
(nick, user, host) = ('', '', '')
self._prefix = prefix
self._nick = ircutils.nick(nick)
self._user = user
self._host = host
self._command = command
self._args = tuple(args)
prefix = property(lambda self: self._prefix)
nick = property(lambda self: self._nick)
user = property(lambda self: self._user)
host = property(lambda self: self._host)
command = property(lambda self: self._command)
args = property(lambda self: self._args)
## def copy(self):
## return self.__class__(command=self.command,
## args=self.args,
## prefix=self.prefix)
def __str__(self):
ret = ''
if self.prefix:
#debug.printf(1)
ret = ':%s %s' % (self.prefix, self.command)
else:
ret = self.command
if self.args:
if len(self.args) > 1:
#debug.printf(2)
ret = '%s %s :%s\r\n' % (ret, ' '.join(self.args[:-1]),
self.args[-1])
else:
#debug.printf(3)
ret = '%s :%s\r\n' % (ret, self.args[0])
else:
#debug.printf(4)
ret = ret + '\r\n'
return ret
def __len__(self):
# This might not take into account the length of the prefix, but leaves
# some room for variation.
ret = 0
if self.prefix:
ret += len(self.prefix)
ret += len(self.command)
if self.args:
for arg in self.args:
ret += len(arg)
ret += 2 # For the colon before the prefix and before the last arg.
ret += 2 # For the ! and the @ in the prefix.
return ret
def __eq__(self, other):
return hash(self) == hash(other) and\
self.command == other.command and\
self.prefix == other.prefix and\
self.args == other.args
def __hash__(self):
return hash(self.command) & hash(self.prefix) & hash(self.args)
def __repr__(self):
return '%s(prefix=%r, command=%r, args=%r)' % \
(self.__class__.__name__, self.prefix, self.command, self.args)
def isAction(msg):
return msg.command == 'PRIVMSG' and \
msg.args[1].startswith('\x01ACTION') and \
msg.args[1].endswith('\x01')
_unactionre = re.compile(r'\x01ACTION (.*)\x01')
def unAction(msg):
return _unactionre.match(msg.args[1]).group(1)
def prettyPrint(msg, addRecipients=False):
def nickorprefix():
return msg.nick or msg.prefix
def nick():
if addRecipients:
return '%s/%s' % (msg.nick, msg.args[0])
else:
return msg.nick
if msg.command == 'PRIVMSG':
m = _unactionre.match(msg.args[1])
if m:
s = '* %s %s' % (nick(), m.group(1))
else:
s = '<%s> %s' % (nick(), msg.args[1])
elif msg.command == 'NOTICE':
s = '-%s- %s' % (nick(), msg.args[1])
elif msg.command == 'JOIN':
s = '*** %s has joined %s' % (msg.nick, msg.args[0])
elif msg.command == 'PART':
if len(msg.args) > 1:
partmsg = ' (%s)' % msg.args[1]
else:
partmsg = ''
s = '*** %s has parted %s%s' % (msg.nick, msg.args[0], partmsg)
elif msg.command == 'KICK':
if len(msg.args) > 2:
kickmsg = ' (%s)' % msg.args[1]
else:
kickmsg = ''
s = '*** %s was kicked by %s%s' % (msg.args[1], msg.nick, kickmsg)
elif msg.command == 'MODE':
s = '*** %s sets mode: %s' % (nickorprefix(), ' '.join(msg.args))
elif msg.command == 'QUIT':
if msg.args:
quitmsg = ' (%s)' % msg.args[0]
else:
quitmsg = ''
s = '*** %s has quit IRC%s' % quitmsg
elif msg.command == 'TOPIC':
s = '*** %s changes topic to %s' % (nickorprefix(), msg.args[1])
return s
###
# Various IrcMsg functions
###
MODE = 'MODE'
def pong(payload):
"""Takes a payload and returns the proper PONG IrcMsg"""
return IrcMsg(command='PONG', args=(payload,))
def fromPong(msg):
"""Takes a PONG IrcMsg and returns the payload."""
assert msg.command == 'PONG'
return msg.args[0]
def ping(payload):
"""Takes a payload and returns the proper PING IrcMsg"""
return IrcMsg(command='PING', args=(payload,))
def fromPing(msg):
"""Takes a PING IrcMsg and returns the payload."""
assert msg.command == 'PING'
return msg.args[0]
def op(channel, nick):
return IrcMsg(command=MODE, args=(channel, '+o', nick))
def ops(channel, nicks):
return IrcMsg(command=MODE,
args=(channel, '+' + ('o'*len(nicks)), nicks))
def deop(channel, nick):
return IrcMsg(command=MODE, args=(channel, '-o', nick))
def deops(channel, nicks):
return IrcMsg(command=MODE, args=(channel, '-' + ('o'*len(nicks)), nicks))
def halfop(channel, nick):
return IrcMsg(command=MODE, args=(channel, '+h', nick))
def halfops(channel, nicks):
return IrcMsg(command=MODE, args=(channel, '+' + ('h'*len(nicks)), nicks))
def dehalfop(channel, nick):
return IrcMsg(command=MODE, args=(channel, '-h', nick))
def dehalfops(channel, nicks):
return IrcMsg(command=MODE, args=(channel, '-' + ('h'*len(nicks)), nicks))
def voice(channel, nick):
return IrcMsg(command=MODE, args=(channel, '+v', nick))
def voices(channel, nicks):
return IrcMsg(command=MODE, args=(channel, '+' + ('v'*len(nicks)), nicks))
def devoice(channel, nick):
return IrcMsg(command=MODE, args=(channel, '-v', nick))
def devoices(channel, nicks):
return IrcMsg(command=MODE, args=(channel, '-' + ('v'*len(nicks)), nicks))
def ban(channel, hostmask):
return IrcMsg(command=MODE, args=(channel, '+b', hostmask))
def bans(channel, hostmasks):
return IrcMsg(command=MODE,
args=(channel, '+' + ('b'*len(hostmasks)), hostmasks))
def unban(channel, hostmask):
return IrcMsg(command=MODE, args=(channel, '-b', hostmask))
def unbans(channel, hostmasks):
return IrcMsg(command=MODE,
args=(channel, '-' + ('b'*len(hostmasks)), hostmasks))
def kick(channel, nick, msg=''):
if msg:
return IrcMsg(command='KICK', args=(channel, nick, msg))
else:
return IrcMsg(command='KICK', args=(channel, nick))
def kicks(channel, nicks, msg=''):
if msg:
return IrcMsg(command='KICK', args=(channel, ','.join(nicks), msg))
else:
return IrcMsg(command='KICK', args=(channel, ','.join(nicks)))
def privmsg(recipient, msg):
return IrcMsg(command='PRIVMSG', args=(recipient, msg))
def action(recipient, msg):
return IrcMsg(command='PRIVMSG', args=(recipient,'\x01ACTION %s\x01'% msg))
def notice(recipient, msg):
return IrcMsg(command='NOTICE', args=(recipient, msg))
def join(channel):
return IrcMsg(command='JOIN', args=(channel,))
def joins(channels):
return IrcMsg(command='JOIN', args=(','.join(channels),))
def part(channel, msg=""):
if msg:
return IrcMsg(command='PART', args=(channel, msg))
else:
return IrcMsg(command='PART', args=(channel,))
def parts(channels, msg=""):
if msg:
return IrcMsg(command='PART', args=(','.join(channels), msg,))
else:
return IrcMsg(command='PART', args=(','.join(channels),))
def quit(msg=''):
if msg:
return IrcMsg(command='QUIT', args=(msg,))
else:
return IrcMsg(command='QUIT')
def topic(channel, topic):
return IrcMsg(command='TOPIC', args=(channel, topic))
def nick(nick):
return IrcMsg(command='NICK', args=(nick,))
def user(user, ident):
return IrcMsg(command='USER', args=(ident, '0', '*', user))
def who(hostmask):
return IrcMsg(command='WHO', args=(hostmask,))
def whois(nick):
return IrcMsg(command='WHOIS', args=(nick,))
def invite(channel, user):
return IrcMsg(command='INVITE', args=(channel, user))

192
src/ircutils.py Normal file
View File

@ -0,0 +1,192 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import re
import string
import fnmatch
import world
def isUserHostmask(s):
p1 = s.find('!')
p2 = s.find('@')
if p1 < p2-1 and p1 >= 1 and p2 >= 3 and len(s) > p2+1:
return True
else:
return False
def isServerHostmask(s):
return (not isUserHostmask(s) and s.find('!') == -1 and s.find('@') == -1)
def nickFromHostmask(hostmask):
return nick(hostmask.split('!', 1)[0])
def userFromHostmask(hostmask):
#debug.printf('FOO!: %r' % hostmask)
return hostmask.split('!', 1)[1].split('@', 1)[0]
def hostFromHostmask(hostmask):
return hostmask.split('@', 1)[1]
def splitHostmask(hostmask):
nck, rest = hostmask.split('!', 1)
user, host = rest.split('@', 1)
return (nick(nck), user, host)
def joinHostmask(nick, ident, host):
return '%s!%s@%s' % (nick, ident, host)
_lowertrans = string.maketrans(string.ascii_uppercase + r'\[]',
string.ascii_lowercase + r'|{}')
def nickToLower(nick):
return nick.translate(_lowertrans)
def nickEqual(nick1, nick2):
return nickToLower(nick1) == nickToLower(nick2)
nickchars = string.ascii_lowercase + string.ascii_uppercase + r'-[]\\`^{}'
_nickre = re.compile(r'^[%s]+$' % re.escape(nickchars))
def isNick(s):
if re.match(_nickre, s):
return True
else:
return False
def isChannel(s):
return (s and s[0] in '#&+!')
def hostmaskPatternEqual(pattern, hostmask):
return fnmatch.fnmatch(nickToLower(hostmask), nickToLower(pattern))
_ipchars = string.digits + '.'
def isIP(s):
"""Not quite perfect, but close enough until I can find the regexp I want.
>>> isIP('255.255.255.255')
1
>>> isIP('abc.abc.abc.abc')
0
"""
return (s.translate(string.ascii, _ipchars) == "")
def banmask(hostmask):
"""Returns a properly generic banning hostmask for a hostmask.
>>> banmask('nick!user@host.domain.tld')
'*!*@*.domain.tld'
>>> banmask('nick!user@10.0.0.1')
'*!*@10.0.0.*'
"""
host = hostFromHostmask(hostmask)
if isIP(host):
return ('*!*@%s.*' % host[:host.rfind('.')])
else:
return ('*!*@*%s' % host[host.find('.'):])
_argModes = 'ovhblkqe'
def separateModes(args):
"""Separates modelines into single mode change tuples.
Examples:
>>> separateModes(['+ooo', 'jemfinch', 'StoneTable', 'philmes'])
[('+o', 'jemfinch'), ('+o', 'StoneTable'), ('+o', 'philmes')]
>>> separateModes(['+o-o', 'jemfinch', 'PeterB'])
[('+o', 'jemfinch'), ('-o', 'PeterB')]
>>> separateModes(['+s-o', 'test'])
[('+s', None), ('-o', 'test')]
>>> separateModes(['+sntl', '100'])
[('+s', None), ('+n', None), ('+t', None), ('+l', '100')]
"""
modes = args[0]
args = list(args[1:])
ret = []
index = 0
length = len(modes)
while index < length:
if modes[index] in '+-':
last = modes[index]
index += 1
else:
if modes[index] in _argModes:
ret.append((last + modes[index], args.pop(0)))
else:
ret.append((last + modes[index], None))
index += 1
return ret
def bold(s):
return "\x02%s\x02" % s
def validArgument(s):
return '\r' not in s and '\n' not in s and '\x00' not in s
def funkyArgument(s):
if validArgument(s):
if s.translate(string.ascii, string.printable) == '':
# All characters must be printable.
return False
else:
return True
else:
return True
def reply(msg):
if isChannel(msg.args[0]):
return msg.args[0]
else:
return msg.nick
class nick(str):
"""This class does case-insensitive comparisons of nicks."""
def __init__(self, s):
self.lowered = nickToLower(s)
def __eq__(self, s):
try:
return nickToLower(s) == self.lowered
except:
return False
def __hash__(self):
return hash(self.lowered)
if __name__ == '__main__':
import sys, doctest
doctest.testmod(sys.modules['__main__'])

1181
src/privmsgs.py Normal file

File diff suppressed because it is too large Load Diff

131
src/repl.py Normal file
View File

@ -0,0 +1,131 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import sys
import traceback
from cStringIO import StringIO
import debug
filename = 'repl'
NotYet = object()
class Repl(object):
def __init__(self, filename='repl'):
self.lines = []
self.filename = filename
self.namespace = {}
self.out = StringIO()
def compile(self, text):
ret = None
try:
code = compile(text, self.filename, 'eval')
sys.stdout = self.out
ret = eval(code, self.namespace, self.namespace)
sys.stdout = sys.__stdout__
self.out.reset()
if self.out.read():
ret = self.out.read() + ret
self.out.reset()
self.out.truncate()
self.namespace['_'] = ret
except:
try:
code = compile(text, self.filename, 'exec')
sys.stdout = self.out
exec code in self.namespace, self.namespace
sys.stdout = sys.__stdout__
self.out.reset()
if self.out.read():
ret = self.out.read() + ret
self.out.reset()
self.out.truncate()
except:
(E, e, tb) = sys.exc_info()
ret = ''.join(traceback.format_exception(E, e, tb))
del tb
return ret
def addLine(self, line):
line = line.rstrip()
self.lines.append(line)
if len(self.lines) > 100:
debug.debugMsg('too many lines in Repl.')
self.lines = []
return None
if line == '' or line == '\n' or line == '\r\n':
text = '\n'.join(self.lines)+'\n\n'
ret = self.compile(text)
if ret is not NotYet:
self.lines = []
return ret
else:
try:
ret = eval(line, self.namespace, self.namespace)
self.lines = []
return ret
except SyntaxError:
try:
exec line in self.namespace, self.namespace
self.lines = []
return None
except:
pass
except:
(E, e, tb) = sys.exc_info()
return ''.join(traceback.format_exception(E, e, tb))
del tb
return NotYet
if __name__ == '__main__':
def writePrompt(prompt):
sys.stdout.write(prompt)
sys.stdout.flush()
prompt = '>>> '
repl = Repl()
while 1:
writePrompt(prompt)
s = sys.stdin.readline()
if s == '':
sys.exit(0)
ret = repl.addLine(s)
if ret is not NotYet:
if ret is not None:
s = str(ret)
sys.stdout.write(s)
if s[-1] != '\n':
sys.stdout.write('\n')
prompt = '>>> '
else:
prompt = '... '

88
src/schedule.py Normal file
View File

@ -0,0 +1,88 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import time
import bisect
import drivers
class Schedule(drivers.IrcDriver):
def __init__(self):
drivers.IrcDriver.__init__(self)
self.schedule = []
self.events = {}
self.counter = 0
def addEvent(self, f, t, name=None):
if name is None:
name = self.counter
self.counter += 1
elif type(name) == int:
raise ValueError, 'int names are reserved for the scheduler.'
#debug.printf('Added event to schedule: %s' % name)
assert name not in self.events
self.events[name] = f
bisect.insort(self.schedule, (t, name))
def removeEvent(self, name):
del self.events[name]
self.schedule = [(t, n) for (t, n) in self.schedule if n != name]
def addPeriodicEvent(self, f, t, name=None):
# Note that you can't name a periodic event -- it'll be erased after
# it's been run only once.
def wrapper():
f()
self.addEvent(wrapper, time.time() + t, name)
wrapper()
removePeriodicEvent = removeEvent
def run(self):
#debug.printf(`(time.time(), self.schedule)`)
while self.schedule and self.schedule[0][0] < time.time():
(t, name) = self.schedule.pop(0)
self.events[name]()
del self.events[name]
try:
ignore(schedule)
except NameError:
schedule = Schedule()
addEvent = schedule.addEvent
removeEvent = schedule.removeEvent
addPeriodicEvent = schedule.addPeriodicEvent
removePeriodicEvent = removeEvent
run = schedule.run

196
src/world.py Normal file
View File

@ -0,0 +1,196 @@
#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
from fix import *
import gc
import time
import copy
import types
import string
import atexit
import linecache
import conf
import debug
# version: The version of the bot.
version = '0.70.0'
###
# End Global Values.
###
try:
ignore(ircs)
except NameError:
ircs = []
try:
ignore(flushers)
except NameError:
flushers = [] # A periodic function will flush all these.
def flush():
for f in flushers:
f()
atexit.register(flush)
try:
ignore(tempvars)
except NameError:
tempvars = {} # A storage place for temporary variables that need to be
# globally accessible.
def upkeep(): # Function to be run on occasion to do upkeep stuff.
gc.collect()
if gc.garbage:
debug.debugMsg('Uncollectable garbge: %s' % gc.garbage, 'normal')
if 'noflush' not in tempvars:
flush()
msg = '%s upkeep ran.' % time.strftime(conf.timestampFormat)
debug.debugMsg(msg, 'verbose')
def superReload(oldmodule):
###
# So here's how this baby works:
# Reload the module.
# Iterate through the old module, finding classes and functions that are
# also in the new module.
# Add an __getattribute__ or __getattr__ method to those classes that are
# present in the new module. This method will, when called, change
# the instance's __class__ to point to the new class.
# Change the func_code, func_defaults, and func_doc of any functions or
# methods or generators we run across, just in case someone's holding
# a reference to them instead of calling them by name.
###
"""Reload a module and make objects auto-update."""
# reload(module) modifies module in-place, so we need a copy of its
# __dict__ to iterate through.
olddict = copy.copy(oldmodule.__dict__)
newmodule = reload(oldmodule)
newdict = newmodule.__dict__
for (name, oldvalue) in olddict.iteritems():
if name in newdict:
newvalue = newdict[name]
oldtype = type(oldvalue)
# We have to pass in newvalue because of Python's scoping.
def updater(self, s, newvalue=newvalue):
# This function is to be an __getattr__ or __getattribute__.
try:
self.__class__ = newvalue
except:
debug.recoverableException()
try:
del self.__class__.__getattribute__
except AttributeError:
del self.__class__.__getattr__
return getattr(self, s)
if oldtype == types.TypeType and \
oldvalue.__module__ == newmodule.__name__:
# New-style classes support __getattribute__, which is
# called on *any* attribute access, so they get updated
# the first time they're used after a reload.
if not (issubclass(oldvalue, str) or \
issubclass(oldvalue, long) or \
issubclass(oldvalue, tuple)):
oldvalue.__getattribute__ = updater
elif oldtype == types.ClassType and\
oldvalue.__module__ == newmodule.__name__:
# Old-style classes can only use getattr, so they might not
# update right away. Hopefully they will, but to solve
# this problem I just use new-style classes.
oldvalue.__getattr__ = updater
elif oldtype == type(newvalue):
if oldtype == types.FunctionType or\
oldtype == types.GeneratorType:
oldvalue.func_code = newvalue.func_code
oldvalue.func_defaults = newvalue.func_defaults
oldvalue.func_doc = newvalue.func_doc
elif oldtype == types.MethodType:
oldfunc = oldvalue.im_func
newfunc = newvalue.im_func
oldfunc.func_code = newfunc.func_code
oldfunc.func_defaults = newfunc.func_defaults
oldfunc.func_doc = newfunc.func_doc
# Update the linecache, so tracebacks show the proper lines.
linecache.checkcache()
return newmodule
'''
try:
# This makes the module reload properly; we don't want to lose oldobjects
# on reload.
oldobjects
except NameError:
oldobjects = {}
def superReload(module):
def updateFunction(old, new, attrs):
"""Update all attrs in old to the same attrs in new."""
for name in attrs:
setattr(old, name, getattr(new, name))
for (name, object) in module.__dict__.iteritems():
# For every attribute of the old module, keep the object.
key = (module.__name__, name)
oldobjects.setdefault(key, []).append(object)
module = reload(module)
for name, newobj in module.__dict__.iteritems():
# For every attribute in the new module...
key = (module.__name__, name)
if key in oldobjects: # If the same attribute was in the old module...
for oldobj in oldobjects[key]:
# Give it the new attributes :)
if type(newobj) == types.ClassType:
toRemove = []
for k in oldobj.__dict__:
if k not in newobj.__dict__:
toRemove.append(k)
for k in toRemove:
del oldobj.__dict__[k]
oldobj.__dict__.update(newobj.__dict__)
# elif type(newobj) == types.TypeType:
# if hasattr(oldobj, '__dict__'):
# oldobj.__dict__.update(newobj.__dict__)
elif type(newobj) == types.FunctionType:
updateFunction(oldobj, newobj, ('func_code',
'func_defaults',
'func_doc'))
elif type(newobj) == types.MethodType:
updateFunction(oldobj.im_func, newobj.im_func,
('func_code', 'func_defaults', 'func_doc'))
return module
'''

3
tools/TOOLS Normal file
View File

@ -0,0 +1,3 @@
These are just things that have come in useful throughout development.
pycheckrc: The ~/.pycheckrc that I use for checking the bot.

31
tools/conv.py Normal file
View File

@ -0,0 +1,31 @@
#!/usr/bin/env python
"""
Converts from a "key => value\n" format to a cdb database.
"""
import sys
sys.path.insert(0, 'src')
import re
import cdb
r = re.compile(r'(.*)\s+=>\s+(.*)\n')
if __name__ == '__main__':
if len(sys.argv) < 2:
print 'Usage: %s <dbname> (reads stdin for data)' % sys.argv[0]
sys.exit(-1)
maker = cdb.Maker(sys.argv[1])
lineno = 1
for line in sys.stdin:
m = r.match(line)
if m:
(key, value) = m.groups()
maker.add(key, value)
lineno += 1
else:
print 'Invalid Syntax, line %s' % lineno
maker.finish()

61
tools/identd.py Normal file
View File

@ -0,0 +1,61 @@
#!/usr/local/bin/python -u
import asyncore
import asynchat
import socket
from random import randrange
import os
class identd_server(asyncore.dispatcher):
def __init__(self, limit=10):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.helpers = []
self.limit = limit
os.chdir('/')
self.bind(('', 113))
os.setgid(25000)
os.setuid(25000)
self.listen(5)
def handle_accept(self):
self.helpers.append(identd_helper(self.accept()))
if len(self.helpers) > self.limit:
i = self.helpers.pop(0)
i.close()
class identd_helper(asynchat.async_chat):
def __init__(self, (sock, addr)):
asynchat.async_chat.__init__(self, sock)
self.sock = sock
self.set_terminator('\r\n')
self.buffer = ''
def collect_incoming_data(self, data):
self.buffer = self.buffer + data
if len(self.buffer) > 512:
self.close()
def found_terminator(self):
self.send('%s : USERID : DOS : user%d\r\n' % (self.buffer, randrange(10000)))
self.close()
if __name__ == '__main__':
import sys
sys.stdin.close()
sys.stdout.close()
sys.stderr.close()
if os.fork() != 0:
sys.exit(0)
os.setsid()
s = identd_server()
os.chdir('/')
os.setgid(25000)
os.setuid(25000)
os.umask(0)
if os.fork() != 0:
sys.exit(0)
asyncore.loop()

190
tools/pycheckrc Normal file
View File

@ -0,0 +1,190 @@
#
# .pycheckrc file created by PyChecker v0.8.11 @ Tue Jul 23 02:43:41 2002
#
# It should be placed in your home directory (value of $HOME).
# If $HOME is not set, it will look in the current directory.
#
# unused imports
importUsed = 1
# unused imports from __init__.py
packageImportUsed = 1
# module imports itself
reimportSelf = 1
# reimporting a module
moduleImportErrors = 1
# module does import and from ... import
mixImport = 1
# unused local variables, except tuples
localVariablesUsed = 1
# all unused local variables, including tuples
unusedLocalTuple = 0
# all unused class data members
membersUsed = 0
# all unused module variables
allVariablesUsed = 0
# unused private module variables
privateVariableUsed = 1
# report each occurrence of global warnings
reportAllGlobals = 1
# functions called with named arguments (like keywords)
namedArgs = 0
# Attributes (members) must be defined in __init__()
onlyCheckInitForMembers = 0
# Subclass.__init__() not defined
initDefinedInSubclass = 0
# Baseclass.__init__() not called
baseClassInitted = 1
# Subclass needs to override methods that only throw exceptions
abstractClasses = 1
# Return None from __init__()
returnNoneFromInit = 1
# unreachable code
unreachableCode = 0
# a constant is used in a conditional statement
constantConditions = 1
# 1 is used in a conditional statement (if 1: or while 1:)
constant1 = 0
# check if iterating over a string
stringIteration = 1
# Calling data members as functions
callingAttribute = 0
# class attribute does not exist
classAttrExists = 1
# First argument to methods
methodArgName = 'self'
# unused method/function arguments
argumentsUsed = 1
# unused method/function variable arguments
varArgumentsUsed = 1
# ignore if self is unused in methods
ignoreSelfUnused = 0
# check if overridden methods have the same signature
checkOverridenMethods = 1
# check if function/class/method names are reused
redefiningFunction = 1
# check if using unary positive (+) which is usually meaningless
unaryPositive = 1
# check if modify (call method) on a parameter that has a default value
modifyDefaultValue = 0
# check if variables are set to different types
inconsistentTypes = 0
# check if unpacking a non-sequence
unpackNonSequence = 1
# check if unpacking sequence with the wrong length
unpackLength = 1
# check if raising or catching bad exceptions
badExceptions = 1
# check consistent return values
checkReturnValues = 1
# check if using implict and explicit return values
checkImplicitReturns = 1
# check that attributes of objects exist
checkObjectAttrs = 1
# various warnings about incorrect usage of __slots__
slots = 1
# using properties with classic classes
classicProperties = 1
# check if __slots__ is empty
emptySlots = 1
# check if using integer division
intDivide = 1
# check if local variable shadows a global
shadows = 0
# check if input() is used
usesInput = 1
# check if the exec statement is used
usesExec = 0
# ignore warnings from files under standard library
ignoreStandardLibrary = 0
# ignore warnings from the list of modules
blacklist = ['Tkinter', 'wxPython', 'gtk', 'GTK', 'GDK', 'asynchat']
# ignore global variables not used if name is one of these values
variablesToIgnore = ['__version__', '__warningregistry__', '__all__', '__credits__', '__author__', '__email__']
# ignore unused locals/arguments if name is one of these values
unusedNames = ['_', 'empty', 'unused', 'dummy', 'irc', 'args', 'msg', 'match']
# ignore use of deprecated modules/functions
deprecated = 1
# maximum lines in a function
maxLines = 200
# maximum branches in a function
maxBranches = 50
# maximum returns in a function
maxReturns = 10
# maximum # of arguments to a function
maxArgs = 10
# maximum # of locals in a function
maxLocals = 40
# maximum # of identifier references (Law of Demeter)
maxReferences = 5
# no module doc strings
noDocModule = 0
# no class doc strings
noDocClass = 0
# no function/method doc strings
noDocFunc = 0
# print internal checker parse structures
printParse = 0
# turn on debugging for checker
debug = 0