mirror of
https://github.com/Mikaela/Limnoria.git
synced 2025-01-30 14:14:37 +01:00
Initial revision
This commit is contained in:
commit
7801c84d84
3
.cvsignore
Normal file
3
.cvsignore
Normal file
@ -0,0 +1,3 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*~
|
6
ACKS
Normal file
6
ACKS
Normal 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
2
BUGS
Normal 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
209
CHANGELOG
Normal 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
54
LICENSE
Normal 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
42
README
Normal 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
15
TODO
Normal 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
17
docs/CAPABILITIES
Normal 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
202
docs/EXAMPLE
Normal 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
4
docs/HINTS
Normal 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
60
docs/OVERVIEW
Normal 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
32
docs/STYLE
Normal 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
2
examples/channels.conf
Normal 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
2
examples/users.conf
Normal 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
3972
others/SOAP.py
Normal file
File diff suppressed because it is too large
Load Diff
274
others/amazon.py
Normal file
274
others/amazon.py
Normal 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
293
others/asynchat.py
Normal 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
551
others/asyncore.py
Normal 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
282
others/cgitb.py
Normal 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(' ' * 5) + ' </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> </big>', link, call)]
|
||||
if index is not None:
|
||||
i = lnum - index
|
||||
for line in lines:
|
||||
num = small(' ' * (5-len(str(i))) + str(i)) + ' '
|
||||
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 = %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 =\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('&', '&').replace('<', '<')
|
||||
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
432
others/google.py
Normal 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
212
others/shlex.py
Normal 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
722
others/unittest.py
Normal 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
1114
others/urllib2.py
Normal file
File diff suppressed because it is too large
Load Diff
107
plugins/BadWords.py
Normal file
107
plugins/BadWords.py
Normal 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
153
plugins/ChannelLogger.py
Normal 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
80
plugins/ChannelStats.py
Normal 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
74
plugins/Ctcp.py
Normal 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
212
plugins/Factoids.py
Normal 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
55
plugins/FixRelayBot.py
Normal 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
219
plugins/FreeBSD.py
Executable 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
327
plugins/FunCommands.py
Normal 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
300
plugins/FunDB.py
Executable 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
174
plugins/Http.py
Normal 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: </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
185
plugins/Moobot.py
Normal 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
80
plugins/NickServ.py
Normal 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
72
plugins/Parter.py
Normal 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
169
plugins/Quotes.py
Normal 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
55
plugins/RawLogger.py
Normal 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
41
plugins/Relay.py
Normal 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
52
plugins/RootWarner.py
Normal 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
|
62
plugins/ThreadedFunCommands.py
Normal file
62
plugins/ThreadedFunCommands.py
Normal 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
102
plugins/Topic.py
Normal 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
89
plugins/Unix.py
Normal 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
44
plugins/Utils.py
Normal 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
45
plugins/baseplugin.py
Normal 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
34
plugins/template.py
Normal 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
19
sandbox/8ball.dat
Normal 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
117
sandbox/Debian.py
Normal 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
126
sandbox/anagrams.py
Normal 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
18
sandbox/averagePrefix.py
Normal 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
60
sandbox/debian.py
Normal 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
450
sandbox/excuses.dat
Normal 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
47
sandbox/friendly.py
Normal 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
23
sandbox/larts.dat
Normal 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
60
sandbox/makescript.py
Executable 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
49
sandbox/rbot.py
Normal 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
18
sandbox/twistedDrivers.py
Normal 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
34
sandbox/usersXml.py
Normal 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
17
scripts/clean.py
Executable 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
18
scripts/dumpdb.py
Executable 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
23
scripts/makedb.py
Executable 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
11
scripts/newplugin.py
Executable 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
35
scripts/removeSpaces.py
Executable 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
112
src/ansi.py
Normal 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
237
src/asyncoreDrivers.py
Normal 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
151
src/bot.py
Executable 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
358
src/callbacks.py
Normal 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
450
src/cdb.py
Normal 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
173
src/conf.py
Normal 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
187
src/debug.py
Normal 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
141
src/drivers.py
Normal 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
204
src/fix.py
Normal 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
484
src/ircdb.py
Normal 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
389
src/irclib.py
Normal 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
328
src/ircmsgs.py
Normal 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
192
src/ircutils.py
Normal 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
1181
src/privmsgs.py
Normal file
File diff suppressed because it is too large
Load Diff
131
src/repl.py
Normal file
131
src/repl.py
Normal 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
88
src/schedule.py
Normal 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
196
src/world.py
Normal 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
3
tools/TOOLS
Normal 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
31
tools/conv.py
Normal 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
61
tools/identd.py
Normal 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
190
tools/pycheckrc
Normal 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
|
||||
|
Loading…
Reference in New Issue
Block a user